2
0
Эх сурвалжийг харах

Merge branch 'master' into hotfix-android-unicode-ime-input

sanikoyes 11 жил өмнө
parent
commit
77a840e350
100 өөрчлөгдсөн 3882 нэмэгдсэн , 982 устгасан
  1. 1 0
      .gitignore
  2. 1 1
      SConstruct
  3. 72 3
      core/bind/core_bind.cpp
  4. 14 0
      core/bind/core_bind.h
  5. 55 0
      core/func_ref.cpp
  6. 23 0
      core/func_ref.h
  7. 10 13
      core/globals.cpp
  8. 2 2
      core/globals.h
  9. 0 1
      core/io/file_access_pack.cpp
  10. 8 0
      core/io/http_client.cpp
  11. 136 12
      core/io/marshalls.cpp
  12. 26 8
      core/io/resource_format_binary.cpp
  13. 1 1
      core/io/resource_format_binary.h
  14. 45 6
      core/io/resource_format_xml.cpp
  15. 4 1
      core/io/resource_format_xml.h
  16. 12 1
      core/io/resource_loader.cpp
  17. 1 0
      core/io/resource_loader.h
  18. 0 3
      core/io/resource_saver.h
  19. 9 2
      core/math/math_funcs.cpp
  20. 2 1
      core/message_queue.cpp
  21. 52 2
      core/object.cpp
  22. 6 2
      core/object.h
  23. 23 1
      core/os/file_access.cpp
  24. 3 0
      core/os/file_access.h
  25. 7 0
      core/os/input.cpp
  26. 2 0
      core/os/input.h
  27. 1 1
      core/os/mutex.h
  28. 24 1
      core/os/os.cpp
  29. 9 1
      core/os/os.h
  30. 2 0
      core/register_core_types.cpp
  31. 10 0
      core/ustring.cpp
  32. 2 1
      core/ustring.h
  33. 1 1
      core/variant.cpp
  34. 2 0
      core/variant_call.cpp
  35. 2 2
      core/variant_op.cpp
  36. 87 87
      demos/2d/platformer/player.xml
  37. 13 12
      demos/2d/platformer/stage.xml
  38. 1 1
      doc/base/classes.xml
  39. 1 1
      drivers/mpc/audio_stream_mpc.cpp
  40. 111 0
      drivers/openssl/stream_peer_ssl.cpp
  41. 26 0
      drivers/openssl/stream_peer_ssl.h
  42. 6 0
      drivers/unix/os_unix.cpp
  43. 1 0
      drivers/unix/os_unix.h
  44. 8 2
      drivers/vorbis/audio_stream_ogg_vorbis.cpp
  45. 2 2
      drivers/windows/thread_windows.cpp
  46. 16 2
      main/main.cpp
  47. 1 2
      main/main.h
  48. 0 1
      modules/SCsub
  49. 41 2
      modules/gdscript/gd_functions.cpp
  50. 1 0
      modules/gdscript/gd_functions.h
  51. 16 0
      modules/gdscript/gd_parser.cpp
  52. 1 0
      modules/gdscript/gd_parser.h
  53. 13 0
      modules/gdscript/gd_script.cpp
  54. 18 0
      modules/gdscript/gd_tokenizer.cpp
  55. 0 7
      modules/multiscript/SCsub
  56. 0 11
      modules/multiscript/config.py
  57. 0 498
      modules/multiscript/multi_script.cpp
  58. 0 158
      modules/multiscript/multi_script.h
  59. 0 33
      modules/multiscript/register_types.cpp
  60. 0 30
      modules/multiscript/register_types.h
  61. 2 2
      platform/android/AndroidManifest.xml.template
  62. 3 1
      platform/android/SCsub
  63. 45 28
      platform/android/audio_driver_opensl.cpp
  64. 12 9
      platform/android/audio_driver_opensl.h
  65. 5 6
      platform/android/detect.py
  66. 0 5
      platform/android/java/ant.properties
  67. 36 7
      platform/android/java/src/com/android/godot/Godot.java
  68. 48 3
      platform/android/java/src/com/android/godot/GodotIO.java
  69. 2 2
      platform/android/java/src/com/android/godot/GodotLib.java
  70. 83 0
      platform/android/java/src/com/android/godot/GodotPaymentV3.java
  71. 71 0
      platform/android/java/src/com/android/godot/payments/ConsumeTask.java
  72. 79 0
      platform/android/java/src/com/android/godot/payments/HandlePurchaseTask.java
  73. 42 0
      platform/android/java/src/com/android/godot/payments/PaymentsCache.java
  74. 151 0
      platform/android/java/src/com/android/godot/payments/PaymentsManager.java
  75. 121 0
      platform/android/java/src/com/android/godot/payments/PurchaseTask.java
  76. 97 0
      platform/android/java/src/com/android/godot/payments/ValidateTask.java
  77. 39 0
      platform/android/java/src/com/android/godot/utils/Crypt.java
  78. 54 0
      platform/android/java/src/com/android/godot/utils/CustomSSLSocketFactory.java
  79. 206 0
      platform/android/java/src/com/android/godot/utils/HttpRequester.java
  80. 58 0
      platform/android/java/src/com/android/godot/utils/RequestParams.java
  81. 144 0
      platform/android/java/src/com/android/vending/billing/IInAppBillingService.aidl
  82. 49 3
      platform/android/java_glue.cpp
  83. 9 0
      platform/android/libs/apk_expansion/AndroidManifest.xml
  84. 92 0
      platform/android/libs/apk_expansion/build.xml
  85. 20 0
      platform/android/libs/apk_expansion/proguard-project.txt
  86. 13 0
      platform/android/libs/apk_expansion/project.properties
  87. BIN
      platform/android/libs/apk_expansion/res/drawable-hdpi/notify_panel_notification_icon_bg.png
  88. BIN
      platform/android/libs/apk_expansion/res/drawable-mdpi/notify_panel_notification_icon_bg.png
  89. 104 0
      platform/android/libs/apk_expansion/res/layout/status_bar_ongoing_event_progress_bar.xml
  90. 6 0
      platform/android/libs/apk_expansion/res/values-v11/styles.xml
  91. 5 0
      platform/android/libs/apk_expansion/res/values-v9/styles.xml
  92. 41 0
      platform/android/libs/apk_expansion/res/values/strings.xml
  93. 25 0
      platform/android/libs/apk_expansion/res/values/styles.xml
  94. 236 0
      platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Constants.java
  95. 80 0
      platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java
  96. 277 0
      platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java
  97. 181 0
      platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java
  98. 306 0
      platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Helpers.java
  99. 126 0
      platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java
  100. 83 0
      platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderService.java

+ 1 - 0
.gitignore

@@ -14,6 +14,7 @@ core/version.h
 core/method_bind.inc
 core/global_defaults.cpp
 tools/editor/register_exporters.cpp
+tools/editor/doc_data_compressed.h
 -fpic
 
 # Android specific

+ 1 - 1
SConstruct

@@ -11,7 +11,7 @@ import multiprocessing
 # Enable aggresive compile mode if building on a multi core box
 # only is we have not set the number of jobs already or we do
 # not want it
-if ARGUMENTS.get('spawn_jobs', 'yes') == 'yes' and \
+if ARGUMENTS.get('spawn_jobs', 'no') == 'yes' and \
 	int(GetOption('num_jobs')) <= 1:
 	NUM_JOBS = multiprocessing.cpu_count()
 	if NUM_JOBS > 1:

+ 72 - 3
core/bind/core_bind.cpp

@@ -199,6 +199,14 @@ int _OS::get_iterations_per_second() const {
 
 }
 
+void _OS::set_target_fps(int p_fps) {
+	OS::get_singleton()->set_target_fps(p_fps);
+}
+
+float _OS::get_target_fps() const {
+	return OS::get_singleton()->get_target_fps();
+}
+
 void _OS::set_low_processor_usage_mode(bool p_enabled) {
 
 	OS::get_singleton()->set_low_processor_usage_mode(p_enabled);
@@ -238,6 +246,12 @@ Error _OS::kill(int p_pid) {
 	return OS::get_singleton()->kill(p_pid);
 }
 
+int _OS::get_process_ID() const {
+
+	return OS::get_singleton()->get_process_ID();
+};
+
+
 bool _OS::has_environment(const String& p_var) const {
 
 	return OS::get_singleton()->has_environment(p_var);
@@ -387,6 +401,12 @@ uint32_t _OS::get_ticks_msec() const {
 	return OS::get_singleton()->get_ticks_msec();
 }
 
+
+bool _OS::can_use_threads() const {
+
+	return OS::get_singleton()->can_use_threads();
+}
+
 bool _OS::can_draw() const {
 
 	return OS::get_singleton()->can_draw();
@@ -488,6 +508,27 @@ float _OS::get_frames_per_second() const {
 	return OS::get_singleton()->get_frames_per_second();
 }
 
+Error _OS::native_video_play(String p_path) {
+
+	return OS::get_singleton()->native_video_play(p_path);
+};
+
+bool _OS::native_video_is_playing() {
+
+	return OS::get_singleton()->native_video_is_playing();
+};
+
+void _OS::native_video_pause() {
+
+	OS::get_singleton()->native_video_pause();
+};
+
+void _OS::native_video_stop() {
+
+	OS::get_singleton()->native_video_stop();
+};
+
+
 String _OS::get_custom_level() const {
 
 	return OS::get_singleton()->get_custom_level();
@@ -496,7 +537,7 @@ _OS *_OS::singleton=NULL;
 
 void _OS::_bind_methods() {
 
-	ObjectTypeDB::bind_method(_MD("get_mouse_pos"),&_OS::get_mouse_pos);
+	//ObjectTypeDB::bind_method(_MD("get_mouse_pos"),&_OS::get_mouse_pos);
 	//ObjectTypeDB::bind_method(_MD("is_mouse_grab_enabled"),&_OS::is_mouse_grab_enabled);
 
 	ObjectTypeDB::bind_method(_MD("set_clipboard","clipboard"),&_OS::set_clipboard);
@@ -510,6 +551,8 @@ void _OS::_bind_methods() {
 
 	ObjectTypeDB::bind_method(_MD("set_iterations_per_second","iterations_per_second"),&_OS::set_iterations_per_second);
 	ObjectTypeDB::bind_method(_MD("get_iterations_per_second"),&_OS::get_iterations_per_second);
+	ObjectTypeDB::bind_method(_MD("set_target_fps","target_fps"),&_OS::set_target_fps);
+	ObjectTypeDB::bind_method(_MD("get_target_fps"),&_OS::get_target_fps);
 
 	ObjectTypeDB::bind_method(_MD("has_touchscreen_ui_hint"),&_OS::has_touchscreen_ui_hint);
 
@@ -524,6 +567,7 @@ void _OS::_bind_methods() {
 	ObjectTypeDB::bind_method(_MD("execute","path","arguments","blocking"),&_OS::execute);
 	ObjectTypeDB::bind_method(_MD("kill","pid"),&_OS::kill);
 	ObjectTypeDB::bind_method(_MD("shell_open","uri"),&_OS::shell_open);
+	ObjectTypeDB::bind_method(_MD("get_process_ID"),&_OS::get_process_ID);
 
 	ObjectTypeDB::bind_method(_MD("get_environment","environment"),&_OS::get_environment);
 	ObjectTypeDB::bind_method(_MD("has_environment","environment"),&_OS::has_environment);
@@ -550,7 +594,9 @@ void _OS::_bind_methods() {
 	ObjectTypeDB::bind_method(_MD("get_frames_drawn"),&_OS::get_frames_drawn);
 	ObjectTypeDB::bind_method(_MD("is_stdout_verbose"),&_OS::is_stdout_verbose);
 
-	ObjectTypeDB::bind_method(_MD("get_mouse_button_state"),&_OS::get_mouse_button_state);
+	ObjectTypeDB::bind_method(_MD("can_use_threads"),&_OS::can_use_threads);
+
+	//ObjectTypeDB::bind_method(_MD("get_mouse_button_state"),&_OS::get_mouse_button_state);
 
 	ObjectTypeDB::bind_method(_MD("dump_memory_to_file","file"),&_OS::dump_memory_to_file);
 	ObjectTypeDB::bind_method(_MD("dump_resources_to_file","file"),&_OS::dump_resources_to_file);
@@ -568,6 +614,12 @@ void _OS::_bind_methods() {
 
 	ObjectTypeDB::bind_method(_MD("print_all_textures_by_size"),&_OS::print_all_textures_by_size);
 
+	ObjectTypeDB::bind_method(_MD("native_video_play"),&_OS::native_video_play);
+	ObjectTypeDB::bind_method(_MD("native_video_is_playing"),&_OS::native_video_is_playing);
+	ObjectTypeDB::bind_method(_MD("native_video_stop"),&_OS::native_video_stop);
+	ObjectTypeDB::bind_method(_MD("native_video_pause"),&_OS::native_video_pause);
+
+
 	BIND_CONSTANT( DAY_SUNDAY );
 	BIND_CONSTANT( DAY_MONDAY );
 	BIND_CONSTANT( DAY_TUESDAY );
@@ -983,8 +1035,22 @@ void _File::store_string(const String& p_string){
 
 	f->store_string(p_string);
 }
-void _File::store_line(const String& p_string){
 
+void _File::store_pascal_string(const String& p_string) {
+
+	ERR_FAIL_COND(!f);
+
+	f->store_pascal_string(p_string);
+};
+
+String _File::get_pascal_string() {
+
+	ERR_FAIL_COND_V(!f, "");
+
+	return f->get_pascal_string();
+};
+
+void _File::store_line(const String& p_string){
 
 	ERR_FAIL_COND(!f);
 	f->store_line(p_string);
@@ -1083,6 +1149,9 @@ void _File::_bind_methods() {
 	ObjectTypeDB::bind_method(_MD("store_string","string"),&_File::store_string);
 	ObjectTypeDB::bind_method(_MD("store_var","value"),&_File::store_var);
 
+	ObjectTypeDB::bind_method(_MD("store_pascal_string","string"),&_File::store_pascal_string);
+	ObjectTypeDB::bind_method(_MD("get_pascal_string"),&_File::get_pascal_string);
+
 	ObjectTypeDB::bind_method(_MD("file_exists","path"),&_File::file_exists);
 
 	BIND_CONSTANT( READ );

+ 14 - 0
core/bind/core_bind.h

@@ -98,9 +98,17 @@ public:
 	bool is_video_mode_resizable(int p_screen=0) const;
 	Array get_fullscreen_mode_list(int p_screen=0) const;
 
+	Error native_video_play(String p_path);
+	bool native_video_is_playing();
+	void native_video_pause();
+	void native_video_stop();
+
 	void set_iterations_per_second(int p_ips);
 	int get_iterations_per_second() const;
 
+	void set_target_fps(int p_fps);
+	float get_target_fps() const;
+
 	void set_low_processor_usage_mode(bool p_enabled);
 	bool is_in_low_processor_usage_mode() const;
 
@@ -109,6 +117,8 @@ public:
 	Error kill(int p_pid);
 	Error shell_open(String p_uri);
 
+	int get_process_ID() const;
+
 	bool has_environment(const String& p_var) const;
 	String get_environment(const String& p_var) const;
 
@@ -166,6 +176,7 @@ public:
 	void delay_msec(uint32_t p_msec) const;
 	uint32_t get_ticks_msec() const;
 
+	bool can_use_threads() const;
 
 	bool can_draw() const;
 
@@ -280,6 +291,9 @@ public:
 	void store_string(const String& p_string);
 	void store_line(const String& p_string);
 
+	virtual void store_pascal_string(const String& p_string);
+	virtual String get_pascal_string();
+
 	Vector<String> get_csv_line() const;
 
 

+ 55 - 0
core/func_ref.cpp

@@ -0,0 +1,55 @@
+#include "func_ref.h"
+
+Variant FuncRef::call_func(const Variant** p_args, int p_argcount, Variant::CallError& r_error) {
+
+	if (id==0) {
+		r_error.error=Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL;
+		return Variant();
+	}
+	Object* obj = ObjectDB::get_instance(id);
+
+	if (!obj) {
+		r_error.error=Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL;
+		return Variant();
+	}
+
+	return obj->call(function,p_args,p_argcount,r_error);
+
+}
+
+void FuncRef::set_instance(Object *p_obj){
+
+	ERR_FAIL_NULL(p_obj);
+	id=p_obj->get_instance_ID();
+}
+void FuncRef::set_function(const StringName& p_func){
+
+	function=p_func;
+}
+
+void FuncRef::_bind_methods() {
+
+	{
+		MethodInfo mi;
+		mi.name="call";
+		mi.arguments.push_back( PropertyInfo( Variant::STRING, "method"));
+		Vector<Variant> defargs;
+		for(int i=0;i<10;i++) {
+			mi.arguments.push_back( PropertyInfo( Variant::NIL, "arg"+itos(i)));
+			defargs.push_back(Variant());
+		}
+		ObjectTypeDB::bind_native_method(METHOD_FLAGS_DEFAULT,"call_func",&FuncRef::call_func,mi,defargs);
+
+	}
+
+	ObjectTypeDB::bind_method(_MD("set_instance","instance"),&FuncRef::set_instance);
+	ObjectTypeDB::bind_method(_MD("set_function","name"),&FuncRef::set_function);
+
+}
+
+
+FuncRef::FuncRef(){
+
+	id=0;
+}
+

+ 23 - 0
core/func_ref.h

@@ -0,0 +1,23 @@
+#ifndef FUNC_REF_H
+#define FUNC_REF_H
+
+#include "reference.h"
+
+class FuncRef : public Reference{
+
+	OBJ_TYPE(FuncRef,Reference);
+	ObjectID id;
+	StringName function;
+
+protected:
+
+	static void _bind_methods();
+public:
+
+	Variant call_func(const Variant** p_args, int p_argcount, Variant::CallError& r_error);
+	void set_instance(Object *p_obj);
+	void set_function(const StringName& p_func);
+	FuncRef();
+};
+
+#endif // FUNC_REF_H

+ 10 - 13
core/globals.cpp

@@ -166,10 +166,9 @@ bool Globals::_get(const StringName& p_name,Variant &r_ret) const {
 
 	_THREAD_SAFE_METHOD_
 
-	const VariantContainer *v=props.getptr(p_name);
-	if (!v)
+	if (!props.has(p_name))
 		return false;
-	r_ret=v->variant;
+	r_ret=props[p_name].variant;
 	return true;
 	
 }
@@ -188,18 +187,17 @@ void Globals::_get_property_list(List<PropertyInfo> *p_list) const {
 	
 	_THREAD_SAFE_METHOD_
 
-	const String *k=NULL;
 	Set<_VCSort> vclist;
 	
-	while ((k=props.next(k))) {
+	for(Map<StringName,VariantContainer>::Element *E=props.front();E;E=E->next()) {
 		
-		const VariantContainer *v=props.getptr(*k);
+		const VariantContainer *v=&E->get();
 
 		if (v->hide_from_editor)
 			continue;
 
 		_VCSort vc;
-		vc.name=*k;
+		vc.name=E->key();
 		vc.order=v->order;
 		vc.type=v->variant.get_type();
 		if (vc.name.begins_with("input/") || vc.name.begins_with("import/") || vc.name.begins_with("export/") || vc.name.begins_with("/remap") || vc.name.begins_with("/locale") || vc.name.begins_with("/autoload"))
@@ -1138,24 +1136,23 @@ Error Globals::save_custom(const String& p_path,const CustomMap& p_custom,const
 
 	ERR_FAIL_COND_V(p_path=="",ERR_INVALID_PARAMETER);
 
-	const String *k=NULL;
 	Set<_VCSort> vclist;
 
-	while ((k=props.next(k))) {
+	for(Map<StringName,VariantContainer>::Element *G=props.front();G;G=G->next()) {
 
-		const VariantContainer *v=props.getptr(*k);
+		const VariantContainer *v=&G->get();
 
 		if (v->hide_from_editor)
 			continue;
 
-		if (p_custom.has(*k))
+		if (p_custom.has(G->key()))
 			continue;
 
 		bool discard=false;
 
 		for(const Set<String>::Element *E=p_ignore_masks.front();E;E=E->next()) {
 
-			if ( (*k).match(E->get())) {
+			if ( String(G->key()).match(E->get())) {
 				discard=true;
 				break;
 			}
@@ -1165,7 +1162,7 @@ Error Globals::save_custom(const String& p_path,const CustomMap& p_custom,const
 			continue;
 
 		_VCSort vc;
-		vc.name=*k;
+		vc.name=G->key();//*k;
 		vc.order=v->order;
 		vc.type=v->variant.get_type();
 		vc.flags=PROPERTY_USAGE_CHECKABLE|PROPERTY_USAGE_EDITOR|PROPERTY_USAGE_STORAGE;

+ 2 - 2
core/globals.h

@@ -65,9 +65,9 @@ protected:
 	};
 
 	int last_order;
-	HashMap<String,VariantContainer> props;
+	Map<StringName,VariantContainer> props;
 	String resource_path;
-	HashMap<String,PropertyInfo> custom_prop_info;
+	Map<StringName,PropertyInfo> custom_prop_info;
 	bool disable_platform_override;
 	bool using_datapack;
 

+ 0 - 1
core/io/file_access_pack.cpp

@@ -172,7 +172,6 @@ bool PackedSourcePCK::try_open_pack(const String& p_path) {
 		uint64_t size = f->get_64();
 		uint8_t md5[16];
 		f->get_buffer(md5,16);
-
 		PackedData::get_singleton()->add_path(p_path, path, ofs, size, md5,this);
 	};
 

+ 8 - 0
core/io/http_client.cpp

@@ -97,8 +97,16 @@ Error HTTPClient::request( Method p_method, const String& p_url, const Vector<St
 
 	String request=String(_methods[p_method])+" "+p_url+" HTTP/1.1\r\n";
 	request+="Host: "+conn_host+":"+itos(conn_port)+"\r\n";
+	bool add_clen=p_body.length()>0;
 	for(int i=0;i<p_headers.size();i++) {
 		request+=p_headers[i]+"\r\n";
+		if (add_clen && p_headers[i].find("Content-Length:")==0) {
+			add_clen=false;
+		}
+	}
+	if (add_clen) {
+		request+="Content-Length: "+itos(p_body.utf8().length())+"\r\n";
+		//should it add utf8 encoding? not sure
 	}
 	request+="\r\n";
 	request+=p_body;

+ 136 - 12
core/io/marshalls.cpp

@@ -264,26 +264,94 @@ Error decode_variant(Variant& r_variant,const uint8_t *p_buffer, int p_len,int *
 			}
 
 			r_variant=img;
-			if (r_len)
+			if (r_len) {
+				if (datalen%4)
+					(*r_len)+=4-datalen%4;
+
 				(*r_len)+=4*5+datalen;
+			}
 
 		} break;
 		case Variant::NODE_PATH: {
 
-			ERR_FAIL_COND_V(len<4,ERR_INVALID_DATA);
+			ERR_FAIL_COND_V(len<4,ERR_INVALID_DATA);			
 			uint32_t strlen = decode_uint32(buf);
-			buf+=4;
-			len-=4;
-			ERR_FAIL_COND_V((int)strlen>len,ERR_INVALID_DATA);
 
+			if (strlen&0x80000000) {
+				//new format
+				ERR_FAIL_COND_V(len<12,ERR_INVALID_DATA);
+				Vector<StringName> names;
+				Vector<StringName> subnames;
+				bool absolute;
+				StringName prop;
 
-			String str;
-			str.parse_utf8((const char*)buf,strlen);
+				int i=0;
+				uint32_t namecount=strlen&=0x7FFFFFFF;
+				uint32_t subnamecount = decode_uint32(buf+4);
+				uint32_t flags = decode_uint32(buf+8);
 
-			r_variant=NodePath(str);
+				len-=12;
+				buf+=12;
 
-			if (r_len)
-				(*r_len)+=4+strlen;
+				int total=namecount+subnamecount;
+				if (flags&2)
+					total++;
+
+				if (r_len)
+					(*r_len)+=12;
+
+
+				for(int i=0;i<total;i++) {
+
+					ERR_FAIL_COND_V((int)len<4,ERR_INVALID_DATA);
+					strlen = decode_uint32(buf);
+
+					int pad=0;
+
+					if (strlen%4)
+						pad+=4-strlen%4;
+
+					buf+=4;
+					len-=4;
+					ERR_FAIL_COND_V((int)strlen+pad>len,ERR_INVALID_DATA);
+
+					String str;
+					str.parse_utf8((const char*)buf,strlen);
+
+
+					if (i<namecount)
+						names.push_back(str);
+					else if (i<namecount+subnamecount)
+						subnames.push_back(str);
+					else
+						prop=str;
+
+					buf+=strlen+pad;
+					len-=strlen+pad;
+
+					if (r_len)
+						(*r_len)+=4+strlen+pad;
+
+				}
+
+				r_variant=NodePath(names,subnames,flags&1,prop);
+
+			} else {
+				//old format, just a string
+
+				buf+=4;
+				len-=4;
+				ERR_FAIL_COND_V((int)strlen>len,ERR_INVALID_DATA);
+
+
+				String str;
+				str.parse_utf8((const char*)buf,strlen);
+
+				r_variant=NodePath(str);
+
+				if (r_len)
+					(*r_len)+=4+strlen;
+			}
 
 		} break;
 		/*case Variant::RESOURCE: {
@@ -713,7 +781,59 @@ Error encode_variant(const Variant& p_variant, uint8_t *r_buffer, int &r_len) {
 			r_len+=4;
 
 		} break;
-		case Variant::NODE_PATH:
+		case Variant::NODE_PATH: {
+
+			NodePath np=p_variant;
+			if (buf) {
+				encode_uint32(uint32_t(np.get_name_count())|0x80000000,buf);	//for compatibility with the old format
+				encode_uint32(np.get_subname_count(),buf+4);
+				uint32_t flags=0;
+				if (np.is_absolute())
+					flags|=1;
+				if (np.get_property()!=StringName())
+					flags|=2;
+
+				encode_uint32(flags,buf+8);
+
+				buf+=12;
+			}
+
+			r_len+=12;
+
+			int total = np.get_name_count()+np.get_subname_count();
+			if (np.get_property()!=StringName())
+				total++;
+
+			for(int i=0;i<total;i++) {
+
+				String str;
+
+				if (i<np.get_name_count())
+					str=np.get_name(i);
+				else if (i<np.get_name_count()+np.get_subname_count())
+					str=np.get_subname(i-np.get_subname_count());
+				else
+					str=np.get_property();
+
+				CharString utf8 = str.utf8();
+
+				int pad = 0;
+
+				if (utf8.length()%4)
+					pad=4-utf8.length()%4;
+
+				if (buf) {
+					encode_uint32(utf8.length(),buf);
+					buf+=4;
+					copymem(buf,utf8.get_data(),utf8.length());
+					buf+=pad+utf8.length();
+				}
+
+
+				r_len+=4+utf8.length()+pad;
+			}
+
+		} break;
 		case Variant::STRING: {
 
 
@@ -879,7 +999,11 @@ Error encode_variant(const Variant& p_variant, uint8_t *r_buffer, int &r_len) {
 				copymem(&buf[20],&r[0],ds);
 			}
 
-			r_len+=data.size()+5*4;
+			int pad=0;
+			if (data.size()%4)
+				pad=4-data.size()%4;
+
+			r_len+=data.size()+5*4+pad;
 
 		} break;		
 		/*case Variant::RESOURCE: {

+ 26 - 8
core/io/resource_format_binary.cpp

@@ -647,7 +647,7 @@ Error ResourceInteractiveLoaderBinary::poll(){
 		}
 
 		stage++;
-		return OK;
+		return error;
 	}
 
 	s-=external_resources.size();
@@ -804,7 +804,12 @@ void ResourceInteractiveLoaderBinary::get_dependencies(FileAccess *p_f,List<Stri
 
 	for(int i=0;i<external_resources.size();i++) {
 
-		p_dependencies->push_back(external_resources[i].path);
+		String dep=external_resources[i].path;
+		if (dep.ends_with("*")) {
+			dep=ResourceLoader::guess_full_filename(dep,external_resources[i].type);
+		}
+
+		p_dependencies->push_back(dep);
 	}
 
 }
@@ -892,6 +897,19 @@ void ResourceInteractiveLoaderBinary::open(FileAccess *p_f) {
 
 	}
 
+	//see if the exporter has different set of external resources for more efficient loading
+	String preload_depts = "deps/"+res_path.md5_text();
+	if (Globals::get_singleton()->has(preload_depts)) {
+		external_resources.clear();
+		//ignore external resources and use these
+		NodePath depts=Globals::get_singleton()->get(preload_depts);
+		external_resources.resize(depts.get_name_count());
+		for(int i=0;i<depts.get_name_count();i++) {
+			external_resources[i].path=depts.get_name(i);
+		}
+		print_line(res_path+" - EXTERNAL RESOURCES: "+itos(external_resources.size()));
+	}
+
 	print_bl("ext resources: "+itos(ext_resources_size));
 	uint32_t int_resources_size=f->get_32();
 
@@ -931,6 +949,7 @@ String ResourceInteractiveLoaderBinary::recognize(FileAccess *p_f) {
 
 	} else if (header[0]!='R' || header[1]!='S' || header[2]!='R' || header[3]!='C') {
 		//not normal
+		error=ERR_FILE_UNRECOGNIZED;
 		return "";
 	}
 
@@ -1412,8 +1431,6 @@ void ResourceFormatSaverBinaryInstance::write_variant(const Variant& p_property,
 				f->store_32(OBJECT_EXTERNAL_RESOURCE);
 				save_unicode_string(res->get_save_type());
 				String path=relative_paths?local_path.path_to_file(res->get_path()):res->get_path();
-				if (no_extensions)
-					path=path.basename()+".*";
 				save_unicode_string(path);
 			} else {
 
@@ -1439,7 +1456,7 @@ void ResourceFormatSaverBinaryInstance::write_variant(const Variant& p_property,
 
 			f->store_32(VARIANT_DICTIONARY);
 			Dictionary d = p_property;
-            f->store_32(uint32_t(d.size())|(d.is_shared()?0x80000000:0));
+			f->store_32(uint32_t(d.size())|(d.is_shared()?0x80000000:0));
 
 			List<Variant> keys;
 			d.get_key_list(&keys);
@@ -1734,7 +1751,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path,const RES& p_
 	skip_editor=p_flags&ResourceSaver::FLAG_OMIT_EDITOR_PROPERTIES;
 	bundle_resources=p_flags&ResourceSaver::FLAG_BUNDLE_RESOURCES;
 	big_endian=p_flags&ResourceSaver::FLAG_SAVE_BIG_ENDIAN;
-	no_extensions=p_flags&ResourceSaver::FLAG_NO_EXTENSION;
+
 
 	local_path=p_path.get_base_dir();
 	//bin_meta_idx = get_string_index("__bin_meta__"); //is often used, so create
@@ -1816,8 +1833,6 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path,const RES& p_
 
 		save_unicode_string(E->get()->get_save_type());
 		String path = E->get()->get_path();
-		if (no_extensions)
-			path=path.basename()+".*";
 		save_unicode_string(path);
 	}
 	// save internal resource table
@@ -1861,6 +1876,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path,const RES& p_
 	}
 
 	f->seek_end();
+	print_line("SAVING: "+p_path);
 	if (p_resource->get_import_metadata().is_valid()) {
 		uint64_t md_pos = f->get_pos();
 		Ref<ResourceImportMetadata> imd=p_resource->get_import_metadata();
@@ -1869,6 +1885,8 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path,const RES& p_
 		for(int i=0;i<imd->get_source_count();i++) {
 			save_unicode_string(imd->get_source_path(i));
 			save_unicode_string(imd->get_source_md5(i));
+			print_line("SAVE PATH: "+imd->get_source_path(i));
+			print_line("SAVE MD5: "+imd->get_source_md5(i));
 		}
 		List<String> options;
 		imd->get_options(&options);

+ 1 - 1
core/io/resource_format_binary.h

@@ -120,7 +120,7 @@ class ResourceFormatSaverBinaryInstance  {
 
 	String local_path;
 
-	bool no_extensions;
+
 	bool relative_paths;
 	bool bundle_resources;
 	bool skip_editor;

+ 45 - 6
core/io/resource_format_xml.cpp

@@ -1357,6 +1357,31 @@ Error ResourceInteractiveLoaderXML::poll() {
 	if (error!=OK)
 		return error;
 
+	if (ext_resources.size()) {
+
+		error=ERR_FILE_CORRUPT;
+		String path=ext_resources.front()->get();
+
+		RES res = ResourceLoader::load(path);
+
+		if (res.is_null()) {
+
+			if (ResourceLoader::get_abort_on_missing_resources()) {
+				ERR_EXPLAIN(local_path+":"+itos(get_current_line())+": editor exported unexisting resource at: "+path);
+				ERR_FAIL_V(error);
+			} else {
+				ResourceLoader::notify_load_error("Resource Not Found: "+path);
+			}
+		} else {
+
+			resource_cache.push_back(res);
+		}
+
+		error=OK;
+		ext_resources.pop_front();
+		resource_current++;
+		return error;
+	}
 
 	bool exit;
 	Tag *tag = parse_tag(&exit);
@@ -1528,7 +1553,7 @@ int ResourceInteractiveLoaderXML::get_stage() const {
 }
 int ResourceInteractiveLoaderXML::get_stage_count() const {
 
-	return resources_total;
+	return resources_total+ext_resources.size();
 }
 
 ResourceInteractiveLoaderXML::~ResourceInteractiveLoaderXML() {
@@ -1573,6 +1598,12 @@ void ResourceInteractiveLoaderXML::get_dependencies(FileAccess *f,List<String> *
 			path=Globals::get_singleton()->localize_path(local_path.get_base_dir()+"/"+path);
 		}
 
+		if (path.ends_with("*")) {
+			ERR_FAIL_COND(!tag->args.has("type"));
+			String type = tag->args["type"];
+			path = ResourceLoader::guess_full_filename(path,type);
+		}
+
 		p_dependencies->push_back(path);
 
 		Error err = close_tag("ext_resource");
@@ -1642,6 +1673,19 @@ void ResourceInteractiveLoaderXML::open(FileAccess *p_f) {
 
 	}
 
+	String preload_depts = "deps/"+local_path.md5_text();
+	if (Globals::get_singleton()->has(preload_depts)) {
+		ext_resources.clear();
+		//ignore external resources and use these
+		NodePath depts=Globals::get_singleton()->get(preload_depts);
+
+		for(int i=0;i<depts.get_name_count();i++) {
+			ext_resources.push_back(depts.get_name(i));
+		}
+		print_line(local_path+" - EXTERNAL RESOURCES: "+itos(ext_resources.size()));
+	}
+
+
 }
 
 String ResourceInteractiveLoaderXML::recognize(FileAccess *p_f) {
@@ -1969,8 +2013,6 @@ void ResourceFormatSaverXMLInstance::write_property(const String& p_name,const V
 			if (res->get_path().length() && res->get_path().find("::")==-1) {
 				//external resource
 				String path=relative_paths?local_path.path_to_file(res->get_path()):res->get_path();
-				if (no_extension)
-					path=path.basename()+".*";
 				escape(path);
 				params+=" path=\""+path+"\"";
 			} else {
@@ -2458,7 +2500,6 @@ Error ResourceFormatSaverXMLInstance::save(const String &p_path,const RES& p_res
 	relative_paths=p_flags&ResourceSaver::FLAG_RELATIVE_PATHS;
 	skip_editor=p_flags&ResourceSaver::FLAG_OMIT_EDITOR_PROPERTIES;
 	bundle_resources=p_flags&ResourceSaver::FLAG_BUNDLE_RESOURCES;
-	no_extension=p_flags&ResourceSaver::FLAG_NO_EXTENSION;
 	depth=0;
 
 	// save resources
@@ -2475,8 +2516,6 @@ Error ResourceFormatSaverXMLInstance::save(const String &p_path,const RES& p_res
 
 		write_tabs();
 		String p = E->get()->get_path();
-		if (no_extension)
-			p=p.basename()+".*";
 
 		enter_tag("ext_resource","path=\""+p+"\" type=\""+E->get()->get_save_type()+"\""); //bundled
 		exit_tag("ext_resource"); //bundled

+ 4 - 1
core/io/resource_format_xml.h

@@ -50,6 +50,10 @@ class ResourceInteractiveLoaderXML : public ResourceInteractiveLoader {
 
 	_FORCE_INLINE_ Error _parse_array_element(Vector<char> &buff,bool p_number_only,FileAccess *f,bool *end);
 
+
+
+	List<StringName> ext_resources;
+
 	int resources_total;
 	int resource_current;
 	String resource_type;
@@ -113,7 +117,6 @@ class ResourceFormatSaverXMLInstance  {
 
 
 
-	bool no_extension;
 	bool relative_paths;
 	bool bundle_resources;
 	bool skip_editor;

+ 12 - 1
core/io/resource_loader.cpp

@@ -166,7 +166,7 @@ RES ResourceLoader::load(const String &p_path,const String& p_type_hint,bool p_n
 	String remapped_path = PathRemap::get_singleton()->get_remap(local_path);
 
 	if (OS::get_singleton()->is_stdout_verbose())
-		print_line("load resource: ");
+		print_line("load resource: "+remapped_path);
 
 	String extension=remapped_path.extension();
 	bool found=false;
@@ -233,6 +233,10 @@ Ref<ResourceImportMetadata> ResourceLoader::load_import_metadata(const String &p
 
 
 String ResourceLoader::find_complete_path(const String& p_path,const String& p_type) {
+	//this is an old vestige when the engine saved files without extension.
+	//remains here for compatibility with old projects and only because it
+	//can be sometimes nice to open files using .* from a script and have it guess
+	//the right extension.
 
 	String local_path = p_path;
 	if (local_path.ends_with("*")) {
@@ -353,6 +357,13 @@ void ResourceLoader::get_dependencies(const String& p_path,List<String> *p_depen
 	}
 }
 
+String ResourceLoader::guess_full_filename(const String &p_path,const String& p_type) {
+
+	String local_path = Globals::get_singleton()->localize_path(p_path);
+
+	return find_complete_path(local_path,p_type);
+
+}
 
 String ResourceLoader::get_resource_type(const String &p_path) {
 

+ 1 - 0
core/io/resource_loader.h

@@ -102,6 +102,7 @@ public:
 	static String get_resource_type(const String &p_path);
 	static void get_dependencies(const String& p_path,List<String> *p_dependencies);
 
+	static String guess_full_filename(const String &p_path,const String& p_type);
 
 	static void set_timestamp_on_load(bool p_timestamp) { timestamp_on_load=p_timestamp; }
 

+ 0 - 3
core/io/resource_saver.h

@@ -74,9 +74,6 @@ public:
 		FLAG_OMIT_EDITOR_PROPERTIES=8,
 		FLAG_SAVE_BIG_ENDIAN=16,
 		FLAG_COMPRESS=32,
-		FLAG_NO_EXTENSION=64,
-
-
 	};
 
 

+ 9 - 2
core/math/math_funcs.cpp

@@ -220,9 +220,16 @@ int Math::decimals(double p_step) {
 
 double Math::ease(double p_x, double p_c) {
 
+	if (p_x<0)
+		p_x=0;
+	else if (p_x>1.0)
+		p_x=1.0;
 	if (p_c>0) {
-
-		return Math::pow(p_x,p_c);
+		if (p_c<1.0) {
+			return 1.0-Math::pow(1.0-p_x,1.0/p_c);
+		} else {
+			return Math::pow(p_x,p_c);
+		}
 	} else  if (p_c<0) {
 		//inout ease
 

+ 2 - 1
core/message_queue.cpp

@@ -378,11 +378,12 @@ void MessageQueue::flush() {
 			}
 
 		}
-		message->~Message();
 
 		read_pos+=sizeof(Message);
 		if (message->type!=TYPE_NOTIFICATION)
 			read_pos+=sizeof(Variant)*message->args;
+		message->~Message();
+
 		_THREAD_SAFE_UNLOCK_
 
 	}

+ 52 - 2
core/object.cpp

@@ -33,6 +33,30 @@
 #include "message_queue.h"
 #include "core_string_names.h"
 #include "translation.h"
+
+#ifdef DEBUG_ENABLED
+
+struct _ObjectDebugLock {
+
+	Object *obj;
+
+	_ObjectDebugLock(Object *p_obj) {
+		obj=p_obj;
+		obj->_lock_index.ref();
+	}
+	~_ObjectDebugLock() {
+		obj->_lock_index.unref();
+	}
+};
+
+#define OBJ_DEBUG_LOCK _ObjectDebugLock _debug_lock(this);
+
+#else
+
+#define OBJ_DEBUG_LOCK
+
+#endif
+
 Array convert_property_list(const List<PropertyInfo> * p_list) {
 
 	Array va;
@@ -562,13 +586,22 @@ void Object::call_multilevel(const StringName& p_method,const Variant** p_args,i
 			ERR_FAIL();
 			return;
 		}
+
+
+		if (_lock_index.get()>1) {
+			ERR_EXPLAIN("Object is locked and can't be freed.");
+			ERR_FAIL();
+			return;
+		}
 #endif
+
 		//must be here, must be before everything,
 		memdelete(this);
 		return;
 	}
 
 	//Variant ret;
+	OBJ_DEBUG_LOCK
 
 	Variant::CallError error;
 
@@ -594,6 +627,7 @@ void Object::call_multilevel_reversed(const StringName& p_method,const Variant**
 	MethodBind *method=ObjectTypeDB::get_method(get_type_name(),p_method);
 
 	Variant::CallError error;
+	OBJ_DEBUG_LOCK
 
 	if (method) {
 
@@ -813,6 +847,15 @@ Variant Object::call(const StringName& p_method,const Variant** p_args,int p_arg
 			ERR_EXPLAIN("Can't 'free' a reference.");
 			ERR_FAIL_V(Variant());
 		}
+
+		if (_lock_index.get()>1) {
+			r_error.argument=0;
+			r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD;
+			ERR_EXPLAIN("Object is locked and can't be freed.");
+			ERR_FAIL_V(Variant());
+
+		}
+
 #endif
 		//must be here, must be before everything,
 		memdelete(this);
@@ -821,7 +864,7 @@ Variant Object::call(const StringName& p_method,const Variant** p_args,int p_arg
 	}
 
 	Variant ret;
-
+	OBJ_DEBUG_LOCK
 	if (script_instance) {
 		ret = script_instance->call(p_method,p_args,p_argcount,r_error);
 		//force jumptable
@@ -902,7 +945,7 @@ void Object::set_script(const RefPtr& p_script) {
 	Ref<Script> s(script);
 
 	if (!s.is_null() && s->can_instance() ) {
-		
+		OBJ_DEBUG_LOCK
 		script_instance = s->instance_create(this);
 
 	}
@@ -1066,6 +1109,8 @@ void Object::emit_signal(const StringName& p_name,VARIANT_ARG_DECLARE) {
 
 	int ssize = slot_map.size();
 
+	OBJ_DEBUG_LOCK
+
 	for(int i=0;i<ssize;i++) {
 
 		const Connection &c = slot_map.getv(i).conn;
@@ -1536,6 +1581,11 @@ Object::Object() {
 	_edited=false;
 #endif
 
+#ifdef DEBUG_ENABLED
+	_lock_index.init(1);
+#endif
+
+
 
 }
 

+ 6 - 2
core/object.h

@@ -331,7 +331,9 @@ public:
 		Connection(const Variant& p_variant);
 	};
 private:
-
+#ifdef DEBUG_ENABLED
+friend class _ObjectDebugLock;
+#endif
 friend bool predelete_handler(Object*);
 friend void postinitialize_handler(Object*);
 
@@ -365,7 +367,9 @@ friend void postinitialize_handler(Object*);
 
 	HashMap< StringName, Signal, StringNameHasher> signal_map;
 	List<Connection> connections;
-
+#ifdef DEBUG_ENABLED
+	SafeRefCount _lock_index;
+#endif
 	bool _block_signals;
 	int _predelete_ok;
 	Set<Object*> change_receptors;

+ 23 - 1
core/os/file_access.cpp

@@ -428,8 +428,30 @@ void FileAccess::store_string(const String& p_string) {
 	CharString cs=p_string.utf8();
 	store_buffer((uint8_t*)&cs[0],cs.length());
 
-
 }
+
+void FileAccess::store_pascal_string(const String& p_string) {
+
+	CharString cs = p_string.utf8();
+	store_32(cs.length());
+	store_buffer((uint8_t*)&cs[0], cs.length());
+};
+
+String FileAccess::get_pascal_string() {
+
+	uint32_t sl = get_32();
+	CharString cs;
+	cs.resize(sl+1);
+	get_buffer((uint8_t*)cs.ptr(),sl);
+	cs[sl]=0;
+
+	String ret;
+	ret.parse_utf8(cs.ptr());
+
+	return ret;
+};
+
+
 void FileAccess::store_line(const String& p_line) {
 
 	store_string(p_line);

+ 3 - 0
core/os/file_access.h

@@ -125,6 +125,9 @@ public:
 	virtual void store_string(const String& p_string);
 	virtual void store_line(const String& p_string);
 
+	virtual void store_pascal_string(const String& p_string);
+	virtual String get_pascal_string();
+
 	virtual void store_buffer(const uint8_t *p_src,int p_length); ///< store an array of bytes 
 	
 	virtual bool file_exists(const String& p_name)=0; ///< return true if a file exists 

+ 7 - 0
core/os/input.cpp

@@ -56,6 +56,7 @@ void Input::_bind_methods() {
 	ObjectTypeDB::bind_method(_MD("get_accelerometer"),&Input::get_accelerometer);
 	ObjectTypeDB::bind_method(_MD("get_mouse_pos"),&Input::get_mouse_pos);
 	ObjectTypeDB::bind_method(_MD("get_mouse_speed"),&Input::get_mouse_speed);
+	ObjectTypeDB::bind_method(_MD("get_mouse_button_mask"),&Input::get_mouse_button_mask);
 	ObjectTypeDB::bind_method(_MD("set_mouse_mode","mode"),&Input::set_mouse_mode);
 	ObjectTypeDB::bind_method(_MD("get_mouse_mode"),&Input::get_mouse_mode);
 
@@ -280,6 +281,12 @@ Point2 InputDefault::get_mouse_speed() const {
 	return mouse_speed_track.speed;
 }
 
+int InputDefault::get_mouse_button_mask() const {
+
+	return OS::get_singleton()->get_mouse_button_state();
+}
+
+
 void InputDefault::iteration(float p_step) {
 
 

+ 2 - 0
core/os/input.h

@@ -64,6 +64,7 @@ public:
 
 	virtual Point2 get_mouse_pos() const=0;
 	virtual Point2 get_mouse_speed() const=0;
+	virtual int get_mouse_button_mask() const=0;
 
 	virtual Vector3 get_accelerometer()=0;
 
@@ -120,6 +121,7 @@ public:
 
 	virtual Point2 get_mouse_pos() const;
 	virtual Point2 get_mouse_speed() const;
+	virtual int get_mouse_button_mask() const;
 
 	void parse_input_event(const InputEvent& p_event);
 	void set_accelerometer(const Vector3& p_accel);

+ 1 - 1
core/os/mutex.h

@@ -50,7 +50,7 @@ public:
 
 	virtual void lock()=0; ///< Lock the mutex, block if locked by someone else
 	virtual void unlock()=0; ///< Unlock the mutex, let other threads continue
-	virtual Error try_lock()=0; ///< Attempt to lock the mutex, true on success, false means it can't lock.
+	virtual Error try_lock()=0; ///< Attempt to lock the mutex, OK on success, ERROR means it can't lock.
 
 	static Mutex * create(bool p_recursive=true); ///< Create a mutex
 	

+ 24 - 1
core/os/os.cpp

@@ -92,6 +92,14 @@ int OS::get_iterations_per_second() const {
 	return ips;
 }
 
+void OS::set_target_fps(int p_fps) {
+	_target_fps=p_fps>0? p_fps : 0;
+}
+
+float OS::get_target_fps() const {
+	return _target_fps;
+}
+
 void OS::set_low_processor_usage_mode(bool p_enabled) {
 
 	low_processor_usage_mode=p_enabled;
@@ -116,6 +124,11 @@ String OS::get_executable_path() const {
 	return _execpath;
 }
 
+int OS::get_process_ID() const {
+
+	return -1;
+};
+
 uint64_t OS::get_frames_drawn() {
 
 	return frames_drawn;
@@ -430,7 +443,7 @@ Error OS::native_video_play(String p_path) {
 	return FAILED;
 };
 
-bool OS::native_video_is_playing() {
+bool OS::native_video_is_playing() const {
 
 	return false;
 };
@@ -447,6 +460,15 @@ void OS::set_mouse_mode(MouseMode p_mode) {
 
 }
 
+bool OS::can_use_threads() const {
+
+#ifdef NO_THREADS
+	return false;
+#else
+	return true;
+#endif
+}
+
 OS::MouseMode OS::get_mouse_mode() const{
 
 	return MOUSE_MODE_VISIBLE;
@@ -465,6 +487,7 @@ OS::OS() {
 	_exit_code=0;
 	_orientation=SCREEN_LANDSCAPE;
 	_fps=1;
+	_target_fps=0;
 	_render_thread_mode=RENDER_THREAD_SAFE;
 	Math::seed(1234567);
 }

+ 9 - 1
core/os/os.h

@@ -54,6 +54,7 @@ class OS {
 	int _exit_code;
 	int _orientation;
 	float _fps;
+	int _target_fps;
 
 	char *last_error;
 
@@ -149,14 +150,19 @@ public:
 	virtual void set_iterations_per_second(int p_ips);
 	virtual int get_iterations_per_second() const;
 
+	virtual void set_target_fps(int p_fps);
+	virtual float get_target_fps() const;
+
 	virtual float get_frames_per_second() const { return _fps; };
 
+
 	virtual void set_low_processor_usage_mode(bool p_enabled);
 	virtual bool is_in_low_processor_usage_mode() const;
 
 	virtual String get_executable_path() const;
 	virtual Error execute(const String& p_path, const List<String>& p_arguments,bool p_blocking,ProcessID *r_child_id=NULL,String* r_pipe=NULL,int *r_exitcode=NULL)=0;
 	virtual Error kill(const ProcessID& p_pid)=0;
+	virtual int get_process_ID() const;
 
 	virtual Error shell_open(String p_uri);
 	virtual Error set_cwd(const String& p_cwd);
@@ -316,10 +322,12 @@ public:
 	virtual String get_unique_ID() const;
 
 	virtual Error native_video_play(String p_path);
-	virtual bool native_video_is_playing();
+	virtual bool native_video_is_playing() const;
 	virtual void native_video_pause();
 	virtual void native_video_stop();
 
+	virtual bool can_use_threads() const;
+
 	virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, Object* p_obj, String p_callback);
 	virtual Error dialog_input_text(String p_title, String p_description, String p_partial, Object* p_obj, String p_callback);
 

+ 2 - 0
core/register_core_types.cpp

@@ -49,6 +49,7 @@
 #include "core/io/xml_parser.h"
 #include "io/http_client.h"
 #include "packed_data_container.h"
+#include "func_ref.h"
 
 #ifdef XML_ENABLED
 static ResourceFormatSaverXML *resource_saver_xml=NULL;
@@ -135,6 +136,7 @@ void register_core_types() {
 	ObjectTypeDB::register_type<Reference>();
 	ObjectTypeDB::register_type<ResourceImportMetadata>();
 	ObjectTypeDB::register_type<Resource>();
+	ObjectTypeDB::register_type<FuncRef>();
 	ObjectTypeDB::register_virtual_type<StreamPeer>();
 	ObjectTypeDB::register_create_type<StreamPeerTCP>();
 	ObjectTypeDB::register_create_type<TCP_Server>();

+ 10 - 0
core/ustring.cpp

@@ -31,6 +31,7 @@
 #include "os/memory.h"
 #include "print_string.h"
 #include "math_funcs.h"
+#include "io/md5.h"
 #include "ucaps.h"
 #include "color.h"
 #define MAX_DIGITS 6
@@ -2264,6 +2265,15 @@ uint64_t String::hash64() const {
 
 }
 
+String String::md5_text() const {
+
+	CharString cs=utf8();
+	MD5_CTX ctx;
+	MD5Init(&ctx);
+	MD5Update(&ctx,(unsigned char*)cs.ptr(),cs.length());
+	MD5Final(&ctx);
+	return String::md5(ctx.digest);
+}
 
 String String::insert(int p_at_pos,String p_string) const {
 

+ 2 - 1
core/ustring.h

@@ -181,7 +181,8 @@ public:
 	static uint32_t hash(const char* p_cstr,int p_len); /* hash the string */
 	static uint32_t hash(const char* p_cstr); /* hash the string */
 	uint32_t hash() const; /* hash the string */
-	uint64_t hash64() const; /* hash the string */
+	uint64_t hash64() const; /* hash the string */	
+	String md5_text() const;
 	
 	inline bool empty() const { return length() == 0; }	
 

+ 1 - 1
core/variant.cpp

@@ -56,7 +56,7 @@ String Variant::get_type_name(Variant::Type p_type) {
 		} break;
 		case REAL: {
 
-			return "real";
+			return "float";
 
 		} break;
 		case STRING: {

+ 2 - 0
core/variant_call.cpp

@@ -600,6 +600,7 @@ static void _call_##m_type##_##m_method(Variant& r_ret,Variant& p_self,const Var
 	VCALL_PTR0R( Matrix3, determinant );
 	VCALL_PTR2R( Matrix3, rotated );
 	VCALL_PTR1R( Matrix3, scaled );
+	VCALL_PTR0R( Matrix3, get_scale );
 	VCALL_PTR0R( Matrix3, get_euler );
 	VCALL_PTR1R( Matrix3, tdotx );
 	VCALL_PTR1R( Matrix3, tdoty );
@@ -1390,6 +1391,7 @@ _VariantCall::addfunc(Variant::m_vtype,Variant::m_ret,_SCS(#m_method),VCALL(m_cl
 	ADDFUNC0(MATRIX3,REAL,Matrix3,determinant,varray());
 	ADDFUNC2(MATRIX3,MATRIX3,Matrix3,rotated,VECTOR3,"axis",REAL,"phi",varray());
 	ADDFUNC1(MATRIX3,MATRIX3,Matrix3,scaled,VECTOR3,"scale",varray());
+	ADDFUNC0(MATRIX3,VECTOR3,Matrix3,get_scale,varray());
 	ADDFUNC0(MATRIX3,VECTOR3,Matrix3,get_euler,varray());
 	ADDFUNC1(MATRIX3,REAL,Matrix3,tdotx,VECTOR3,"with",varray());
 	ADDFUNC1(MATRIX3,REAL,Matrix3,tdoty,VECTOR3,"with",varray());

+ 2 - 2
core/variant_op.cpp

@@ -103,8 +103,8 @@ case m_name: {\
 		case BOOL: _RETURN( p_a._data.m_type m_op p_b._data._bool);\
 		case INT: _RETURN( p_a._data.m_type m_op p_b._data._int);\
 		case REAL: _RETURN( p_a._data.m_type m_op p_b._data._real);\
-		case VECTOR2: _RETURN( p_a._data.m_type m_op *reinterpret_cast<const Vector2*>(p_a._data._mem));\
-		case VECTOR3: _RETURN( p_a._data.m_type m_op *reinterpret_cast<const Vector3*>(p_a._data._mem));\
+		case VECTOR2: _RETURN( p_a._data.m_type m_op *reinterpret_cast<const Vector2*>(p_b._data._mem));\
+		case VECTOR3: _RETURN( p_a._data.m_type m_op *reinterpret_cast<const Vector3*>(p_b._data._mem));\
 		default: {}\
 	}\
 	r_valid=false;\

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 87 - 87
demos/2d/platformer/player.xml


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 13 - 12
demos/2d/platformer/stage.xml


+ 1 - 1
doc/base/classes.xml

@@ -26402,7 +26402,7 @@
 			<description>
 			</description>
 		</signal>
-		<signal name="release">
+		<signal name="released">
 			<description>
 			</description>
 		</signal>

+ 1 - 1
drivers/mpc/audio_stream_mpc.cpp

@@ -140,7 +140,7 @@ mpc_bool_t AudioStreamMPC::_mpc_canseek(mpc_reader *p_reader) {
 
 bool AudioStreamMPC::_can_mix() const {
 
-	return active && !paused;
+	return /*active &&*/ !paused;
 }
 
 

+ 111 - 0
drivers/openssl/stream_peer_ssl.cpp

@@ -0,0 +1,111 @@
+#include "stream_peer_ssl.h"
+
+
+int StreamPeerSSL::bio_create( BIO *b ) {
+	b->init = 1;
+	b->num = 0;
+	b->ptr = NULL;
+	b->flags = 0;
+	return 1;
+}
+
+int StreamPeerSSL::bio_destroy( BIO *b ) {
+
+	if ( b == NULL ) return 0;
+	b->ptr = NULL;		/* sb_tls_remove() will free it */
+	b->init = 0;
+	b->flags = 0;
+	return 1;
+}
+
+int StreamPeerSSL::bio_read( BIO *b, char *buf, int len ) {
+
+	if ( buf == NULL || len <= 0 ) return 0;
+
+	StreamPeerSSL * sp = (StreamPeerSSL*)b->ptr;
+
+	if (sp->base.is_null())
+		return 0;
+
+
+
+	BIO_clear_retry_flags( b );
+
+	Error err;
+	int ret=0;
+	if (sp->block) {
+		err = sp->base->get_data((const uint8_t*)buf,len);
+		if (err==OK)
+			ret=len;
+	} else {
+
+		err = sp->base->get_partial_data((const uint8_t*)buf,len,ret);
+		if (err==OK && ret!=len) {
+			BIO_set_retry_write( b );
+		}
+
+	}
+
+	return ret;
+}
+
+int StreamPeerSSL::bio_write( BIO *b, const char *buf, int len ) {
+
+	if ( buf == NULL || len <= 0 ) return 0;
+
+	StreamPeerSSL * sp = (StreamPeerSSL*)b->ptr;
+
+	if (sp->base.is_null())
+		return 0;
+
+	BIO_clear_retry_flags( b );
+
+	Error err;
+	int wrote=0;
+	if (sp->block) {
+		err = sp->base->put_data((const uint8_t*)buf,len);
+		if (err==OK)
+			wrote=len;
+	} else {
+
+		err = sp->base->put_partial_data((const uint8_t*)buf,len,wrote);
+		if (err==OK && wrote!=len) {
+			BIO_set_retry_write( b );
+		}
+
+	}
+
+	return wrote;
+}
+
+long StreamPeerSSL::bio_ctrl( BIO *b, int cmd, long num, void *ptr ) {
+	if ( cmd == BIO_CTRL_FLUSH ) {
+		/* The OpenSSL library needs this */
+		return 1;
+	}
+	return 0;
+}
+
+int StreamPeerSSL::bio_gets( BIO *b, char *buf, int len ) {
+	return -1;
+}
+
+int StreamPeerSSL::bio_puts( BIO *b, const char *str ) {
+	return StreamPeerSSL::bio_write( b, str, strlen( str ) );
+}
+
+BIO_METHOD StreamPeerSSL::bio_methods =
+{
+	( 100 | 0x400 ),		/* it's a source/sink BIO */
+	"sockbuf glue",
+	StreamPeerSSL::bio_write,
+	StreamPeerSSL::bio_read,
+	StreamPeerSSL::bio_puts,
+	StreamPeerSSL::bio_gets,
+	StreamPeerSSL::bio_ctrl,
+	StreamPeerSSL::bio_create,
+	StreamPeerSSL::bio_destroy
+};
+
+StreamPeerSSL::StreamPeerSSL() {
+}

+ 26 - 0
drivers/openssl/stream_peer_ssl.h

@@ -0,0 +1,26 @@
+#ifndef STREAM_PEER_SSL_H
+#define STREAM_PEER_SSL_H
+
+#include "io/stream_peer.h"
+
+class StreamPeerSSL : public StreamPeer {
+
+	OBJ_TYPE(StreamPeerSSL,StreamPeer);
+
+	Ref<StreamPeer> base;
+	bool block;
+	static BIO_METHOD bio_methods;
+
+	static int bio_create( BIO *b );
+	static int bio_destroy( BIO *b );
+	static int bio_read( BIO *b, char *buf, int len );
+	static int bio_write( BIO *b, const char *buf, int len );
+	static long bio_ctrl( BIO *b, int cmd, long num, void *ptr );
+	static int bio_gets( BIO *b, char *buf, int len );
+	static int bio_puts( BIO *b, const char *str );
+
+public:
+	StreamPeerSSL();
+};
+
+#endif // STREAM_PEER_SSL_H

+ 6 - 0
drivers/unix/os_unix.cpp

@@ -333,6 +333,12 @@ Error OS_Unix::kill(const ProcessID& p_pid) {
 	return ret?ERR_INVALID_PARAMETER:OK;
 }
 
+int OS_Unix::get_process_ID() const {
+
+	return getpid();
+};
+
+
 bool OS_Unix::has_environment(const String& p_var) const {
 
 	return getenv(p_var.utf8().get_data())!=NULL;

+ 1 - 0
drivers/unix/os_unix.h

@@ -98,6 +98,7 @@ public:
 
 	virtual Error execute(const String& p_path, const List<String>& p_arguments,bool p_blocking,ProcessID *r_child_id=NULL,String* r_pipe=NULL,int *r_exitcode=NULL);
 	virtual Error kill(const ProcessID& p_pid);
+	virtual int get_process_ID() const;
 
 	virtual bool has_environment(const String& p_var) const;
 	virtual String get_environment(const String& p_var) const;

+ 8 - 2
drivers/vorbis/audio_stream_ogg_vorbis.cpp

@@ -97,7 +97,7 @@ long AudioStreamOGGVorbis::_ov_tell_func(void *_f) {
 
 bool AudioStreamOGGVorbis::_can_mix() const {
 
-	return playing && !paused;
+	return /*playing &&*/ !paused;
 }
 
 
@@ -125,6 +125,8 @@ void AudioStreamOGGVorbis::update() {
 		if (ret<0) {
 
 			playing = false;
+			setting_up=false;
+
 			ERR_EXPLAIN("Error reading OGG Vorbis File: "+file);
 			ERR_BREAK(ret<0);
 		} else if (ret==0) { // end of song, reload?
@@ -135,7 +137,8 @@ void AudioStreamOGGVorbis::update() {
 
 			if (!has_loop()) {
 
-				playing=false;	
+				playing=false;
+				setting_up=false;
 				repeats=1;
 				return;
 			}
@@ -145,6 +148,7 @@ void AudioStreamOGGVorbis::update() {
 			int errv = ov_open_callbacks(f,&vf,NULL,0,_ov_callbacks);
 			if (errv!=0) {
 				playing=false;
+				setting_up=false;
 				return; // :(
 			}
 
@@ -179,6 +183,8 @@ void AudioStreamOGGVorbis::play() {
 	playing=false;
 	setting_up=true;
 	update();
+	if (!setting_up)
+		return;
 	setting_up=false;
 	playing=true;
 }

+ 2 - 2
drivers/windows/thread_windows.cpp

@@ -46,7 +46,7 @@ DWORD ThreadWindows::thread_callback( LPVOID userdata ) {
 
 	ThreadWindows *t=reinterpret_cast<ThreadWindows*>(userdata);
 	t->callback(t->user);
-	t->id=(ID)0; // must implement
+	t->id=(ID)GetCurrentThreadId(); // must implement
 	return 0;
 }
 
@@ -67,7 +67,7 @@ Thread* ThreadWindows::create_func_windows(ThreadCreateCallback p_callback,void
 }
 Thread::ID ThreadWindows::get_thread_ID_func_windows() {
 
-	return (ID)0; //must implement
+	return (ID)GetCurrentThreadId(); //must implement
 }
 void ThreadWindows::wait_to_finish_func_windows(Thread* p_thread) {
 

+ 16 - 2
main/main.cpp

@@ -577,12 +577,15 @@ Error Main::setup(const char *execpath,int argc, char *argv[],bool p_second_phas
 		video_mode.height=globals->get("display/height");
 	if (use_custom_res && globals->has("display/fullscreen"))
 		video_mode.fullscreen=globals->get("display/fullscreen");
+	if (use_custom_res && globals->has("display/resizable"))
+		video_mode.resizable=globals->get("display/resizable");
 
 
 
 	GLOBAL_DEF("display/width",video_mode.width);
 	GLOBAL_DEF("display/height",video_mode.height);
 	GLOBAL_DEF("display/fullscreen",video_mode.fullscreen);
+	GLOBAL_DEF("display/resizable",video_mode.resizable);
 	if (rtm==-1) {
 		rtm=GLOBAL_DEF("render/thread_model",OS::RENDER_THREAD_SAFE);
 	}
@@ -648,7 +651,8 @@ Error Main::setup(const char *execpath,int argc, char *argv[],bool p_second_phas
 			OS::get_singleton()->set_screen_orientation(OS::SCREEN_LANDSCAPE);
 	}
 
-	OS::get_singleton()->set_iterations_per_second(GLOBAL_DEF("display/target_fps",60));
+	OS::get_singleton()->set_iterations_per_second(GLOBAL_DEF("physics/fixed_fps",60));
+	OS::get_singleton()->set_target_fps(GLOBAL_DEF("application/target_fps",0));
 
 	if (!OS::get_singleton()->_verbose_stdout) //overrided
 		OS::get_singleton()->_verbose_stdout=GLOBAL_DEF("debug/verbose_stdout",false);
@@ -1210,6 +1214,7 @@ bool Main::start() {
 }
 
 uint64_t Main::last_ticks=0;
+uint64_t Main::target_ticks=0;
 float Main::time_accum=0;
 uint32_t Main::frames=0;
 uint32_t Main::frame=0;
@@ -1295,7 +1300,6 @@ bool Main::iteration() {
 		}
 	} else {
 		VisualServer::get_singleton()->flush(); // flush visual commands
-
 	}
 
 	if (AudioServer::get_singleton())
@@ -1343,6 +1347,16 @@ bool Main::iteration() {
 			OS::get_singleton()->delay_usec( OS::get_singleton()->get_frame_delay()*1000 );
 	}
 
+	int taret_fps = OS::get_singleton()->get_target_fps();
+	if (taret_fps>0) {
+		uint64_t time_step = 1000000L/taret_fps;
+		target_ticks += time_step;
+		uint64_t current_ticks = OS::get_singleton()->get_ticks_usec();
+		if (current_ticks<target_ticks) OS::get_singleton()->delay_usec(target_ticks-current_ticks);
+		current_ticks = OS::get_singleton()->get_ticks_usec();
+		target_ticks = MIN(MAX(target_ticks,current_ticks-time_step),current_ticks+time_step);
+	}
+
 	return exit;
 }
 

+ 1 - 2
main/main.h

@@ -40,13 +40,12 @@
 class Main {
 
 	static void print_help(const char* p_binary);
-
 	static uint64_t last_ticks;
+	static uint64_t target_ticks;
 	static float time_accum;
 	static uint32_t frames;
 	static uint32_t frame;
 	static bool force_redraw_requested;
-
 public:
 
 	static Error setup(const char *execpath,int argc, char *argv[],bool p_second_phase=true);

+ 0 - 1
modules/SCsub

@@ -10,7 +10,6 @@ env.modules_sources=[
 #env.add_source_files(env.modules_sources,"*.cpp")
 Export('env')
 
-
 for x in env.module_list:
 	if (x in env.disabled_modules):
 		continue

+ 41 - 2
modules/gdscript/gd_functions.cpp

@@ -31,6 +31,7 @@
 #include "object_type_db.h"
 #include "reference.h"
 #include "gd_script.h"
+#include "func_ref.h"
 #include "os/os.h"
 
 const char *GDFunctions::get_func_name(Function p_func) {
@@ -80,6 +81,7 @@ const char *GDFunctions::get_func_name(Function p_func) {
 		"clamp",
 		"nearest_po2",
 		"weakref",
+		"funcref",
 		"convert",
 		"typeof",
 		"str",
@@ -451,6 +453,36 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va
 
 
 
+		} break;
+		case FUNC_FUNCREF: {
+			VALIDATE_ARG_COUNT(2);
+			if (p_args[0]->get_type()!=Variant::OBJECT) {
+
+				r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
+				r_error.argument=0;
+				r_error.expected=Variant::OBJECT;
+				r_ret=Variant();
+				return;
+
+			}
+			if (p_args[1]->get_type()!=Variant::STRING && p_args[1]->get_type()!=Variant::NODE_PATH) {
+
+				r_error.error=Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
+				r_error.argument=1;
+				r_error.expected=Variant::STRING;
+				r_ret=Variant();
+				return;
+
+			}
+
+			Ref<FuncRef> fr = memnew( FuncRef);
+
+			Object *obj = *p_args[0];
+			fr->set_instance(*p_args[0]);
+			fr->set_function(*p_args[1]);
+
+			r_ret=fr;
+
 		} break;
 		case TYPE_CONVERT: {
 			VALIDATE_ARG_COUNT(2);
@@ -678,7 +710,7 @@ void GDFunctions::call(Function p_func,const Variant **p_args,int p_arg_count,Va
 			}
 			r_ret=ResourceLoader::load(*p_args[0]);
 
-		}
+		} break;
 		case INST2DICT: {
 
 			VALIDATE_ARG_COUNT(1);
@@ -1063,7 +1095,7 @@ MethodInfo GDFunctions::get_info(Function p_func) {
 			return mi;
 		} break;
 		case MATH_RAND: {
-			MethodInfo mi("rand");
+			MethodInfo mi("randi");
 			mi.return_val.type=Variant::INT;
 			return mi;
 		} break;
@@ -1129,6 +1161,13 @@ MethodInfo GDFunctions::get_info(Function p_func) {
 			mi.return_val.type=Variant::OBJECT;
 			return mi;
 
+		} break;
+		case FUNC_FUNCREF: {
+
+			MethodInfo mi("funcref",PropertyInfo(Variant::OBJECT,"instance"),PropertyInfo(Variant::STRING,"funcname"));
+			mi.return_val.type=Variant::OBJECT;
+			return mi;
+
 		} break;
 		case TYPE_CONVERT: {
 

+ 1 - 0
modules/gdscript/gd_functions.h

@@ -77,6 +77,7 @@ public:
 		LOGIC_CLAMP,
 		LOGIC_NEAREST_PO2,
 		OBJ_WEAKREF,
+		FUNC_FUNCREF,
 		TYPE_CONVERT,
 		TYPE_OF,
 		TEXT_STR,

+ 16 - 0
modules/gdscript/gd_parser.cpp

@@ -174,10 +174,19 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
 		/* Parse Operand */
 		/*****************/
 
+		if (parenthesis>0) {
+			//remove empty space (only allowed if inside parenthesis
+			while(tokenizer->get_token()==GDTokenizer::TK_NEWLINE) {
+				tokenizer->advance();
+			}
+		}
+
 		if (tokenizer->get_token()==GDTokenizer::TK_PARENTHESIS_OPEN) {
 			//subexpression ()
 			tokenizer->advance();
+			parenthesis++;
 			Node* subexpr = _parse_expression(p_parent,p_static);
+			parenthesis--;
 			if (!subexpr)
 				return NULL;
 
@@ -629,6 +638,12 @@ GDParser::Node* GDParser::_parse_expression(Node *p_parent,bool p_static,bool p_
 		/* Parse Operator */
 		/******************/
 
+		if (parenthesis>0) {
+			//remove empty space (only allowed if inside parenthesis
+			while(tokenizer->get_token()==GDTokenizer::TK_NEWLINE) {
+				tokenizer->advance();
+			}
+		}
 
 		Expression e;
 		e.is_op=false;
@@ -2475,6 +2490,7 @@ void GDParser::clear() {
 	tab_level.push_back(0);
 	error_line=0;
 	error_column=0;
+	parenthesis=0;
 	current_export.type=Variant::NIL;
 	error="";
 

+ 1 - 0
modules/gdscript/gd_parser.h

@@ -356,6 +356,7 @@ private:
 	template<class T>
 	T* alloc_node();
 
+	int parenthesis;
 	bool error_set;
 	String error;
 	int error_line;

+ 13 - 0
modules/gdscript/gd_script.cpp

@@ -635,6 +635,19 @@ Variant GDFunction::call(GDInstance *p_instance,const Variant **p_args, int p_ar
 								err.argument-=1;
 							}
 						}
+					} if (methodstr=="free") {
+
+						if (err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) {
+
+							if (base->is_ref()) {
+								err_text="Attempted to free a reference.";
+								break;
+							} else if (base->get_type()==Variant::OBJECT) {
+
+								err_text="Attempted to free a locked object (calling or emitting).";
+								break;
+							}
+						}
 					}
 					err_text=_get_call_error(err,"function '"+methodstr+"' in base '"+basestr+"'",(const Variant**)argptrs);
 					break;

+ 18 - 0
modules/gdscript/gd_tokenizer.cpp

@@ -242,6 +242,24 @@ void GDTokenizerText::_advance() {
 			case 0:
 				_make_token(TK_EOF);
 				break;
+			case '\\':
+				INCPOS(1);
+				if (GETCHAR(0)=='\r') {
+					INCPOS(1);
+				}
+
+				if (GETCHAR(0)!='\n') {
+					_make_error("Expected newline after '\\'.");
+					return;
+				}
+
+				INCPOS(1);
+
+				while(GETCHAR(0)==' ' || GETCHAR(0)=='\t') {
+					INCPOS(1);
+				}
+
+				continue;
 			case '\t':
 			case '\r':
 			case ' ':

+ 0 - 7
modules/multiscript/SCsub

@@ -1,7 +0,0 @@
-Import('env')
-
-env.add_source_files(env.modules_sources,"*.cpp")
-
-Export('env')
-
-

+ 0 - 11
modules/multiscript/config.py

@@ -1,11 +0,0 @@
-
-
-def can_build(platform):
-  return True
-  
-  
-def configure(env):
-	pass
-  
-  
-  

+ 0 - 498
modules/multiscript/multi_script.cpp

@@ -1,498 +0,0 @@
-/*************************************************************************/
-/*  multi_script.cpp                                                     */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                    http://www.godotengine.org                         */
-/*************************************************************************/
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-#include "multi_script.h"
-
-bool MultiScriptInstance::set(const StringName& p_name, const Variant& p_value) {
-
-	ScriptInstance **sarr = instances.ptr();
-	int sc = instances.size();
-
-	for(int i=0;i<sc;i++) {
-
-		if (!sarr[i])
-			continue;
-
-		bool found = sarr[i]->set(p_name,p_value);
-		if (found)
-			return true;
-	}
-
-	if (String(p_name).begins_with("script_")) {
-		bool valid;
-		owner->set(p_name,p_value,&valid);
-		return valid;
-	}
-	return false;
-
-}
-
-bool MultiScriptInstance::get(const StringName& p_name, Variant &r_ret) const{
-
-	ScriptInstance **sarr = instances.ptr();
-	int sc = instances.size();
-
-	for(int i=0;i<sc;i++) {
-
-		if (!sarr[i])
-			continue;
-
-		bool found = sarr[i]->get(p_name,r_ret);
-		if (found)
-			return true;
-	}
-	if (String(p_name).begins_with("script_")) {
-		bool valid;
-		r_ret=owner->get(p_name,&valid);
-		return valid;
-	}
-	return false;
-
-}
-void MultiScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const{
-
-	ScriptInstance **sarr = instances.ptr();
-	int sc = instances.size();
-
-
-	Set<String> existing;
-
-	for(int i=0;i<sc;i++) {
-
-		if (!sarr[i])
-			continue;
-
-		List<PropertyInfo> pl;
-		sarr[i]->get_property_list(&pl);
-
-		for(List<PropertyInfo>::Element *E=pl.front();E;E=E->next()) {
-
-			if (existing.has(E->get().name))
-				continue;
-
-			p_properties->push_back(E->get());
-			existing.insert(E->get().name);
-		}
-	}
-
-	p_properties->push_back( PropertyInfo(Variant::NIL,"Scripts",PROPERTY_HINT_NONE,String(),PROPERTY_USAGE_CATEGORY) );
-
-	for(int i=0;i<owner->scripts.size();i++) {
-
-		p_properties->push_back( PropertyInfo(Variant::OBJECT,"script_"+String::chr('a'+i),PROPERTY_HINT_RESOURCE_TYPE,"Script",PROPERTY_USAGE_EDITOR) );
-
-	}
-
-	if (owner->scripts.size()<25) {
-
-		p_properties->push_back( PropertyInfo(Variant::OBJECT,"script_"+String::chr('a'+(owner->scripts.size())),PROPERTY_HINT_RESOURCE_TYPE,"Script",PROPERTY_USAGE_EDITOR) );
-	}
-
-}
-
-void MultiScriptInstance::get_method_list(List<MethodInfo> *p_list) const{
-
-	ScriptInstance **sarr = instances.ptr();
-	int sc = instances.size();
-
-
-	Set<StringName> existing;
-
-	for(int i=0;i<sc;i++) {
-
-		if (!sarr[i])
-			continue;
-
-		List<MethodInfo> ml;
-		sarr[i]->get_method_list(&ml);
-
-		for(List<MethodInfo>::Element *E=ml.front();E;E=E->next()) {
-
-			if (existing.has(E->get().name))
-				continue;
-
-			p_list->push_back(E->get());
-			existing.insert(E->get().name);
-		}
-	}
-
-}
-bool MultiScriptInstance::has_method(const StringName& p_method) const{
-
-	ScriptInstance **sarr = instances.ptr();
-	int sc = instances.size();
-
-	for(int i=0;i<sc;i++) {
-
-		if (!sarr[i])
-			continue;
-
-		if (sarr[i]->has_method(p_method))
-			return true;
-	}
-
-	return false;
-
-}
-
-Variant MultiScriptInstance::call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error) {
-
-	ScriptInstance **sarr = instances.ptr();
-	int sc = instances.size();
-
-	for(int i=0;i<sc;i++) {
-
-		if (!sarr[i])
-			continue;
-
-		Variant r = sarr[i]->call(p_method,p_args,p_argcount,r_error);
-		if (r_error.error==Variant::CallError::CALL_OK)
-			return r;
-		else if (r_error.error!=Variant::CallError::CALL_ERROR_INVALID_METHOD)
-			return r;
-	}
-
-	r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD;
-	return Variant();
-
-}
-
-void MultiScriptInstance::call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount){
-
-	ScriptInstance **sarr = instances.ptr();
-	int sc = instances.size();
-
-	for(int i=0;i<sc;i++) {
-
-		if (!sarr[i])
-			continue;
-
-		sarr[i]->call_multilevel(p_method,p_args,p_argcount);
-	}
-
-
-}
-void MultiScriptInstance::notification(int p_notification){
-
-	ScriptInstance **sarr = instances.ptr();
-	int sc = instances.size();
-
-	for(int i=0;i<sc;i++) {
-
-		if (!sarr[i])
-			continue;
-
-		sarr[i]->notification(p_notification);
-	}
-
-}
-
-
-Ref<Script> MultiScriptInstance::get_script() const {
-
-	return owner;
-}
-
-ScriptLanguage *MultiScriptInstance::get_language() {
-
-	return MultiScriptLanguage::get_singleton();
-}
-
-MultiScriptInstance::~MultiScriptInstance() {
-
-	owner->remove_instance(object);
-}
-
-
-///////////////////
-
-
-bool MultiScript::is_tool() const {
-
-	for(int i=0;i<scripts.size();i++) {
-
-		if (scripts[i]->is_tool())
-			return true;
-	}
-
-	return false;
-}
-
-bool MultiScript::_set(const StringName& p_name, const Variant& p_value) {
-
-	_THREAD_SAFE_METHOD_
-
-	String s = String(p_name);
-	if (s.begins_with("script_")) {
-
-		int idx = s[7];
-		if (idx==0)
-			return false;
-		idx-='a';
-
-		ERR_FAIL_COND_V(idx<0,false);
-
-		Ref<Script> s = p_value;
-
-		if (idx<scripts.size()) {
-
-
-			if (s.is_null())
-				remove_script(idx);
-			else
-				set_script(idx,s);
-		} else if (idx==scripts.size()) {
-			if (s.is_null())
-				return false;
-			add_script(s);
-		} else
-			return false;
-
-		return true;
-	}
-
-	return false;
-}
-
-bool MultiScript::_get(const StringName& p_name,Variant &r_ret) const{
-
-	_THREAD_SAFE_METHOD_
-
-	String s = String(p_name);
-	if (s.begins_with("script_")) {
-
-		int idx = s[7];
-		if (idx==0)
-			return false;
-		idx-='a';
-
-		ERR_FAIL_COND_V(idx<0,false);
-
-		if (idx<scripts.size()) {
-
-			r_ret=get_script(idx);
-			return true;
-		} else if (idx==scripts.size()) {
-			r_ret=Ref<Script>();
-			return true;
-		}
-	}
-
-	return false;
-}
-void MultiScript::_get_property_list( List<PropertyInfo> *p_list) const{
-
-	_THREAD_SAFE_METHOD_
-
-	for(int i=0;i<scripts.size();i++) {
-
-		p_list->push_back( PropertyInfo(Variant::OBJECT,"script_"+String::chr('a'+i),PROPERTY_HINT_RESOURCE_TYPE,"Script") );
-
-	}
-
-	if (scripts.size()<25) {
-
-		p_list->push_back( PropertyInfo(Variant::OBJECT,"script_"+String::chr('a'+(scripts.size())),PROPERTY_HINT_RESOURCE_TYPE,"Script") );
-	}
-}
-
-void MultiScript::set_script(int p_idx,const Ref<Script>& p_script ) {
-
-	_THREAD_SAFE_METHOD_
-
-	ERR_FAIL_INDEX(p_idx,scripts.size());
-	ERR_FAIL_COND( p_script.is_null() );
-
-	scripts[p_idx]=p_script;
-	Ref<Script> s=p_script;
-
-	for (Map<Object*,MultiScriptInstance*>::Element *E=instances.front();E;E=E->next()) {
-
-
-		MultiScriptInstance*msi=E->get();
-		ScriptInstance *si = msi->instances[p_idx];
-		if (si) {
-			msi->instances[p_idx]=NULL;
-			memdelete(si);
-		}
-
-		if (p_script->can_instance())
-			msi->instances[p_idx]=s->instance_create(msi->object);
-
-	}
-
-
-}
-
-
-Ref<Script> MultiScript::get_script(int p_idx) const{
-
-	_THREAD_SAFE_METHOD_
-
-	ERR_FAIL_INDEX_V(p_idx,scripts.size(),Ref<Script>());
-
-	return scripts[p_idx];
-
-}
-void MultiScript::add_script(const Ref<Script>& p_script){
-
-	_THREAD_SAFE_METHOD_
-	ERR_FAIL_COND( p_script.is_null() );
-	scripts.push_back(p_script);
-	Ref<Script> s=p_script;
-
-	for (Map<Object*,MultiScriptInstance*>::Element *E=instances.front();E;E=E->next()) {
-
-
-		MultiScriptInstance*msi=E->get();
-
-		if (p_script->can_instance())
-			msi->instances.push_back( s->instance_create(msi->object) );
-		else
-			msi->instances.push_back(NULL);
-
-		msi->object->_change_notify();
-
-	}
-
-
-	_change_notify();
-}
-
-
-void MultiScript::remove_script(int p_idx) {
-
-	_THREAD_SAFE_METHOD_
-
-	ERR_FAIL_INDEX(p_idx,scripts.size());
-
-	scripts.remove(p_idx);
-
-	for (Map<Object*,MultiScriptInstance*>::Element *E=instances.front();E;E=E->next()) {
-
-
-		MultiScriptInstance*msi=E->get();
-		ScriptInstance *si = msi->instances[p_idx];
-		msi->instances.remove(p_idx);
-		if (si) {
-			memdelete(si);
-		}
-
-		msi->object->_change_notify();
-	}
-
-
-}
-
-
-void MultiScript::remove_instance(Object *p_object) {
-
-	_THREAD_SAFE_METHOD_
-	instances.erase(p_object);
-}
-
-bool MultiScript::can_instance() const {
-
-	return true;
-}
-
-StringName MultiScript::get_instance_base_type() const {
-
-	return StringName();
-}
-ScriptInstance* MultiScript::instance_create(Object *p_this) {
-
-	_THREAD_SAFE_METHOD_
-	MultiScriptInstance *msi = memnew( MultiScriptInstance );
-	msi->object=p_this;
-	msi->owner=this;
-	for(int i=0;i<scripts.size();i++) {
-
-		ScriptInstance *si;
-
-		if (scripts[i]->can_instance())
-			si = scripts[i]->instance_create(p_this);
-		else
-			si=NULL;
-
-		msi->instances.push_back(si);
-	}
-
-	instances[p_this]=msi;
-	p_this->_change_notify();
-	return msi;
-}
-bool MultiScript::instance_has(const Object *p_this) const {
-
-	_THREAD_SAFE_METHOD_
-	return instances.has((Object*)p_this);
-}
-
-bool MultiScript::has_source_code() const {
-
-	return false;
-}
-String MultiScript::get_source_code() const {
-
-	return "";
-}
-void MultiScript::set_source_code(const String& p_code) {
-
-
-}
-Error MultiScript::reload() {
-
-	for(int i=0;i<scripts.size();i++)
-		scripts[i]->reload();
-
-	return OK;
-}
-
-String MultiScript::get_node_type() const {
-
-	return "";
-}
-
-void MultiScript::_bind_methods() {
-
-
-}
-
-ScriptLanguage *MultiScript::get_language() const {
-
-	return MultiScriptLanguage::get_singleton();
-}
-
-
-///////////////
-
-MultiScript::MultiScript() {
-}
-
-
-MultiScriptLanguage *MultiScriptLanguage::singleton=NULL;

+ 0 - 158
modules/multiscript/multi_script.h

@@ -1,158 +0,0 @@
-/*************************************************************************/
-/*  multi_script.h                                                       */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                    http://www.godotengine.org                         */
-/*************************************************************************/
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-#ifndef MULTI_SCRIPT_H
-#define MULTI_SCRIPT_H
-
-#include "script_language.h"
-#include "os/thread_safe.h"
-
-class MultiScript;
-
-class MultiScriptInstance : public ScriptInstance {
-friend class MultiScript;
-	mutable Vector<ScriptInstance*> instances;
-	Object *object;
-	mutable MultiScript *owner;
-
-public:
-	virtual bool set(const StringName& p_name, const Variant& p_value);
-	virtual bool get(const StringName& p_name, Variant &r_ret) const;
-	virtual void get_property_list(List<PropertyInfo> *p_properties) const;
-
-	virtual void get_method_list(List<MethodInfo> *p_list) const;
-	virtual bool has_method(const StringName& p_method) const;
-	virtual Variant call(const StringName& p_method,const Variant** p_args,int p_argcount,Variant::CallError &r_error);
-	virtual void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount);
-	virtual void notification(int p_notification);
-
-
-	virtual Ref<Script> get_script() const;
-
-	virtual ScriptLanguage *get_language();
-	virtual ~MultiScriptInstance();
-};
-
-
-class MultiScript : public Script {
-
-	_THREAD_SAFE_CLASS_
-friend class MultiScriptInstance;
-	OBJ_TYPE( MultiScript,Script);
-
-	Vector<Ref<Script> > scripts;
-
-	Map<Object*,MultiScriptInstance*> instances;
-protected:
-
-	bool _set(const StringName& p_name, const Variant& p_value);
-	bool _get(const StringName& p_name,Variant &r_ret) const;
-	void _get_property_list( List<PropertyInfo> *p_list) const;
-
-	static void _bind_methods();
-
-public:
-
-	void remove_instance(Object *p_object);
-	virtual bool can_instance() const;
-
-	virtual StringName get_instance_base_type() const;
-	virtual ScriptInstance* instance_create(Object *p_this);
-	virtual bool instance_has(const Object *p_this) const;
-
-	virtual bool has_source_code() const;
-	virtual String get_source_code() const;
-	virtual void set_source_code(const String& p_code);
-	virtual Error reload();
-
-	virtual bool is_tool() const;
-
-	virtual String get_node_type() const;
-
-
-	void set_script(int p_idx,const Ref<Script>& p_script );
-	Ref<Script> get_script(int p_idx) const;
-	void remove_script(int p_idx);
-	void add_script(const Ref<Script>& p_script);
-
-	virtual ScriptLanguage *get_language() const;
-
-	MultiScript();
-};
-
-
-class MultiScriptLanguage : public ScriptLanguage {
-
-	static MultiScriptLanguage *singleton;
-public:
-
-	static _FORCE_INLINE_ MultiScriptLanguage *get_singleton() { return singleton; }
-	virtual String get_name() const { return "MultiScript"; }
-
-	/* LANGUAGE FUNCTIONS */
-	virtual void init() {}
-	virtual String get_type() const { return "MultiScript"; }
-	virtual String get_extension() const { return ""; }
-	virtual Error execute_file(const String& p_path) { return OK; }
-	virtual void finish() {}
-
-	/* EDITOR FUNCTIONS */
-	virtual void get_reserved_words(List<String> *p_words) const {}
-	virtual void get_comment_delimiters(List<String> *p_delimiters) const {}
-	virtual void get_string_delimiters(List<String> *p_delimiters) const {}
-	virtual String get_template(const String& p_class_name, const String& p_base_class_name) const { return ""; }
-	virtual bool validate(const String& p_script, int &r_line_error,int &r_col_error,String& r_test_error,const String& p_path="",List<String>* r_fn=NULL) const { return true; }
-	virtual Script *create_script() const { return memnew( MultiScript ); }
-	virtual bool has_named_classes() const { return false; }
-	virtual int find_function(const String& p_function,const String& p_code) const { return -1; }
-	virtual String make_function(const String& p_class,const String& p_name,const StringArray& p_args) const { return ""; }
-
-	/* DEBUGGER FUNCTIONS */
-
-	virtual String debug_get_error() const { return ""; }
-	virtual int debug_get_stack_level_count() const { return 0; }
-	virtual int debug_get_stack_level_line(int p_level) const { return 0; }
-	virtual String debug_get_stack_level_function(int p_level) const { return ""; }
-	virtual String debug_get_stack_level_source(int p_level) const { return ""; }
-	virtual void debug_get_stack_level_locals(int p_level,List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1) {}
-	virtual void debug_get_stack_level_members(int p_level,List<String> *p_members, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1) {}
-	virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1) {}
-	virtual String debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems=-1,int p_max_depth=-1) { return ""; }
-
-	/* LOADER FUNCTIONS */
-
-	virtual void get_recognized_extensions(List<String> *p_extensions) const {}
-	virtual void get_public_functions(List<MethodInfo> *p_functions) const {}
-	virtual void get_public_constants(List<Pair<String,Variant> > *p_constants) const {}
-
-	MultiScriptLanguage() { singleton=this; }
-	virtual ~MultiScriptLanguage() {};
-};
-
-
-#endif // MULTI_SCRIPT_H

+ 0 - 33
modules/multiscript/register_types.cpp

@@ -1,33 +0,0 @@
-/*************************************************/
-/*  register_script_types.cpp                    */
-/*************************************************/
-/*            This file is part of:              */
-/*                GODOT ENGINE                   */
-/*************************************************/
-/*       Source code within this file is:        */
-/*  (c) 2007-2010 Juan Linietsky, Ariel Manzur   */
-/*             All Rights Reserved.              */
-/*************************************************/
-
-#include "register_types.h"
-
-#include "multi_script.h"
-#include "io/resource_loader.h"
-
-static MultiScriptLanguage *script_multi_script=NULL;
-
-void register_multiscript_types() {
-
-
-	script_multi_script = memnew( MultiScriptLanguage );
-	ScriptServer::register_language(script_multi_script);
-	ObjectTypeDB::register_type<MultiScript>();
-
-
-}
-void unregister_multiscript_types() {
-
-	if (script_multi_script) {
-		memdelete(script_multi_script);
-	}
-}

+ 0 - 30
modules/multiscript/register_types.h

@@ -1,30 +0,0 @@
-/*************************************************************************/
-/*  register_types.h                                                     */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                    http://www.godotengine.org                         */
-/*************************************************************************/
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-void register_multiscript_types();
-void unregister_multiscript_types();

+ 2 - 2
platform/android/AndroidManifest.xml.template

@@ -11,7 +11,7 @@
                   android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                   android:launchMode="singleTask"
                   android:screenOrientation="landscape"
-                  android:configChanges="orientation|keyboardHidden">
+                  android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize">
                                                                                                   
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -35,6 +35,6 @@ $$ADD_APPLICATION_CHUNKS$$
 	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
 	
-    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="11"/>
+    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="15"/>
          
 </manifest> 

+ 3 - 1
platform/android/SCsub

@@ -8,7 +8,7 @@ android_files = [
 	'godot_android.cpp',
 	'file_access_android.cpp',
 	'dir_access_android.cpp',
-	'audio_driver_android.cpp',
+	'audio_driver_opensl.cpp',
 	'file_access_jandroid.cpp',
 	'dir_access_jandroid.cpp',
 	'thread_jandroid.cpp',
@@ -37,7 +37,9 @@ abspath=env.Dir(".").abspath
 pp_basein = open(abspath+"/project.properties.template","rb")
 pp_baseout = open(abspath+"/java/project.properties","wb")
 pp_baseout.write( pp_basein.read() )
+
 refcount=1
+
 for x in env.android_source_modules:
 	pp_baseout.write("android.library.reference."+str(refcount)+"="+x+"\n")
 	refcount+=1

+ 45 - 28
platform/android/audio_driver_android.cpp → platform/android/audio_driver_opensl.cpp

@@ -1,5 +1,5 @@
 /*************************************************************************/
-/*  audio_driver_android.cpp                                             */
+/*  audio_driver_opensl.cpp                                             */
 /*************************************************************************/
 /*                       This file is part of:                           */
 /*                           GODOT ENGINE                                */
@@ -26,9 +26,8 @@
 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
-#include "audio_driver_android.h"
+#include "audio_driver_opensl.h"
 #include <string.h>
-#ifdef ANDROID_NATIVE_ACTIVITY
 
 
 
@@ -40,21 +39,32 @@
 /* Structure for passing information to callback function */
 
 
-void AudioDriverAndroid::_buffer_callback(
+void AudioDriverOpenSL::_buffer_callback(
     SLAndroidSimpleBufferQueueItf queueItf
  /*   SLuint32 eventFlags,
     const void * pBuffer,
     SLuint32 bufferSize,
     SLuint32 dataUsed*/) {
 
+	bool mix=true;
 
+	if (pause) {
+		mix=false;
+	} else if (mutex) {
+		mix = mutex->try_lock()==OK;
+	}
 
-	if (mutex)
-		mutex->lock();
+	if (mix) {
+		audio_server_process(buffer_size,mixdown_buffer);
+	} else {
 
-	audio_server_process(buffer_size,mixdown_buffer);
+		int32_t* src_buff=mixdown_buffer;
+		for(int i=0;i<buffer_size*2;i++) {
+			src_buff[i]=0;
+		}
+	}
 
-	if (mutex)
+	if (mutex && mix)
 		mutex->unlock();
 
 
@@ -87,7 +97,7 @@ void AudioDriverAndroid::_buffer_callback(
 #endif
 }
 
-void AudioDriverAndroid::_buffer_callbacks(
+void AudioDriverOpenSL::_buffer_callbacks(
     SLAndroidSimpleBufferQueueItf queueItf,
     /*SLuint32 eventFlags,
     const void * pBuffer,
@@ -96,7 +106,7 @@ void AudioDriverAndroid::_buffer_callbacks(
     void *pContext) {
 
 
-	AudioDriverAndroid *ad = (AudioDriverAndroid*)pContext;
+	AudioDriverOpenSL *ad = (AudioDriverOpenSL*)pContext;
 
 //	ad->_buffer_callback(queueItf,eventFlags,pBuffer,bufferSize,dataUsed);
 	ad->_buffer_callback(queueItf);
@@ -104,17 +114,17 @@ void AudioDriverAndroid::_buffer_callbacks(
 }
 
 
-AudioDriverAndroid* AudioDriverAndroid::s_ad=NULL;
+AudioDriverOpenSL* AudioDriverOpenSL::s_ad=NULL;
 
-const char* AudioDriverAndroid::get_name() const {
+const char* AudioDriverOpenSL::get_name() const {
 
 	return "Android";
 }
 
 #if 0
-int AudioDriverAndroid::thread_func(SceSize args, void *argp) {
+int AudioDriverOpenSL::thread_func(SceSize args, void *argp) {
 
-	AudioDriverAndroid* ad = s_ad;
+	AudioDriverOpenSL* ad = s_ad;
 	sceAudioOutput2Reserve(AUDIO_OUTPUT_SAMPLE);
 
 	int half=0;
@@ -170,7 +180,7 @@ int AudioDriverAndroid::thread_func(SceSize args, void *argp) {
 }
 
 #endif
-Error AudioDriverAndroid::init(){
+Error AudioDriverOpenSL::init(){
 
 	SLresult
 	res;
@@ -197,7 +207,7 @@ Error AudioDriverAndroid::init(){
 	return OK;
 
 }
-void AudioDriverAndroid::start(){
+void AudioDriverOpenSL::start(){
 
 
 	mutex = Mutex::create();
@@ -357,37 +367,44 @@ void AudioDriverAndroid::start(){
 
 	active=true;
 }
-int AudioDriverAndroid::get_mix_rate() const {
+int AudioDriverOpenSL::get_mix_rate() const {
 
 	return 44100;
 }
-AudioDriverSW::OutputFormat AudioDriverAndroid::get_output_format() const{
+AudioDriverSW::OutputFormat AudioDriverOpenSL::get_output_format() const{
 
 	return OUTPUT_STEREO;
 }
-void AudioDriverAndroid::lock(){
+void AudioDriverOpenSL::lock(){
 
-	//if (active && mutex)
-	//	mutex->lock();
+	if (active && mutex)
+		mutex->lock();
 
 }
-void AudioDriverAndroid::unlock() {
+void AudioDriverOpenSL::unlock() {
 
-	//if (active && mutex)
-	//	mutex->unlock();
+	if (active && mutex)
+		mutex->unlock();
 
 }
-void AudioDriverAndroid::finish(){
+void AudioDriverOpenSL::finish(){
 
 	(*sl)->Destroy(sl);
 
 }
 
+void AudioDriverOpenSL::set_pause(bool p_pause) {
 
-AudioDriverAndroid::AudioDriverAndroid()
+	pause=p_pause;
+}
+
+
+AudioDriverOpenSL::AudioDriverOpenSL()
 {
 	s_ad=this;
-	mutex=NULL;
+	mutex=Mutex::create();//NULL;
+	pause=false;
 }
 
-#endif
+
+

+ 12 - 9
platform/android/audio_driver_android.h → platform/android/audio_driver_opensl.h

@@ -1,5 +1,5 @@
 /*************************************************************************/
-/*  audio_driver_android.h                                               */
+/*  audio_driver_opensl.h                                                */
 /*************************************************************************/
 /*                       This file is part of:                           */
 /*                           GODOT ENGINE                                */
@@ -26,16 +26,18 @@
 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
-#ifndef AUDIO_DRIVER_ANDROID_H
-#define AUDIO_DRIVER_ANDROID_H
+#ifndef AUDIO_DRIVER_OPENSL_H
+#define AUDIO_DRIVER_OPENSL_H
+
 
-#ifdef ANDROID_NATIVE_ACTIVITY
 
 #include "servers/audio/audio_server_sw.h"
 #include "os/mutex.h"
 #include <SLES/OpenSLES.h>
 #include "SLES/OpenSLES_Android.h"
-class AudioDriverAndroid : public AudioDriverSW {
+
+
+class AudioDriverOpenSL : public AudioDriverSW {
 
 	bool active;
 	Mutex *mutex;
@@ -45,7 +47,7 @@ class AudioDriverAndroid : public AudioDriverSW {
 		BUFFER_COUNT=2
 	};
 
-
+	bool pause;
 
 
 	uint32_t buffer_size;
@@ -67,7 +69,7 @@ class AudioDriverAndroid : public AudioDriverSW {
 	SLDataLocator_OutputMix locator_outputmix;
 	SLBufferQueueState state;
 
-	static AudioDriverAndroid* s_ad;
+	static AudioDriverOpenSL* s_ad;
 
 	void _buffer_callback(
 	    SLAndroidSimpleBufferQueueItf queueItf
@@ -97,9 +99,10 @@ public:
 	virtual void unlock();
 	virtual void finish();
 
+	virtual void set_pause(bool p_pause);
 
-	AudioDriverAndroid();
+	AudioDriverOpenSL();
 };
 
 #endif // AUDIO_DRIVER_ANDROID_H
-#endif
+

+ 5 - 6
platform/android/detect.py

@@ -14,6 +14,7 @@ def can_build():
         import os
         if (not os.environ.has_key("ANDROID_NDK_ROOT")):
         	return False
+
 	return True
 
 def get_opts():
@@ -23,7 +24,7 @@ def get_opts():
              ('NDK_TOOLCHAIN', 'toolchain to use for the NDK',"arm-eabi-4.4.0"), 	                      
              #android 2.3       
 		 ('ndk_platform', 'compile for platform: (2.2,2.3)',"2.2"),
-		 ('NDK_TARGET', 'toolchain to use for the NDK',"arm-linux-androideabi-4.7"),
+		 ('NDK_TARGET', 'toolchain to use for the NDK',"arm-linux-androideabi-4.8"),
 	     ('android_stl','enable STL support in android port (for modules)','no'),
 	     ('armv6','compile for older phones running arm v6 (instead of v7+neon+smp)','no')
 
@@ -55,13 +56,10 @@ def configure(env):
 		env.Tool('gcc')
 		env['SPAWN'] = methods.win32_spawn
 
+	env.android_source_modules.append("../libs/apk_expansion")	
 	ndk_platform=""
 
-	if (env["ndk_platform"]=="2.2"):
-		ndk_platform="android-8"
-	else:
-		ndk_platform="android-9"
-		env.Append(CPPFLAGS=["-DANDROID_NATIVE_ACTIVITY"])
+	ndk_platform="android-15"
 
 	print("Godot Android!!!!!")
 
@@ -111,6 +109,7 @@ def configure(env):
 		env['CCFLAGS'] = string.split('-DNO_STATVFS -MMD -MP -MF -fpic -ffunction-sections -funwind-tables -fstack-protector -D__ARM_ARCH_7__ -D__GLIBC__  -Wno-psabi -march=armv6 -mfpu=neon -mfloat-abi=softfp -ftree-vectorize -funsafe-math-optimizations -fno-strict-aliasing -DANDROID -Wa,--noexecstack -DGLES2_ENABLED -DGLES1_ENABLED')
 
 	env.Append(LDPATH=[ld_path])
+	env.Append(LIBS=['OpenSLES'])
 #	env.Append(LIBS=['c','m','stdc++','log','EGL','GLESv1_CM','GLESv2','OpenSLES','supc++','android'])
 	if (env["ndk_platform"]!="2.2"):
 		env.Append(LIBS=['EGL','OpenSLES','android'])

+ 0 - 5
platform/android/java/ant.properties

@@ -15,8 +15,3 @@
 #  'key.alias' for the name of the key to use.
 # The password will be asked during the build when you use the 'release' target.
 
-key.store=my-release-key.keystore
-key.alias=mykey
-
-key.store.password=123456
-key.alias.password=123456

+ 36 - 7
platform/android/java/src/com/android/godot/Godot.java

@@ -49,17 +49,21 @@ import android.media.*;
 import android.hardware.*;
 import android.content.*;
 
+import android.net.Uri;
+import android.media.MediaPlayer;
+
 import java.lang.reflect.Method;
 import java.util.List;
 import java.util.ArrayList;
+import com.android.godot.payments.PaymentsManager;
+import java.io.IOException;
 import android.provider.Settings.Secure;
 import android.widget.FrameLayout;
 import com.android.godot.input.*;
 
 
 public class Godot extends Activity implements SensorEventListener
-{
-
+{	
 	static public class SingletonBase {
 
 		protected void registerClass(String p_name, String[] p_methods) {
@@ -133,8 +137,12 @@ public class Godot extends Activity implements SensorEventListener
 	};
 	public ResultCallback result_callback;
 
+	private PaymentsManager mPaymentsManager = null;
+
 	@Override protected void onActivityResult (int requestCode, int resultCode, Intent data) {
-		if (result_callback != null) {
+		if(requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE){
+			mPaymentsManager.processPurchaseResponse(resultCode, data);
+		}else if (result_callback != null) {
 			result_callback.callback(requestCode, resultCode, data);
 			result_callback = null;
 		};
@@ -163,13 +171,18 @@ public class Godot extends Activity implements SensorEventListener
         io.setEdit(edittext);
 	}
 
+	private static Godot _self;
+	
+	public static Godot getInstance(){
+		return Godot._self;
+	}
+	
 	@Override protected void onCreate(Bundle icicle) {
 
+		System.out.printf("** GODOT ACTIVITY CREATED HERE ***\n");
 
 		super.onCreate(icicle);
-
-
-
+		_self = this;
 		Window window = getWindow();
 		window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
 			| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -184,11 +197,19 @@ public class Godot extends Activity implements SensorEventListener
 
 		result_callback = null;
 		
+		mPaymentsManager = PaymentsManager.createManager(this).initService();
+		
 	//	instanceSingleton( new GodotFacebook(this) );
 
 
 	}
 
+	@Override protected void onDestroy(){
+		
+		if(mPaymentsManager != null ) mPaymentsManager.destroy();
+		super.onDestroy();
+	}
+	
 	@Override protected void onPause() {
 		super.onPause();
 		mView.onPause();
@@ -333,7 +354,15 @@ public class Godot extends Activity implements SensorEventListener
 	@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
 		GodotLib.key(keyCode, event.getUnicodeChar(0), true);
 		return super.onKeyDown(keyCode, event);
-	};
+	}
+
+	public PaymentsManager getPaymentsManager() {
+		return mPaymentsManager;
+	}
+
+//	public void setPaymentsManager(PaymentsManager mPaymentsManager) {
+//		this.mPaymentsManager = mPaymentsManager;
+//	};
 
 
 	// Audio

+ 48 - 3
platform/android/java/src/com/android/godot/GodotIO.java

@@ -59,6 +59,9 @@ public class GodotIO {
 	Godot activity;
 	GodotEditText edit;
 
+	Context applicationContext;
+	MediaPlayer mediaPlayer;
+
 	final int SCREEN_LANDSCAPE=0;
 	final int SCREEN_PORTRAIT=1;
 	final int SCREEN_REVERSE_LANDSCAPE=2;
@@ -328,7 +331,7 @@ public class GodotIO {
 		activity=p_activity;
 		streams=new HashMap<Integer,AssetData>();
 		dirs=new HashMap<Integer,AssetDir>();
-
+		applicationContext = activity.getApplicationContext();
 
 	}
 
@@ -475,8 +478,13 @@ public class GodotIO {
 		if(edit != null)
 			edit.hideKeyboard();
 
-		//InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
-		//inputMgr.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+        InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+        View v = activity.getCurrentFocus();
+        if (v != null) {
+            inputMgr.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+        } else {
+            inputMgr.hideSoftInputFromWindow(new View(activity).getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+        }
 	};
 
 	public void setScreenOrientation(int p_orientation) {
@@ -512,6 +520,43 @@ public class GodotIO {
 		edit = _edit;
 	}
 
+	public void playVideo(String p_path)
+	{
+		Uri filePath = Uri.parse(p_path);
+		mediaPlayer = new MediaPlayer();
+
+		try {
+			mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+			mediaPlayer.setDataSource(applicationContext, filePath);
+			mediaPlayer.prepare();
+			mediaPlayer.start();
+		}
+		catch(IOException e)
+        {
+            System.out.println("IOError while playing video");
+        }
+	}
+
+	public boolean isVideoPlaying() {
+		if (mediaPlayer != null) {
+			return mediaPlayer.isPlaying();
+		}
+		return false;
+	}
+
+	public void pauseVideo() {
+		if (mediaPlayer != null) {
+			mediaPlayer.pause();
+		}
+	}
+
+	public void stopVideo() {
+		if (mediaPlayer != null) {
+			mediaPlayer.release();
+			mediaPlayer = null;
+		}
+	}
+
 	protected static final String PREFS_FILE = "device_id.xml";
 	protected static final String PREFS_DEVICE_ID = "device_id";
 

+ 2 - 2
platform/android/java/src/com/android/godot/GodotLib.java

@@ -58,7 +58,7 @@ public class GodotLib {
      public static native void singleton(String p_name,Object p_object);
      public static native void method(String p_sname,String p_name,String p_ret,String[] p_params);
      public static native String getGlobal(String p_key);
-	 public static native void callobject(int p_ID, String p_method, Object[] p_params);
-	 public static native void calldeferred(int p_ID, String p_method, Object[] p_params);
+	public static native void callobject(int p_ID, String p_method, Object[] p_params);
+	public static native void calldeferred(int p_ID, String p_method, Object[] p_params);
 
 }

+ 83 - 0
platform/android/java/src/com/android/godot/GodotPaymentV3.java

@@ -0,0 +1,83 @@
+package com.android.godot;
+
+
+import android.app.Activity;
+
+
+public class GodotPaymentV3 extends Godot.SingletonBase {
+
+	private Godot activity;
+
+	private Integer purchaseCallbackId = 0;
+
+	private String accessToken;
+	
+	private String purchaseValidationUrlPrefix;
+
+	public void purchase( String _sku) {
+		final String sku = _sku;
+		activity.getPaymentsManager().setBaseSingleton(this);
+		activity.runOnUiThread(new Runnable() {
+			@Override
+			public void run() {
+				activity.getPaymentsManager().requestPurchase(sku);				
+			}
+		});
+	};
+
+
+    static public Godot.SingletonBase initialize(Activity p_activity) {
+
+        return new GodotPaymentV3(p_activity);
+    }
+
+	
+	public GodotPaymentV3(Activity p_activity) {
+
+		registerClass("GodotPayments", new String[] {"purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix"});
+		activity=(Godot) p_activity;
+	}
+
+
+	
+	public void callbackSuccess(){
+        GodotLib.callobject(purchaseCallbackId, "purchase_success", new Object[]{});
+	}
+	
+	public void callbackFail(){
+        GodotLib.callobject(purchaseCallbackId, "purchase_fail", new Object[]{});
+	}
+	
+	public void callbackCancel(){
+		GodotLib.callobject(purchaseCallbackId, "purchase_cancel", new Object[]{});
+	}
+	
+	public int getPurchaseCallbackId() {
+		return purchaseCallbackId;
+	}
+
+	public void setPurchaseCallbackId(int purchaseCallbackId) {
+		this.purchaseCallbackId = purchaseCallbackId;
+	}
+
+
+
+	public String getPurchaseValidationUrlPrefix(){
+		return this.purchaseValidationUrlPrefix ;
+	}
+
+	public void setPurchaseValidationUrlPrefix(String url){
+		this.purchaseValidationUrlPrefix = url;
+	}
+
+
+	public String getAccessToken() {
+		return accessToken;
+	}
+
+
+	public void setAccessToken(String accessToken) {
+		this.accessToken = accessToken;
+	}
+	
+}

+ 71 - 0
platform/android/java/src/com/android/godot/payments/ConsumeTask.java

@@ -0,0 +1,71 @@
+package com.android.godot.payments;
+
+import com.android.vending.billing.IInAppBillingService;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class ConsumeTask {
+
+	private Context context;
+	
+	private IInAppBillingService mService;
+	public ConsumeTask(IInAppBillingService mService, Context context ){
+		this.context = context;
+		this.mService = mService;
+	}
+	
+
+	public void consume(final String sku){
+//		Log.d("XXX", "Consuming product " + sku);
+		PaymentsCache pc = new PaymentsCache(context);
+		Boolean isBlocked = pc.getConsumableFlag("block", sku);
+		String _token = pc.getConsumableValue("token", sku);
+//		Log.d("XXX", "token " + _token);		
+		if(!isBlocked && _token == null){
+//			_token = "inapp:"+context.getPackageName()+":android.test.purchased";
+//			Log.d("XXX", "Consuming product " + sku + " with token " + _token);
+		}else if(!isBlocked){
+//			Log.d("XXX", "It is not blocked ¿?");
+			return;
+		}else if(_token == null){
+//			Log.d("XXX", "No token available");
+			this.error("No token for sku:" + sku);
+			return;
+		}
+		final String token = _token;
+		new AsyncTask<String, String, String>(){
+
+			@Override
+			protected String doInBackground(String... params) {
+				try {
+//					Log.d("XXX", "Requesting to release item.");
+					int response = mService.consumePurchase(3, context.getPackageName(), token);
+//					Log.d("XXX", "release response code: " + response);
+					if(response == 0 || response == 8){
+						return null;
+					}
+				} catch (RemoteException e) {
+					return e.getMessage();
+					
+				}
+				return "Some error";
+			}
+			
+			protected void onPostExecute(String param){
+				if(param == null){
+					success();
+				}else{
+					error(param);
+				}
+			}
+			
+		}.execute();
+	}
+	
+	abstract protected void success();
+	abstract protected void error(String message);
+	
+}

+ 79 - 0
platform/android/java/src/com/android/godot/payments/HandlePurchaseTask.java

@@ -0,0 +1,79 @@
+package com.android.godot.payments;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.android.godot.GodotLib;
+import com.android.godot.utils.Crypt;
+import com.android.vending.billing.IInAppBillingService;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class HandlePurchaseTask {
+
+	private Activity context;
+	
+	public HandlePurchaseTask(Activity context ){
+		this.context = context;
+	}
+	
+	
+	public void handlePurchaseRequest(int resultCode, Intent data){
+//		Log.d("XXX", "Handling purchase response");
+//		int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
+		PaymentsCache pc = new PaymentsCache(context);
+		
+		String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
+//		String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
+		
+		if (resultCode == Activity.RESULT_OK) {
+			
+			try {
+				Log.d("SARLANGA", purchaseData);
+				
+				
+				JSONObject jo = new JSONObject(purchaseData);
+//				String sku = jo.getString("productId");
+//				alert("You have bought the " + sku + ". Excellent choice, aventurer!");
+//				String orderId = jo.getString("orderId");
+//				String packageName = jo.getString("packageName");
+				String productId = jo.getString("productId");
+//				Long purchaseTime = jo.getLong("purchaseTime");
+//				Integer state = jo.getInt("purchaseState");
+				String developerPayload = jo.getString("developerPayload");
+				String purchaseToken = jo.getString("purchaseToken");
+				
+				if(! pc.getConsumableValue("validation_hash", productId).equals(developerPayload) ) {
+					error("Untrusted callback");
+					return;
+				}
+				
+				pc.setConsumableValue("ticket", productId, purchaseData);
+				pc.setConsumableFlag("block", productId, true);
+				pc.setConsumableValue("token", productId, purchaseToken);
+				
+				success(purchaseToken, productId);
+				return;
+			}	catch (JSONException e) {
+				error(e.getMessage());
+			}
+		}else if( resultCode == Activity.RESULT_CANCELED){
+			canceled();
+		}
+	}
+
+	abstract protected void success(String purchaseToken, String sku);
+	abstract protected void error(String message);
+	abstract protected void canceled();
+
+	
+}

+ 42 - 0
platform/android/java/src/com/android/godot/payments/PaymentsCache.java

@@ -0,0 +1,42 @@
+package com.android.godot.payments;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class PaymentsCache {
+	
+	public Context context;
+
+	public PaymentsCache(Context context){
+		this.context = context;
+	}
+	
+	
+	public void setConsumableFlag(String set, String sku, Boolean flag){
+		SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); 
+	    SharedPreferences.Editor editor = sharedPref.edit();
+	    editor.putBoolean(sku, flag);
+	    editor.commit();
+}
+
+	public boolean getConsumableFlag(String set, String sku){
+	    SharedPreferences sharedPref = context.getSharedPreferences(
+	    		"consumables_" + set, Context.MODE_PRIVATE);
+	    return sharedPref.getBoolean(sku, false);
+	}
+
+
+	public void setConsumableValue(String set, String sku, String value){
+		SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); 
+	    SharedPreferences.Editor editor = sharedPref.edit();
+	    editor.putString(sku, value);
+	    editor.commit();
+	}
+
+	public String getConsumableValue(String set, String sku){
+	    SharedPreferences sharedPref = context.getSharedPreferences(
+	    		"consumables_" + set, Context.MODE_PRIVATE);
+	    return sharedPref.getString(sku, null);
+	}
+
+}

+ 151 - 0
platform/android/java/src/com/android/godot/payments/PaymentsManager.java

@@ -0,0 +1,151 @@
+package com.android.godot.payments;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import com.android.godot.Godot;
+import com.android.godot.GodotPaymentV3;
+import com.android.vending.billing.IInAppBillingService;
+
+public class PaymentsManager {
+
+	public static final int BILLING_RESPONSE_RESULT_OK = 0;
+
+	
+	public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001;
+	
+	
+	private Activity activity;
+	IInAppBillingService mService;
+
+	
+	public void setActivity(Activity activity){
+		this.activity = activity;
+	}
+
+	public static PaymentsManager createManager(Activity activity){
+		PaymentsManager manager = new PaymentsManager(activity);
+		return manager;
+	}
+	
+	private PaymentsManager(Activity activity){
+		this.activity = activity;
+	}
+	
+	public PaymentsManager initService(){
+		activity.bindService(
+				new Intent("com.android.vending.billing.InAppBillingService.BIND"), 
+				mServiceConn, 
+				Context.BIND_AUTO_CREATE);
+		return this;
+	}
+
+	public void destroy(){
+		if (mService != null) {
+	        activity.unbindService(mServiceConn);
+	    }  
+	}
+	
+	ServiceConnection mServiceConn = new ServiceConnection() {
+	    @Override
+	    public void onServiceDisconnected(ComponentName name) {
+	    	mService = null;
+	    }
+
+	    @Override
+	    public void onServiceConnected(ComponentName name, 
+	    		IBinder service) {
+		mService = IInAppBillingService.Stub.asInterface(service);
+	    }
+	};
+	
+	public void requestPurchase(String sku){
+		new PurchaseTask(mService, Godot.getInstance()) {
+			
+			@Override
+			protected void error(String message) {
+				godotPaymentV3.callbackFail();
+				
+			}
+			
+			@Override
+			protected void canceled() {
+				godotPaymentV3.callbackCancel();
+			}
+		}.purchase(sku);
+
+	}
+
+	public void processPurchaseResponse(int resultCode, Intent data) {
+		new HandlePurchaseTask(activity){
+
+			@Override
+			protected void success(String purchaseToken, String sku) {
+				validatePurchase(purchaseToken, sku);
+			}
+
+			@Override
+			protected void error(String message) {
+				godotPaymentV3.callbackFail();
+				
+			}
+
+			@Override
+			protected void canceled() {
+				godotPaymentV3.callbackCancel();
+				
+			}}.handlePurchaseRequest(resultCode, data);
+	}
+	
+	public void validatePurchase(String purchaseToken, final String sku){
+		
+		new ValidateTask(activity, godotPaymentV3){
+
+			@Override
+			protected void success() {
+				
+				new ConsumeTask(mService, activity) {
+					
+					@Override
+					protected void success() {
+						godotPaymentV3.callbackSuccess();
+						
+					}
+					
+					@Override
+					protected void error(String message) {
+						godotPaymentV3.callbackFail();
+						
+					}
+				}.consume(sku);
+				
+			}
+
+			@Override
+			protected void error(String message) {
+				godotPaymentV3.callbackFail();
+				
+			}
+
+			@Override
+			protected void canceled() {
+				godotPaymentV3.callbackCancel();
+				
+			}
+		}.validatePurchase(sku);
+	}
+	
+	private GodotPaymentV3 godotPaymentV3;
+	
+	public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
+		this.godotPaymentV3 = godotPaymentV3;
+		
+	}
+
+
+}
+

+ 121 - 0
platform/android/java/src/com/android/godot/payments/PurchaseTask.java

@@ -0,0 +1,121 @@
+package com.android.godot.payments;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.android.godot.GodotLib;
+import com.android.godot.utils.Crypt;
+import com.android.vending.billing.IInAppBillingService;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class PurchaseTask {
+
+	private Activity context;
+	
+	private IInAppBillingService mService;
+	public PurchaseTask(IInAppBillingService mService, Activity context ){
+		this.context = context;
+		this.mService = mService;
+	}
+	
+
+	private boolean isLooping = false;
+	
+	public void purchase(final String sku){
+//		Log.d("XXX", "Starting purchase");
+		PaymentsCache pc = new PaymentsCache(context);
+		Boolean isBlocked = pc.getConsumableFlag("block", sku);
+//		if(isBlocked){
+//			Log.d("XXX", "Is awaiting payment confirmation");
+//			error("Awaiting payment confirmation");
+//			return;
+//		}
+		final String hash = Crypt.createRandomHash() + Crypt.createRandomHash();
+
+		Bundle buyIntentBundle;
+		try {
+			buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, "inapp", hash  );
+		} catch (RemoteException e) {
+//			Log.d("XXX", "Error: " + e.getMessage());
+			error(e.getMessage());
+			return;
+		}
+		Object rc = buyIntentBundle.get("RESPONSE_CODE");
+		int responseCode = 0;
+		if(rc == null){
+			responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK;
+		}else if( rc instanceof Integer){
+			responseCode = ((Integer)rc).intValue();
+		}else if( rc instanceof Long){
+			responseCode = (int)((Long)rc).longValue();
+		}
+//		Log.d("XXX", "Buy intent response code: " + responseCode);
+		if(responseCode == 1 || responseCode == 3 || responseCode == 4){
+			canceled();
+			return ;
+		}
+		if(responseCode == 7){
+			new ConsumeTask(mService, context) {
+				
+				@Override
+				protected void success() {
+//					Log.d("XXX", "Product was erroniously purchased!");
+					if(isLooping){
+//						Log.d("XXX", "It is looping");
+						error("Error while purchasing product");
+						return;
+					}
+					isLooping=true;
+					PurchaseTask.this.purchase(sku);
+					
+				}
+				
+				@Override
+				protected void error(String message) {
+					PurchaseTask.this.error(message);
+					
+				}
+			}.consume(sku);
+			return;
+		}
+		
+		
+		PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
+		pc.setConsumableValue("validation_hash", sku, hash);
+		try {
+			if(context == null){
+//				Log.d("XXX", "No context!");
+			}
+			if(pendingIntent == null){
+//				Log.d("XXX", "No pending intent");
+			}
+//			Log.d("XXX", "Starting activity for purchase!");
+			context.startIntentSenderForResult(
+					pendingIntent.getIntentSender(),
+					PaymentsManager.REQUEST_CODE_FOR_PURCHASE, 
+					new Intent(), 
+					Integer.valueOf(0), Integer.valueOf(0),
+					   Integer.valueOf(0));
+		} catch (SendIntentException e) {
+			error(e.getMessage());
+		}
+		
+		
+		
+	}
+
+	abstract protected void error(String message);
+	abstract protected void canceled();
+
+	
+}

+ 97 - 0
platform/android/java/src/com/android/godot/payments/ValidateTask.java

@@ -0,0 +1,97 @@
+package com.android.godot.payments;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.android.godot.Godot;
+import com.android.godot.GodotLib;
+import com.android.godot.GodotPaymentV3;
+import com.android.godot.utils.Crypt;
+import com.android.godot.utils.HttpRequester;
+import com.android.godot.utils.RequestParams;
+import com.android.vending.billing.IInAppBillingService;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class ValidateTask {
+
+	private Activity context;
+	private GodotPaymentV3 godotPaymentsV3;
+	public ValidateTask(Activity context, GodotPaymentV3 godotPaymentsV3){
+		this.context = context;
+		this.godotPaymentsV3 = godotPaymentsV3;
+	}
+	
+	public void validatePurchase(final String sku){
+		new AsyncTask<String, String, String>(){
+
+			
+			private ProgressDialog dialog;
+
+			@Override
+			protected void onPreExecute(){
+				dialog = ProgressDialog.show(context, null, "Please wait...");
+			}
+			
+			@Override
+			protected String doInBackground(String... params) {
+				PaymentsCache pc = new PaymentsCache(context);
+				String url = godotPaymentsV3.getPurchaseValidationUrlPrefix();
+				RequestParams param = new RequestParams();
+				param.setUrl(url);
+				param.put("ticket", pc.getConsumableValue("ticket", sku));
+				param.put("purchaseToken", pc.getConsumableValue("token", sku));
+				param.put("sku", sku);
+//				Log.d("XXX", "Haciendo request a " + url);
+//				Log.d("XXX", "ticket: " + pc.getConsumableValue("ticket", sku));
+//				Log.d("XXX", "purchaseToken: " + pc.getConsumableValue("token", sku));
+//				Log.d("XXX", "sku: " + sku);
+				param.put("package", context.getApplicationContext().getPackageName());
+				HttpRequester requester = new HttpRequester();
+				String jsonResponse = requester.post(param);
+//				Log.d("XXX", "Validation response:\n"+jsonResponse);
+				return jsonResponse;
+			}
+			
+			@Override
+			protected void onPostExecute(String response){
+				if(dialog != null){
+					dialog.dismiss();
+				}
+				JSONObject j;
+				try {
+					j = new JSONObject(response);
+					if(j.getString("status").equals("OK")){
+						success();
+						return;
+					}else if(j.getString("status") != null){
+						error(j.getString("message"));
+					}else{
+						error("Connection error");
+					}
+				} catch (JSONException e) {
+					error(e.getMessage());
+				}catch (Exception e){
+					error(e.getMessage());
+				}
+
+				
+			}
+			
+		}.execute();
+	}
+	abstract protected void success();
+	abstract protected void error(String message);
+	abstract protected void canceled();
+
+	
+}

+ 39 - 0
platform/android/java/src/com/android/godot/utils/Crypt.java

@@ -0,0 +1,39 @@
+package com.android.godot.utils;
+
+import java.security.MessageDigest;
+import java.util.Random;
+
+public class Crypt {
+
+	public static String md5(String input){
+        try {
+            // Create MD5 Hash
+            MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
+            digest.update(input.getBytes());
+            byte messageDigest[] = digest.digest();
+            
+            // Create Hex String
+            StringBuffer hexString = new StringBuffer();
+            for (int i=0; i<messageDigest.length; i++)
+                hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
+            return hexString.toString();
+            
+        } catch (Exception e) {
+            e.printStackTrace();
+        }   
+        return "";
+	}
+	
+	public static String createRandomHash(){
+		return md5(Long.toString(createRandomLong()));
+	}
+	
+	public static long createAbsRandomLong(){
+		return Math.abs(createRandomLong());
+	}
+	
+	public static long createRandomLong(){
+		Random r = new Random();
+		return r.nextLong();
+	}
+}

+ 54 - 0
platform/android/java/src/com/android/godot/utils/CustomSSLSocketFactory.java

@@ -0,0 +1,54 @@
+package com.android.godot.utils;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.http.conn.ssl.SSLSocketFactory;
+
+
+/**
+ * 
+ * @author Luis Linietsky <[email protected]>
+ */
+public class CustomSSLSocketFactory extends SSLSocketFactory {
+    SSLContext sslContext = SSLContext.getInstance("TLS");
+
+    public CustomSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+        super(truststore);
+
+        TrustManager tm = new X509TrustManager() {
+            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+            }
+
+            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+            }
+
+            public X509Certificate[] getAcceptedIssuers() {
+                return null;
+            }
+        };
+
+        sslContext.init(null, new TrustManager[] { tm }, null);
+    }
+
+    @Override
+    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
+        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
+    }
+
+    @Override
+    public Socket createSocket() throws IOException {
+        return sslContext.getSocketFactory().createSocket();
+    }
+}

+ 206 - 0
platform/android/java/src/com/android/godot/utils/HttpRequester.java

@@ -0,0 +1,206 @@
+package com.android.godot.utils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EntityUtils;
+
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+/**
+ * 
+ * @author Luis Linietsky <[email protected]>
+ */
+public class HttpRequester {
+	
+	private Context context;
+	private static final int TTL = 600000; // 10 minutos 
+	private long cttl=0;
+	
+	public HttpRequester(){
+//		Log.d("XXX", "Creando http request sin contexto");
+	}
+	
+	public HttpRequester(Context context){
+		this.context=context;
+//		Log.d("XXX", "Creando http request con contexto");
+	}
+	
+	public String post(RequestParams params){
+	    HttpPost httppost = new HttpPost(params.getUrl());
+        try {
+			httppost.setEntity(new UrlEncodedFormEntity(params.toPairsList()));
+			return request(httppost);
+		} catch (UnsupportedEncodingException e) {
+			return null;
+		}
+	}
+	
+	public String get(RequestParams params){
+		String response = getResponseFromCache(params.getUrl());
+		if(response == null){
+//			Log.d("XXX", "Cache miss!");
+		    HttpGet httpget = new HttpGet(params.getUrl());
+		    long timeInit = new Date().getTime();
+		    response = request(httpget);
+		    long delay = new Date().getTime() - timeInit;
+		    Log.d("com.app11tt.android.utils.HttpRequest::get(url)", "Url: " + params.getUrl() + " downloaded in " + String.format("%.03f", delay/1000.0f) + " seconds");
+		    if(response == null || response.length() == 0){
+		    	response = "";
+		    }else{
+		    	saveResponseIntoCache(params.getUrl(), response);
+		    } 
+		}
+		Log.d("XXX", "Req: " + params.getUrl());
+		Log.d("XXX", "Resp: " + response);
+	    return response;
+	}
+	
+	private String request(HttpUriRequest request){
+//		Log.d("XXX", "Haciendo request a: " + request.getURI() );
+		Log.d("PPP", "Haciendo request a: " + request.getURI() );
+		long init = new Date().getTime();
+		HttpClient httpclient = getNewHttpClient();
+		HttpParams httpParameters = httpclient.getParams();
+		HttpConnectionParams.setConnectionTimeout(httpParameters, 0);
+		HttpConnectionParams.setSoTimeout(httpParameters, 0);
+		HttpConnectionParams.setTcpNoDelay(httpParameters, true);
+	    try {
+	        HttpResponse response = httpclient.execute(request);
+	        Log.d("PPP", "Fin de request (" + (new Date().getTime() - init) + ") a: " + request.getURI() );
+//	        Log.d("XXX1", "Status:" + response.getStatusLine().toString());
+	        if(response.getStatusLine().getStatusCode() == 200){
+	        	String strResponse = EntityUtils.toString(response.getEntity());
+//	        	Log.d("XXX2", strResponse);
+	        	return strResponse;
+	        }else{
+	        	Log.d("XXX3", "Response status code:" + response.getStatusLine().getStatusCode() + "\n" + EntityUtils.toString(response.getEntity()));
+	        	return null;
+	        }
+	        
+	    } catch (ClientProtocolException e) {
+	    	Log.d("XXX3", e.getMessage());
+	    } catch (IOException e) {
+	    	Log.d("XXX4", e.getMessage());
+	    }
+		return null;
+	}
+	
+	private HttpClient getNewHttpClient() {
+	    try {
+	        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+	        trustStore.load(null, null);
+
+	        SSLSocketFactory sf = new CustomSSLSocketFactory(trustStore);
+	        sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+
+	        HttpParams params = new BasicHttpParams();
+	        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
+	        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
+
+	        SchemeRegistry registry = new SchemeRegistry();
+	        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
+	        registry.register(new Scheme("https", sf, 443));
+
+	        ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
+
+	        return new DefaultHttpClient(ccm, params);
+	    } catch (Exception e) {
+	        return new DefaultHttpClient();
+	    }
+	}
+	
+	private static String convertStreamToString(InputStream is) {
+	    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+	    StringBuilder sb = new StringBuilder();
+	    String line = null;
+	    try {
+	        while ((line = reader.readLine()) != null) {
+	            sb.append((line + "\n"));
+	        }
+	    } catch (IOException e) {
+	        e.printStackTrace();
+	    } finally {
+	        try {
+	            is.close(); 
+	        } catch (IOException e) {
+	            e.printStackTrace(); 
+	        }
+	    }
+	    return sb.toString();
+	}
+
+	public void saveResponseIntoCache(String request, String response){
+		if(context == null){
+//			Log.d("XXX", "No context, cache failed!");
+			return;
+		}
+        SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE); 
+        SharedPreferences.Editor editor = sharedPref.edit();
+        editor.putString("request_" + Crypt.md5(request), response);
+        editor.putLong("request_" + Crypt.md5(request) + "_ttl", new Date().getTime() + getTtl());
+        editor.commit();
+	}
+	
+	
+	public String getResponseFromCache(String request){
+		if(context == null){
+			Log.d("XXX", "No context, cache miss");
+			return null;
+		}
+        SharedPreferences sharedPref = context.getSharedPreferences( "http_get_cache", Context.MODE_PRIVATE);
+        long ttl = getResponseTtl(request);
+        if(ttl == 0l || (new Date().getTime() - ttl) > 0l){
+        	Log.d("XXX", "Cache invalid ttl:" + ttl + " vs now:" + new Date().getTime());
+        	return null;
+        }
+        return sharedPref.getString("request_" + Crypt.md5(request), null);
+	}
+
+	public long getResponseTtl(String request){
+        SharedPreferences sharedPref = context.getSharedPreferences(
+        		"http_get_cache", Context.MODE_PRIVATE); 
+        return sharedPref.getLong("request_" + Crypt.md5(request) + "_ttl", 0l);
+	}
+
+	public long getTtl() {
+		return cttl > 0 ? cttl : TTL;
+	}
+
+	public void setTtl(long ttl) {
+		this.cttl = (ttl*1000) + new Date().getTime();
+	}
+	
+}

+ 58 - 0
platform/android/java/src/com/android/godot/utils/RequestParams.java

@@ -0,0 +1,58 @@
+package com.android.godot.utils;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicNameValuePair;
+
+/**
+ * 
+ * @author Luis Linietsky <[email protected]>
+ */
+public class RequestParams {
+
+	private HashMap<String,String> params;
+	private String url;
+	
+	public RequestParams(){
+		params = new HashMap<String,String>();
+	}
+	
+	public void put(String key, String value){
+		params.put(key, value);
+	}
+	
+	public String get(String key){
+		return params.get(key);
+	}
+	
+	public void remove(Object key){
+		params.remove(key);
+	}
+	
+	public boolean has(String key){
+		return params.containsKey(key);
+	}
+	
+	public List<NameValuePair> toPairsList(){
+		List<NameValuePair>  fields = new ArrayList<NameValuePair>();
+
+		for(String key : params.keySet()){
+			fields.add(new BasicNameValuePair(key, this.get(key)));
+		}
+		return fields;
+	}
+
+	public String getUrl() {
+		return url;
+	}
+
+	public void setUrl(String url) {
+		this.url = url;
+	}
+
+	
+}

+ 144 - 0
platform/android/java/src/com/android/vending/billing/IInAppBillingService.aidl

@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2012 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.vending.billing;
+
+import android.os.Bundle;
+
+/**
+ * InAppBillingService is the service that provides in-app billing version 3 and beyond.
+ * This service provides the following features:
+ * 1. Provides a new API to get details of in-app items published for the app including
+ *    price, type, title and description.
+ * 2. The purchase flow is synchronous and purchase information is available immediately
+ *    after it completes.
+ * 3. Purchase information of in-app purchases is maintained within the Google Play system
+ *    till the purchase is consumed.
+ * 4. An API to consume a purchase of an inapp item. All purchases of one-time
+ *    in-app items are consumable and thereafter can be purchased again.
+ * 5. An API to get current purchases of the user immediately. This will not contain any
+ *    consumed purchases.
+ *
+ * All calls will give a response code with the following possible values
+ * RESULT_OK = 0 - success
+ * RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
+ * RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
+ * RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
+ * RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
+ * RESULT_ERROR = 6 - Fatal error during the API action
+ * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
+ * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
+ */
+interface IInAppBillingService {
+    /**
+     * Checks support for the requested billing API version, package and in-app type.
+     * Minimum API version supported by this interface is 3.
+     * @param apiVersion the billing version which the app is using
+     * @param packageName the package name of the calling app
+     * @param type type of the in-app item being purchased "inapp" for one-time purchases
+     *        and "subs" for subscription.
+     * @return RESULT_OK(0) on success, corresponding result code on failures
+     */
+    int isBillingSupported(int apiVersion, String packageName, String type);
+
+    /**
+     * Provides details of a list of SKUs
+     * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
+     * with a list JSON strings containing the productId, price, title and description.
+     * This API can be called with a maximum of 20 SKUs.
+     * @param apiVersion billing API version that the Third-party is using
+     * @param packageName the package name of the calling app
+     * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
+     * @return Bundle containing the following key-value pairs
+     *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+     *              failure as listed above.
+     *         "DETAILS_LIST" with a StringArrayList containing purchase information
+     *              in JSON format similar to:
+     *              '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
+     *                 "title : "Example Title", "description" : "This is an example description" }'
+     */
+    Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
+
+    /**
+     * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
+     * the type, a unique purchase token and an optional developer payload.
+     * @param apiVersion billing API version that the app is using
+     * @param packageName package name of the calling app
+     * @param sku the SKU of the in-app item as published in the developer console
+     * @param type the type of the in-app item ("inapp" for one-time purchases
+     *        and "subs" for subscription).
+     * @param developerPayload optional argument to be sent back with the purchase information
+     * @return Bundle containing the following key-value pairs
+     *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+     *              failure as listed above.
+     *         "BUY_INTENT" - PendingIntent to start the purchase flow
+     *
+     * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
+     * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
+     * If the purchase is successful, the result data will contain the following key-value pairs
+     *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+     *              failure as listed above.
+     *         "INAPP_PURCHASE_DATA" - String in JSON format similar to
+     *              '{"orderId":"12999763169054705758.1371079406387615",
+     *                "packageName":"com.example.app",
+     *                "productId":"exampleSku",
+     *                "purchaseTime":1345678900000,
+     *                "purchaseToken" : "122333444455555",
+     *                "developerPayload":"example developer payload" }'
+     *         "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
+     *                                  was signed with the private key of the developer
+     *                                  TODO: change this to app-specific keys.
+     */
+    Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
+        String developerPayload);
+
+    /**
+     * Returns the current SKUs owned by the user of the type and package name specified along with
+     * purchase information and a signature of the data to be validated.
+     * This will return all SKUs that have been purchased in V3 and managed items purchased using
+     * V1 and V2 that have not been consumed.
+     * @param apiVersion billing API version that the app is using
+     * @param packageName package name of the calling app
+     * @param type the type of the in-app items being requested
+     *        ("inapp" for one-time purchases and "subs" for subscription).
+     * @param continuationToken to be set as null for the first call, if the number of owned
+     *        skus are too many, a continuationToken is returned in the response bundle.
+     *        This method can be called again with the continuation token to get the next set of
+     *        owned skus.
+     * @return Bundle containing the following key-value pairs
+     *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+     *              failure as listed above.
+     *         "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
+     *         "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
+     *         "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
+     *                                      of the purchase information
+     *         "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
+     *                                      next set of in-app purchases. Only set if the
+     *                                      user has more owned skus than the current list.
+     */
+    Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
+
+    /**
+     * Consume the last purchase of the given SKU. This will result in this item being removed
+     * from all subsequent responses to getPurchases() and allow re-purchase of this item.
+     * @param apiVersion billing API version that the app is using
+     * @param packageName package name of the calling app
+     * @param purchaseToken token in the purchase information JSON that identifies the purchase
+     *        to be consumed
+     * @return 0 if consumption succeeded. Appropriate error values for failures.
+     */
+    int consumePurchase(int apiVersion, String packageName, String purchaseToken);
+}

+ 49 - 3
platform/android/java_glue.cpp

@@ -440,7 +440,8 @@ public:
 			case Variant::STRING: {
 
 				jobject o = env->CallObjectMethodA(instance,E->get().method,v);
-				String singname = env->GetStringUTFChars((jstring)o, NULL );
+				String str = env->GetStringUTFChars((jstring)o, NULL );
+				ret=str;
 			} break;
 			case Variant::STRING_ARRAY: {
 
@@ -569,6 +570,11 @@ static jmethodID _hideKeyboard=0;
 static jmethodID _setScreenOrientation=0;
 static jmethodID _getUniqueID=0;
 
+static jmethodID _playVideo=0;
+static jmethodID _isVideoPlaying=0;
+static jmethodID _pauseVideo=0;
+static jmethodID _stopVideo=0;
+
 
 static void _gfx_init_func(void* ud, bool gl2) {
 
@@ -629,17 +635,43 @@ static void _hide_vk() {
 	env->CallVoidMethod(godot_io, _hideKeyboard);
 };
 
+// virtual Error native_video_play(String p_path);
+// virtual bool native_video_is_playing();
+// virtual void native_video_pause();
+// virtual void native_video_stop();
+
+static void _play_video(const String& p_path) {
+
+}
+
+static bool _is_video_playing() {
+	JNIEnv* env = ThreadAndroid::get_env();
+	return env->CallBooleanMethod(godot_io, _isVideoPlaying);
+	//return false;
+}
+
+static void _pause_video() {
+	JNIEnv* env = ThreadAndroid::get_env();
+	env->CallVoidMethod(godot_io, _pauseVideo);
+}
+
+static void _stop_video() {
+	JNIEnv* env = ThreadAndroid::get_env();
+	env->CallVoidMethod(godot_io, _stopVideo);
+}
+
 JNIEXPORT void JNICALL Java_com_android_godot_GodotLib_initialize(JNIEnv * env, jobject obj, jobject activity,jboolean p_need_reload_hook) {
 
 	__android_log_print(ANDROID_LOG_INFO,"godot","**INIT EVENT! - %p\n",env);
 
 
 	initialized=true;
-	_godot_instance=activity;
 
 	JavaVM *jvm;
 	env->GetJavaVM(&jvm);
 
+	_godot_instance=env->NewGlobalRef(activity);
+//	_godot_instance=activity;
 
 	__android_log_print(ANDROID_LOG_INFO,"godot","***************** HELLO FROM JNI!!!!!!!!");
 
@@ -676,6 +708,11 @@ JNIEXPORT void JNICALL Java_com_android_godot_GodotLib_initialize(JNIEnv * env,
 			_showKeyboard = env->GetMethodID(c,"showKeyboard","(Ljava/lang/String;)V");
 			_hideKeyboard = env->GetMethodID(c,"hideKeyboard","()V");
 			_setScreenOrientation = env->GetMethodID(c,"setScreenOrientation","(I)V");
+
+			_playVideo = env->GetMethodID(c,"playVideo","(Ljava/lang/String;)V");
+			_isVideoPlaying = env->GetMethodID(c,"isVideoPlaying","()Z");
+			_pauseVideo = env->GetMethodID(c,"pauseVideo","()V");
+			_stopVideo = env->GetMethodID(c,"stopVideo","()V");
 		}
 
 		ThreadAndroid::make_default(jvm);
@@ -686,7 +723,7 @@ JNIEXPORT void JNICALL Java_com_android_godot_GodotLib_initialize(JNIEnv * env,
 
 
 
-    os_android = new OS_Android(_gfx_init_func,env,_open_uri,_get_data_dir,_get_locale, _get_model,_show_vk, _hide_vk,_set_screen_orient,_get_unique_id);
+    os_android = new OS_Android(_gfx_init_func,env,_open_uri,_get_data_dir,_get_locale, _get_model,_show_vk, _hide_vk,_set_screen_orient,_get_unique_id, _play_video, _is_video_playing, _pause_video, _stop_video);
     os_android->set_need_reload_hooks(p_need_reload_hook);
 
 	char wd[500];
@@ -781,8 +818,10 @@ static void _initialize_java_modules() {
 		jmethodID getClassLoader = env->GetMethodID(activityClass,"getClassLoader", "()Ljava/lang/ClassLoader;");
 
 		jobject cls = env->CallObjectMethod(_godot_instance, getClassLoader);
+		//cls=env->NewGlobalRef(cls);
 
 		jclass classLoader = env->FindClass("java/lang/ClassLoader");
+		//classLoader=(jclass)env->NewGlobalRef(classLoader);
 
 		jmethodID findClass = env->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
 
@@ -800,10 +839,17 @@ static void _initialize_java_modules() {
 				ERR_EXPLAIN("Couldn't find singleton for class: "+m);
 				ERR_CONTINUE(!singletonClass);
 			}
+			//singletonClass=(jclass)env->NewGlobalRef(singletonClass);
 
 			__android_log_print(ANDROID_LOG_INFO,"godot","****^*^*?^*^*class data %x",singletonClass);
 			jmethodID initialize = env->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lcom/android/godot/Godot$SingletonBase;");
 
+			if (!initialize) {
+
+				ERR_EXPLAIN("Couldn't find proper initialize function 'public static Godot.SingletonBase Class::initialize(Activity p_activity)' initializer for singleton class: "+m);
+				ERR_CONTINUE(!initialize);
+
+			}
 			jobject obj = env->CallStaticObjectMethod(singletonClass,initialize,_godot_instance);
 			__android_log_print(ANDROID_LOG_INFO,"godot","****^*^*?^*^*class instance %x",obj);
 			jobject gob = env->NewGlobalRef(obj);

+ 9 - 0
platform/android/libs/apk_expansion/AndroidManifest.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.vending.expansion.downloader"
+    android:versionCode="2"
+    android:versionName="1.1" >
+
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="15"/>
+    
+</manifest>

+ 92 - 0
platform/android/libs/apk_expansion/build.xml

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="apk_expansion" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contains the path to the SDK. It should *NOT* be checked into
+         Version Control Systems. -->
+    <property file="local.properties" />
+
+    <!-- The ant.properties file can be created by you. It is only edited by the
+         'android' tool to add properties to it.
+         This is the place to change some Ant specific build properties.
+         Here are some properties you may want to change/update:
+
+         source.dir
+             The name of the source directory. Default is 'src'.
+         out.dir
+             The name of the output directory. Default is 'bin'.
+
+         For other overridable properties, look at the beginning of the rules
+         files in the SDK, at tools/ant/build.xml
+
+         Properties related to the SDK location or the project target should
+         be updated using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems.
+
+         -->
+    <property file="ant.properties" />
+
+    <!-- if sdk.dir was not set from one of the property file, then
+         get it from the ANDROID_HOME env var.
+         This must be done before we load project.properties since
+         the proguard config can use sdk.dir -->
+    <property environment="env" />
+    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
+        <isset property="env.ANDROID_HOME" />
+    </condition>
+
+    <!-- The project.properties file is created and updated by the 'android'
+         tool, as well as ADT.
+
+         This contains project specific properties such as project target, and library
+         dependencies. Lower level build properties are stored in ant.properties
+         (or in .classpath for Eclipse projects).
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems. -->
+    <loadproperties srcFile="project.properties" />
+
+    <!-- quick check on sdk.dir -->
+    <fail
+            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
+            unless="sdk.dir"
+    />
+
+    <!--
+        Import per project custom build rules if present at the root of the project.
+        This is the place to put custom intermediary targets such as:
+            -pre-build
+            -pre-compile
+            -post-compile (This is typically used for code obfuscation.
+                           Compiled code location: ${out.classes.absolute.dir}
+                           If this is not done in place, override ${out.dex.input.absolute.dir})
+            -post-package
+            -post-build
+            -pre-clean
+    -->
+    <import file="custom_rules.xml" optional="true" />
+
+    <!-- Import the actual build file.
+
+         To customize existing targets, there are two options:
+         - Customize only one target:
+             - copy/paste the target into this file, *before* the
+               <import> task.
+             - customize it to your needs.
+         - Customize the whole content of build.xml
+             - copy/paste the content of the rules files (minus the top node)
+               into this file, replacing the <import> task.
+             - customize to your needs.
+
+         ***********************
+         ****** IMPORTANT ******
+         ***********************
+         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+         in order to avoid having your file be overridden by tools such as "android update project"
+    -->
+    <!-- version-tag: 1 -->
+    <import file="${sdk.dir}/tools/ant/build.xml" />
+
+</project>

+ 20 - 0
platform/android/libs/apk_expansion/proguard-project.txt

@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 13 - 0
platform/android/libs/apk_expansion/project.properties

@@ -0,0 +1,13 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-15
+android.library=true
+android.library.reference.1=../play_licensing

BIN
platform/android/libs/apk_expansion/res/drawable-hdpi/notify_panel_notification_icon_bg.png


BIN
platform/android/libs/apk_expansion/res/drawable-mdpi/notify_panel_notification_icon_bg.png


+ 104 - 0
platform/android/libs/apk_expansion/res/layout/status_bar_ongoing_event_progress_bar.xml

@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, 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.
+*/
+-->
+
+<LinearLayout android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:baselineAligned="false"
+    android:orientation="horizontal" android:id="@+id/notificationLayout" xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <RelativeLayout
+        android:layout_width="35dp"
+        android:layout_height="fill_parent"
+        android:paddingTop="10dp"
+        android:paddingBottom="8dp" >
+
+        <ImageView
+            android:id="@+id/appIcon"
+            android:layout_width="fill_parent"
+            android:layout_height="25dp"
+            android:scaleType="centerInside"            
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentTop="true"            
+            android:src="@android:drawable/stat_sys_download" />
+
+        <TextView
+            android:id="@+id/progress_text"
+            style="@style/NotificationText"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentBottom="true"
+            android:layout_gravity="center_horizontal"
+            android:singleLine="true"
+            android:gravity="center" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:layout_width="0dip"
+        android:layout_height="match_parent"
+        android:layout_weight="1.0"
+        android:clickable="true"
+        android:focusable="true"
+        android:paddingTop="10dp"
+        android:paddingRight="8dp"
+        android:paddingBottom="8dp" >
+
+        <TextView
+            android:id="@+id/title"
+            style="@style/NotificationTitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:singleLine="true"/>
+
+        <TextView
+            android:id="@+id/time_remaining"
+            style="@style/NotificationText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:singleLine="true"/>
+        <!-- Only one of progress_bar and paused_text will be visible. -->
+
+        <FrameLayout
+            android:id="@+id/progress_bar_frame"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true" >
+
+            <ProgressBar
+                android:id="@+id/progress_bar"
+                style="?android:attr/progressBarStyleHorizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:paddingRight="25dp" />
+
+            <TextView
+                android:id="@+id/description"
+                style="@style/NotificationTextShadow"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:paddingRight="25dp"
+                android:singleLine="true" />
+        </FrameLayout>
+
+    </RelativeLayout>
+
+</LinearLayout>

+ 6 - 0
platform/android/libs/apk_expansion/res/values-v11/styles.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="NotificationTextSecondary" parent="NotificationText">
+        <item name="android:textSize">12sp</item>
+    </style>
+</resources>

+ 5 - 0
platform/android/libs/apk_expansion/res/values-v9/styles.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="NotificationText" parent="android:TextAppearance.StatusBar.EventContent" />
+    <style name="NotificationTitle" parent="android:TextAppearance.StatusBar.EventContent.Title" />
+</resources>

+ 41 - 0
platform/android/libs/apk_expansion/res/values/strings.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <!-- When a download completes, a notification is displayed, and this
+        string is used to indicate that the download successfully completed.
+        Note that such a download could have been initiated by a variety of
+        applications, including (but not limited to) the browser, an email
+        application, a content marketplace. -->
+    <string name="notification_download_complete">Download complete</string>
+
+    <!-- When a download completes, a notification is displayed, and this
+        string is used to indicate that the download failed.
+        Note that such a download could have been initiated by a variety of
+        applications, including (but not limited to) the browser, an email
+        application, a content marketplace. -->
+    <string name="notification_download_failed">Download unsuccessful</string>
+
+
+    <string name="state_unknown">Starting..."</string>
+    <string name="state_idle">Waiting for download to start</string>
+    <string name="state_fetching_url">Looking for resources to download</string>
+    <string name="state_connecting">Connecting to the download server</string>
+    <string name="state_downloading">Downloading resources</string>
+    <string name="state_completed">Download finished</string>
+    <string name="state_paused_network_unavailable">Download paused because no network is available</string>
+    <string name="state_paused_network_setup_failure">Download paused. Test a website in browser</string>
+    <string name="state_paused_by_request">Download paused</string>
+    <string name="state_paused_wifi_unavailable">Download paused because wifi is unavailable</string>
+    <string name="state_paused_wifi_disabled">Download paused because wifi is disabled</string>
+    <string name="state_paused_roaming">Download paused because you are roaming</string>
+    <string name="state_paused_sdcard_unavailable">Download paused because the external storage is unavailable</string>
+    <string name="state_failed_unlicensed">Download failed because you may not have purchased this app</string>
+    <string name="state_failed_fetching_url">Download failed because the resources could not be found</string>
+    <string name="state_failed_sdcard_full">Download failed because the external storage is full</string>
+    <string name="state_failed_cancelled">Download cancelled</string>
+    <string name="state_failed">Download failed</string>
+
+    <string name="kilobytes_per_second">%1$s KB/s</string>
+    <string name="time_remaining">Time remaining: %1$s</string>
+    <string name="time_remaining_notification">%1$s left</string>
+</resources>

+ 25 - 0
platform/android/libs/apk_expansion/res/values/styles.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <style name="NotificationText">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="NotificationTextShadow" parent="NotificationText">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:shadowColor">@android:color/background_dark</item>
+        <item name="android:shadowDx">1.0</item>
+        <item name="android:shadowDy">1.0</item>
+        <item name="android:shadowRadius">1</item>
+    </style>
+
+    <style name="NotificationTitle">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="ButtonBackground">
+        <item name="android:background">@android:color/background_dark</item>
+    </style>
+
+</resources>

+ 236 - 0
platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Constants.java

@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2012 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.google.android.vending.expansion.downloader;
+
+import java.io.File;
+
+
+/**
+ * Contains the internal constants that are used in the download manager.
+ * As a general rule, modifying these constants should be done with care.
+ */
+public class Constants {    
+    /** Tag used for debugging/logging */
+    public static final String TAG = "LVLDL";
+
+    /**
+     * Expansion path where we store obb files
+     */
+    public static final String EXP_PATH = File.separator + "Android"
+            + File.separator + "obb" + File.separator;
+    
+    /** The intent that gets sent when the service must wake up for a retry */
+    public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP";
+
+    /** the intent that gets sent when clicking a successful download */
+    public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN";
+
+    /** the intent that gets sent when clicking an incomplete/failed download  */
+    public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST";
+
+    /** the intent that gets sent when deleting the notification of a completed download */
+    public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE";
+
+    /**
+     * When a number has to be appended to the filename, this string is used to separate the
+     * base filename from the sequence number
+     */
+    public static final String FILENAME_SEQUENCE_SEPARATOR = "-";
+
+    /** The default user agent used for downloads */
+    public static final String DEFAULT_USER_AGENT = "Android.LVLDM";
+
+    /** The buffer size used to stream the data */
+    public static final int BUFFER_SIZE = 4096;
+
+    /** The minimum amount of progress that has to be done before the progress bar gets updated */
+    public static final int MIN_PROGRESS_STEP = 4096;
+
+    /** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */
+    public static final long MIN_PROGRESS_TIME = 1000;
+
+    /** The maximum number of rows in the database (FIFO) */
+    public static final int MAX_DOWNLOADS = 1000;
+
+    /**
+     * The number of times that the download manager will retry its network
+     * operations when no progress is happening before it gives up.
+     */
+    public static final int MAX_RETRIES = 5;
+
+    /**
+     * The minimum amount of time that the download manager accepts for
+     * a Retry-After response header with a parameter in delta-seconds.
+     */
+    public static final int MIN_RETRY_AFTER = 30; // 30s
+
+    /**
+     * The maximum amount of time that the download manager accepts for
+     * a Retry-After response header with a parameter in delta-seconds.
+     */
+    public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h
+
+    /**
+     * The maximum number of redirects.
+     */
+    public static final int MAX_REDIRECTS = 5; // can't be more than 7.
+
+    /**
+     * The time between a failure and the first retry after an IOException.
+     * Each subsequent retry grows exponentially, doubling each time.
+     * The time is in seconds.
+     */
+    public static final int RETRY_FIRST_DELAY = 30;
+
+    /** Enable separate connectivity logging */
+    public static final boolean LOGX = true;
+
+    /** Enable verbose logging */
+    public static final boolean LOGV = false;
+    
+    /** Enable super-verbose logging */
+    private static final boolean LOCAL_LOGVV = false;
+    public static final boolean LOGVV = LOCAL_LOGVV && LOGV;
+    
+    /**
+     * This download has successfully completed.
+     * Warning: there might be other status values that indicate success
+     * in the future.
+     * Use isSucccess() to capture the entire category.
+     */
+    public static final int STATUS_SUCCESS = 200;
+
+    /**
+     * This request couldn't be parsed. This is also used when processing
+     * requests with unknown/unsupported URI schemes.
+     */
+    public static final int STATUS_BAD_REQUEST = 400;
+
+    /**
+     * This download can't be performed because the content type cannot be
+     * handled.
+     */
+    public static final int STATUS_NOT_ACCEPTABLE = 406;
+
+    /**
+     * This download cannot be performed because the length cannot be
+     * determined accurately. This is the code for the HTTP error "Length
+     * Required", which is typically used when making requests that require
+     * a content length but don't have one, and it is also used in the
+     * client when a response is received whose length cannot be determined
+     * accurately (therefore making it impossible to know when a download
+     * completes).
+     */
+    public static final int STATUS_LENGTH_REQUIRED = 411;
+
+    /**
+     * This download was interrupted and cannot be resumed.
+     * This is the code for the HTTP error "Precondition Failed", and it is
+     * also used in situations where the client doesn't have an ETag at all.
+     */
+    public static final int STATUS_PRECONDITION_FAILED = 412;
+
+    /**
+     * The lowest-valued error status that is not an actual HTTP status code.
+     */
+    public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488;
+
+    /**
+     * The requested destination file already exists.
+     */
+    public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488;
+
+    /**
+     * Some possibly transient error occurred, but we can't resume the download.
+     */
+    public static final int STATUS_CANNOT_RESUME = 489;
+
+    /**
+     * This download was canceled
+     */
+    public static final int STATUS_CANCELED = 490;
+
+    /**
+     * This download has completed with an error.
+     * Warning: there will be other status values that indicate errors in
+     * the future. Use isStatusError() to capture the entire category.
+     */
+    public static final int STATUS_UNKNOWN_ERROR = 491;
+
+    /**
+     * This download couldn't be completed because of a storage issue.
+     * Typically, that's because the filesystem is missing or full.
+     * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
+     * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
+     */
+    public static final int STATUS_FILE_ERROR = 492;
+
+    /**
+     * This download couldn't be completed because of an HTTP
+     * redirect response that the download manager couldn't
+     * handle.
+     */
+    public static final int STATUS_UNHANDLED_REDIRECT = 493;
+
+    /**
+     * This download couldn't be completed because of an
+     * unspecified unhandled HTTP code.
+     */
+    public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
+
+    /**
+     * This download couldn't be completed because of an
+     * error receiving or processing data at the HTTP level.
+     */
+    public static final int STATUS_HTTP_DATA_ERROR = 495;
+
+    /**
+     * This download couldn't be completed because of an
+     * HttpException while setting up the request.
+     */
+    public static final int STATUS_HTTP_EXCEPTION = 496;
+
+    /**
+     * This download couldn't be completed because there were
+     * too many redirects.
+     */
+    public static final int STATUS_TOO_MANY_REDIRECTS = 497;
+
+    /**
+     * This download couldn't be completed due to insufficient storage
+     * space.  Typically, this is because the SD card is full.
+     */
+    public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
+
+    /**
+     * This download couldn't be completed because no external storage
+     * device was found.  Typically, this is because the SD card is not
+     * mounted.
+     */
+    public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
+
+    /**
+     * The wake duration to check to see if a download is possible.
+     */
+    public static final long WATCHDOG_WAKE_TIMER = 60*1000;    
+
+    /**
+     * The wake duration to check to see if the process was killed.
+     */
+    public static final long ACTIVE_THREAD_WATCHDOG = 5*1000;    
+
+}

+ 80 - 0
platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 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.google.android.vending.expansion.downloader;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+/**
+ * This class contains progress information about the active download(s).
+ *
+ * When you build the Activity that initiates a download and tracks the
+ * progress by implementing the {@link IDownloaderClient} interface, you'll
+ * receive a DownloadProgressInfo object in each call to the {@link
+ * IDownloaderClient#onDownloadProgress} method. This allows you to update
+ * your activity's UI with information about the download progress, such
+ * as the progress so far, time remaining and current speed.
+ */
+public class DownloadProgressInfo implements Parcelable {
+    public long mOverallTotal;
+    public long mOverallProgress;
+    public long mTimeRemaining; // time remaining
+    public float mCurrentSpeed; // speed in KB/S
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel p, int i) {
+        p.writeLong(mOverallTotal);
+        p.writeLong(mOverallProgress);
+        p.writeLong(mTimeRemaining);
+        p.writeFloat(mCurrentSpeed);
+    }
+
+    public DownloadProgressInfo(Parcel p) {
+        mOverallTotal = p.readLong();
+        mOverallProgress = p.readLong();
+        mTimeRemaining = p.readLong();
+        mCurrentSpeed = p.readFloat();
+    }
+
+    public DownloadProgressInfo(long overallTotal, long overallProgress,
+            long timeRemaining,
+            float currentSpeed) {
+        this.mOverallTotal = overallTotal;
+        this.mOverallProgress = overallProgress;
+        this.mTimeRemaining = timeRemaining;
+        this.mCurrentSpeed = currentSpeed;
+    }
+
+    public static final Creator<DownloadProgressInfo> CREATOR = new Creator<DownloadProgressInfo>() {
+        @Override
+        public DownloadProgressInfo createFromParcel(Parcel parcel) {
+            return new DownloadProgressInfo(parcel);
+        }
+
+        @Override
+        public DownloadProgressInfo[] newArray(int i) {
+            return new DownloadProgressInfo[i];
+        }
+    };
+
+}

+ 277 - 0
platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java

@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2012 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.google.android.vending.expansion.downloader;
+
+import com.google.android.vending.expansion.downloader.impl.DownloaderService;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+
+
+/**
+ * This class binds the service API to your application client.  It contains the IDownloaderClient proxy,
+ * which is used to call functions in your client as well as the Stub, which is used to call functions
+ * in the client implementation of IDownloaderClient.
+ * 
+ * <p>The IPC is implemented using an Android Messenger and a service Binder.  The connect method
+ * should be called whenever the client wants to bind to the service.  It opens up a service connection
+ * that ends up calling the onServiceConnected client API that passes the service messenger
+ * in.  If the client wants to be notified by the service, it is responsible for then passing its
+ * messenger to the service in a separate call.
+ *
+ * <p>Critical methods are {@link #startDownloadServiceIfRequired} and {@link #CreateStub}.
+ *
+ * <p>When your application first starts, you should first check whether your app's expansion files are
+ * already on the device. If not, you should then call {@link #startDownloadServiceIfRequired}, which
+ * starts your {@link impl.DownloaderService} to download the expansion files if necessary. The method
+ * returns a value indicating whether download is required or not.
+ *
+ * <p>If a download is required, {@link #startDownloadServiceIfRequired} begins the download through
+ * the specified service and you should then call {@link #CreateStub} to instantiate a member {@link
+ * IStub} object that you need in order to receive calls through your {@link IDownloaderClient}
+ * interface.
+ */
+public class DownloaderClientMarshaller {
+    public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10;
+    public static final int MSG_ONDOWNLOADPROGRESS = 11;
+    public static final int MSG_ONSERVICECONNECTED = 12;
+
+    public static final String PARAM_NEW_STATE = "newState";
+    public static final String PARAM_PROGRESS = "progress";
+    public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER;
+
+    public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED;
+    public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED;
+    public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED;
+
+    private static class Proxy implements IDownloaderClient {
+        private Messenger mServiceMessenger;
+
+        @Override
+        public void onDownloadStateChanged(int newState) {
+            Bundle params = new Bundle(1);
+            params.putInt(PARAM_NEW_STATE, newState);
+            send(MSG_ONDOWNLOADSTATE_CHANGED, params);
+        }
+
+        @Override
+        public void onDownloadProgress(DownloadProgressInfo progress) {
+            Bundle params = new Bundle(1);
+            params.putParcelable(PARAM_PROGRESS, progress);
+            send(MSG_ONDOWNLOADPROGRESS, params);
+        }
+
+        private void send(int method, Bundle params) {
+            Message m = Message.obtain(null, method);
+            m.setData(params);
+            try {
+                mServiceMessenger.send(m);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+        
+        public Proxy(Messenger msg) {
+            mServiceMessenger = msg;
+        }
+
+        @Override
+        public void onServiceConnected(Messenger m) {
+            /**
+             * This is never called through the proxy.
+             */
+        }
+    }
+
+    private static class Stub implements IStub {
+        private IDownloaderClient mItf = null;
+        private Class<?> mDownloaderServiceClass;
+        private boolean mBound;
+        private Messenger mServiceMessenger;
+        private Context mContext;
+        /**
+         * Target we publish for clients to send messages to IncomingHandler.
+         */
+        final Messenger mMessenger = new Messenger(new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_ONDOWNLOADPROGRESS:                        
+                        Bundle bun = msg.getData();
+                        if ( null != mContext ) {
+                            bun.setClassLoader(mContext.getClassLoader());
+                            DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData()
+                                    .getParcelable(PARAM_PROGRESS);
+                            mItf.onDownloadProgress(dpi);
+                        }
+                        break;
+                    case MSG_ONDOWNLOADSTATE_CHANGED:
+                        mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE));
+                        break;
+                    case MSG_ONSERVICECONNECTED:
+                        mItf.onServiceConnected(
+                                (Messenger) msg.getData().getParcelable(PARAM_MESSENGER));
+                        break;
+                }
+            }
+        });
+
+        public Stub(IDownloaderClient itf, Class<?> downloaderService) {
+            mItf = itf;
+            mDownloaderServiceClass = downloaderService;
+        }
+
+        /**
+         * Class for interacting with the main interface of the service.
+         */
+        private ServiceConnection mConnection = new ServiceConnection() {
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                // This is called when the connection with the service has been
+                // established, giving us the object we can use to
+                // interact with the service. We are communicating with the
+                // service using a Messenger, so here we get a client-side
+                // representation of that from the raw IBinder object.
+                mServiceMessenger = new Messenger(service);
+                mItf.onServiceConnected(
+                        mServiceMessenger);
+            }
+
+            public void onServiceDisconnected(ComponentName className) {
+                // This is called when the connection with the service has been
+                // unexpectedly disconnected -- that is, its process crashed.
+                mServiceMessenger = null;
+            }
+        };
+
+        @Override
+        public void connect(Context c) {
+            mContext = c;
+            Intent bindIntent = new Intent(c, mDownloaderServiceClass);
+            bindIntent.putExtra(PARAM_MESSENGER, mMessenger);
+            if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) {
+                if ( Constants.LOGVV ) {
+                    Log.d(Constants.TAG, "Service Unbound");
+                }
+            } else {
+                mBound = true;
+            }
+                
+        }
+
+        @Override
+        public void disconnect(Context c) {
+            if (mBound) {
+                c.unbindService(mConnection);
+                mBound = false;
+            }
+            mContext = null;
+        }
+
+        @Override
+        public Messenger getMessenger() {
+            return mMessenger;
+        }
+    }
+
+    /**
+     * Returns a proxy that will marshal calls to IDownloaderClient methods
+     * 
+     * @param msg
+     * @return
+     */
+    public static IDownloaderClient CreateProxy(Messenger msg) {
+        return new Proxy(msg);
+    }
+
+    /**
+     * Returns a stub object that, when connected, will listen for marshaled
+     * {@link IDownloaderClient} methods and translate them into calls to the supplied
+     * interface.
+     * 
+     * @param itf An implementation of IDownloaderClient that will be called
+     *            when remote method calls are unmarshaled.
+     * @param downloaderService The class for your implementation of {@link
+     * impl.DownloaderService}.
+     * @return The {@link IStub} that allows you to connect to the service such that
+     * your {@link IDownloaderClient} receives status updates.
+     */
+    public static IStub CreateStub(IDownloaderClient itf, Class<?> downloaderService) {
+        return new Stub(itf, downloaderService);
+    }
+    
+    /**
+     * Starts the download if necessary. This function starts a flow that does `
+     * many things. 1) Checks to see if the APK version has been checked and
+     * the metadata database updated 2) If the APK version does not match,
+     * checks the new LVL status to see if a new download is required 3) If the
+     * APK version does match, then checks to see if the download(s) have been
+     * completed 4) If the downloads have been completed, returns
+     * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the
+     * startup of an application to quickly ascertain if the application needs
+     * to wait to hear about any updated APK expansion files. Note that this does
+     * mean that the application MUST be run for the first time with a network
+     * connection, even if Market delivers all of the files.
+     * 
+     * @param context Your application Context.
+     * @param notificationClient A PendingIntent to start the Activity in your application
+     * that shows the download progress and which will also start the application when download
+     * completes.
+     * @param serviceClass the class of your {@link imp.DownloaderService} implementation
+     * @return whether the service was started and the reason for starting the service.
+     * Either {@link #NO_DOWNLOAD_REQUIRED}, {@link #LVL_CHECK_REQUIRED}, or {@link
+     * #DOWNLOAD_REQUIRED}.
+     * @throws NameNotFoundException
+     */
+    public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, 
+            Class<?> serviceClass)
+            throws NameNotFoundException {
+        return DownloaderService.startDownloadServiceIfRequired(context, notificationClient,
+                serviceClass);
+    }
+    
+    /**
+     * This version assumes that the intent contains the pending intent as a parameter. This
+     * is used for responding to alarms.
+     * <p>The pending intent must be in an extra with the key {@link 
+     * impl.DownloaderService#EXTRA_PENDING_INTENT}.
+     * 
+     * @param context
+     * @param notificationClient
+     * @param serviceClass the class of the service to start
+     * @return
+     * @throws NameNotFoundException
+     */
+    public static int startDownloadServiceIfRequired(Context context, Intent notificationClient, 
+            Class<?> serviceClass)
+            throws NameNotFoundException {
+        return DownloaderService.startDownloadServiceIfRequired(context, notificationClient,
+                serviceClass);
+    }    
+
+}

+ 181 - 0
platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java

@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2012 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.google.android.vending.expansion.downloader;
+
+import com.google.android.vending.expansion.downloader.impl.DownloaderService;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+
+
+/**
+ * This class is used by the client activity to proxy requests to the Downloader
+ * Service.
+ *
+ * Most importantly, you must call {@link #CreateProxy} during the {@link
+ * IDownloaderClient#onServiceConnected} callback in your activity in order to instantiate
+ * an {@link IDownloaderService} object that you can then use to issue commands to the {@link
+ * DownloaderService} (such as to pause and resume downloads).
+ */
+public class DownloaderServiceMarshaller {
+
+    public static final int MSG_REQUEST_ABORT_DOWNLOAD =
+            1;
+    public static final int MSG_REQUEST_PAUSE_DOWNLOAD =
+            2;
+    public static final int MSG_SET_DOWNLOAD_FLAGS =
+            3;
+    public static final int MSG_REQUEST_CONTINUE_DOWNLOAD =
+            4;
+    public static final int MSG_REQUEST_DOWNLOAD_STATE =
+            5;
+    public static final int MSG_REQUEST_CLIENT_UPDATE =
+            6;
+
+    public static final String PARAMS_FLAGS = "flags";
+    public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER;
+
+    private static class Proxy implements IDownloaderService {
+        private Messenger mMsg;
+
+        private void send(int method, Bundle params) {
+            Message m = Message.obtain(null, method);
+            m.setData(params);
+            try {
+                mMsg.send(m);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+
+        public Proxy(Messenger msg) {
+            mMsg = msg;
+        }
+
+        @Override
+        public void requestAbortDownload() {
+            send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle());
+        }
+
+        @Override
+        public void requestPauseDownload() {
+            send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle());
+        }
+
+        @Override
+        public void setDownloadFlags(int flags) {
+            Bundle params = new Bundle();
+            params.putInt(PARAMS_FLAGS, flags);
+            send(MSG_SET_DOWNLOAD_FLAGS, params);
+        }
+
+        @Override
+        public void requestContinueDownload() {
+            send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle());
+        }
+
+        @Override
+        public void requestDownloadStatus() {
+            send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle());
+        }
+
+        @Override
+        public void onClientUpdated(Messenger clientMessenger) {
+            Bundle bundle = new Bundle(1);
+            bundle.putParcelable(PARAM_MESSENGER, clientMessenger);
+            send(MSG_REQUEST_CLIENT_UPDATE, bundle);
+        }
+    }
+
+    private static class Stub implements IStub {
+        private IDownloaderService mItf = null;
+        final Messenger mMessenger = new Messenger(new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_REQUEST_ABORT_DOWNLOAD:
+                        mItf.requestAbortDownload();
+                        break;
+                    case MSG_REQUEST_CONTINUE_DOWNLOAD:
+                        mItf.requestContinueDownload();
+                        break;
+                    case MSG_REQUEST_PAUSE_DOWNLOAD:
+                        mItf.requestPauseDownload();
+                        break;
+                    case MSG_SET_DOWNLOAD_FLAGS:
+                        mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS));
+                        break;
+                    case MSG_REQUEST_DOWNLOAD_STATE:
+                        mItf.requestDownloadStatus();
+                        break;
+                    case MSG_REQUEST_CLIENT_UPDATE:
+                        mItf.onClientUpdated((Messenger) msg.getData().getParcelable(
+                                PARAM_MESSENGER));
+                        break;
+                }
+            }
+        });
+
+        public Stub(IDownloaderService itf) {
+            mItf = itf;
+        }
+
+        @Override
+        public Messenger getMessenger() {
+            return mMessenger;
+        }
+
+        @Override
+        public void connect(Context c) {
+
+        }
+
+        @Override
+        public void disconnect(Context c) {
+
+        }
+    }
+
+    /**
+     * Returns a proxy that will marshall calls to IDownloaderService methods
+     * 
+     * @param ctx
+     * @return
+     */
+    public static IDownloaderService CreateProxy(Messenger msg) {
+        return new Proxy(msg);
+    }
+
+    /**
+     * Returns a stub object that, when connected, will listen for marshalled
+     * IDownloaderService methods and translate them into calls to the supplied
+     * interface.
+     * 
+     * @param itf An implementation of IDownloaderService that will be called
+     *            when remote method calls are unmarshalled.
+     * @return
+     */
+    public static IStub CreateStub(IDownloaderService itf) {
+        return new Stub(itf);
+    }
+
+}

+ 306 - 0
platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Helpers.java

@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2012 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.google.android.vending.expansion.downloader;
+
+import com.android.vending.expansion.downloader.R;
+
+import android.content.Context;
+import android.os.Environment;
+import android.os.StatFs;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Random;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Some helper functions for the download manager
+ */
+public class Helpers {
+
+    public static Random sRandom = new Random(SystemClock.uptimeMillis());
+
+    /** Regex used to parse content-disposition headers */
+    private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern
+            .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
+
+    private Helpers() {
+    }
+
+    /*
+     * Parse the Content-Disposition HTTP Header. The format of the header is
+     * defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This
+     * header provides a filename for content that is going to be downloaded to
+     * the file system. We only support the attachment type.
+     */
+    static String parseContentDisposition(String contentDisposition) {
+        try {
+            Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
+            if (m.find()) {
+                return m.group(1);
+            }
+        } catch (IllegalStateException ex) {
+            // This function is defined as returning null when it can't parse
+            // the header
+        }
+        return null;
+    }
+
+    /**
+     * @return the root of the filesystem containing the given path
+     */
+    public static File getFilesystemRoot(String path) {
+        File cache = Environment.getDownloadCacheDirectory();
+        if (path.startsWith(cache.getPath())) {
+            return cache;
+        }
+        File external = Environment.getExternalStorageDirectory();
+        if (path.startsWith(external.getPath())) {
+            return external;
+        }
+        throw new IllegalArgumentException(
+                "Cannot determine filesystem root for " + path);
+    }
+
+    public static boolean isExternalMediaMounted() {
+        if (!Environment.getExternalStorageState().equals(
+                Environment.MEDIA_MOUNTED)) {
+            // No SD card found.
+            if ( Constants.LOGVV ) {
+                Log.d(Constants.TAG, "no external storage");
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @return the number of bytes available on the filesystem rooted at the
+     *         given File
+     */
+    public static long getAvailableBytes(File root) {
+        StatFs stat = new StatFs(root.getPath());
+        // put a bit of margin (in case creating the file grows the system by a
+        // few blocks)
+        long availableBlocks = (long) stat.getAvailableBlocks() - 4;
+        return stat.getBlockSize() * availableBlocks;
+    }
+
+    /**
+     * Checks whether the filename looks legitimate
+     */
+    public static boolean isFilenameValid(String filename) {
+        filename = filename.replaceFirst("/+", "/"); // normalize leading
+                                                     // slashes
+        return filename.startsWith(Environment.getDownloadCacheDirectory().toString())
+                || filename.startsWith(Environment.getExternalStorageDirectory().toString());
+    }
+
+    /*
+     * Delete the given file from device
+     */
+    /* package */static void deleteFile(String path) {
+        try {
+            File file = new File(path);
+            file.delete();
+        } catch (Exception e) {
+            Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e);
+        }
+    }
+
+    /**
+     * Showing progress in MB here. It would be nice to choose the unit (KB, MB,
+     * GB) based on total file size, but given what we know about the expected
+     * ranges of file sizes for APK expansion files, it's probably not necessary.
+     * 
+     * @param overallProgress
+     * @param overallTotal
+     * @return
+     */
+
+    static public String getDownloadProgressString(long overallProgress, long overallTotal) {
+        if (overallTotal == 0) {
+            if ( Constants.LOGVV ) {
+                Log.e(Constants.TAG, "Notification called when total is zero");
+            }
+            return "";
+        }
+        return String.format("%.2f",
+                (float) overallProgress / (1024.0f * 1024.0f))
+                + "MB /" +
+                String.format("%.2f", (float) overallTotal /
+                        (1024.0f * 1024.0f)) + "MB";
+    }
+
+    /**
+     * Adds a percentile to getDownloadProgressString.
+     * 
+     * @param overallProgress
+     * @param overallTotal
+     * @return
+     */
+    static public String getDownloadProgressStringNotification(long overallProgress,
+            long overallTotal) {
+        if (overallTotal == 0) {
+            if ( Constants.LOGVV ) {
+                Log.e(Constants.TAG, "Notification called when total is zero");
+            }
+            return "";
+        }
+        return getDownloadProgressString(overallProgress, overallTotal) + " (" +
+                getDownloadProgressPercent(overallProgress, overallTotal) + ")";
+    }
+
+    public static String getDownloadProgressPercent(long overallProgress, long overallTotal) {
+        if (overallTotal == 0) {
+            if ( Constants.LOGVV ) {
+                Log.e(Constants.TAG, "Notification called when total is zero");
+            }
+            return "";
+        }
+        return Long.toString(overallProgress * 100 / overallTotal) + "%";
+    }
+
+    public static String getSpeedString(float bytesPerMillisecond) {
+        return String.format("%.2f", bytesPerMillisecond * 1000 / 1024);
+    }
+
+    public static String getTimeRemaining(long durationInMilliseconds) {
+        SimpleDateFormat sdf;
+        if (durationInMilliseconds > 1000 * 60 * 60) {
+            sdf = new SimpleDateFormat("HH:mm", Locale.getDefault());
+        } else {
+            sdf = new SimpleDateFormat("mm:ss", Locale.getDefault());
+        }
+        return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset()));
+    }
+
+    /**
+     * Returns the file name (without full path) for an Expansion APK file from
+     * the given context.
+     * 
+     * @param c the context
+     * @param mainFile true for main file, false for patch file
+     * @param versionCode the version of the file
+     * @return String the file name of the expansion file
+     */
+    public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) {
+        return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb";
+    }
+
+    /**
+     * Returns the filename (where the file should be saved) from info about a
+     * download
+     */
+    static public String generateSaveFileName(Context c, String fileName) {
+        String path = getSaveFilePath(c)
+                + File.separator + fileName;
+        return path;
+    }
+
+    static public String getSaveFilePath(Context c) {
+        File root = Environment.getExternalStorageDirectory();
+        String path = root.toString() + Constants.EXP_PATH + c.getPackageName();
+        return path;
+    }
+
+    /**
+     * Helper function to ascertain the existence of a file and return
+     * true/false appropriately
+     * 
+     * @param c the app/activity/service context
+     * @param fileName the name (sans path) of the file to query
+     * @param fileSize the size that the file must match
+     * @param deleteFileOnMismatch if the file sizes do not match, delete the
+     *            file
+     * @return true if it does exist, false otherwise
+     */
+    static public boolean doesFileExist(Context c, String fileName, long fileSize,
+            boolean deleteFileOnMismatch) {
+        // the file may have been delivered by Market --- let's make sure
+        // it's the size we expect
+        File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName));
+        if (fileForNewFile.exists()) {
+            if (fileForNewFile.length() == fileSize) {
+                return true;
+            }
+            if (deleteFileOnMismatch) {
+                // delete the file --- we won't be able to resume
+                // because we cannot confirm the integrity of the file
+                fileForNewFile.delete();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Converts download states that are returned by the {@link 
+     * IDownloaderClient#onDownloadStateChanged} callback into usable strings.
+     * This is useful if using the state strings built into the library to display user messages.
+     * @param state One of the STATE_* constants from {@link IDownloaderClient}.
+     * @return string resource ID for the corresponding string.
+     */
+    static public int getDownloaderStringResourceIDFromState(int state) {
+        switch (state) {
+            case IDownloaderClient.STATE_IDLE:
+                return R.string.state_idle;
+            case IDownloaderClient.STATE_FETCHING_URL:
+                return R.string.state_fetching_url;
+            case IDownloaderClient.STATE_CONNECTING:
+                return R.string.state_connecting;
+            case IDownloaderClient.STATE_DOWNLOADING:
+                return R.string.state_downloading;
+            case IDownloaderClient.STATE_COMPLETED:
+                return R.string.state_completed;
+            case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE:
+                return R.string.state_paused_network_unavailable;
+            case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
+                return R.string.state_paused_by_request;
+            case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
+                return R.string.state_paused_wifi_disabled;
+            case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
+                return R.string.state_paused_wifi_unavailable;
+            case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED:
+                return R.string.state_paused_wifi_disabled;
+            case IDownloaderClient.STATE_PAUSED_NEED_WIFI:
+                return R.string.state_paused_wifi_unavailable;
+            case IDownloaderClient.STATE_PAUSED_ROAMING:
+                return R.string.state_paused_roaming;
+            case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE:
+                return R.string.state_paused_network_setup_failure;
+            case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
+                return R.string.state_paused_sdcard_unavailable;
+            case IDownloaderClient.STATE_FAILED_UNLICENSED:
+                return R.string.state_failed_unlicensed;
+            case IDownloaderClient.STATE_FAILED_FETCHING_URL:
+                return R.string.state_failed_fetching_url;
+            case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
+                return R.string.state_failed_sdcard_full;
+            case IDownloaderClient.STATE_FAILED_CANCELED:
+                return R.string.state_failed_cancelled;
+            default:
+                return R.string.state_unknown;
+        }
+    }
+
+}

+ 126 - 0
platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java

@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012 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.google.android.vending.expansion.downloader;
+
+import android.os.Messenger;
+
+/**
+ * This interface should be implemented by the client activity for the
+ * downloader. It is used to pass status from the service to the client.
+ */
+public interface IDownloaderClient {
+    static final int STATE_IDLE = 1;
+    static final int STATE_FETCHING_URL = 2;
+    static final int STATE_CONNECTING = 3;
+    static final int STATE_DOWNLOADING = 4;
+    static final int STATE_COMPLETED = 5;
+
+    static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6;
+    static final int STATE_PAUSED_BY_REQUEST = 7;
+
+    /**
+     * Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and
+     * STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and
+     * cellular permission will restart the service. Wi-Fi disabled means that
+     * the Wi-Fi manager is returning that Wi-Fi is not enabled, while in the
+     * other case Wi-Fi is enabled but not available.
+     */
+    static final int STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8;
+    static final int STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9;
+
+    /**
+     * Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that
+     * Wi-Fi is unavailable and cellular permission will NOT restart the
+     * service. Wi-Fi disabled means that the Wi-Fi manager is returning that
+     * Wi-Fi is not enabled, while in the other case Wi-Fi is enabled but not
+     * available.
+     * <p>
+     * The service does not return these values. We recommend that app
+     * developers with very large payloads do not allow these payloads to be
+     * downloaded over cellular connections.
+     */
+    static final int STATE_PAUSED_WIFI_DISABLED = 10;
+    static final int STATE_PAUSED_NEED_WIFI = 11;
+
+    static final int STATE_PAUSED_ROAMING = 12;
+
+    /**
+     * Scary case. We were on a network that redirected us to another website
+     * that delivered us the wrong file.
+     */
+    static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13;
+
+    static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14;
+
+    static final int STATE_FAILED_UNLICENSED = 15;
+    static final int STATE_FAILED_FETCHING_URL = 16;
+    static final int STATE_FAILED_SDCARD_FULL = 17;
+    static final int STATE_FAILED_CANCELED = 18;
+
+    static final int STATE_FAILED = 19;
+
+    /**
+     * Called internally by the stub when the service is bound to the client.
+     * <p>
+     * Critical implementation detail. In onServiceConnected we create the
+     * remote service and marshaler. This is how we pass the client information
+     * back to the service so the client can be properly notified of changes. We
+     * must do this every time we reconnect to the service.
+     * <p>
+     * That is, when you receive this callback, you should call
+     * {@link DownloaderServiceMarshaller#CreateProxy} to instantiate a member
+     * instance of {@link IDownloaderService}, then call
+     * {@link IDownloaderService#onClientUpdated} with the Messenger retrieved
+     * from your {@link IStub} proxy object.
+     * 
+     * @param m the service Messenger. This Messenger is used to call the
+     *            service API from the client.
+     */
+    void onServiceConnected(Messenger m);
+
+    /**
+     * Called when the download state changes. Depending on the state, there may
+     * be user requests. The service is free to change the download state in the
+     * middle of a user request, so the client should be able to handle this.
+     * <p>
+     * The Downloader Library includes a collection of string resources that
+     * correspond to each of the states, which you can use to provide users a
+     * useful message based on the state provided in this callback. To fetch the
+     * appropriate string for a state, call
+     * {@link Helpers#getDownloaderStringResourceIDFromState}.
+     * <p>
+     * What this means to the developer: The application has gotten a message
+     * that the download has paused due to lack of WiFi. The developer should
+     * then show UI asking the user if they want to enable downloading over
+     * cellular connections with appropriate warnings. If the application
+     * suddenly starts downloading, the application should revert to showing the
+     * progress again, rather than leaving up the download over cellular UI up.
+     * 
+     * @param newState one of the STATE_* values defined in IDownloaderClient
+     */
+    void onDownloadStateChanged(int newState);
+
+    /**
+     * Shows the download progress. This is intended to be used to fill out a
+     * client UI. This progress should only be shown in a few states such as
+     * STATE_DOWNLOADING.
+     * 
+     * @param progress the DownloadProgressInfo object containing the current
+     *            progress of all downloads.
+     */
+    void onDownloadProgress(DownloadProgressInfo progress);
+}

+ 83 - 0
platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderService.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 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.google.android.vending.expansion.downloader;
+
+import com.google.android.vending.expansion.downloader.impl.DownloaderService;
+import android.os.Messenger;
+
+/**
+ * This interface is implemented by the DownloaderService and by the
+ * DownloaderServiceMarshaller. It contains functions to control the service.
+ * When a client binds to the service, it must call the onClientUpdated
+ * function.
+ * <p>
+ * You can acquire a proxy that implements this interface for your service by
+ * calling {@link DownloaderServiceMarshaller#CreateProxy} during the
+ * {@link IDownloaderClient#onServiceConnected} callback. At which point, you
+ * should immediately call {@link #onClientUpdated}.
+ */
+public interface IDownloaderService {
+    /**
+     * Set this flag in response to the
+     * IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then
+     * call RequestContinueDownload to resume a download
+     */
+    public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1;
+
+    /**
+     * Request that the service abort the current download. The service should
+     * respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}.
+     */
+    void requestAbortDownload();
+
+    /**
+     * Request that the service pause the current download. The service should
+     * respond by changing the state to
+     * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}.
+     */
+    void requestPauseDownload();
+
+    /**
+     * Request that the service continue a paused download, when in any paused
+     * or failed state, including
+     * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}.
+     */
+    void requestContinueDownload();
+
+    /**
+     * Set the flags for this download (e.g.
+     * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}).
+     * 
+     * @param flags
+     */
+    void setDownloadFlags(int flags);
+
+    /**
+     * Requests that the download status be sent to the client.
+     */
+    void requestDownloadStatus();
+
+    /**
+     * Call this when you get {@link
+     * IDownloaderClient.onServiceConnected(Messenger m)} from the
+     * DownloaderClient to register the client with the service. It will
+     * automatically send the current status to the client.
+     * 
+     * @param clientMessenger
+     */
+    void onClientUpdated(Messenger clientMessenger);
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно