summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuan Linietsky <reduzio@gmail.com>2014-03-13 22:57:24 -0300
committerJuan Linietsky <reduzio@gmail.com>2014-03-13 22:57:24 -0300
commit31ce3c5fd0300aac1e86bced1efc5f9ec94bdb6b (patch)
treeb6d3a290333c72940b49ed4c930ff6858a59515e
parenta65edb4caabec21654c56552e11aacf0fd9291de (diff)
-fix bug in cache for atlas import/export
-fix some menus -fixed bug in out transition curves -detect and remove file:/// in collada -remove multiscript for now -remove dependencies on mouse in OS, moved to Input -avoid fscache from screwing up (fix might make it slower, but it works) -funcref was missing, it's there now
-rw-r--r--core/bind/core_bind.cpp58
-rw-r--r--core/bind/core_bind.h9
-rw-r--r--core/func_ref.cpp55
-rw-r--r--core/func_ref.h23
-rw-r--r--core/globals.cpp23
-rw-r--r--core/globals.h4
-rw-r--r--core/io/file_access_pack.cpp1
-rw-r--r--core/io/marshalls.cpp148
-rw-r--r--core/io/resource_format_binary.cpp33
-rw-r--r--core/io/resource_format_binary.h2
-rw-r--r--core/io/resource_format_xml.cpp51
-rw-r--r--core/io/resource_format_xml.h5
-rw-r--r--core/io/resource_loader.cpp13
-rw-r--r--core/io/resource_loader.h1
-rw-r--r--core/io/resource_saver.h3
-rw-r--r--core/math/math_funcs.cpp11
-rw-r--r--core/os/file_access.cpp24
-rw-r--r--core/os/file_access.h3
-rw-r--r--core/os/input.cpp7
-rw-r--r--core/os/input.h2
-rw-r--r--core/os/mutex.h2
-rw-r--r--core/os/os.cpp11
-rw-r--r--core/os/os.h4
-rw-r--r--core/register_core_types.cpp2
-rw-r--r--core/ustring.cpp10
-rw-r--r--core/ustring.h3
-rw-r--r--drivers/mpc/audio_stream_mpc.cpp2
-rw-r--r--drivers/openssl/stream_peer_ssl.cpp111
-rw-r--r--drivers/openssl/stream_peer_ssl.h26
-rw-r--r--drivers/vorbis/audio_stream_ogg_vorbis.cpp10
-rw-r--r--modules/gdscript/gd_functions.cpp41
-rw-r--r--modules/gdscript/gd_functions.h1
-rw-r--r--modules/multiscript/SCsub7
-rw-r--r--modules/multiscript/config.py11
-rw-r--r--modules/multiscript/multi_script.cpp498
-rw-r--r--modules/multiscript/multi_script.h158
-rw-r--r--modules/multiscript/register_types.cpp32
-rw-r--r--modules/multiscript/register_types.h30
-rw-r--r--platform/android/AndroidManifest.xml.template2
-rw-r--r--platform/android/SCsub4
-rw-r--r--platform/android/audio_driver_opensl.cpp (renamed from platform/android/audio_driver_android.cpp)73
-rw-r--r--platform/android/audio_driver_opensl.h (renamed from platform/android/audio_driver_android.h)21
-rw-r--r--platform/android/detect.py11
-rw-r--r--platform/android/java/ant.properties8
-rw-r--r--platform/android/java/src/com/android/godot/Godot.java44
-rw-r--r--platform/android/java/src/com/android/godot/GodotIO.java42
-rw-r--r--platform/android/java/src/com/android/godot/GodotLib.java6
-rw-r--r--platform/android/java_glue.cpp43
-rw-r--r--platform/android/libs/apk_expansion/AndroidManifest.xml9
-rw-r--r--platform/android/libs/apk_expansion/build.xml92
-rw-r--r--platform/android/libs/apk_expansion/proguard-project.txt20
-rw-r--r--platform/android/libs/apk_expansion/project.properties13
-rw-r--r--platform/android/libs/apk_expansion/res/drawable-hdpi/notify_panel_notification_icon_bg.pngbin0 -> 1027 bytes
-rw-r--r--platform/android/libs/apk_expansion/res/drawable-mdpi/notify_panel_notification_icon_bg.pngbin0 -> 1125 bytes
-rw-r--r--platform/android/libs/apk_expansion/res/layout/status_bar_ongoing_event_progress_bar.xml104
-rw-r--r--platform/android/libs/apk_expansion/res/values-v11/styles.xml6
-rw-r--r--platform/android/libs/apk_expansion/res/values-v9/styles.xml5
-rw-r--r--platform/android/libs/apk_expansion/res/values/strings.xml41
-rw-r--r--platform/android/libs/apk_expansion/res/values/styles.xml25
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Constants.java236
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java80
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java277
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java181
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Helpers.java306
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java126
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderService.java83
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IStub.java41
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/SystemFacade.java123
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java536
-rwxr-xr-xplatform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java112
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java30
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java92
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java231
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java963
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java1341
-rwxr-xr-xplatform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java510
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java200
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java101
-rw-r--r--platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java116
-rw-r--r--platform/android/libs/play_licensing/AndroidManifest.xml24
-rw-r--r--platform/android/libs/play_licensing/aidl/ILicenseResultListener.aidl23
-rw-r--r--platform/android/libs/play_licensing/aidl/ILicensingService.aidl25
-rw-r--r--platform/android/libs/play_licensing/build.xml92
-rw-r--r--platform/android/libs/play_licensing/proguard-project.txt20
-rw-r--r--platform/android/libs/play_licensing/project.properties12
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/AESObfuscator.java110
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/APKExpansionPolicy.java397
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/DeviceLimiter.java47
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicenseResultListener.java99
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicensingService.java99
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseChecker.java351
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseCheckerCallback.java67
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseValidator.java224
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/NullDeviceLimiter.java32
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Obfuscator.java48
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Policy.java59
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/PreferenceObfuscator.java77
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ResponseData.java79
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ServerManagedPolicy.java276
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/StrictPolicy.java63
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ValidationException.java33
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64.java570
-rw-r--r--platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64DecoderException.java32
-rw-r--r--platform/android/os_android.cpp29
-rw-r--r--platform/android/os_android.h30
-rw-r--r--platform/android/project.properties.template2
-rwxr-xr-xplatform/android/sign.sh2
-rw-r--r--platform/iphone/detect.py3
-rw-r--r--platform/iphone/game_center.h1
-rwxr-xr-xplatform/iphone/gl_view.h4
-rwxr-xr-xplatform/iphone/gl_view.mm60
-rw-r--r--platform/iphone/godot_iphone.cpp2
-rw-r--r--platform/iphone/os_iphone.cpp27
-rw-r--r--platform/iphone/os_iphone.h5
-rw-r--r--platform/iphone/view_controller.h2
-rw-r--r--platform/iphone/view_controller.mm26
-rw-r--r--platform/isim/detect.py1
-rw-r--r--scene/2d/area_2d.cpp1
-rw-r--r--scene/2d/camera_2d.cpp978
-rw-r--r--scene/2d/physics_body_2d.cpp4
-rw-r--r--scene/2d/visibility_notifier_2d.cpp4
-rw-r--r--scene/io/resource_format_image.cpp12
-rw-r--r--scene/resources/audio_stream_resampled.cpp1
-rw-r--r--servers/visual/visual_server_wrap_mt.cpp11
-rw-r--r--servers/visual/visual_server_wrap_mt.h426
-rw-r--r--tools/collada/collada.cpp3
-rw-r--r--tools/editor/editor_file_system.cpp4
-rw-r--r--tools/editor/editor_import_export.cpp88
-rw-r--r--tools/editor/editor_node.cpp6
-rw-r--r--tools/editor/io_plugins/editor_font_import_plugin.cpp43
-rw-r--r--tools/editor/io_plugins/editor_texture_import_plugin.cpp18
-rw-r--r--tools/editor/project_export.cpp4
-rw-r--r--tools/editor/resources_dock.cpp2
-rw-r--r--tools/editor/scene_tree_editor.cpp9
-rw-r--r--tools/editor/scene_tree_editor.h2
-rw-r--r--tools/ios_xcode_template/godot_ios.xcodeproj/project.xcworkspace/xcuserdata/punto.xcuserdatad/UserInterfaceState.xcuserstatebin11866 -> 13553 bytes
136 files changed, 10782 insertions, 1576 deletions
diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp
index 73f6f753b9..28906354ab 100644
--- a/core/bind/core_bind.cpp
+++ b/core/bind/core_bind.cpp
@@ -387,6 +387,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 +494,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 +523,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);
@@ -550,7 +577,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 +597,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 +1018,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 +1132,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 );
diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h
index 9545fc65fb..0c80fb3fc4 100644
--- a/core/bind/core_bind.h
+++ b/core/bind/core_bind.h
@@ -98,6 +98,11 @@ 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;
@@ -166,6 +171,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 +286,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;
diff --git a/core/func_ref.cpp b/core/func_ref.cpp
new file mode 100644
index 0000000000..0e43112de8
--- /dev/null
+++ b/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;
+}
+
diff --git a/core/func_ref.h b/core/func_ref.h
new file mode 100644
index 0000000000..28d0e737be
--- /dev/null
+++ b/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
diff --git a/core/globals.cpp b/core/globals.cpp
index 997e2a2d85..7df7680827 100644
--- a/core/globals.cpp
+++ b/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;
diff --git a/core/globals.h b/core/globals.h
index 08d9f08088..b8dc3f9367 100644
--- a/core/globals.h
+++ b/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;
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index edecbb6a3e..45e6990ad2 100644
--- a/core/io/file_access_pack.cpp
+++ b/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);
};
diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp
index f5f9d34439..df951c759a 100644
--- a/core/io/marshalls.cpp
+++ b/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: {
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index c54398935e..47f278596b 100644
--- a/core/io/resource_format_binary.cpp
+++ b/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();
@@ -1412,8 +1430,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 +1455,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 +1750,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 +1832,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 +1875,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 +1884,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);
diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h
index 006148f5a8..bd33fee82c 100644
--- a/core/io/resource_format_binary.h
+++ b/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;
diff --git a/core/io/resource_format_xml.cpp b/core/io/resource_format_xml.cpp
index fc5aecfd99..f3c0f1cb8b 100644
--- a/core/io/resource_format_xml.cpp
+++ b/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
diff --git a/core/io/resource_format_xml.h b/core/io/resource_format_xml.h
index 05313ffbd7..7874431a38 100644
--- a/core/io/resource_format_xml.h
+++ b/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;
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index 5ee48bae25..d2610d5d4f 100644
--- a/core/io/resource_loader.cpp
+++ b/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) {
diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h
index 70b1a79582..ab23158785 100644
--- a/core/io/resource_loader.h
+++ b/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; }
diff --git a/core/io/resource_saver.h b/core/io/resource_saver.h
index 4b794247e0..fd4575c872 100644
--- a/core/io/resource_saver.h
+++ b/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,
-
-
};
diff --git a/core/math/math_funcs.cpp b/core/math/math_funcs.cpp
index 5d3887d72c..92236a374f 100644
--- a/core/math/math_funcs.cpp
+++ b/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
diff --git a/core/os/file_access.cpp b/core/os/file_access.cpp
index 23250a7345..31e7d19bae 100644
--- a/core/os/file_access.cpp
+++ b/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);
diff --git a/core/os/file_access.h b/core/os/file_access.h
index bcdae61487..793e971a4c 100644
--- a/core/os/file_access.h
+++ b/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
diff --git a/core/os/input.cpp b/core/os/input.cpp
index 3266e4cc10..e732eac323 100644
--- a/core/os/input.cpp
+++ b/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 {
+
+ OS::get_singleton()->get_mouse_button_state();
+}
+
+
void InputDefault::iteration(float p_step) {
diff --git a/core/os/input.h b/core/os/input.h
index 5987d6ef6c..cc51dbf42f 100644
--- a/core/os/input.h
+++ b/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);
diff --git a/core/os/mutex.h b/core/os/mutex.h
index 512180d6c7..241d70e232 100644
--- a/core/os/mutex.h
+++ b/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
diff --git a/core/os/os.cpp b/core/os/os.cpp
index 141d5f2b58..c9a5cb1af8 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -430,7 +430,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 +447,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;
diff --git a/core/os/os.h b/core/os/os.h
index d77d9bee7f..c790b38635 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -316,10 +316,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);
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index 6d107f97e7..7e7ce3479f 100644
--- a/core/register_core_types.cpp
+++ b/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>();
diff --git a/core/ustring.cpp b/core/ustring.cpp
index b0f06c6ab6..f53829fe21 100644
--- a/core/ustring.cpp
+++ b/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 {
diff --git a/core/ustring.h b/core/ustring.h
index 007565c825..13db00f07f 100644
--- a/core/ustring.h
+++ b/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; }
diff --git a/drivers/mpc/audio_stream_mpc.cpp b/drivers/mpc/audio_stream_mpc.cpp
index 91912c8948..e1f9aacf5f 100644
--- a/drivers/mpc/audio_stream_mpc.cpp
+++ b/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;
}
diff --git a/drivers/openssl/stream_peer_ssl.cpp b/drivers/openssl/stream_peer_ssl.cpp
new file mode 100644
index 0000000000..aaedd7dde9
--- /dev/null
+++ b/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() {
+}
diff --git a/drivers/openssl/stream_peer_ssl.h b/drivers/openssl/stream_peer_ssl.h
new file mode 100644
index 0000000000..a126f6122c
--- /dev/null
+++ b/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
diff --git a/drivers/vorbis/audio_stream_ogg_vorbis.cpp b/drivers/vorbis/audio_stream_ogg_vorbis.cpp
index 0964a22c94..d9b7b1d161 100644
--- a/drivers/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/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;
}
diff --git a/modules/gdscript/gd_functions.cpp b/modules/gdscript/gd_functions.cpp
index 2930d9322c..c099c3f33c 100644
--- a/modules/gdscript/gd_functions.cpp
+++ b/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",
@@ -452,6 +454,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);
VALIDATE_ARG_NUM(1);
@@ -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);
@@ -1130,6 +1162,13 @@ MethodInfo GDFunctions::get_info(Function p_func) {
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: {
MethodInfo mi("convert",PropertyInfo(Variant::NIL,"what"),PropertyInfo(Variant::INT,"type"));
diff --git a/modules/gdscript/gd_functions.h b/modules/gdscript/gd_functions.h
index 2ab397d18a..9255e5e2c5 100644
--- a/modules/gdscript/gd_functions.h
+++ b/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,
diff --git a/modules/multiscript/SCsub b/modules/multiscript/SCsub
deleted file mode 100644
index d20da72b72..0000000000
--- a/modules/multiscript/SCsub
+++ /dev/null
@@ -1,7 +0,0 @@
-Import('env')
-
-env.add_source_files(env.modules_sources,"*.cpp")
-
-Export('env')
-
-
diff --git a/modules/multiscript/config.py b/modules/multiscript/config.py
deleted file mode 100644
index f9bd7da08d..0000000000
--- a/modules/multiscript/config.py
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-def can_build(platform):
- return True
-
-
-def configure(env):
- pass
-
-
-
diff --git a/modules/multiscript/multi_script.cpp b/modules/multiscript/multi_script.cpp
deleted file mode 100644
index 1924cf2a6e..0000000000
--- a/modules/multiscript/multi_script.cpp
+++ /dev/null
@@ -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;
diff --git a/modules/multiscript/multi_script.h b/modules/multiscript/multi_script.h
deleted file mode 100644
index 87d4b4e4c8..0000000000
--- a/modules/multiscript/multi_script.h
+++ /dev/null
@@ -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
diff --git a/modules/multiscript/register_types.cpp b/modules/multiscript/register_types.cpp
deleted file mode 100644
index 4b25656723..0000000000
--- a/modules/multiscript/register_types.cpp
+++ /dev/null
@@ -1,32 +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);
-}
diff --git a/modules/multiscript/register_types.h b/modules/multiscript/register_types.h
deleted file mode 100644
index d137b1c63f..0000000000
--- a/modules/multiscript/register_types.h
+++ /dev/null
@@ -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();
diff --git a/platform/android/AndroidManifest.xml.template b/platform/android/AndroidManifest.xml.template
index 208d24059e..41be3e9d80 100644
--- a/platform/android/AndroidManifest.xml.template
+++ b/platform/android/AndroidManifest.xml.template
@@ -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>
diff --git a/platform/android/SCsub b/platform/android/SCsub
index 327b1ffe09..5464376c31 100644
--- a/platform/android/SCsub
+++ b/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
diff --git a/platform/android/audio_driver_android.cpp b/platform/android/audio_driver_opensl.cpp
index 4f7b4ee348..857d1a4a54 100644
--- a/platform/android/audio_driver_android.cpp
+++ b/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
+
+
diff --git a/platform/android/audio_driver_android.h b/platform/android/audio_driver_opensl.h
index 655772c772..d852928ab8 100644
--- a/platform/android/audio_driver_android.h
+++ b/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
+
diff --git a/platform/android/detect.py b/platform/android/detect.py
index b89024a81a..17e97fae0f 100644
--- a/platform/android/detect.py
+++ b/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'])
diff --git a/platform/android/java/ant.properties b/platform/android/java/ant.properties
index 950b8b0199..dc3868516d 100644
--- a/platform/android/java/ant.properties
+++ b/platform/android/java/ant.properties
@@ -15,8 +15,8 @@
# '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=/home/luis/Downloads/carnavalguachin.keystore
+key.alias=momoselacome
-key.store.password=123456
-key.alias.password=123456
+key.store.password=12345678
+key.alias.password=12345678
diff --git a/platform/android/java/src/com/android/godot/Godot.java b/platform/android/java/src/com/android/godot/Godot.java
index cf1545df82..9868e62957 100644
--- a/platform/android/java/src/com/android/godot/Godot.java
+++ b/platform/android/java/src/com/android/godot/Godot.java
@@ -49,15 +49,19 @@ 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;
public class Godot extends Activity implements SensorEventListener
-{
-
+{
static public class SingletonBase {
protected void registerClass(String p_name, String[] p_methods) {
@@ -131,8 +135,12 @@ public class Godot extends Activity implements SensorEventListener
};
public ResultCallback result_callback;
+ private PaymentsManager mPaymentsManager;
+
@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;
};
@@ -152,13 +160,17 @@ public class Godot extends Activity implements SensorEventListener
}
+ private static Godot _self;
+
+ public static Godot getInstance(){
+ return Godot._self;
+ }
+
@Override protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
-
-
-
+ _self = this;
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -172,12 +184,20 @@ public class Godot extends Activity implements SensorEventListener
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
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();
@@ -291,7 +311,15 @@ public class Godot extends Activity implements SensorEventListener
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
GodotLib.key(event.getUnicodeChar(0), true);
return super.onKeyDown(keyCode, event);
- };
+ }
+
+ public PaymentsManager getPaymentsManager() {
+ return mPaymentsManager;
+ }
+
+// public void setPaymentsManager(PaymentsManager mPaymentsManager) {
+// this.mPaymentsManager = mPaymentsManager;
+// };
// Audio
diff --git a/platform/android/java/src/com/android/godot/GodotIO.java b/platform/android/java/src/com/android/godot/GodotIO.java
index 1f3967cb8f..4b6a44335c 100644
--- a/platform/android/java/src/com/android/godot/GodotIO.java
+++ b/platform/android/java/src/com/android/godot/GodotIO.java
@@ -57,6 +57,9 @@ public class GodotIO {
AssetManager am;
Activity activity;
+ Context applicationContext;
+ MediaPlayer mediaPlayer;
+
final int SCREEN_LANDSCAPE=0;
final int SCREEN_PORTRAIT=1;
final int SCREEN_REVERSE_LANDSCAPE=2;
@@ -326,7 +329,7 @@ public class GodotIO {
activity=p_activity;
streams=new HashMap<Integer,AssetData>();
dirs=new HashMap<Integer,AssetDir>();
-
+ applicationContext = activity.getApplicationContext();
}
@@ -502,6 +505,43 @@ public class GodotIO {
}
};
+ 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";
diff --git a/platform/android/java/src/com/android/godot/GodotLib.java b/platform/android/java/src/com/android/godot/GodotLib.java
index f0ec3e97c6..ffa3f306f1 100644
--- a/platform/android/java/src/com/android/godot/GodotLib.java
+++ b/platform/android/java/src/com/android/godot/GodotLib.java
@@ -51,14 +51,14 @@ public class GodotLib {
public static native void step();
public static native void touch(int what,int pointer,int howmany, int[] arr);
public static native void accelerometer(float x, float y, float z);
- public static native void key(int p_unicode_char, boolean p_pressed);
+ public static native void key(int p_unicode_char, boolean p_pressed);
public static native void focusin();
public static native void focusout();
public static native void audio();
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);
}
diff --git a/platform/android/java_glue.cpp b/platform/android/java_glue.cpp
index cbe17f2f73..dbd9f2bf06 100644
--- a/platform/android/java_glue.cpp
+++ b/platform/android/java_glue.cpp
@@ -568,6 +568,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) {
@@ -628,6 +633,31 @@ 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);
@@ -675,6 +705,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);
@@ -685,7 +720,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];
@@ -803,6 +838,12 @@ static void _initialize_java_modules() {
__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);
diff --git a/platform/android/libs/apk_expansion/AndroidManifest.xml b/platform/android/libs/apk_expansion/AndroidManifest.xml
new file mode 100644
index 0000000000..20b74a2988
--- /dev/null
+++ b/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> \ No newline at end of file
diff --git a/platform/android/libs/apk_expansion/build.xml b/platform/android/libs/apk_expansion/build.xml
new file mode 100644
index 0000000000..5b2f2c590e
--- /dev/null
+++ b/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>
diff --git a/platform/android/libs/apk_expansion/proguard-project.txt b/platform/android/libs/apk_expansion/proguard-project.txt
new file mode 100644
index 0000000000..f2fe1559a2
--- /dev/null
+++ b/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 *;
+#}
diff --git a/platform/android/libs/apk_expansion/project.properties b/platform/android/libs/apk_expansion/project.properties
new file mode 100644
index 0000000000..eda83430bf
--- /dev/null
+++ b/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
diff --git a/platform/android/libs/apk_expansion/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/libs/apk_expansion/res/drawable-hdpi/notify_panel_notification_icon_bg.png
new file mode 100644
index 0000000000..f5b762ecf3
--- /dev/null
+++ b/platform/android/libs/apk_expansion/res/drawable-hdpi/notify_panel_notification_icon_bg.png
Binary files differ
diff --git a/platform/android/libs/apk_expansion/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/libs/apk_expansion/res/drawable-mdpi/notify_panel_notification_icon_bg.png
new file mode 100644
index 0000000000..9ecb8af06c
--- /dev/null
+++ b/platform/android/libs/apk_expansion/res/drawable-mdpi/notify_panel_notification_icon_bg.png
Binary files differ
diff --git a/platform/android/libs/apk_expansion/res/layout/status_bar_ongoing_event_progress_bar.xml b/platform/android/libs/apk_expansion/res/layout/status_bar_ongoing_event_progress_bar.xml
new file mode 100644
index 0000000000..23bac02294
--- /dev/null
+++ b/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> \ No newline at end of file
diff --git a/platform/android/libs/apk_expansion/res/values-v11/styles.xml b/platform/android/libs/apk_expansion/res/values-v11/styles.xml
new file mode 100644
index 0000000000..f2013bc0bf
--- /dev/null
+++ b/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> \ No newline at end of file
diff --git a/platform/android/libs/apk_expansion/res/values-v9/styles.xml b/platform/android/libs/apk_expansion/res/values-v9/styles.xml
new file mode 100644
index 0000000000..736e77a5d6
--- /dev/null
+++ b/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> \ No newline at end of file
diff --git a/platform/android/libs/apk_expansion/res/values/strings.xml b/platform/android/libs/apk_expansion/res/values/strings.xml
new file mode 100644
index 0000000000..b84749faf2
--- /dev/null
+++ b/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> \ No newline at end of file
diff --git a/platform/android/libs/apk_expansion/res/values/styles.xml b/platform/android/libs/apk_expansion/res/values/styles.xml
new file mode 100644
index 0000000000..a442f61e7e
--- /dev/null
+++ b/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> \ No newline at end of file
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Constants.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Constants.java
new file mode 100644
index 0000000000..ff2c6f535a
--- /dev/null
+++ b/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;
+
+} \ No newline at end of file
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java
new file mode 100644
index 0000000000..9cb294d721
--- /dev/null
+++ b/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];
+ }
+ };
+
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java
new file mode 100644
index 0000000000..2201751254
--- /dev/null
+++ b/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);
+ }
+
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java
new file mode 100644
index 0000000000..054eaa9895
--- /dev/null
+++ b/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);
+ }
+
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Helpers.java
new file mode 100644
index 0000000000..1e84e54a0f
--- /dev/null
+++ b/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;
+ }
+ }
+
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java
new file mode 100644
index 0000000000..b8511a62a0
--- /dev/null
+++ b/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);
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderService.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderService.java
new file mode 100644
index 0000000000..4789afe19c
--- /dev/null
+++ b/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);
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IStub.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IStub.java
new file mode 100644
index 0000000000..d5bc3a843e
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IStub.java
@@ -0,0 +1,41 @@
+/*
+ * 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.content.Context;
+import android.os.Messenger;
+
+/**
+ * This is the interface that is used to connect/disconnect from the downloader
+ * service.
+ * <p>
+ * You should get a proxy object that implements this interface by calling
+ * {@link DownloaderClientMarshaller#CreateStub} in your activity when the
+ * downloader service starts. Then, call {@link #connect} during your activity's
+ * onResume() and call {@link #disconnect} during onStop().
+ * <p>
+ * Then during the {@link IDownloaderClient#onServiceConnected} callback, you
+ * should call {@link #getMessenger} to pass the stub's Messenger object to
+ * {@link IDownloaderService#onClientUpdated}.
+ */
+public interface IStub {
+ Messenger getMessenger();
+
+ void connect(Context c);
+
+ void disconnect(Context c);
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/SystemFacade.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/SystemFacade.java
new file mode 100644
index 0000000000..12edd97ab2
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/SystemFacade.java
@@ -0,0 +1,123 @@
+/*
+ * 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.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+/**
+ * Contains useful helper functions, typically tied to the application context.
+ */
+class SystemFacade {
+ private Context mContext;
+ private NotificationManager mNotificationManager;
+
+ public SystemFacade(Context context) {
+ mContext = context;
+ mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ public long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ public Integer getActiveNetworkType() {
+ ConnectivityManager connectivity =
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (connectivity == null) {
+ Log.w(Constants.TAG, "couldn't get connectivity manager");
+ return null;
+ }
+
+ NetworkInfo activeInfo = connectivity.getActiveNetworkInfo();
+ if (activeInfo == null) {
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "network is not available");
+ }
+ return null;
+ }
+ return activeInfo.getType();
+ }
+
+ public boolean isNetworkRoaming() {
+ ConnectivityManager connectivity =
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (connectivity == null) {
+ Log.w(Constants.TAG, "couldn't get connectivity manager");
+ return false;
+ }
+
+ NetworkInfo info = connectivity.getActiveNetworkInfo();
+ boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE);
+ TelephonyManager tm = (TelephonyManager) mContext
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ if (null == tm) {
+ Log.w(Constants.TAG, "couldn't get telephony manager");
+ return false;
+ }
+ boolean isRoaming = isMobile && tm.isNetworkRoaming();
+ if (Constants.LOGVV && isRoaming) {
+ Log.v(Constants.TAG, "network is roaming");
+ }
+ return isRoaming;
+ }
+
+ public Long getMaxBytesOverMobile() {
+ return (long) Integer.MAX_VALUE;
+ }
+
+ public Long getRecommendedMaxBytesOverMobile() {
+ return 2097152L;
+ }
+
+ public void sendBroadcast(Intent intent) {
+ mContext.sendBroadcast(intent);
+ }
+
+ public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException {
+ return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid;
+ }
+
+ public void postNotification(long id, Notification notification) {
+ /**
+ * TODO: The system notification manager takes ints, not longs, as IDs,
+ * but the download manager uses IDs take straight from the database,
+ * which are longs. This will have to be dealt with at some point.
+ */
+ mNotificationManager.notify((int) id, notification);
+ }
+
+ public void cancelNotification(long id) {
+ mNotificationManager.cancel((int) id);
+ }
+
+ public void cancelAllNotifications() {
+ mNotificationManager.cancelAll();
+ }
+
+ public void startThread(Thread thread) {
+ thread.start();
+ }
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java
new file mode 100644
index 0000000000..4667acce67
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java
@@ -0,0 +1,536 @@
+/*
+ * 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.
+ */
+
+/*
+ * This is a port of AndroidHttpClient to pre-Froyo devices, that takes advantage of
+ * the SSLSessionCache added Froyo devices using reflection.
+ */
+
+package com.google.android.vending.expansion.downloader.impl;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.client.protocol.ClientContext;
+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.scheme.SocketFactory;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.RequestWrapper;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+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.BasicHttpContext;
+import org.apache.http.protocol.BasicHttpProcessor;
+import org.apache.http.protocol.HttpContext;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.SSLCertificateSocketFactory;
+import android.os.Looper;
+import android.util.Log;
+
+/**
+ * Subclass of the Apache {@link DefaultHttpClient} that is configured with
+ * reasonable default settings and registered schemes for Android, and
+ * also lets the user add {@link HttpRequestInterceptor} classes.
+ * Don't create this directly, use the {@link #newInstance} factory method.
+ *
+ * <p>This client processes cookies but does not retain them by default.
+ * To retain cookies, simply add a cookie store to the HttpContext:</p>
+ *
+ * <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
+ */
+public final class AndroidHttpClient implements HttpClient {
+
+ static Class<?> sSslSessionCacheClass;
+ static {
+ // if we are on Froyo+ devices, we can take advantage of the SSLSessionCache
+ try {
+ sSslSessionCacheClass = Class.forName("android.net.SSLSessionCache");
+ } catch (Exception e) {
+
+ }
+ }
+
+ // Gzip of data shorter than this probably won't be worthwhile
+ public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
+
+ // Default connection and socket timeout of 60 seconds. Tweak to taste.
+ private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000;
+
+ private static final String TAG = "AndroidHttpClient";
+
+
+ /** Interceptor throws an exception if the executing thread is blocked */
+ private static final HttpRequestInterceptor sThreadCheckInterceptor =
+ new HttpRequestInterceptor() {
+ public void process(HttpRequest request, HttpContext context) {
+ // Prevent the HttpRequest from being sent on the main thread
+ if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) {
+ throw new RuntimeException("This thread forbids HTTP requests");
+ }
+ }
+ };
+
+ /**
+ * Create a new HttpClient with reasonable defaults (which you can update).
+ *
+ * @param userAgent to report in your HTTP requests
+ * @param context to use for caching SSL sessions (may be null for no caching)
+ * @return AndroidHttpClient for you to use for all your requests.
+ */
+ public static AndroidHttpClient newInstance(String userAgent, Context context) {
+ HttpParams params = new BasicHttpParams();
+
+ // Turn off stale checking. Our connections break all the time anyway,
+ // and it's not worth it to pay the penalty of checking every time.
+ HttpConnectionParams.setStaleCheckingEnabled(params, false);
+
+ HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT);
+ HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT);
+ HttpConnectionParams.setSocketBufferSize(params, 8192);
+
+ // Don't handle redirects -- return them to the caller. Our code
+ // often wants to re-POST after a redirect, which we must do ourselves.
+ HttpClientParams.setRedirecting(params, false);
+
+ Object sessionCache = null;
+ // Use a session cache for SSL sockets -- Froyo only
+ if ( null != context && null != sSslSessionCacheClass ) {
+ Constructor<?> ct;
+ try {
+ ct = sSslSessionCacheClass.getConstructor(Context.class);
+ sessionCache = ct.newInstance(context);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InstantiationException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ // Set the specified user agent and register standard protocols.
+ HttpProtocolParams.setUserAgent(params, userAgent);
+ SchemeRegistry schemeRegistry = new SchemeRegistry();
+ schemeRegistry.register(new Scheme("http",
+ PlainSocketFactory.getSocketFactory(), 80));
+ SocketFactory sslCertificateSocketFactory = null;
+ if ( null != sessionCache ) {
+ Method getHttpSocketFactoryMethod;
+ try {
+ getHttpSocketFactoryMethod = SSLCertificateSocketFactory.class.getDeclaredMethod("getHttpSocketFactory",Integer.TYPE, sSslSessionCacheClass);
+ sslCertificateSocketFactory = (SocketFactory)getHttpSocketFactoryMethod.invoke(null, SOCKET_OPERATION_TIMEOUT, sessionCache);
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ if ( null == sslCertificateSocketFactory ) {
+ sslCertificateSocketFactory = SSLSocketFactory.getSocketFactory();
+ }
+ schemeRegistry.register(new Scheme("https",
+ sslCertificateSocketFactory, 443));
+
+ ClientConnectionManager manager =
+ new ThreadSafeClientConnManager(params, schemeRegistry);
+
+ // We use a factory method to modify superclass initialization
+ // parameters without the funny call-a-static-method dance.
+ return new AndroidHttpClient(manager, params);
+ }
+
+ /**
+ * Create a new HttpClient with reasonable defaults (which you can update).
+ * @param userAgent to report in your HTTP requests.
+ * @return AndroidHttpClient for you to use for all your requests.
+ */
+ public static AndroidHttpClient newInstance(String userAgent) {
+ return newInstance(userAgent, null /* session cache */);
+ }
+
+ private final HttpClient delegate;
+
+ private RuntimeException mLeakedException = new IllegalStateException(
+ "AndroidHttpClient created and never closed");
+
+ private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) {
+ this.delegate = new DefaultHttpClient(ccm, params) {
+ @Override
+ protected BasicHttpProcessor createHttpProcessor() {
+ // Add interceptor to prevent making requests from main thread.
+ BasicHttpProcessor processor = super.createHttpProcessor();
+ processor.addRequestInterceptor(sThreadCheckInterceptor);
+ processor.addRequestInterceptor(new CurlLogger());
+
+ return processor;
+ }
+
+ @Override
+ protected HttpContext createHttpContext() {
+ // Same as DefaultHttpClient.createHttpContext() minus the
+ // cookie store.
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(
+ ClientContext.AUTHSCHEME_REGISTRY,
+ getAuthSchemes());
+ context.setAttribute(
+ ClientContext.COOKIESPEC_REGISTRY,
+ getCookieSpecs());
+ context.setAttribute(
+ ClientContext.CREDS_PROVIDER,
+ getCredentialsProvider());
+ return context;
+ }
+ };
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ if (mLeakedException != null) {
+ Log.e(TAG, "Leak found", mLeakedException);
+ mLeakedException = null;
+ }
+ }
+
+ /**
+ * Modifies a request to indicate to the server that we would like a
+ * gzipped response. (Uses the "Accept-Encoding" HTTP header.)
+ * @param request the request to modify
+ * @see #getUngzippedContent
+ */
+ public static void modifyRequestToAcceptGzipResponse(HttpRequest request) {
+ request.addHeader("Accept-Encoding", "gzip");
+ }
+
+ /**
+ * Gets the input stream from a response entity. If the entity is gzipped
+ * then this will get a stream over the uncompressed data.
+ *
+ * @param entity the entity whose content should be read
+ * @return the input stream to read from
+ * @throws IOException
+ */
+ public static InputStream getUngzippedContent(HttpEntity entity)
+ throws IOException {
+ InputStream responseStream = entity.getContent();
+ if (responseStream == null) return responseStream;
+ Header header = entity.getContentEncoding();
+ if (header == null) return responseStream;
+ String contentEncoding = header.getValue();
+ if (contentEncoding == null) return responseStream;
+ if (contentEncoding.contains("gzip")) responseStream
+ = new GZIPInputStream(responseStream);
+ return responseStream;
+ }
+
+ /**
+ * Release resources associated with this client. You must call this,
+ * or significant resources (sockets and memory) may be leaked.
+ */
+ public void close() {
+ if (mLeakedException != null) {
+ getConnectionManager().shutdown();
+ mLeakedException = null;
+ }
+ }
+
+ public HttpParams getParams() {
+ return delegate.getParams();
+ }
+
+ public ClientConnectionManager getConnectionManager() {
+ return delegate.getConnectionManager();
+ }
+
+ public HttpResponse execute(HttpUriRequest request) throws IOException {
+ return delegate.execute(request);
+ }
+
+ public HttpResponse execute(HttpUriRequest request, HttpContext context)
+ throws IOException {
+ return delegate.execute(request, context);
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request)
+ throws IOException {
+ return delegate.execute(target, request);
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request,
+ HttpContext context) throws IOException {
+ return delegate.execute(target, request, context);
+ }
+
+ public <T> T execute(HttpUriRequest request,
+ ResponseHandler<? extends T> responseHandler)
+ throws IOException, ClientProtocolException {
+ return delegate.execute(request, responseHandler);
+ }
+
+ public <T> T execute(HttpUriRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ return delegate.execute(request, responseHandler, context);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler) throws IOException,
+ ClientProtocolException {
+ return delegate.execute(target, request, responseHandler);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ return delegate.execute(target, request, responseHandler, context);
+ }
+
+ /**
+ * Compress data to send to server.
+ * Creates a Http Entity holding the gzipped data.
+ * The data will not be compressed if it is too short.
+ * @param data The bytes to compress
+ * @return Entity holding the data
+ */
+ public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver)
+ throws IOException {
+ AbstractHttpEntity entity;
+ if (data.length < getMinGzipSize(resolver)) {
+ entity = new ByteArrayEntity(data);
+ } else {
+ ByteArrayOutputStream arr = new ByteArrayOutputStream();
+ OutputStream zipper = new GZIPOutputStream(arr);
+ zipper.write(data);
+ zipper.close();
+ entity = new ByteArrayEntity(arr.toByteArray());
+ entity.setContentEncoding("gzip");
+ }
+ return entity;
+ }
+
+ /**
+ * Retrieves the minimum size for compressing data.
+ * Shorter data will not be compressed.
+ */
+ public static long getMinGzipSize(ContentResolver resolver) {
+ return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a constant.
+ }
+
+ /* cURL logging support. */
+
+ /**
+ * Logging tag and level.
+ */
+ private static class LoggingConfiguration {
+
+ private final String tag;
+ private final int level;
+
+ private LoggingConfiguration(String tag, int level) {
+ this.tag = tag;
+ this.level = level;
+ }
+
+ /**
+ * Returns true if logging is turned on for this configuration.
+ */
+ private boolean isLoggable() {
+ return Log.isLoggable(tag, level);
+ }
+
+ /**
+ * Prints a message using this configuration.
+ */
+ private void println(String message) {
+ Log.println(level, tag, message);
+ }
+ }
+
+ /** cURL logging configuration. */
+ private volatile LoggingConfiguration curlConfiguration;
+
+ /**
+ * Enables cURL request logging for this client.
+ *
+ * @param name to log messages with
+ * @param level at which to log messages (see {@link android.util.Log})
+ */
+ public void enableCurlLogging(String name, int level) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+ if (level < Log.VERBOSE || level > Log.ASSERT) {
+ throw new IllegalArgumentException("Level is out of range ["
+ + Log.VERBOSE + ".." + Log.ASSERT + "]");
+ }
+
+ curlConfiguration = new LoggingConfiguration(name, level);
+ }
+
+ /**
+ * Disables cURL logging for this client.
+ */
+ public void disableCurlLogging() {
+ curlConfiguration = null;
+ }
+
+ /**
+ * Logs cURL commands equivalent to requests.
+ */
+ private class CurlLogger implements HttpRequestInterceptor {
+ public void process(HttpRequest request, HttpContext context)
+ throws HttpException, IOException {
+ LoggingConfiguration configuration = curlConfiguration;
+ if (configuration != null
+ && configuration.isLoggable()
+ && request instanceof HttpUriRequest) {
+ // Never print auth token -- we used to check ro.secure=0 to
+ // enable that, but can't do that in unbundled code.
+ configuration.println(toCurl((HttpUriRequest) request, false));
+ }
+ }
+ }
+
+ /**
+ * Generates a cURL command equivalent to the given request.
+ */
+ private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append("curl ");
+
+ for (Header header: request.getAllHeaders()) {
+ if (!logAuthToken
+ && (header.getName().equals("Authorization") ||
+ header.getName().equals("Cookie"))) {
+ continue;
+ }
+ builder.append("--header \"");
+ builder.append(header.toString().trim());
+ builder.append("\" ");
+ }
+
+ URI uri = request.getURI();
+
+ // If this is a wrapped request, use the URI from the original
+ // request instead. getURI() on the wrapper seems to return a
+ // relative URI. We want an absolute URI.
+ if (request instanceof RequestWrapper) {
+ HttpRequest original = ((RequestWrapper) request).getOriginal();
+ if (original instanceof HttpUriRequest) {
+ uri = ((HttpUriRequest) original).getURI();
+ }
+ }
+
+ builder.append("\"");
+ builder.append(uri);
+ builder.append("\"");
+
+ if (request instanceof HttpEntityEnclosingRequest) {
+ HttpEntityEnclosingRequest entityRequest =
+ (HttpEntityEnclosingRequest) request;
+ HttpEntity entity = entityRequest.getEntity();
+ if (entity != null && entity.isRepeatable()) {
+ if (entity.getContentLength() < 1024) {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ entity.writeTo(stream);
+ String entityString = stream.toString();
+
+ // TODO: Check the content type, too.
+ builder.append(" --data-ascii \"")
+ .append(entityString)
+ .append("\"");
+ } else {
+ builder.append(" [TOO MUCH DATA TO INCLUDE]");
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Returns the date of the given HTTP date string. This method can identify
+ * and parse the date formats emitted by common HTTP servers, such as
+ * <a href="http://www.ietf.org/rfc/rfc0822.txt">RFC 822</a>,
+ * <a href="http://www.ietf.org/rfc/rfc0850.txt">RFC 850</a>,
+ * <a href="http://www.ietf.org/rfc/rfc1036.txt">RFC 1036</a>,
+ * <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</a> and
+ * <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/asctime.html">ANSI
+ * C's asctime()</a>.
+ *
+ * @return the number of milliseconds since Jan. 1, 1970, midnight GMT.
+ * @throws IllegalArgumentException if {@code dateString} is not a date or
+ * of an unsupported format.
+ */
+ public static long parseDate(String dateString) {
+ return HttpDateTime.parse(dateString);
+ }
+} \ No newline at end of file
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java
new file mode 100755
index 0000000000..b77af7e085
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java
@@ -0,0 +1,112 @@
+/*
+ * 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.impl;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * This service differs from IntentService in a few minor ways/ It will not
+ * auto-stop itself after the intent is handled unless the target returns "true"
+ * in should stop. Since the goal of this service is to handle a single kind of
+ * intent, it does not queue up batches of intents of the same type.
+ */
+public abstract class CustomIntentService extends Service {
+ private String mName;
+ private boolean mRedelivery;
+ private volatile ServiceHandler mServiceHandler;
+ private volatile Looper mServiceLooper;
+ private static final String LOG_TAG = "CancellableIntentService";
+ private static final int WHAT_MESSAGE = -10;
+
+ public CustomIntentService(String paramString) {
+ this.mName = paramString;
+ }
+
+ @Override
+ public IBinder onBind(Intent paramIntent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ HandlerThread localHandlerThread = new HandlerThread("IntentService["
+ + this.mName + "]");
+ localHandlerThread.start();
+ this.mServiceLooper = localHandlerThread.getLooper();
+ this.mServiceHandler = new ServiceHandler(this.mServiceLooper);
+ }
+
+ @Override
+ public void onDestroy() {
+ Thread localThread = this.mServiceLooper.getThread();
+ if ((localThread != null) && (localThread.isAlive())) {
+ localThread.interrupt();
+ }
+ this.mServiceLooper.quit();
+ Log.d(LOG_TAG, "onDestroy");
+ }
+
+ protected abstract void onHandleIntent(Intent paramIntent);
+
+ protected abstract boolean shouldStop();
+
+ @Override
+ public void onStart(Intent paramIntent, int startId) {
+ if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) {
+ Message localMessage = this.mServiceHandler.obtainMessage();
+ localMessage.arg1 = startId;
+ localMessage.obj = paramIntent;
+ localMessage.what = WHAT_MESSAGE;
+ this.mServiceHandler.sendMessage(localMessage);
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent paramIntent, int flags, int startId) {
+ onStart(paramIntent, startId);
+ return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
+ }
+
+ public void setIntentRedelivery(boolean enabled) {
+ this.mRedelivery = enabled;
+ }
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message paramMessage) {
+ CustomIntentService.this
+ .onHandleIntent((Intent) paramMessage.obj);
+ if (shouldStop()) {
+ Log.d(LOG_TAG, "stopSelf");
+ CustomIntentService.this.stopSelf(paramMessage.arg1);
+ Log.d(LOG_TAG, "afterStopSelf");
+ }
+ }
+ }
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java
new file mode 100644
index 0000000000..9a0ca02122
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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.impl;
+
+/**
+ * Uses the class-loader model to utilize the updated notification builders in
+ * Honeycomb while maintaining a compatible version for older devices.
+ */
+public class CustomNotificationFactory {
+ static public DownloadNotification.ICustomNotification createCustomNotification() {
+ if (android.os.Build.VERSION.SDK_INT > 13)
+ return new V14CustomNotification();
+ else
+ return new V3CustomNotification();
+ }
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java
new file mode 100644
index 0000000000..45111b16a3
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java
@@ -0,0 +1,92 @@
+/*
+ * 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.impl;
+
+import com.google.android.vending.expansion.downloader.Constants;
+import com.google.android.vending.expansion.downloader.Helpers;
+
+import android.util.Log;
+
+/**
+ * Representation of information about an individual download from the database.
+ */
+public class DownloadInfo {
+ public String mUri;
+ public final int mIndex;
+ public final String mFileName;
+ public String mETag;
+ public long mTotalBytes;
+ public long mCurrentBytes;
+ public long mLastMod;
+ public int mStatus;
+ public int mControl;
+ public int mNumFailed;
+ public int mRetryAfter;
+ public int mRedirectCount;
+
+ boolean mInitialized;
+
+ public int mFuzz;
+
+ public DownloadInfo(int index, String fileName, String pkg) {
+ mFuzz = Helpers.sRandom.nextInt(1001);
+ mFileName = fileName;
+ mIndex = index;
+ }
+
+ public void resetDownload() {
+ mCurrentBytes = 0;
+ mETag = "";
+ mLastMod = 0;
+ mStatus = 0;
+ mControl = 0;
+ mNumFailed = 0;
+ mRetryAfter = 0;
+ mRedirectCount = 0;
+ }
+
+ /**
+ * Returns the time when a download should be restarted.
+ */
+ public long restartTime(long now) {
+ if (mNumFailed == 0) {
+ return now;
+ }
+ if (mRetryAfter > 0) {
+ return mLastMod + mRetryAfter;
+ }
+ return mLastMod +
+ Constants.RETRY_FIRST_DELAY *
+ (1000 + mFuzz) * (1 << (mNumFailed - 1));
+ }
+
+ public void logVerboseInfo() {
+ Log.v(Constants.TAG, "Service adding new entry");
+ Log.v(Constants.TAG, "FILENAME: " + mFileName);
+ Log.v(Constants.TAG, "URI : " + mUri);
+ Log.v(Constants.TAG, "FILENAME: " + mFileName);
+ Log.v(Constants.TAG, "CONTROL : " + mControl);
+ Log.v(Constants.TAG, "STATUS : " + mStatus);
+ Log.v(Constants.TAG, "FAILED_C: " + mNumFailed);
+ Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter);
+ Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount);
+ Log.v(Constants.TAG, "LAST_MOD: " + mLastMod);
+ Log.v(Constants.TAG, "TOTAL : " + mTotalBytes);
+ Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes);
+ Log.v(Constants.TAG, "ETAG : " + mETag);
+ }
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java
new file mode 100644
index 0000000000..eef205d7b7
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java
@@ -0,0 +1,231 @@
+/*
+ * 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.impl;
+
+import com.android.vending.expansion.downloader.R;
+import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
+import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
+import com.google.android.vending.expansion.downloader.Helpers;
+import com.google.android.vending.expansion.downloader.IDownloaderClient;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Messenger;
+
+/**
+ * This class handles displaying the notification associated with the download
+ * queue going on in the download manager. It handles multiple status types;
+ * Some require user interaction and some do not. Some of the user interactions
+ * may be transient. (for example: the user is queried to continue the download
+ * on 3G when it started on WiFi, but then the phone locks onto WiFi again so
+ * the prompt automatically goes away)
+ * <p/>
+ * The application interface for the downloader also needs to understand and
+ * handle these transient states.
+ */
+public class DownloadNotification implements IDownloaderClient {
+
+ private int mState;
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+ private String mCurrentTitle;
+
+ private IDownloaderClient mClientProxy;
+ final ICustomNotification mCustomNotification;
+ private Notification mNotification;
+ private Notification mCurrentNotification;
+ private CharSequence mLabel;
+ private String mCurrentText;
+ private PendingIntent mContentIntent;
+ private DownloadProgressInfo mProgressInfo;
+
+ static final String LOGTAG = "DownloadNotification";
+ static final int NOTIFICATION_ID = LOGTAG.hashCode();
+
+ public PendingIntent getClientIntent() {
+ return mContentIntent;
+ }
+
+ public void setClientIntent(PendingIntent mClientIntent) {
+ this.mContentIntent = mClientIntent;
+ }
+
+ public void resendState() {
+ if (null != mClientProxy) {
+ mClientProxy.onDownloadStateChanged(mState);
+ }
+ }
+
+ @Override
+ public void onDownloadStateChanged(int newState) {
+ if (null != mClientProxy) {
+ mClientProxy.onDownloadStateChanged(newState);
+ }
+ if (newState != mState) {
+ mState = newState;
+ if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) {
+ return;
+ }
+ int stringDownloadID;
+ int iconResource;
+ boolean ongoingEvent;
+
+ // get the new title string and paused text
+ switch (newState) {
+ case 0:
+ iconResource = android.R.drawable.stat_sys_warning;
+ stringDownloadID = R.string.state_unknown;
+ ongoingEvent = false;
+ break;
+
+ case IDownloaderClient.STATE_DOWNLOADING:
+ iconResource = android.R.drawable.stat_sys_download;
+ stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
+ ongoingEvent = true;
+ break;
+
+ case IDownloaderClient.STATE_FETCHING_URL:
+ case IDownloaderClient.STATE_CONNECTING:
+ iconResource = android.R.drawable.stat_sys_download_done;
+ stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
+ ongoingEvent = true;
+ break;
+
+ case IDownloaderClient.STATE_COMPLETED:
+ case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
+ iconResource = android.R.drawable.stat_sys_download_done;
+ stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
+ ongoingEvent = false;
+ break;
+
+ case IDownloaderClient.STATE_FAILED:
+ case IDownloaderClient.STATE_FAILED_CANCELED:
+ case IDownloaderClient.STATE_FAILED_FETCHING_URL:
+ case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
+ case IDownloaderClient.STATE_FAILED_UNLICENSED:
+ iconResource = android.R.drawable.stat_sys_warning;
+ stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
+ ongoingEvent = false;
+ break;
+
+ default:
+ iconResource = android.R.drawable.stat_sys_warning;
+ stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
+ ongoingEvent = true;
+ break;
+ }
+ mCurrentText = mContext.getString(stringDownloadID);
+ mCurrentTitle = mLabel.toString();
+ mCurrentNotification.tickerText = mLabel + ": " + mCurrentText;
+ mCurrentNotification.icon = iconResource;
+ mCurrentNotification.setLatestEventInfo(mContext, mCurrentTitle, mCurrentText,
+ mContentIntent);
+ if (ongoingEvent) {
+ mCurrentNotification.flags |= Notification.FLAG_ONGOING_EVENT;
+ } else {
+ mCurrentNotification.flags &= ~Notification.FLAG_ONGOING_EVENT;
+ mCurrentNotification.flags |= Notification.FLAG_AUTO_CANCEL;
+ }
+ mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification);
+ }
+ }
+
+ @Override
+ public void onDownloadProgress(DownloadProgressInfo progress) {
+ mProgressInfo = progress;
+ if (null != mClientProxy) {
+ mClientProxy.onDownloadProgress(progress);
+ }
+ if (progress.mOverallTotal <= 0) {
+ // we just show the text
+ mNotification.tickerText = mCurrentTitle;
+ mNotification.icon = android.R.drawable.stat_sys_download;
+ mNotification.setLatestEventInfo(mContext, mLabel, mCurrentText, mContentIntent);
+ mCurrentNotification = mNotification;
+ } else {
+ mCustomNotification.setCurrentBytes(progress.mOverallProgress);
+ mCustomNotification.setTotalBytes(progress.mOverallTotal);
+ mCustomNotification.setIcon(android.R.drawable.stat_sys_download);
+ mCustomNotification.setPendingIntent(mContentIntent);
+ mCustomNotification.setTicker(mLabel + ": " + mCurrentText);
+ mCustomNotification.setTitle(mLabel);
+ mCustomNotification.setTimeRemaining(progress.mTimeRemaining);
+ mCurrentNotification = mCustomNotification.updateNotification(mContext);
+ }
+ mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification);
+ }
+
+ public interface ICustomNotification {
+ void setTitle(CharSequence title);
+
+ void setTicker(CharSequence ticker);
+
+ void setPendingIntent(PendingIntent mContentIntent);
+
+ void setTotalBytes(long totalBytes);
+
+ void setCurrentBytes(long currentBytes);
+
+ void setIcon(int iconResource);
+
+ void setTimeRemaining(long timeRemaining);
+
+ Notification updateNotification(Context c);
+ }
+
+ /**
+ * Called in response to onClientUpdated. Creates a new proxy and notifies
+ * it of the current state.
+ *
+ * @param msg the client Messenger to notify
+ */
+ public void setMessenger(Messenger msg) {
+ mClientProxy = DownloaderClientMarshaller.CreateProxy(msg);
+ if (null != mProgressInfo) {
+ mClientProxy.onDownloadProgress(mProgressInfo);
+ }
+ if (mState != -1) {
+ mClientProxy.onDownloadStateChanged(mState);
+ }
+ }
+
+ /**
+ * Constructor
+ *
+ * @param ctx The context to use to obtain access to the Notification
+ * Service
+ */
+ DownloadNotification(Context ctx, CharSequence applicationLabel) {
+ mState = -1;
+ mContext = ctx;
+ mLabel = applicationLabel;
+ mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ mCustomNotification = CustomNotificationFactory
+ .createCustomNotification();
+ mNotification = new Notification();
+ mCurrentNotification = mNotification;
+
+ }
+
+ @Override
+ public void onServiceConnected(Messenger m) {
+ }
+
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java
new file mode 100644
index 0000000000..056d1eca0b
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java
@@ -0,0 +1,963 @@
+/*
+ * 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.impl;
+
+import com.google.android.vending.expansion.downloader.Constants;
+import com.google.android.vending.expansion.downloader.Helpers;
+import com.google.android.vending.expansion.downloader.IDownloaderClient;
+
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.conn.params.ConnRouteParams;
+
+import android.content.Context;
+import android.net.Proxy;
+import android.os.PowerManager;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SyncFailedException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Locale;
+
+/**
+ * Runs an actual download
+ */
+public class DownloadThread {
+
+ private Context mContext;
+ private DownloadInfo mInfo;
+ private DownloaderService mService;
+ private final DownloadsDB mDB;
+ private final DownloadNotification mNotification;
+ private String mUserAgent;
+
+ public DownloadThread(DownloadInfo info, DownloaderService service,
+ DownloadNotification notification) {
+ mContext = service;
+ mInfo = info;
+ mService = service;
+ mNotification = notification;
+ mDB = DownloadsDB.getDB(service);
+ mUserAgent = "APKXDL (Linux; U; Android " + android.os.Build.VERSION.RELEASE + ";"
+ + Locale.getDefault().toString() + "; " + android.os.Build.DEVICE + "/"
+ + android.os.Build.ID + ")" +
+ service.getPackageName();
+ }
+
+ /**
+ * Returns the default user agent
+ */
+ private String userAgent() {
+ return mUserAgent;
+ }
+
+ /**
+ * State for the entire run() method.
+ */
+ private static class State {
+ public String mFilename;
+ public FileOutputStream mStream;
+ public boolean mCountRetry = false;
+ public int mRetryAfter = 0;
+ public int mRedirectCount = 0;
+ public String mNewUri;
+ public boolean mGotData = false;
+ public String mRequestUri;
+
+ public State(DownloadInfo info, DownloaderService service) {
+ mRedirectCount = info.mRedirectCount;
+ mRequestUri = info.mUri;
+ mFilename = service.generateTempSaveFileName(info.mFileName);
+ }
+ }
+
+ /**
+ * State within executeDownload()
+ */
+ private static class InnerState {
+ public int mBytesSoFar = 0;
+ public int mBytesThisSession = 0;
+ public String mHeaderETag;
+ public boolean mContinuingDownload = false;
+ public String mHeaderContentLength;
+ public String mHeaderContentDisposition;
+ public String mHeaderContentLocation;
+ public int mBytesNotified = 0;
+ public long mTimeLastNotification = 0;
+ }
+
+ /**
+ * Raised from methods called by run() to indicate that the current request
+ * should be stopped immediately. Note the message passed to this exception
+ * will be logged and therefore must be guaranteed not to contain any PII,
+ * meaning it generally can't include any information about the request URI,
+ * headers, or destination filename.
+ */
+ private class StopRequest extends Throwable {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 6338592678988347973L;
+ public int mFinalStatus;
+
+ public StopRequest(int finalStatus, String message) {
+ super(message);
+ mFinalStatus = finalStatus;
+ }
+
+ public StopRequest(int finalStatus, String message, Throwable throwable) {
+ super(message, throwable);
+ mFinalStatus = finalStatus;
+ }
+ }
+
+ /**
+ * Raised from methods called by executeDownload() to indicate that the
+ * download should be retried immediately.
+ */
+ private class RetryDownload extends Throwable {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 6196036036517540229L;
+ }
+
+ /**
+ * Returns the preferred proxy to be used by clients. This is a wrapper
+ * around {@link android.net.Proxy#getHost()}. Currently no proxy will be
+ * returned for localhost or if the active network is Wi-Fi.
+ *
+ * @param context the context which will be passed to
+ * {@link android.net.Proxy#getHost()}
+ * @param url the target URL for the request
+ * @note Calling this method requires permission
+ * android.permission.ACCESS_NETWORK_STATE
+ * @return The preferred proxy to be used by clients, or null if there is no
+ * proxy.
+ */
+ public HttpHost getPreferredHttpHost(Context context,
+ String url) {
+ if (!isLocalHost(url) && !mService.isWiFi()) {
+ final String proxyHost = Proxy.getHost(context);
+ if (proxyHost != null) {
+ return new HttpHost(proxyHost, Proxy.getPort(context), "http");
+ }
+ }
+
+ return null;
+ }
+
+ static final private boolean isLocalHost(String url) {
+ if (url == null) {
+ return false;
+ }
+
+ try {
+ final URI uri = URI.create(url);
+ final String host = uri.getHost();
+ if (host != null) {
+ // TODO: InetAddress.isLoopbackAddress should be used to check
+ // for localhost. However no public factory methods exist which
+ // can be used without triggering DNS lookup if host is not
+ // localhost.
+ if (host.equalsIgnoreCase("localhost") ||
+ host.equals("127.0.0.1") ||
+ host.equals("[::1]")) {
+ return true;
+ }
+ }
+ } catch (IllegalArgumentException iex) {
+ // Ignore (URI.create)
+ }
+
+ return false;
+ }
+
+ /**
+ * Executes the download in a separate thread
+ */
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ State state = new State(mInfo, mService);
+ AndroidHttpClient client = null;
+ PowerManager.WakeLock wakeLock = null;
+ int finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR;
+
+ try {
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
+ wakeLock.acquire();
+
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName);
+ Log.v(Constants.TAG, " at " + mInfo.mUri);
+ }
+
+ client = AndroidHttpClient.newInstance(userAgent(), mContext);
+
+ boolean finished = false;
+ while (!finished) {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName);
+ Log.v(Constants.TAG, " at " + mInfo.mUri);
+ }
+ // Set or unset proxy, which may have changed since last GET
+ // request.
+ // setDefaultProxy() supports null as proxy parameter.
+ ConnRouteParams.setDefaultProxy(client.getParams(),
+ getPreferredHttpHost(mContext, state.mRequestUri));
+ HttpGet request = new HttpGet(state.mRequestUri);
+ try {
+ executeDownload(state, client, request);
+ finished = true;
+ } catch (RetryDownload exc) {
+ // fall through
+ } finally {
+ request.abort();
+ request = null;
+ }
+ }
+
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "download completed for " + mInfo.mFileName);
+ Log.v(Constants.TAG, " at " + mInfo.mUri);
+ }
+ finalizeDestinationFile(state);
+ finalStatus = DownloaderService.STATUS_SUCCESS;
+ } catch (StopRequest error) {
+ // remove the cause before printing, in case it contains PII
+ Log.w(Constants.TAG,
+ "Aborting request for download " + mInfo.mFileName + ": " + error.getMessage());
+ error.printStackTrace();
+ finalStatus = error.mFinalStatus;
+ // fall through to finally block
+ } catch (Throwable ex) { // sometimes the socket code throws unchecked
+ // exceptions
+ Log.w(Constants.TAG, "Exception for " + mInfo.mFileName + ": " + ex);
+ finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR;
+ // falls through to the code that reports an error
+ } finally {
+ if (wakeLock != null) {
+ wakeLock.release();
+ wakeLock = null;
+ }
+ if (client != null) {
+ client.close();
+ client = null;
+ }
+ cleanupDestination(state, finalStatus);
+ notifyDownloadCompleted(finalStatus, state.mCountRetry, state.mRetryAfter,
+ state.mRedirectCount, state.mGotData, state.mFilename);
+ }
+ }
+
+ /**
+ * Fully execute a single download request - setup and send the request,
+ * handle the response, and transfer the data to the destination file.
+ */
+ private void executeDownload(State state, AndroidHttpClient client, HttpGet request)
+ throws StopRequest, RetryDownload {
+ InnerState innerState = new InnerState();
+ byte data[] = new byte[Constants.BUFFER_SIZE];
+
+ checkPausedOrCanceled(state);
+
+ setupDestinationFile(state, innerState);
+ addRequestHeaders(innerState, request);
+
+ // check just before sending the request to avoid using an invalid
+ // connection at all
+ checkConnectivity(state);
+
+ mNotification.onDownloadStateChanged(IDownloaderClient.STATE_CONNECTING);
+ HttpResponse response = sendRequest(state, client, request);
+ handleExceptionalStatus(state, innerState, response);
+
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "received response for " + mInfo.mUri);
+ }
+
+ processResponseHeaders(state, innerState, response);
+ InputStream entityStream = openResponseEntity(state, response);
+ mNotification.onDownloadStateChanged(IDownloaderClient.STATE_DOWNLOADING);
+ transferData(state, innerState, data, entityStream);
+ }
+
+ /**
+ * Check if current connectivity is valid for this request.
+ */
+ private void checkConnectivity(State state) throws StopRequest {
+ switch (mService.getNetworkAvailabilityState(mDB)) {
+ case DownloaderService.NETWORK_OK:
+ return;
+ case DownloaderService.NETWORK_NO_CONNECTION:
+ throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK,
+ "waiting for network to return");
+ case DownloaderService.NETWORK_TYPE_DISALLOWED_BY_REQUESTOR:
+ throw new StopRequest(
+ DownloaderService.STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION,
+ "waiting for wifi or for download over cellular to be authorized");
+ case DownloaderService.NETWORK_CANNOT_USE_ROAMING:
+ throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK,
+ "roaming is not allowed");
+ case DownloaderService.NETWORK_UNUSABLE_DUE_TO_SIZE:
+ throw new StopRequest(DownloaderService.STATUS_QUEUED_FOR_WIFI, "waiting for wifi");
+ }
+ }
+
+ /**
+ * Transfer as much data as possible from the HTTP response to the
+ * destination file.
+ *
+ * @param data buffer to use to read data
+ * @param entityStream stream for reading the HTTP response entity
+ */
+ private void transferData(State state, InnerState innerState, byte[] data,
+ InputStream entityStream) throws StopRequest {
+ for (;;) {
+ int bytesRead = readFromResponse(state, innerState, data, entityStream);
+ if (bytesRead == -1) { // success, end of stream already reached
+ handleEndOfStream(state, innerState);
+ return;
+ }
+
+ state.mGotData = true;
+ writeDataToDestination(state, data, bytesRead);
+ innerState.mBytesSoFar += bytesRead;
+ innerState.mBytesThisSession += bytesRead;
+ reportProgress(state, innerState);
+
+ checkPausedOrCanceled(state);
+ }
+ }
+
+ /**
+ * Called after a successful completion to take any necessary action on the
+ * downloaded file.
+ */
+ private void finalizeDestinationFile(State state) throws StopRequest {
+ syncDestination(state);
+ String tempFilename = state.mFilename;
+ String finalFilename = Helpers.generateSaveFileName(mService, mInfo.mFileName);
+ if (!state.mFilename.equals(finalFilename)) {
+ File startFile = new File(tempFilename);
+ File destFile = new File(finalFilename);
+ if (mInfo.mTotalBytes != -1 && mInfo.mCurrentBytes == mInfo.mTotalBytes) {
+ if (!startFile.renameTo(destFile)) {
+ throw new StopRequest(DownloaderService.STATUS_FILE_ERROR,
+ "unable to finalize destination file");
+ }
+ } else {
+ throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY,
+ "file delivered with incorrect size. probably due to network not browser configured");
+ }
+ }
+ }
+
+ /**
+ * Called just before the thread finishes, regardless of status, to take any
+ * necessary action on the downloaded file.
+ */
+ private void cleanupDestination(State state, int finalStatus) {
+ closeDestination(state);
+ if (state.mFilename != null && DownloaderService.isStatusError(finalStatus)) {
+ new File(state.mFilename).delete();
+ state.mFilename = null;
+ }
+ }
+
+ /**
+ * Sync the destination file to storage.
+ */
+ private void syncDestination(State state) {
+ FileOutputStream downloadedFileStream = null;
+ try {
+ downloadedFileStream = new FileOutputStream(state.mFilename, true);
+ downloadedFileStream.getFD().sync();
+ } catch (FileNotFoundException ex) {
+ Log.w(Constants.TAG, "file " + state.mFilename + " not found: " + ex);
+ } catch (SyncFailedException ex) {
+ Log.w(Constants.TAG, "file " + state.mFilename + " sync failed: " + ex);
+ } catch (IOException ex) {
+ Log.w(Constants.TAG, "IOException trying to sync " + state.mFilename + ": " + ex);
+ } catch (RuntimeException ex) {
+ Log.w(Constants.TAG, "exception while syncing file: ", ex);
+ } finally {
+ if (downloadedFileStream != null) {
+ try {
+ downloadedFileStream.close();
+ } catch (IOException ex) {
+ Log.w(Constants.TAG, "IOException while closing synced file: ", ex);
+ } catch (RuntimeException ex) {
+ Log.w(Constants.TAG, "exception while closing file: ", ex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Close the destination output stream.
+ */
+ private void closeDestination(State state) {
+ try {
+ // close the file
+ if (state.mStream != null) {
+ state.mStream.close();
+ state.mStream = null;
+ }
+ } catch (IOException ex) {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "exception when closing the file after download : " + ex);
+ }
+ // nothing can really be done if the file can't be closed
+ }
+ }
+
+ /**
+ * Check if the download has been paused or canceled, stopping the request
+ * appropriately if it has been.
+ */
+ private void checkPausedOrCanceled(State state) throws StopRequest {
+ if (mService.getControl() == DownloaderService.CONTROL_PAUSED) {
+ int status = mService.getStatus();
+ switch (status) {
+ case DownloaderService.STATUS_PAUSED_BY_APP:
+ throw new StopRequest(mService.getStatus(),
+ "download paused");
+ }
+ }
+ }
+
+ /**
+ * Report download progress through the database if necessary.
+ */
+ private void reportProgress(State state, InnerState innerState) {
+ long now = System.currentTimeMillis();
+ if (innerState.mBytesSoFar - innerState.mBytesNotified
+ > Constants.MIN_PROGRESS_STEP
+ && now - innerState.mTimeLastNotification
+ > Constants.MIN_PROGRESS_TIME) {
+ // we store progress updates to the database here
+ mInfo.mCurrentBytes = innerState.mBytesSoFar;
+ mDB.updateDownloadCurrentBytes(mInfo);
+
+ innerState.mBytesNotified = innerState.mBytesSoFar;
+ innerState.mTimeLastNotification = now;
+
+ long totalBytesSoFar = innerState.mBytesThisSession + mService.mBytesSoFar;
+
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "downloaded " + mInfo.mCurrentBytes + " out of "
+ + mInfo.mTotalBytes);
+ Log.v(Constants.TAG, " total " + totalBytesSoFar + " out of "
+ + mService.mTotalLength);
+ }
+
+ mService.notifyUpdateBytes(totalBytesSoFar);
+ }
+ }
+
+ /**
+ * Write a data buffer to the destination file.
+ *
+ * @param data buffer containing the data to write
+ * @param bytesRead how many bytes to write from the buffer
+ */
+ private void writeDataToDestination(State state, byte[] data, int bytesRead)
+ throws StopRequest {
+ for (;;) {
+ try {
+ if (state.mStream == null) {
+ state.mStream = new FileOutputStream(state.mFilename, true);
+ }
+ state.mStream.write(data, 0, bytesRead);
+ // we close after every write --- this may be too inefficient
+ closeDestination(state);
+ return;
+ } catch (IOException ex) {
+ if (!Helpers.isExternalMediaMounted()) {
+ throw new StopRequest(DownloaderService.STATUS_DEVICE_NOT_FOUND_ERROR,
+ "external media not mounted while writing destination file");
+ }
+
+ long availableBytes =
+ Helpers.getAvailableBytes(Helpers.getFilesystemRoot(state.mFilename));
+ if (availableBytes < bytesRead) {
+ throw new StopRequest(DownloaderService.STATUS_INSUFFICIENT_SPACE_ERROR,
+ "insufficient space while writing destination file", ex);
+ }
+ throw new StopRequest(DownloaderService.STATUS_FILE_ERROR,
+ "while writing destination file: " + ex.toString(), ex);
+ }
+ }
+ }
+
+ /**
+ * Called when we've reached the end of the HTTP response stream, to update
+ * the database and check for consistency.
+ */
+ private void handleEndOfStream(State state, InnerState innerState) throws StopRequest {
+ mInfo.mCurrentBytes = innerState.mBytesSoFar;
+ // this should always be set from the market
+ // if ( innerState.mHeaderContentLength == null ) {
+ // mInfo.mTotalBytes = innerState.mBytesSoFar;
+ // }
+ mDB.updateDownload(mInfo);
+
+ boolean lengthMismatched = (innerState.mHeaderContentLength != null)
+ && (innerState.mBytesSoFar != Integer.parseInt(innerState.mHeaderContentLength));
+ if (lengthMismatched) {
+ if (cannotResume(innerState)) {
+ throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME,
+ "mismatched content length");
+ } else {
+ throw new StopRequest(getFinalStatusForHttpError(state),
+ "closed socket before end of file");
+ }
+ }
+ }
+
+ private boolean cannotResume(InnerState innerState) {
+ return innerState.mBytesSoFar > 0 && innerState.mHeaderETag == null;
+ }
+
+ /**
+ * Read some data from the HTTP response stream, handling I/O errors.
+ *
+ * @param data buffer to use to read data
+ * @param entityStream stream for reading the HTTP response entity
+ * @return the number of bytes actually read or -1 if the end of the stream
+ * has been reached
+ */
+ private int readFromResponse(State state, InnerState innerState, byte[] data,
+ InputStream entityStream) throws StopRequest {
+ try {
+ return entityStream.read(data);
+ } catch (IOException ex) {
+ logNetworkState();
+ mInfo.mCurrentBytes = innerState.mBytesSoFar;
+ mDB.updateDownload(mInfo);
+ if (cannotResume(innerState)) {
+ String message = "while reading response: " + ex.toString()
+ + ", can't resume interrupted download with no ETag";
+ throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME,
+ message, ex);
+ } else {
+ throw new StopRequest(getFinalStatusForHttpError(state),
+ "while reading response: " + ex.toString(), ex);
+ }
+ }
+ }
+
+ /**
+ * Open a stream for the HTTP response entity, handling I/O errors.
+ *
+ * @return an InputStream to read the response entity
+ */
+ private InputStream openResponseEntity(State state, HttpResponse response)
+ throws StopRequest {
+ try {
+ return response.getEntity().getContent();
+ } catch (IOException ex) {
+ logNetworkState();
+ throw new StopRequest(getFinalStatusForHttpError(state),
+ "while getting entity: " + ex.toString(), ex);
+ }
+ }
+
+ private void logNetworkState() {
+ if (Constants.LOGX) {
+ Log.i(Constants.TAG,
+ "Net "
+ + (mService.getNetworkAvailabilityState(mDB) == DownloaderService.NETWORK_OK ? "Up"
+ : "Down"));
+ }
+ }
+
+ /**
+ * Read HTTP response headers and take appropriate action, including setting
+ * up the destination file and updating the database.
+ */
+ private void processResponseHeaders(State state, InnerState innerState, HttpResponse response)
+ throws StopRequest {
+ if (innerState.mContinuingDownload) {
+ // ignore response headers on resume requests
+ return;
+ }
+
+ readResponseHeaders(state, innerState, response);
+
+ try {
+ state.mFilename = mService.generateSaveFile(mInfo.mFileName, mInfo.mTotalBytes);
+ } catch (DownloaderService.GenerateSaveFileError exc) {
+ throw new StopRequest(exc.mStatus, exc.mMessage);
+ }
+ try {
+ state.mStream = new FileOutputStream(state.mFilename);
+ } catch (FileNotFoundException exc) {
+ // make sure the directory exists
+ File pathFile = new File(Helpers.getSaveFilePath(mService));
+ try {
+ if (pathFile.mkdirs()) {
+ state.mStream = new FileOutputStream(state.mFilename);
+ }
+ } catch (Exception ex) {
+ throw new StopRequest(DownloaderService.STATUS_FILE_ERROR,
+ "while opening destination file: " + exc.toString(), exc);
+ }
+ }
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "writing " + mInfo.mUri + " to " + state.mFilename);
+ }
+
+ updateDatabaseFromHeaders(state, innerState);
+ // check connectivity again now that we know the total size
+ checkConnectivity(state);
+ }
+
+ /**
+ * Update necessary database fields based on values of HTTP response headers
+ * that have been read.
+ */
+ private void updateDatabaseFromHeaders(State state, InnerState innerState) {
+ mInfo.mETag = innerState.mHeaderETag;
+ mDB.updateDownload(mInfo);
+ }
+
+ /**
+ * Read headers from the HTTP response and store them into local state.
+ */
+ private void readResponseHeaders(State state, InnerState innerState, HttpResponse response)
+ throws StopRequest {
+ Header header = response.getFirstHeader("Content-Disposition");
+ if (header != null) {
+ innerState.mHeaderContentDisposition = header.getValue();
+ }
+ header = response.getFirstHeader("Content-Location");
+ if (header != null) {
+ innerState.mHeaderContentLocation = header.getValue();
+ }
+ header = response.getFirstHeader("ETag");
+ if (header != null) {
+ innerState.mHeaderETag = header.getValue();
+ }
+ String headerTransferEncoding = null;
+ header = response.getFirstHeader("Transfer-Encoding");
+ if (header != null) {
+ headerTransferEncoding = header.getValue();
+ }
+ String headerContentType = null;
+ header = response.getFirstHeader("Content-Type");
+ if (header != null) {
+ headerContentType = header.getValue();
+ if (!headerContentType.equals("application/vnd.android.obb")) {
+ throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY,
+ "file delivered with incorrect Mime type");
+ }
+ }
+
+ if (headerTransferEncoding == null) {
+ header = response.getFirstHeader("Content-Length");
+ if (header != null) {
+ innerState.mHeaderContentLength = header.getValue();
+ // this is always set from Market
+ long contentLength = Long.parseLong(innerState.mHeaderContentLength);
+ if (contentLength != -1 && contentLength != mInfo.mTotalBytes) {
+ // we're most likely on a bad wifi connection -- we should
+ // probably
+ // also look at the mime type --- but the size mismatch is
+ // enough
+ // to tell us that something is wrong here
+ Log.e(Constants.TAG, "Incorrect file size delivered.");
+ }
+ }
+ } else {
+ // Ignore content-length with transfer-encoding - 2616 4.4 3
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG,
+ "ignoring content-length because of xfer-encoding");
+ }
+ }
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "Content-Disposition: " +
+ innerState.mHeaderContentDisposition);
+ Log.v(Constants.TAG, "Content-Length: " + innerState.mHeaderContentLength);
+ Log.v(Constants.TAG, "Content-Location: " + innerState.mHeaderContentLocation);
+ Log.v(Constants.TAG, "ETag: " + innerState.mHeaderETag);
+ Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding);
+ }
+
+ boolean noSizeInfo = innerState.mHeaderContentLength == null
+ && (headerTransferEncoding == null
+ || !headerTransferEncoding.equalsIgnoreCase("chunked"));
+ if (noSizeInfo) {
+ throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR,
+ "can't know size of download, giving up");
+ }
+ }
+
+ /**
+ * Check the HTTP response status and handle anything unusual (e.g. not
+ * 200/206).
+ */
+ private void handleExceptionalStatus(State state, InnerState innerState, HttpResponse response)
+ throws StopRequest, RetryDownload {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 503 && mInfo.mNumFailed < Constants.MAX_RETRIES) {
+ handleServiceUnavailable(state, response);
+ }
+ if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307) {
+ handleRedirect(state, response, statusCode);
+ }
+
+ int expectedStatus = innerState.mContinuingDownload ? 206
+ : DownloaderService.STATUS_SUCCESS;
+ if (statusCode != expectedStatus) {
+ handleOtherStatus(state, innerState, statusCode);
+ } else {
+ // no longer redirected
+ state.mRedirectCount = 0;
+ }
+ }
+
+ /**
+ * Handle a status that we don't know how to deal with properly.
+ */
+ private void handleOtherStatus(State state, InnerState innerState, int statusCode)
+ throws StopRequest {
+ int finalStatus;
+ if (DownloaderService.isStatusError(statusCode)) {
+ finalStatus = statusCode;
+ } else if (statusCode >= 300 && statusCode < 400) {
+ finalStatus = DownloaderService.STATUS_UNHANDLED_REDIRECT;
+ } else if (innerState.mContinuingDownload && statusCode == DownloaderService.STATUS_SUCCESS) {
+ finalStatus = DownloaderService.STATUS_CANNOT_RESUME;
+ } else {
+ finalStatus = DownloaderService.STATUS_UNHANDLED_HTTP_CODE;
+ }
+ throw new StopRequest(finalStatus, "http error " + statusCode);
+ }
+
+ /**
+ * Handle a 3xx redirect status.
+ */
+ private void handleRedirect(State state, HttpResponse response, int statusCode)
+ throws StopRequest, RetryDownload {
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "got HTTP redirect " + statusCode);
+ }
+ if (state.mRedirectCount >= Constants.MAX_REDIRECTS) {
+ throw new StopRequest(DownloaderService.STATUS_TOO_MANY_REDIRECTS, "too many redirects");
+ }
+ Header header = response.getFirstHeader("Location");
+ if (header == null) {
+ return;
+ }
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "Location :" + header.getValue());
+ }
+
+ String newUri;
+ try {
+ newUri = new URI(mInfo.mUri).resolve(new URI(header.getValue())).toString();
+ } catch (URISyntaxException ex) {
+ if (Constants.LOGV) {
+ Log.d(Constants.TAG, "Couldn't resolve redirect URI " + header.getValue()
+ + " for " + mInfo.mUri);
+ }
+ throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR,
+ "Couldn't resolve redirect URI");
+ }
+ ++state.mRedirectCount;
+ state.mRequestUri = newUri;
+ if (statusCode == 301 || statusCode == 303) {
+ // use the new URI for all future requests (should a retry/resume be
+ // necessary)
+ state.mNewUri = newUri;
+ }
+ throw new RetryDownload();
+ }
+
+ /**
+ * Add headers for this download to the HTTP request to allow for resume.
+ */
+ private void addRequestHeaders(InnerState innerState, HttpGet request) {
+ if (innerState.mContinuingDownload) {
+ if (innerState.mHeaderETag != null) {
+ request.addHeader("If-Match", innerState.mHeaderETag);
+ }
+ request.addHeader("Range", "bytes=" + innerState.mBytesSoFar + "-");
+ }
+ }
+
+ /**
+ * Handle a 503 Service Unavailable status by processing the Retry-After
+ * header.
+ */
+ private void handleServiceUnavailable(State state, HttpResponse response) throws StopRequest {
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "got HTTP response code 503");
+ }
+ state.mCountRetry = true;
+ Header header = response.getFirstHeader("Retry-After");
+ if (header != null) {
+ try {
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "Retry-After :" + header.getValue());
+ }
+ state.mRetryAfter = Integer.parseInt(header.getValue());
+ if (state.mRetryAfter < 0) {
+ state.mRetryAfter = 0;
+ } else {
+ if (state.mRetryAfter < Constants.MIN_RETRY_AFTER) {
+ state.mRetryAfter = Constants.MIN_RETRY_AFTER;
+ } else if (state.mRetryAfter > Constants.MAX_RETRY_AFTER) {
+ state.mRetryAfter = Constants.MAX_RETRY_AFTER;
+ }
+ state.mRetryAfter += Helpers.sRandom.nextInt(Constants.MIN_RETRY_AFTER + 1);
+ state.mRetryAfter *= 1000;
+ }
+ } catch (NumberFormatException ex) {
+ // ignored - retryAfter stays 0 in this case.
+ }
+ }
+ throw new StopRequest(DownloaderService.STATUS_WAITING_TO_RETRY,
+ "got 503 Service Unavailable, will retry later");
+ }
+
+ /**
+ * Send the request to the server, handling any I/O exceptions.
+ */
+ private HttpResponse sendRequest(State state, AndroidHttpClient client, HttpGet request)
+ throws StopRequest {
+ try {
+ return client.execute(request);
+ } catch (IllegalArgumentException ex) {
+ throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR,
+ "while trying to execute request: " + ex.toString(), ex);
+ } catch (IOException ex) {
+ logNetworkState();
+ throw new StopRequest(getFinalStatusForHttpError(state),
+ "while trying to execute request: " + ex.toString(), ex);
+ }
+ }
+
+ private int getFinalStatusForHttpError(State state) {
+ if (mService.getNetworkAvailabilityState(mDB) != DownloaderService.NETWORK_OK) {
+ return DownloaderService.STATUS_WAITING_FOR_NETWORK;
+ } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) {
+ state.mCountRetry = true;
+ return DownloaderService.STATUS_WAITING_TO_RETRY;
+ } else {
+ Log.w(Constants.TAG, "reached max retries for " + mInfo.mNumFailed);
+ return DownloaderService.STATUS_HTTP_DATA_ERROR;
+ }
+ }
+
+ /**
+ * Prepare the destination file to receive data. If the file already exists,
+ * we'll set up appropriately for resumption.
+ */
+ private void setupDestinationFile(State state, InnerState innerState)
+ throws StopRequest {
+ if (state.mFilename != null) { // only true if we've already run a
+ // thread for this download
+ if (!Helpers.isFilenameValid(state.mFilename)) {
+ // this should never happen
+ throw new StopRequest(DownloaderService.STATUS_FILE_ERROR,
+ "found invalid internal destination filename");
+ }
+ // We're resuming a download that got interrupted
+ File f = new File(state.mFilename);
+ if (f.exists()) {
+ long fileLength = f.length();
+ if (fileLength == 0) {
+ // The download hadn't actually started, we can restart from
+ // scratch
+ f.delete();
+ state.mFilename = null;
+ } else if (mInfo.mETag == null) {
+ // This should've been caught upon failure
+ f.delete();
+ throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME,
+ "Trying to resume a download that can't be resumed");
+ } else {
+ // All right, we'll be able to resume this download
+ try {
+ state.mStream = new FileOutputStream(state.mFilename, true);
+ } catch (FileNotFoundException exc) {
+ throw new StopRequest(DownloaderService.STATUS_FILE_ERROR,
+ "while opening destination for resuming: " + exc.toString(), exc);
+ }
+ innerState.mBytesSoFar = (int) fileLength;
+ if (mInfo.mTotalBytes != -1) {
+ innerState.mHeaderContentLength = Long.toString(mInfo.mTotalBytes);
+ }
+ innerState.mHeaderETag = mInfo.mETag;
+ innerState.mContinuingDownload = true;
+ }
+ }
+ }
+
+ if (state.mStream != null) {
+ closeDestination(state);
+ }
+ }
+
+ /**
+ * Stores information about the completed download, and notifies the
+ * initiating application.
+ */
+ private void notifyDownloadCompleted(
+ int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData,
+ String filename) {
+ updateDownloadDatabase(
+ status, countRetry, retryAfter, redirectCount, gotData, filename);
+ if (DownloaderService.isStatusCompleted(status)) {
+ // TBD: send status update?
+ }
+ }
+
+ private void updateDownloadDatabase(
+ int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData,
+ String filename) {
+ mInfo.mStatus = status;
+ mInfo.mRetryAfter = retryAfter;
+ mInfo.mRedirectCount = redirectCount;
+ mInfo.mLastMod = System.currentTimeMillis();
+ if (!countRetry) {
+ mInfo.mNumFailed = 0;
+ } else if (gotData) {
+ mInfo.mNumFailed = 1;
+ } else {
+ mInfo.mNumFailed++;
+ }
+ mDB.updateDownload(mInfo);
+ }
+
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java
new file mode 100644
index 0000000000..627bf3eedd
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java
@@ -0,0 +1,1341 @@
+/*
+ * 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.impl;
+
+import com.google.android.vending.expansion.downloader.Constants;
+import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
+import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
+import com.google.android.vending.expansion.downloader.Helpers;
+import com.google.android.vending.expansion.downloader.IDownloaderClient;
+import com.google.android.vending.expansion.downloader.IDownloaderService;
+import com.google.android.vending.expansion.downloader.IStub;
+import com.google.android.vending.licensing.AESObfuscator;
+import com.google.android.vending.licensing.APKExpansionPolicy;
+import com.google.android.vending.licensing.LicenseChecker;
+import com.google.android.vending.licensing.LicenseCheckerCallback;
+import com.google.android.vending.licensing.Policy;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Messenger;
+import android.os.SystemClock;
+import android.provider.Settings.Secure;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.io.File;
+
+/**
+ * Performs the background downloads requested by applications that use the
+ * Downloads provider. This service does not run as a foreground task, so
+ * Android may kill it off at will, but it will try to restart itself if it can.
+ * Note that Android by default will kill off any process that has an open file
+ * handle on the shared (SD Card) partition if the partition is unmounted.
+ */
+public abstract class DownloaderService extends CustomIntentService implements IDownloaderService {
+
+ public DownloaderService() {
+ super("LVLDownloadService");
+ }
+
+ private static final String LOG_TAG = "LVLDL";
+
+ // the following NETWORK_* constants are used to indicates specific reasons
+ // for disallowing a
+ // download from using a network, since specific causes can require special
+ // handling
+
+ /**
+ * The network is usable for the given download.
+ */
+ public static final int NETWORK_OK = 1;
+
+ /**
+ * There is no network connectivity.
+ */
+ public static final int NETWORK_NO_CONNECTION = 2;
+
+ /**
+ * The download exceeds the maximum size for this network.
+ */
+ public static final int NETWORK_UNUSABLE_DUE_TO_SIZE = 3;
+
+ /**
+ * The download exceeds the recommended maximum size for this network, the
+ * user must confirm for this download to proceed without WiFi.
+ */
+ public static final int NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE = 4;
+
+ /**
+ * The current connection is roaming, and the download can't proceed over a
+ * roaming connection.
+ */
+ public static final int NETWORK_CANNOT_USE_ROAMING = 5;
+
+ /**
+ * The app requesting the download specific that it can't use the current
+ * network connection.
+ */
+ public static final int NETWORK_TYPE_DISALLOWED_BY_REQUESTOR = 6;
+
+ /**
+ * For intents used to notify the user that a download exceeds a size
+ * threshold, if this extra is true, WiFi is required for this download
+ * size; otherwise, it is only recommended.
+ */
+ public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired";
+ public static final String EXTRA_FILE_NAME = "downloadId";
+
+ /**
+ * Used with DOWNLOAD_STATUS
+ */
+ public static final String EXTRA_STATUS_STATE = "ESS";
+ public static final String EXTRA_STATUS_TOTAL_SIZE = "ETS";
+ public static final String EXTRA_STATUS_CURRENT_FILE_SIZE = "CFS";
+ public static final String EXTRA_STATUS_TOTAL_PROGRESS = "TFP";
+ public static final String EXTRA_STATUS_CURRENT_PROGRESS = "CFP";
+
+ public static final String ACTION_DOWNLOADS_CHANGED = "downloadsChanged";
+
+ /**
+ * Broadcast intent action sent by the download manager when a download
+ * completes.
+ */
+ public final static String ACTION_DOWNLOAD_COMPLETE = "lvldownloader.intent.action.DOWNLOAD_COMPLETE";
+
+ /**
+ * Broadcast intent action sent by the download manager when download status
+ * changes.
+ */
+ public final static String ACTION_DOWNLOAD_STATUS = "lvldownloader.intent.action.DOWNLOAD_STATUS";
+
+ /*
+ * Lists the states that the download manager can set on a download to
+ * notify applications of the download progress. The codes follow the HTTP
+ * families:<br> 1xx: informational<br> 2xx: success<br> 3xx: redirects (not
+ * used by the download manager)<br> 4xx: client errors<br> 5xx: server
+ * errors
+ */
+
+ /**
+ * Returns whether the status is informational (i.e. 1xx).
+ */
+ public static boolean isStatusInformational(int status) {
+ return (status >= 100 && status < 200);
+ }
+
+ /**
+ * Returns whether the status is a success (i.e. 2xx).
+ */
+ public static boolean isStatusSuccess(int status) {
+ return (status >= 200 && status < 300);
+ }
+
+ /**
+ * Returns whether the status is an error (i.e. 4xx or 5xx).
+ */
+ public static boolean isStatusError(int status) {
+ return (status >= 400 && status < 600);
+ }
+
+ /**
+ * Returns whether the status is a client error (i.e. 4xx).
+ */
+ public static boolean isStatusClientError(int status) {
+ return (status >= 400 && status < 500);
+ }
+
+ /**
+ * Returns whether the status is a server error (i.e. 5xx).
+ */
+ public static boolean isStatusServerError(int status) {
+ return (status >= 500 && status < 600);
+ }
+
+ /**
+ * Returns whether the download has completed (either with success or
+ * error).
+ */
+ public static boolean isStatusCompleted(int status) {
+ return (status >= 200 && status < 300)
+ || (status >= 400 && status < 600);
+ }
+
+ /**
+ * This download hasn't stated yet
+ */
+ public static final int STATUS_PENDING = 190;
+
+ /**
+ * This download has started
+ */
+ public static final int STATUS_RUNNING = 192;
+
+ /**
+ * This download has been paused by the owning app.
+ */
+ public static final int STATUS_PAUSED_BY_APP = 193;
+
+ /**
+ * This download encountered some network error and is waiting before
+ * retrying the request.
+ */
+ public static final int STATUS_WAITING_TO_RETRY = 194;
+
+ /**
+ * This download is waiting for network connectivity to proceed.
+ */
+ public static final int STATUS_WAITING_FOR_NETWORK = 195;
+
+ /**
+ * This download is waiting for a Wi-Fi connection to proceed or for
+ * permission to download over cellular.
+ */
+ public static final int STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION = 196;
+
+ /**
+ * This download is waiting for a Wi-Fi connection to proceed.
+ */
+ public static final int STATUS_QUEUED_FOR_WIFI = 197;
+
+ /**
+ * 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.
+ *
+ * @hide
+ */
+ public static final int STATUS_SUCCESS = 200;
+
+ /**
+ * The requested URL is no longer available
+ */
+ public static final int STATUS_FORBIDDEN = 403;
+
+ /**
+ * The file was delivered incorrectly
+ */
+ public static final int STATUS_FILE_DELIVERED_INCORRECTLY = 487;
+
+ /**
+ * 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
+ *
+ * @hide
+ */
+ 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.
+ *
+ * @hide
+ */
+ 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.
+ *
+ * @hide
+ */
+ public static final int STATUS_UNHANDLED_REDIRECT = 493;
+
+ /**
+ * This download couldn't be completed because of an unspecified unhandled
+ * HTTP code.
+ *
+ * @hide
+ */
+ 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.
+ *
+ * @hide
+ */
+ public static final int STATUS_HTTP_DATA_ERROR = 495;
+
+ /**
+ * This download couldn't be completed because of an HttpException while
+ * setting up the request.
+ *
+ * @hide
+ */
+ public static final int STATUS_HTTP_EXCEPTION = 496;
+
+ /**
+ * This download couldn't be completed because there were too many
+ * redirects.
+ *
+ * @hide
+ */
+ 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.
+ *
+ * @hide
+ */
+ 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.
+ *
+ * @hide
+ */
+ public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
+
+ /**
+ * This download is allowed to run.
+ *
+ * @hide
+ */
+ public static final int CONTROL_RUN = 0;
+
+ /**
+ * This download must pause at the first opportunity.
+ *
+ * @hide
+ */
+ public static final int CONTROL_PAUSED = 1;
+
+ /**
+ * This download is visible but only shows in the notifications while it's
+ * in progress.
+ *
+ * @hide
+ */
+ public static final int VISIBILITY_VISIBLE = 0;
+
+ /**
+ * This download is visible and shows in the notifications while in progress
+ * and after completion.
+ *
+ * @hide
+ */
+ public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
+
+ /**
+ * This download doesn't show in the UI or in the notifications.
+ *
+ * @hide
+ */
+ public static final int VISIBILITY_HIDDEN = 2;
+
+ /**
+ * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
+ * {@link ConnectivityManager#TYPE_MOBILE}.
+ */
+ public static final int NETWORK_MOBILE = 1 << 0;
+
+ /**
+ * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
+ * {@link ConnectivityManager#TYPE_WIFI}.
+ */
+ public static final int NETWORK_WIFI = 1 << 1;
+
+ private final static String TEMP_EXT = ".tmp";
+
+ /**
+ * Service thread status
+ */
+ private static boolean sIsRunning;
+
+ @Override
+ public IBinder onBind(Intent paramIntent) {
+ Log.d(Constants.TAG, "Service Bound");
+ return this.mServiceMessenger.getBinder();
+ }
+
+ /**
+ * Network state.
+ */
+ private boolean mIsConnected;
+ private boolean mIsFailover;
+ private boolean mIsCellularConnection;
+ private boolean mIsRoaming;
+ private boolean mIsAtLeast3G;
+ private boolean mIsAtLeast4G;
+ private boolean mStateChanged;
+
+ /**
+ * Download state
+ */
+ private int mControl;
+ private int mStatus;
+
+ public boolean isWiFi() {
+ return mIsConnected && !mIsCellularConnection;
+ }
+
+ /**
+ * Bindings to important services
+ */
+ private ConnectivityManager mConnectivityManager;
+ private WifiManager mWifiManager;
+
+ /**
+ * Package we are downloading for (defaults to package of application)
+ */
+ private PackageInfo mPackageInfo;
+
+ /**
+ * Byte counts
+ */
+ long mBytesSoFar;
+ long mTotalLength;
+ int mFileCount;
+
+ /**
+ * Used for calculating time remaining and speed
+ */
+ long mBytesAtSample;
+ long mMillisecondsAtSample;
+ float mAverageDownloadSpeed;
+
+ /**
+ * Our binding to the network state broadcasts
+ */
+ private BroadcastReceiver mConnReceiver;
+ final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this);
+ final private Messenger mServiceMessenger = mServiceStub.getMessenger();
+ private Messenger mClientMessenger;
+ private DownloadNotification mNotification;
+ private PendingIntent mPendingIntent;
+ private PendingIntent mAlarmIntent;
+
+ /**
+ * Updates the network type based upon the type and subtype returned from
+ * the connectivity manager. Subtype is only used for cellular signals.
+ *
+ * @param type
+ * @param subType
+ */
+ private void updateNetworkType(int type, int subType) {
+ switch (type) {
+ case ConnectivityManager.TYPE_WIFI:
+ case ConnectivityManager.TYPE_ETHERNET:
+ case ConnectivityManager.TYPE_BLUETOOTH:
+ mIsCellularConnection = false;
+ mIsAtLeast3G = false;
+ mIsAtLeast4G = false;
+ break;
+ case ConnectivityManager.TYPE_WIMAX:
+ mIsCellularConnection = true;
+ mIsAtLeast3G = true;
+ mIsAtLeast4G = true;
+ break;
+ case ConnectivityManager.TYPE_MOBILE:
+ mIsCellularConnection = true;
+ switch (subType) {
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ mIsAtLeast3G = false;
+ mIsAtLeast4G = false;
+ break;
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ mIsAtLeast3G = true;
+ mIsAtLeast4G = false;
+ break;
+ case TelephonyManager.NETWORK_TYPE_LTE: // 4G
+ case TelephonyManager.NETWORK_TYPE_EHRPD: // 3G ++ interop
+ // with 4G
+ case TelephonyManager.NETWORK_TYPE_HSPAP: // 3G ++ but
+ // marketed as
+ // 4G
+ mIsAtLeast3G = true;
+ mIsAtLeast4G = true;
+ break;
+ default:
+ mIsCellularConnection = false;
+ mIsAtLeast3G = false;
+ mIsAtLeast4G = false;
+ }
+ }
+ }
+
+ private void updateNetworkState(NetworkInfo info) {
+ boolean isConnected = mIsConnected;
+ boolean isFailover = mIsFailover;
+ boolean isCellularConnection = mIsCellularConnection;
+ boolean isRoaming = mIsRoaming;
+ boolean isAtLeast3G = mIsAtLeast3G;
+ if (null != info) {
+ mIsRoaming = info.isRoaming();
+ mIsFailover = info.isFailover();
+ mIsConnected = info.isConnected();
+ updateNetworkType(info.getType(), info.getSubtype());
+ } else {
+ mIsRoaming = false;
+ mIsFailover = false;
+ mIsConnected = false;
+ updateNetworkType(-1, -1);
+ }
+ mStateChanged = (mStateChanged || isConnected != mIsConnected
+ || isFailover != mIsFailover
+ || isCellularConnection != mIsCellularConnection
+ || isRoaming != mIsRoaming || isAtLeast3G != mIsAtLeast3G);
+ if (Constants.LOGVV) {
+ if (mStateChanged) {
+ Log.v(LOG_TAG, "Network state changed: ");
+ Log.v(LOG_TAG, "Starting State: " +
+ (isConnected ? "Connected " : "Not Connected ") +
+ (isCellularConnection ? "Cellular " : "WiFi ") +
+ (isRoaming ? "Roaming " : "Local ") +
+ (isAtLeast3G ? "3G+ " : "<3G "));
+ Log.v(LOG_TAG, "Ending State: " +
+ (mIsConnected ? "Connected " : "Not Connected ") +
+ (mIsCellularConnection ? "Cellular " : "WiFi ") +
+ (mIsRoaming ? "Roaming " : "Local ") +
+ (mIsAtLeast3G ? "3G+ " : "<3G "));
+
+ if (isServiceRunning()) {
+ if (mIsRoaming) {
+ mStatus = STATUS_WAITING_FOR_NETWORK;
+ mControl = CONTROL_PAUSED;
+ } else if (mIsCellularConnection) {
+ DownloadsDB db = DownloadsDB.getDB(this);
+ int flags = db.getFlags();
+ if (0 == (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) {
+ mStatus = STATUS_QUEUED_FOR_WIFI;
+ mControl = CONTROL_PAUSED;
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Polls the network state, setting the flags appropriately.
+ */
+ void pollNetworkState() {
+ if (null == mConnectivityManager) {
+ mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+ if (null == mWifiManager) {
+ mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ }
+ if (mConnectivityManager == null) {
+ Log.w(Constants.TAG,
+ "couldn't get connectivity manager to poll network state");
+ } else {
+ NetworkInfo activeInfo = mConnectivityManager
+ .getActiveNetworkInfo();
+ updateNetworkState(activeInfo);
+ }
+ }
+
+ public static final int NO_DOWNLOAD_REQUIRED = 0;
+ public static final int LVL_CHECK_REQUIRED = 1;
+ public static final int DOWNLOAD_REQUIRED = 2;
+
+ public static final String EXTRA_PACKAGE_NAME = "EPN";
+ public static final String EXTRA_PENDING_INTENT = "EPI";
+ public static final String EXTRA_MESSAGE_HANDLER = "EMH";
+
+ /**
+ * Returns true if the LVL check is required
+ *
+ * @param db a downloads DB synchronized with the latest state
+ * @param pi the package info for the project
+ * @return returns true if the filenames need to be returned
+ */
+ private static boolean isLVLCheckRequired(DownloadsDB db, PackageInfo pi) {
+ // we need to update the LVL check and get a successful status to
+ // proceed
+ if (db.mVersionCode != pi.versionCode) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Careful! Only use this internally.
+ *
+ * @return whether we think the service is running
+ */
+ private static synchronized boolean isServiceRunning() {
+ return sIsRunning;
+ }
+
+ private static synchronized void setServiceRunning(boolean isRunning) {
+ sIsRunning = isRunning;
+ }
+
+ public static int startDownloadServiceIfRequired(Context context,
+ Intent intent, Class<?> serviceClass) throws NameNotFoundException {
+ final PendingIntent pendingIntent = (PendingIntent) intent
+ .getParcelableExtra(EXTRA_PENDING_INTENT);
+ return startDownloadServiceIfRequired(context, pendingIntent,
+ serviceClass);
+ }
+
+ public static int startDownloadServiceIfRequired(Context context,
+ PendingIntent pendingIntent, Class<?> serviceClass)
+ throws NameNotFoundException
+ {
+ String packageName = context.getPackageName();
+ String className = serviceClass.getName();
+
+ return startDownloadServiceIfRequired(context, pendingIntent,
+ packageName, className);
+ }
+
+ /**
+ * 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
+ * @param thisIntent
+ * @return true if the app should wait for more guidance from the
+ * downloader, false if the app can continue
+ * @throws NameNotFoundException
+ */
+ public static int startDownloadServiceIfRequired(Context context,
+ PendingIntent pendingIntent, String classPackage, String className)
+ throws NameNotFoundException {
+ // first: do we need to do an LVL update?
+ // we begin by getting our APK version from the package manager
+ final PackageInfo pi = context.getPackageManager().getPackageInfo(
+ context.getPackageName(), 0);
+
+ int status = NO_DOWNLOAD_REQUIRED;
+
+ // the database automatically reads the metadata for version code
+ // and download status when the instance is created
+ DownloadsDB db = DownloadsDB.getDB(context);
+
+ // we need to update the LVL check and get a successful status to
+ // proceed
+ if (isLVLCheckRequired(db, pi)) {
+ status = LVL_CHECK_REQUIRED;
+ }
+ // we don't have to update LVL. do we still have a download to start?
+ if (db.mStatus == 0) {
+ DownloadInfo[] infos = db.getDownloads();
+ if (null != infos) {
+ for (DownloadInfo info : infos) {
+ if (!Helpers.doesFileExist(context, info.mFileName, info.mTotalBytes, true)) {
+ status = DOWNLOAD_REQUIRED;
+ db.updateStatus(-1);
+ break;
+ }
+ }
+ }
+ } else {
+ status = DOWNLOAD_REQUIRED;
+ }
+ switch (status) {
+ case DOWNLOAD_REQUIRED:
+ case LVL_CHECK_REQUIRED:
+ Intent fileIntent = new Intent();
+ fileIntent.setClassName(classPackage, className);
+ fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
+ context.startService(fileIntent);
+ break;
+ }
+ return status;
+ }
+
+ @Override
+ public void requestAbortDownload() {
+ mControl = CONTROL_PAUSED;
+ mStatus = STATUS_CANCELED;
+ }
+
+ @Override
+ public void requestPauseDownload() {
+ mControl = CONTROL_PAUSED;
+ mStatus = STATUS_PAUSED_BY_APP;
+ }
+
+ @Override
+ public void setDownloadFlags(int flags) {
+ DownloadsDB.getDB(this).updateFlags(flags);
+ }
+
+ @Override
+ public void requestContinueDownload() {
+ if (mControl == CONTROL_PAUSED) {
+ mControl = CONTROL_RUN;
+ }
+ Intent fileIntent = new Intent(this, this.getClass());
+ fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent);
+ this.startService(fileIntent);
+ }
+
+ public abstract String getPublicKey();
+
+ public abstract byte[] getSALT();
+
+ public abstract String getAlarmReceiverClassName();
+
+ private class LVLRunnable implements Runnable {
+ LVLRunnable(Context context, PendingIntent intent) {
+ mContext = context;
+ mPendingIntent = intent;
+ }
+
+ final Context mContext;
+
+ @Override
+ public void run() {
+ setServiceRunning(true);
+ mNotification.onDownloadStateChanged(IDownloaderClient.STATE_FETCHING_URL);
+ String deviceId = Secure.getString(mContext.getContentResolver(),
+ Secure.ANDROID_ID);
+
+ final APKExpansionPolicy aep = new APKExpansionPolicy(mContext,
+ new AESObfuscator(getSALT(), mContext.getPackageName(), deviceId));
+
+ // reset our policy back to the start of the world to force a
+ // re-check
+ aep.resetPolicy();
+
+ // let's try and get the OBB file from LVL first
+ // Construct the LicenseChecker with a Policy.
+ final LicenseChecker checker = new LicenseChecker(mContext, aep,
+ getPublicKey() // Your public licensing key.
+ );
+ checker.checkAccess(new LicenseCheckerCallback() {
+
+ @Override
+ public void allow(int reason) {
+ try {
+ int count = aep.getExpansionURLCount();
+ DownloadsDB db = DownloadsDB.getDB(mContext);
+ int status = 0;
+ if (count != 0) {
+ for (int i = 0; i < count; i++) {
+ String currentFileName = aep
+ .getExpansionFileName(i);
+ if (null != currentFileName) {
+ DownloadInfo di = new DownloadInfo(i,
+ currentFileName, mContext.getPackageName());
+
+ long fileSize = aep.getExpansionFileSize(i);
+ if (handleFileUpdated(db, i, currentFileName,
+ fileSize)) {
+ status |= -1;
+ di.resetDownload();
+ di.mUri = aep.getExpansionURL(i);
+ di.mTotalBytes = fileSize;
+ di.mStatus = status;
+ db.updateDownload(di);
+ } else {
+ // we need to read the download
+ // information
+ // from
+ // the database
+ DownloadInfo dbdi = db
+ .getDownloadInfoByFileName(di.mFileName);
+ if (null == dbdi) {
+ // the file exists already and is
+ // the
+ // correct size
+ // was delivered by Market or
+ // through
+ // another mechanism
+ Log.d(LOG_TAG, "file " + di.mFileName
+ + " found. Not downloading.");
+ di.mStatus = STATUS_SUCCESS;
+ di.mTotalBytes = fileSize;
+ di.mCurrentBytes = fileSize;
+ di.mUri = aep.getExpansionURL(i);
+ db.updateDownload(di);
+ } else if (dbdi.mStatus != STATUS_SUCCESS) {
+ // we just update the URL
+ dbdi.mUri = aep.getExpansionURL(i);
+ db.updateDownload(dbdi);
+ status |= -1;
+ }
+ }
+ }
+ }
+ }
+ // first: do we need to do an LVL update?
+ // we begin by getting our APK version from the package
+ // manager
+ PackageInfo pi;
+ try {
+ pi = mContext.getPackageManager().getPackageInfo(
+ mContext.getPackageName(), 0);
+ db.updateMetadata(pi.versionCode, status);
+ Class<?> serviceClass = DownloaderService.this.getClass();
+ switch (startDownloadServiceIfRequired(mContext, mPendingIntent,
+ serviceClass)) {
+ case NO_DOWNLOAD_REQUIRED:
+ mNotification
+ .onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED);
+ break;
+ case LVL_CHECK_REQUIRED:
+ // DANGER WILL ROBINSON!
+ Log.e(LOG_TAG, "In LVL checking loop!");
+ mNotification
+ .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED);
+ throw new RuntimeException(
+ "Error with LVL checking and database integrity");
+ case DOWNLOAD_REQUIRED:
+ // do nothing. the download will notify the
+ // application
+ // when things are done
+ break;
+ }
+ } catch (NameNotFoundException e1) {
+ e1.printStackTrace();
+ throw new RuntimeException(
+ "Error with getting information from package name");
+ }
+ } finally {
+ setServiceRunning(false);
+ }
+ }
+
+ @Override
+ public void dontAllow(int reason) {
+ try
+ {
+ switch (reason) {
+ case Policy.NOT_LICENSED:
+ mNotification
+ .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED);
+ break;
+ case Policy.RETRY:
+ mNotification
+ .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL);
+ break;
+ }
+ } finally {
+ setServiceRunning(false);
+ }
+
+ }
+
+ @Override
+ public void applicationError(int errorCode) {
+ try {
+ mNotification
+ .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL);
+ } finally {
+ setServiceRunning(false);
+ }
+ }
+
+ });
+
+ }
+
+ };
+
+ /**
+ * Updates the LVL information from the server.
+ *
+ * @param context
+ */
+ public void updateLVL(final Context context) {
+ Context c = context.getApplicationContext();
+ Handler h = new Handler(c.getMainLooper());
+ h.post(new LVLRunnable(c, mPendingIntent));
+ }
+
+ /**
+ * The APK has been updated and a filename has been sent down from the
+ * Market call. If the file has the same name as the previous file, we do
+ * nothing as the file is guaranteed to be the same. If the file does not
+ * have the same name, we download it if it hasn't already been delivered by
+ * Market.
+ *
+ * @param index the index of the file from market (0 = main, 1 = patch)
+ * @param filename the name of the new file
+ * @param fileSize the size of the new file
+ * @return
+ */
+ public boolean handleFileUpdated(DownloadsDB db, int index,
+ String filename, long fileSize) {
+ DownloadInfo di = db.getDownloadInfoByFileName(filename);
+ if (null != di) {
+ String oldFile = di.mFileName;
+ // cleanup
+ if (null != oldFile) {
+ if (filename.equals(oldFile)) {
+ return false;
+ }
+
+ // remove partially downloaded file if it is there
+ String deleteFile = Helpers.generateSaveFileName(this, oldFile);
+ File f = new File(deleteFile);
+ if (f.exists())
+ f.delete();
+ }
+ }
+ return !Helpers.doesFileExist(this, filename, fileSize, true);
+ }
+
+ private void scheduleAlarm(long wakeUp) {
+ AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ if (alarms == null) {
+ Log.e(Constants.TAG, "couldn't get alarm manager");
+ return;
+ }
+
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms");
+ }
+
+ String className = getAlarmReceiverClassName();
+ Intent intent = new Intent(Constants.ACTION_RETRY);
+ intent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent);
+ intent.setClassName(this.getPackageName(),
+ className);
+ mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent,
+ PendingIntent.FLAG_ONE_SHOT);
+ alarms.set(
+ AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + wakeUp, mAlarmIntent
+ );
+ }
+
+ private void cancelAlarms() {
+ if (null != mAlarmIntent) {
+ AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ if (alarms == null) {
+ Log.e(Constants.TAG, "couldn't get alarm manager");
+ return;
+ }
+ alarms.cancel(mAlarmIntent);
+ mAlarmIntent = null;
+ }
+ }
+
+ /**
+ * We use this to track network state, such as when WiFi, Cellular, etc. is
+ * enabled when downloads are paused or in progress.
+ */
+ private class InnerBroadcastReceiver extends BroadcastReceiver {
+ final Service mService;
+
+ InnerBroadcastReceiver(Service service) {
+ mService = service;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ pollNetworkState();
+ if (mStateChanged
+ && !isServiceRunning()) {
+ Log.d(Constants.TAG, "InnerBroadcastReceiver Called");
+ Intent fileIntent = new Intent(context, mService.getClass());
+ fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent);
+ // send a new intent to the service
+ context.startService(fileIntent);
+ }
+ }
+ };
+
+ /**
+ * This is the main thread for the Downloader. This thread is responsible
+ * for queuing up downloads and other goodness.
+ */
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ setServiceRunning(true);
+ try {
+ // the database automatically reads the metadata for version code
+ // and download status when the instance is created
+ DownloadsDB db = DownloadsDB.getDB(this);
+ final PendingIntent pendingIntent = (PendingIntent) intent
+ .getParcelableExtra(EXTRA_PENDING_INTENT);
+
+ if (null != pendingIntent)
+ {
+ mNotification.setClientIntent(pendingIntent);
+ mPendingIntent = pendingIntent;
+ } else if (null != mPendingIntent) {
+ mNotification.setClientIntent(mPendingIntent);
+ } else {
+ Log.e(LOG_TAG, "Downloader started in bad state without notification intent.");
+ return;
+ }
+
+ // when the LVL check completes, a successful response will update
+ // the service
+ if (isLVLCheckRequired(db, mPackageInfo)) {
+ updateLVL(this);
+ return;
+ }
+
+ // get each download
+ DownloadInfo[] infos = db.getDownloads();
+ mBytesSoFar = 0;
+ mTotalLength = 0;
+ mFileCount = infos.length;
+ for (DownloadInfo info : infos) {
+ // We do an (simple) integrity check on each file, just to make
+ // sure
+ if (info.mStatus == STATUS_SUCCESS) {
+ // verify that the file matches the state
+ if (!Helpers.doesFileExist(this, info.mFileName, info.mTotalBytes, true)) {
+ info.mStatus = 0;
+ info.mCurrentBytes = 0;
+ }
+ }
+ // get aggregate data
+ mTotalLength += info.mTotalBytes;
+ mBytesSoFar += info.mCurrentBytes;
+ }
+
+ // loop through all downloads and fetch them
+ pollNetworkState();
+ if (null == mConnReceiver) {
+
+ /**
+ * We use this to track network state, such as when WiFi,
+ * Cellular, etc. is enabled when downloads are paused or in
+ * progress.
+ */
+ mConnReceiver = new InnerBroadcastReceiver(this);
+ IntentFilter intentFilter = new IntentFilter(
+ ConnectivityManager.CONNECTIVITY_ACTION);
+ intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ registerReceiver(mConnReceiver, intentFilter);
+ }
+
+ for (DownloadInfo info : infos) {
+ long startingCount = info.mCurrentBytes;
+
+ if (info.mStatus != STATUS_SUCCESS) {
+ DownloadThread dt = new DownloadThread(info, this, mNotification);
+ cancelAlarms();
+ scheduleAlarm(Constants.ACTIVE_THREAD_WATCHDOG);
+ dt.run();
+ cancelAlarms();
+ }
+ db.updateFromDb(info);
+ boolean setWakeWatchdog = false;
+ int notifyStatus;
+ switch (info.mStatus) {
+ case STATUS_FORBIDDEN:
+ // the URL is out of date
+ updateLVL(this);
+ return;
+ case STATUS_SUCCESS:
+ mBytesSoFar += info.mCurrentBytes - startingCount;
+ db.updateMetadata(mPackageInfo.versionCode, 0);
+ continue;
+ case STATUS_FILE_DELIVERED_INCORRECTLY:
+ // we may be on a network that is returning us a web
+ // page on redirect
+ notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE;
+ info.mCurrentBytes = 0;
+ db.updateDownload(info);
+ setWakeWatchdog = true;
+ break;
+ case STATUS_PAUSED_BY_APP:
+ notifyStatus = IDownloaderClient.STATE_PAUSED_BY_REQUEST;
+ break;
+ case STATUS_WAITING_FOR_NETWORK:
+ case STATUS_WAITING_TO_RETRY:
+ notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE;
+ setWakeWatchdog = true;
+ break;
+ case STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION:
+ case STATUS_QUEUED_FOR_WIFI:
+ // look for more detail here
+ if (null != mWifiManager) {
+ if (!mWifiManager.isWifiEnabled()) {
+ notifyStatus = IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION;
+ setWakeWatchdog = true;
+ break;
+ }
+ }
+ notifyStatus = IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION;
+ setWakeWatchdog = true;
+ break;
+ case STATUS_CANCELED:
+ notifyStatus = IDownloaderClient.STATE_FAILED_CANCELED;
+ setWakeWatchdog = true;
+ break;
+
+ case STATUS_INSUFFICIENT_SPACE_ERROR:
+ notifyStatus = IDownloaderClient.STATE_FAILED_SDCARD_FULL;
+ setWakeWatchdog = true;
+ break;
+
+ case STATUS_DEVICE_NOT_FOUND_ERROR:
+ notifyStatus = IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE;
+ setWakeWatchdog = true;
+ break;
+
+ default:
+ notifyStatus = IDownloaderClient.STATE_FAILED;
+ break;
+ }
+ if (setWakeWatchdog) {
+ scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER);
+ } else {
+ cancelAlarms();
+ }
+ // failure or pause state
+ mNotification.onDownloadStateChanged(notifyStatus);
+ return;
+ }
+
+ // all downloads complete
+ mNotification.onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED);
+ } finally {
+ setServiceRunning(false);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (null != mConnReceiver) {
+ unregisterReceiver(mConnReceiver);
+ mConnReceiver = null;
+ }
+ mServiceStub.disconnect(this);
+ super.onDestroy();
+ }
+
+ public int getNetworkAvailabilityState(DownloadsDB db) {
+ if (mIsConnected) {
+ if (!mIsCellularConnection)
+ return NETWORK_OK;
+ int flags = db.mFlags;
+ if (mIsRoaming)
+ return NETWORK_CANNOT_USE_ROAMING;
+ if (0 != (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) {
+ return NETWORK_OK;
+ } else {
+ return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR;
+ }
+ }
+ return NETWORK_NO_CONNECTION;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ try {
+ mPackageInfo = getPackageManager().getPackageInfo(
+ getPackageName(), 0);
+ ApplicationInfo ai = getApplicationInfo();
+ CharSequence applicationLabel = getPackageManager().getApplicationLabel(ai);
+ mNotification = new DownloadNotification(this, applicationLabel);
+
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Exception thrown from methods called by generateSaveFile() for any fatal
+ * error.
+ */
+ public static class GenerateSaveFileError extends Exception {
+ private static final long serialVersionUID = 3465966015408936540L;
+ int mStatus;
+ String mMessage;
+
+ public GenerateSaveFileError(int status, String message) {
+ mStatus = status;
+ mMessage = message;
+ }
+ }
+
+ /**
+ * Returns the filename (where the file should be saved) from info about a
+ * download
+ */
+ public String generateTempSaveFileName(String fileName) {
+ String path = Helpers.getSaveFilePath(this)
+ + File.separator + fileName + TEMP_EXT;
+ return path;
+ }
+
+ /**
+ * Creates a filename (where the file should be saved) from info about a
+ * download.
+ */
+ public String generateSaveFile(String filename, long filesize)
+ throws GenerateSaveFileError {
+ String path = generateTempSaveFileName(filename);
+ File expPath = new File(path);
+ if (!Helpers.isExternalMediaMounted()) {
+ Log.d(Constants.TAG, "External media not mounted: " + path);
+ throw new GenerateSaveFileError(STATUS_DEVICE_NOT_FOUND_ERROR,
+ "external media is not yet mounted");
+
+ }
+ if (expPath.exists()) {
+ Log.d(Constants.TAG, "File already exists: " + path);
+ throw new GenerateSaveFileError(STATUS_FILE_ALREADY_EXISTS_ERROR,
+ "requested destination file already exists");
+ }
+ if (Helpers.getAvailableBytes(Helpers.getFilesystemRoot(path)) < filesize) {
+ throw new GenerateSaveFileError(STATUS_INSUFFICIENT_SPACE_ERROR,
+ "insufficient space on external storage");
+ }
+ return path;
+ }
+
+ /**
+ * @return a non-localized string appropriate for logging corresponding to
+ * one of the NETWORK_* constants.
+ */
+ public String getLogMessageForNetworkError(int networkError) {
+ switch (networkError) {
+ case NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE:
+ return "download size exceeds recommended limit for mobile network";
+
+ case NETWORK_UNUSABLE_DUE_TO_SIZE:
+ return "download size exceeds limit for mobile network";
+
+ case NETWORK_NO_CONNECTION:
+ return "no network connection available";
+
+ case NETWORK_CANNOT_USE_ROAMING:
+ return "download cannot use the current network connection because it is roaming";
+
+ case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR:
+ return "download was requested to not use the current network type";
+
+ default:
+ return "unknown error with network connectivity";
+ }
+ }
+
+ public int getControl() {
+ return mControl;
+ }
+
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Calculating a moving average for the speed so we don't get jumpy
+ * calculations for time etc.
+ */
+ static private final float SMOOTHING_FACTOR = 0.005f;
+
+ public void notifyUpdateBytes(long totalBytesSoFar) {
+ long timeRemaining;
+ long currentTime = SystemClock.uptimeMillis();
+ if (0 != mMillisecondsAtSample) {
+ // we have a sample.
+ long timePassed = currentTime - mMillisecondsAtSample;
+ long bytesInSample = totalBytesSoFar - mBytesAtSample;
+ float currentSpeedSample = (float) bytesInSample / (float) timePassed;
+ if (0 != mAverageDownloadSpeed) {
+ mAverageDownloadSpeed = SMOOTHING_FACTOR * currentSpeedSample
+ + (1 - SMOOTHING_FACTOR) * mAverageDownloadSpeed;
+ } else {
+ mAverageDownloadSpeed = currentSpeedSample;
+ }
+ timeRemaining = (long) ((mTotalLength - totalBytesSoFar) / mAverageDownloadSpeed);
+ } else {
+ timeRemaining = -1;
+ }
+ mMillisecondsAtSample = currentTime;
+ mBytesAtSample = totalBytesSoFar;
+ mNotification.onDownloadProgress(
+ new DownloadProgressInfo(mTotalLength,
+ totalBytesSoFar,
+ timeRemaining,
+ mAverageDownloadSpeed)
+ );
+
+ }
+
+ @Override
+ protected boolean shouldStop() {
+ // the database automatically reads the metadata for version code
+ // and download status when the instance is created
+ DownloadsDB db = DownloadsDB.getDB(this);
+ if (db.mStatus == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void requestDownloadStatus() {
+ mNotification.resendState();
+ }
+
+ @Override
+ public void onClientUpdated(Messenger clientMessenger) {
+ this.mClientMessenger = clientMessenger;
+ mNotification.setMessenger(mClientMessenger);
+ }
+
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java
new file mode 100755
index 0000000000..250299c400
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java
@@ -0,0 +1,510 @@
+/*
+ * 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.impl;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDoneException;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteStatement;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+public class DownloadsDB {
+ private static final String DATABASE_NAME = "DownloadsDB";
+ private static final int DATABASE_VERSION = 7;
+ public static final String LOG_TAG = DownloadsDB.class.getName();
+ final SQLiteOpenHelper mHelper;
+ SQLiteStatement mGetDownloadByIndex;
+ SQLiteStatement mUpdateCurrentBytes;
+ private static DownloadsDB mDownloadsDB;
+ long mMetadataRowID = -1;
+ int mVersionCode = -1;
+ int mStatus = -1;
+ int mFlags;
+
+ static public synchronized DownloadsDB getDB(Context paramContext) {
+ if (null == mDownloadsDB) {
+ return new DownloadsDB(paramContext);
+ }
+ return mDownloadsDB;
+ }
+
+ private SQLiteStatement getDownloadByIndexStatement() {
+ if (null == mGetDownloadByIndex) {
+ mGetDownloadByIndex = mHelper.getReadableDatabase().compileStatement(
+ "SELECT " + BaseColumns._ID + " FROM "
+ + DownloadColumns.TABLE_NAME + " WHERE "
+ + DownloadColumns.INDEX + " = ?");
+ }
+ return mGetDownloadByIndex;
+ }
+
+ private SQLiteStatement getUpdateCurrentBytesStatement() {
+ if (null == mUpdateCurrentBytes) {
+ mUpdateCurrentBytes = mHelper.getReadableDatabase().compileStatement(
+ "UPDATE " + DownloadColumns.TABLE_NAME + " SET " + DownloadColumns.CURRENTBYTES
+ + " = ?" +
+ " WHERE " + DownloadColumns.INDEX + " = ?");
+ }
+ return mUpdateCurrentBytes;
+ }
+
+ private DownloadsDB(Context paramContext) {
+ this.mHelper = new DownloadsContentDBHelper(paramContext);
+ final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
+ // Query for the version code, the row ID of the metadata (for future
+ // updating) the status and the flags
+ Cursor cur = sqldb.rawQuery("SELECT " +
+ MetadataColumns.APKVERSION + "," +
+ BaseColumns._ID + "," +
+ MetadataColumns.DOWNLOAD_STATUS + "," +
+ MetadataColumns.FLAGS +
+ " FROM "
+ + MetadataColumns.TABLE_NAME + " LIMIT 1", null);
+ if (null != cur && cur.moveToFirst()) {
+ mVersionCode = cur.getInt(0);
+ mMetadataRowID = cur.getLong(1);
+ mStatus = cur.getInt(2);
+ mFlags = cur.getInt(3);
+ cur.close();
+ }
+ mDownloadsDB = this;
+ }
+
+ protected DownloadInfo getDownloadInfoByFileName(String fileName) {
+ final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
+ Cursor itemcur = null;
+ try {
+ itemcur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION,
+ DownloadColumns.FILENAME + " = ?",
+ new String[] {
+ fileName
+ }, null, null, null);
+ if (null != itemcur && itemcur.moveToFirst()) {
+ return getDownloadInfoFromCursor(itemcur);
+ }
+ } finally {
+ if (null != itemcur)
+ itemcur.close();
+ }
+ return null;
+ }
+
+ public long getIDForDownloadInfo(final DownloadInfo di) {
+ return getIDByIndex(di.mIndex);
+ }
+
+ public long getIDByIndex(int index) {
+ SQLiteStatement downloadByIndex = getDownloadByIndexStatement();
+ downloadByIndex.clearBindings();
+ downloadByIndex.bindLong(1, index);
+ try {
+ return downloadByIndex.simpleQueryForLong();
+ } catch (SQLiteDoneException e) {
+ return -1;
+ }
+ }
+
+ public void updateDownloadCurrentBytes(final DownloadInfo di) {
+ SQLiteStatement downloadCurrentBytes = getUpdateCurrentBytesStatement();
+ downloadCurrentBytes.clearBindings();
+ downloadCurrentBytes.bindLong(1, di.mCurrentBytes);
+ downloadCurrentBytes.bindLong(2, di.mIndex);
+ downloadCurrentBytes.execute();
+ }
+
+ public void close() {
+ this.mHelper.close();
+ }
+
+ protected static class DownloadsContentDBHelper extends SQLiteOpenHelper {
+ DownloadsContentDBHelper(Context paramContext) {
+ super(paramContext, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ private String createTableQueryFromArray(String paramString,
+ String[][] paramArrayOfString) {
+ StringBuilder localStringBuilder = new StringBuilder();
+ localStringBuilder.append("CREATE TABLE ");
+ localStringBuilder.append(paramString);
+ localStringBuilder.append(" (");
+ int i = paramArrayOfString.length;
+ for (int j = 0;; j++) {
+ if (j >= i) {
+ localStringBuilder
+ .setLength(localStringBuilder.length() - 1);
+ localStringBuilder.append(");");
+ return localStringBuilder.toString();
+ }
+ String[] arrayOfString = paramArrayOfString[j];
+ localStringBuilder.append(' ');
+ localStringBuilder.append(arrayOfString[0]);
+ localStringBuilder.append(' ');
+ localStringBuilder.append(arrayOfString[1]);
+ localStringBuilder.append(',');
+ }
+ }
+
+ /**
+ * These two arrays must match and have the same order. For every Schema
+ * there must be a corresponding table name.
+ */
+ static final private String[][][] sSchemas = {
+ DownloadColumns.SCHEMA, MetadataColumns.SCHEMA
+ };
+
+ static final private String[] sTables = {
+ DownloadColumns.TABLE_NAME, MetadataColumns.TABLE_NAME
+ };
+
+ /**
+ * Goes through all of the tables in sTables and drops each table if it
+ * exists. Altered to no longer make use of reflection.
+ */
+ private void dropTables(SQLiteDatabase paramSQLiteDatabase) {
+ for (String table : sTables) {
+ try {
+ paramSQLiteDatabase.execSQL("DROP TABLE IF EXISTS " + table);
+ } catch (Exception localException) {
+ localException.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Goes through all of the tables in sTables and creates a database with
+ * the corresponding schema described in sSchemas. Altered to no longer
+ * make use of reflection.
+ */
+ public void onCreate(SQLiteDatabase paramSQLiteDatabase) {
+ int numSchemas = sSchemas.length;
+ for (int i = 0; i < numSchemas; i++) {
+ try {
+ String[][] schema = (String[][]) sSchemas[i];
+ paramSQLiteDatabase.execSQL(createTableQueryFromArray(
+ sTables[i], schema));
+ } catch (Exception localException) {
+ while (true)
+ localException.printStackTrace();
+ }
+ }
+ }
+
+ public void onUpgrade(SQLiteDatabase paramSQLiteDatabase,
+ int paramInt1, int paramInt2) {
+ Log.w(DownloadsContentDBHelper.class.getName(),
+ "Upgrading database from version " + paramInt1 + " to "
+ + paramInt2 + ", which will destroy all old data");
+ dropTables(paramSQLiteDatabase);
+ onCreate(paramSQLiteDatabase);
+ }
+ }
+
+ public static class MetadataColumns implements BaseColumns {
+ public static final String APKVERSION = "APKVERSION";
+ public static final String DOWNLOAD_STATUS = "DOWNLOADSTATUS";
+ public static final String FLAGS = "DOWNLOADFLAGS";
+
+ public static final String[][] SCHEMA = {
+ {
+ BaseColumns._ID, "INTEGER PRIMARY KEY"
+ },
+ {
+ APKVERSION, "INTEGER"
+ }, {
+ DOWNLOAD_STATUS, "INTEGER"
+ },
+ {
+ FLAGS, "INTEGER"
+ }
+ };
+ public static final String TABLE_NAME = "MetadataColumns";
+ public static final String _ID = "MetadataColumns._id";
+ }
+
+ public static class DownloadColumns implements BaseColumns {
+ public static final String INDEX = "FILEIDX";
+ public static final String URI = "URI";
+ public static final String FILENAME = "FN";
+ public static final String ETAG = "ETAG";
+
+ public static final String TOTALBYTES = "TOTALBYTES";
+ public static final String CURRENTBYTES = "CURRENTBYTES";
+ public static final String LASTMOD = "LASTMOD";
+
+ public static final String STATUS = "STATUS";
+ public static final String CONTROL = "CONTROL";
+ public static final String NUM_FAILED = "FAILCOUNT";
+ public static final String RETRY_AFTER = "RETRYAFTER";
+ public static final String REDIRECT_COUNT = "REDIRECTCOUNT";
+
+ public static final String[][] SCHEMA = {
+ {
+ BaseColumns._ID, "INTEGER PRIMARY KEY"
+ },
+ {
+ INDEX, "INTEGER UNIQUE"
+ }, {
+ URI, "TEXT"
+ },
+ {
+ FILENAME, "TEXT UNIQUE"
+ }, {
+ ETAG, "TEXT"
+ },
+ {
+ TOTALBYTES, "INTEGER"
+ }, {
+ CURRENTBYTES, "INTEGER"
+ },
+ {
+ LASTMOD, "INTEGER"
+ }, {
+ STATUS, "INTEGER"
+ },
+ {
+ CONTROL, "INTEGER"
+ }, {
+ NUM_FAILED, "INTEGER"
+ },
+ {
+ RETRY_AFTER, "INTEGER"
+ }, {
+ REDIRECT_COUNT, "INTEGER"
+ }
+ };
+ public static final String TABLE_NAME = "DownloadColumns";
+ public static final String _ID = "DownloadColumns._id";
+ }
+
+ private static final String[] DC_PROJECTION = {
+ DownloadColumns.FILENAME,
+ DownloadColumns.URI, DownloadColumns.ETAG,
+ DownloadColumns.TOTALBYTES, DownloadColumns.CURRENTBYTES,
+ DownloadColumns.LASTMOD, DownloadColumns.STATUS,
+ DownloadColumns.CONTROL, DownloadColumns.NUM_FAILED,
+ DownloadColumns.RETRY_AFTER, DownloadColumns.REDIRECT_COUNT,
+ DownloadColumns.INDEX
+ };
+
+ private static final int FILENAME_IDX = 0;
+ private static final int URI_IDX = 1;
+ private static final int ETAG_IDX = 2;
+ private static final int TOTALBYTES_IDX = 3;
+ private static final int CURRENTBYTES_IDX = 4;
+ private static final int LASTMOD_IDX = 5;
+ private static final int STATUS_IDX = 6;
+ private static final int CONTROL_IDX = 7;
+ private static final int NUM_FAILED_IDX = 8;
+ private static final int RETRY_AFTER_IDX = 9;
+ private static final int REDIRECT_COUNT_IDX = 10;
+ private static final int INDEX_IDX = 11;
+
+ /**
+ * This function will add a new file to the database if it does not exist.
+ *
+ * @param di DownloadInfo that we wish to store
+ * @return the row id of the record to be updated/inserted, or -1
+ */
+ public boolean updateDownload(DownloadInfo di) {
+ ContentValues cv = new ContentValues();
+ cv.put(DownloadColumns.INDEX, di.mIndex);
+ cv.put(DownloadColumns.FILENAME, di.mFileName);
+ cv.put(DownloadColumns.URI, di.mUri);
+ cv.put(DownloadColumns.ETAG, di.mETag);
+ cv.put(DownloadColumns.TOTALBYTES, di.mTotalBytes);
+ cv.put(DownloadColumns.CURRENTBYTES, di.mCurrentBytes);
+ cv.put(DownloadColumns.LASTMOD, di.mLastMod);
+ cv.put(DownloadColumns.STATUS, di.mStatus);
+ cv.put(DownloadColumns.CONTROL, di.mControl);
+ cv.put(DownloadColumns.NUM_FAILED, di.mNumFailed);
+ cv.put(DownloadColumns.RETRY_AFTER, di.mRetryAfter);
+ cv.put(DownloadColumns.REDIRECT_COUNT, di.mRedirectCount);
+ return updateDownload(di, cv);
+ }
+
+ public boolean updateDownload(DownloadInfo di, ContentValues cv) {
+ long id = di == null ? -1 : getIDForDownloadInfo(di);
+ try {
+ final SQLiteDatabase sqldb = mHelper.getWritableDatabase();
+ if (id != -1) {
+ if (1 != sqldb.update(DownloadColumns.TABLE_NAME,
+ cv, DownloadColumns._ID + " = " + id, null)) {
+ return false;
+ }
+ } else {
+ return -1 != sqldb.insert(DownloadColumns.TABLE_NAME,
+ DownloadColumns.URI, cv);
+ }
+ } catch (android.database.sqlite.SQLiteException ex) {
+ ex.printStackTrace();
+ }
+ return false;
+ }
+
+ public int getLastCheckedVersionCode() {
+ return mVersionCode;
+ }
+
+ public boolean isDownloadRequired() {
+ final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
+ Cursor cur = sqldb.rawQuery("SELECT Count(*) FROM "
+ + DownloadColumns.TABLE_NAME + " WHERE "
+ + DownloadColumns.STATUS + " <> 0", null);
+ try {
+ if (null != cur && cur.moveToFirst()) {
+ return 0 == cur.getInt(0);
+ }
+ } finally {
+ if (null != cur)
+ cur.close();
+ }
+ return true;
+ }
+
+ public int getFlags() {
+ return mFlags;
+ }
+
+ public boolean updateFlags(int flags) {
+ if (mFlags != flags) {
+ ContentValues cv = new ContentValues();
+ cv.put(MetadataColumns.FLAGS, flags);
+ if (updateMetadata(cv)) {
+ mFlags = flags;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ };
+
+ public boolean updateStatus(int status) {
+ if (mStatus != status) {
+ ContentValues cv = new ContentValues();
+ cv.put(MetadataColumns.DOWNLOAD_STATUS, status);
+ if (updateMetadata(cv)) {
+ mStatus = status;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ };
+
+ public boolean updateMetadata(ContentValues cv) {
+ final SQLiteDatabase sqldb = mHelper.getWritableDatabase();
+ if (-1 == this.mMetadataRowID) {
+ long newID = sqldb.insert(MetadataColumns.TABLE_NAME,
+ MetadataColumns.APKVERSION, cv);
+ if (-1 == newID)
+ return false;
+ mMetadataRowID = newID;
+ } else {
+ if (0 == sqldb.update(MetadataColumns.TABLE_NAME, cv,
+ BaseColumns._ID + " = " + mMetadataRowID, null))
+ return false;
+ }
+ return true;
+ }
+
+ public boolean updateMetadata(int apkVersion, int downloadStatus) {
+ ContentValues cv = new ContentValues();
+ cv.put(MetadataColumns.APKVERSION, apkVersion);
+ cv.put(MetadataColumns.DOWNLOAD_STATUS, downloadStatus);
+ if (updateMetadata(cv)) {
+ mVersionCode = apkVersion;
+ mStatus = downloadStatus;
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ public boolean updateFromDb(DownloadInfo di) {
+ final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
+ Cursor cur = null;
+ try {
+ cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION,
+ DownloadColumns.FILENAME + "= ?",
+ new String[] {
+ di.mFileName
+ }, null, null, null);
+ if (null != cur && cur.moveToFirst()) {
+ setDownloadInfoFromCursor(di, cur);
+ return true;
+ }
+ return false;
+ } finally {
+ if (null != cur) {
+ cur.close();
+ }
+ }
+ }
+
+ public void setDownloadInfoFromCursor(DownloadInfo di, Cursor cur) {
+ di.mUri = cur.getString(URI_IDX);
+ di.mETag = cur.getString(ETAG_IDX);
+ di.mTotalBytes = cur.getLong(TOTALBYTES_IDX);
+ di.mCurrentBytes = cur.getLong(CURRENTBYTES_IDX);
+ di.mLastMod = cur.getLong(LASTMOD_IDX);
+ di.mStatus = cur.getInt(STATUS_IDX);
+ di.mControl = cur.getInt(CONTROL_IDX);
+ di.mNumFailed = cur.getInt(NUM_FAILED_IDX);
+ di.mRetryAfter = cur.getInt(RETRY_AFTER_IDX);
+ di.mRedirectCount = cur.getInt(REDIRECT_COUNT_IDX);
+ }
+
+ public DownloadInfo getDownloadInfoFromCursor(Cursor cur) {
+ DownloadInfo di = new DownloadInfo(cur.getInt(INDEX_IDX),
+ cur.getString(FILENAME_IDX), this.getClass().getPackage()
+ .getName());
+ setDownloadInfoFromCursor(di, cur);
+ return di;
+ }
+
+ public DownloadInfo[] getDownloads() {
+ final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
+ Cursor cur = null;
+ try {
+ cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, null,
+ null, null, null, null);
+ if (null != cur && cur.moveToFirst()) {
+ DownloadInfo[] retInfos = new DownloadInfo[cur.getCount()];
+ int idx = 0;
+ do {
+ DownloadInfo di = getDownloadInfoFromCursor(cur);
+ retInfos[idx++] = di;
+ } while (cur.moveToNext());
+ return retInfos;
+ }
+ return null;
+ } finally {
+ if (null != cur) {
+ cur.close();
+ }
+ }
+ }
+
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java
new file mode 100644
index 0000000000..3f440e9893
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java
@@ -0,0 +1,200 @@
+/*
+ * 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.impl;
+
+import android.text.format.Time;
+
+import java.util.Calendar;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper for parsing an HTTP date.
+ */
+public final class HttpDateTime {
+
+ /*
+ * Regular expression for parsing HTTP-date. Wdy, DD Mon YYYY HH:MM:SS GMT
+ * RFC 822, updated by RFC 1123 Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850,
+ * obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format
+ * with following variations Wdy, DD-Mon-YYYY HH:MM:SS GMT Wdy, (SP)D Mon
+ * YYYY HH:MM:SS GMT Wdy,DD Mon YYYY HH:MM:SS GMT Wdy, DD-Mon-YY HH:MM:SS
+ * GMT Wdy, DD Mon YYYY HH:MM:SS -HHMM Wdy, DD Mon YYYY HH:MM:SS Wdy Mon
+ * (SP)D HH:MM:SS YYYY Wdy Mon DD HH:MM:SS YYYY GMT HH can be H if the first
+ * digit is zero. Mon can be the full name of the month.
+ */
+ private static final String HTTP_DATE_RFC_REGEXP =
+ "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
+ + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
+
+ private static final String HTTP_DATE_ANSIC_REGEXP =
+ "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
+ + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
+
+ /**
+ * The compiled version of the HTTP-date regular expressions.
+ */
+ private static final Pattern HTTP_DATE_RFC_PATTERN =
+ Pattern.compile(HTTP_DATE_RFC_REGEXP);
+ private static final Pattern HTTP_DATE_ANSIC_PATTERN =
+ Pattern.compile(HTTP_DATE_ANSIC_REGEXP);
+
+ private static class TimeOfDay {
+ TimeOfDay(int h, int m, int s) {
+ this.hour = h;
+ this.minute = m;
+ this.second = s;
+ }
+
+ int hour;
+ int minute;
+ int second;
+ }
+
+ public static long parse(String timeString)
+ throws IllegalArgumentException {
+
+ int date = 1;
+ int month = Calendar.JANUARY;
+ int year = 1970;
+ TimeOfDay timeOfDay;
+
+ Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString);
+ if (rfcMatcher.find()) {
+ date = getDate(rfcMatcher.group(1));
+ month = getMonth(rfcMatcher.group(2));
+ year = getYear(rfcMatcher.group(3));
+ timeOfDay = getTime(rfcMatcher.group(4));
+ } else {
+ Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString);
+ if (ansicMatcher.find()) {
+ month = getMonth(ansicMatcher.group(1));
+ date = getDate(ansicMatcher.group(2));
+ timeOfDay = getTime(ansicMatcher.group(3));
+ year = getYear(ansicMatcher.group(4));
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ // FIXME: Y2038 BUG!
+ if (year >= 2038) {
+ year = 2038;
+ month = Calendar.JANUARY;
+ date = 1;
+ }
+
+ Time time = new Time(Time.TIMEZONE_UTC);
+ time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date,
+ month, year);
+ return time.toMillis(false /* use isDst */);
+ }
+
+ private static int getDate(String dateString) {
+ if (dateString.length() == 2) {
+ return (dateString.charAt(0) - '0') * 10
+ + (dateString.charAt(1) - '0');
+ } else {
+ return (dateString.charAt(0) - '0');
+ }
+ }
+
+ /*
+ * jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0
+ * + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20
+ * + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19
+ * = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9
+ */
+ private static int getMonth(String monthString) {
+ int hash = Character.toLowerCase(monthString.charAt(0)) +
+ Character.toLowerCase(monthString.charAt(1)) +
+ Character.toLowerCase(monthString.charAt(2)) - 3 * 'a';
+ switch (hash) {
+ case 22:
+ return Calendar.JANUARY;
+ case 10:
+ return Calendar.FEBRUARY;
+ case 29:
+ return Calendar.MARCH;
+ case 32:
+ return Calendar.APRIL;
+ case 36:
+ return Calendar.MAY;
+ case 42:
+ return Calendar.JUNE;
+ case 40:
+ return Calendar.JULY;
+ case 26:
+ return Calendar.AUGUST;
+ case 37:
+ return Calendar.SEPTEMBER;
+ case 35:
+ return Calendar.OCTOBER;
+ case 48:
+ return Calendar.NOVEMBER;
+ case 9:
+ return Calendar.DECEMBER;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ private static int getYear(String yearString) {
+ if (yearString.length() == 2) {
+ int year = (yearString.charAt(0) - '0') * 10
+ + (yearString.charAt(1) - '0');
+ if (year >= 70) {
+ return year + 1900;
+ } else {
+ return year + 2000;
+ }
+ } else if (yearString.length() == 3) {
+ // According to RFC 2822, three digit years should be added to 1900.
+ int year = (yearString.charAt(0) - '0') * 100
+ + (yearString.charAt(1) - '0') * 10
+ + (yearString.charAt(2) - '0');
+ return year + 1900;
+ } else if (yearString.length() == 4) {
+ return (yearString.charAt(0) - '0') * 1000
+ + (yearString.charAt(1) - '0') * 100
+ + (yearString.charAt(2) - '0') * 10
+ + (yearString.charAt(3) - '0');
+ } else {
+ return 1970;
+ }
+ }
+
+ private static TimeOfDay getTime(String timeString) {
+ // HH might be H
+ int i = 0;
+ int hour = timeString.charAt(i++) - '0';
+ if (timeString.charAt(i) != ':')
+ hour = hour * 10 + (timeString.charAt(i++) - '0');
+ // Skip ':'
+ i++;
+
+ int minute = (timeString.charAt(i++) - '0') * 10
+ + (timeString.charAt(i++) - '0');
+ // Skip ':'
+ i++;
+
+ int second = (timeString.charAt(i++) - '0') * 10
+ + (timeString.charAt(i++) - '0');
+
+ return new TimeOfDay(hour, minute, second);
+ }
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java
new file mode 100644
index 0000000000..e736603e2a
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java
@@ -0,0 +1,101 @@
+/*
+ * 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.impl;
+
+import com.android.vending.expansion.downloader.R;
+import com.google.android.vending.expansion.downloader.Helpers;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+
+public class V14CustomNotification implements DownloadNotification.ICustomNotification {
+
+ CharSequence mTitle;
+ CharSequence mTicker;
+ int mIcon;
+ long mTotalKB = -1;
+ long mCurrentKB = -1;
+ long mTimeRemaining;
+ PendingIntent mPendingIntent;
+
+ @Override
+ public void setIcon(int icon) {
+ mIcon = icon;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ }
+
+ @Override
+ public void setTotalBytes(long totalBytes) {
+ mTotalKB = totalBytes;
+ }
+
+ @Override
+ public void setCurrentBytes(long currentBytes) {
+ mCurrentKB = currentBytes;
+ }
+
+ void setProgress(Notification.Builder builder) {
+
+ }
+
+ @Override
+ public Notification updateNotification(Context c) {
+ Notification.Builder builder = new Notification.Builder(c);
+ builder.setContentTitle(mTitle);
+ if (mTotalKB > 0 && -1 != mCurrentKB) {
+ builder.setProgress((int) (mTotalKB >> 8), (int) (mCurrentKB >> 8), false);
+ } else {
+ builder.setProgress(0, 0, true);
+ }
+ builder.setContentText(Helpers.getDownloadProgressString(mCurrentKB, mTotalKB));
+ builder.setContentInfo(c.getString(R.string.time_remaining_notification,
+ Helpers.getTimeRemaining(mTimeRemaining)));
+ if (mIcon != 0) {
+ builder.setSmallIcon(mIcon);
+ } else {
+ int iconResource = android.R.drawable.stat_sys_download;
+ builder.setSmallIcon(iconResource);
+ }
+ builder.setOngoing(true);
+ builder.setTicker(mTicker);
+ builder.setContentIntent(mPendingIntent);
+ builder.setOnlyAlertOnce(true);
+
+ return builder.getNotification();
+ }
+
+ @Override
+ public void setPendingIntent(PendingIntent contentIntent) {
+ mPendingIntent = contentIntent;
+ }
+
+ @Override
+ public void setTicker(CharSequence ticker) {
+ mTicker = ticker;
+ }
+
+ @Override
+ public void setTimeRemaining(long timeRemaining) {
+ mTimeRemaining = timeRemaining;
+ }
+
+}
diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java
new file mode 100644
index 0000000000..e3666e05b9
--- /dev/null
+++ b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java
@@ -0,0 +1,116 @@
+/*
+ * 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.impl;
+
+import com.android.vending.expansion.downloader.R;
+import com.google.android.vending.expansion.downloader.Helpers;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.BitmapFactory;
+import android.view.View;
+import android.widget.RemoteViews;
+
+public class V3CustomNotification implements DownloadNotification.ICustomNotification {
+
+ CharSequence mTitle;
+ CharSequence mTicker;
+ int mIcon;
+ long mTotalBytes = -1;
+ long mCurrentBytes = -1;
+ long mTimeRemaining;
+ PendingIntent mPendingIntent;
+ Notification mNotification = new Notification();
+
+ @Override
+ public void setIcon(int icon) {
+ mIcon = icon;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ }
+
+ @Override
+ public void setTotalBytes(long totalBytes) {
+ mTotalBytes = totalBytes;
+ }
+
+ @Override
+ public void setCurrentBytes(long currentBytes) {
+ mCurrentBytes = currentBytes;
+ }
+
+ @Override
+ public Notification updateNotification(Context c) {
+ Notification n = mNotification;
+
+ n.icon = mIcon;
+
+ n.flags |= Notification.FLAG_ONGOING_EVENT;
+
+ if (android.os.Build.VERSION.SDK_INT > 10) {
+ n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; // only matters for
+ // Honeycomb
+ }
+
+ // Build the RemoteView object
+ RemoteViews expandedView = new RemoteViews(
+ c.getPackageName(),
+ R.layout.status_bar_ongoing_event_progress_bar);
+
+ expandedView.setTextViewText(R.id.title, mTitle);
+ // look at strings
+ expandedView.setViewVisibility(R.id.description, View.VISIBLE);
+ expandedView.setTextViewText(R.id.description,
+ Helpers.getDownloadProgressString(mCurrentBytes, mTotalBytes));
+ expandedView.setViewVisibility(R.id.progress_bar_frame, View.VISIBLE);
+ expandedView.setProgressBar(R.id.progress_bar,
+ (int) (mTotalBytes >> 8),
+ (int) (mCurrentBytes >> 8),
+ mTotalBytes <= 0);
+ expandedView.setViewVisibility(R.id.time_remaining, View.VISIBLE);
+ expandedView.setTextViewText(
+ R.id.time_remaining,
+ c.getString(R.string.time_remaining_notification,
+ Helpers.getTimeRemaining(mTimeRemaining)));
+ expandedView.setTextViewText(R.id.progress_text,
+ Helpers.getDownloadProgressPercent(mCurrentBytes, mTotalBytes));
+ expandedView.setImageViewResource(R.id.appIcon, mIcon);
+ n.contentView = expandedView;
+ n.contentIntent = mPendingIntent;
+ return n;
+ }
+
+ @Override
+ public void setPendingIntent(PendingIntent contentIntent) {
+ mPendingIntent = contentIntent;
+ }
+
+ @Override
+ public void setTicker(CharSequence ticker) {
+ mTicker = ticker;
+ }
+
+ @Override
+ public void setTimeRemaining(long timeRemaining) {
+ mTimeRemaining = timeRemaining;
+ }
+
+}
diff --git a/platform/android/libs/play_licensing/AndroidManifest.xml b/platform/android/libs/play_licensing/AndroidManifest.xml
new file mode 100644
index 0000000000..c7849130c3
--- /dev/null
+++ b/platform/android/libs/play_licensing/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.vending.licensing"
+ android:versionCode="2"
+ android:versionName="1.5">
+ <!-- Devices >= 3 have version of Android Market that supports licensing. -->
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="15" />
+ <!-- Required permission to check licensing. -->
+ <uses-permission android:name="com.android.vending.CHECK_LICENSE" />
+</manifest>
diff --git a/platform/android/libs/play_licensing/aidl/ILicenseResultListener.aidl b/platform/android/libs/play_licensing/aidl/ILicenseResultListener.aidl
new file mode 100644
index 0000000000..c816558afc
--- /dev/null
+++ b/platform/android/libs/play_licensing/aidl/ILicenseResultListener.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+// Android library projects do not yet support AIDL, so this has been
+// precompiled into the src directory.
+oneway interface ILicenseResultListener {
+ void verifyLicense(int responseCode, String signedData, String signature);
+}
diff --git a/platform/android/libs/play_licensing/aidl/ILicensingService.aidl b/platform/android/libs/play_licensing/aidl/ILicensingService.aidl
new file mode 100644
index 0000000000..664510ce0c
--- /dev/null
+++ b/platform/android/libs/play_licensing/aidl/ILicensingService.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+import com.android.vending.licensing.ILicenseResultListener;
+
+// Android library projects do not yet support AIDL, so this has been
+// precompiled into the src directory.
+oneway interface ILicensingService {
+ void checkLicense(long nonce, String packageName, in ILicenseResultListener listener);
+}
diff --git a/platform/android/libs/play_licensing/build.xml b/platform/android/libs/play_licensing/build.xml
new file mode 100644
index 0000000000..0e800d6b9b
--- /dev/null
+++ b/platform/android/libs/play_licensing/build.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="play_licensing" 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>
diff --git a/platform/android/libs/play_licensing/proguard-project.txt b/platform/android/libs/play_licensing/proguard-project.txt
new file mode 100644
index 0000000000..f2fe1559a2
--- /dev/null
+++ b/platform/android/libs/play_licensing/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 *;
+#}
diff --git a/platform/android/libs/play_licensing/project.properties b/platform/android/libs/play_licensing/project.properties
new file mode 100644
index 0000000000..f28bc833e1
--- /dev/null
+++ b/platform/android/libs/play_licensing/project.properties
@@ -0,0 +1,12 @@
+# 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.
+
+android.library=true
+# Project target.
+target=android-15
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/AESObfuscator.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/AESObfuscator.java
new file mode 100644
index 0000000000..ee12c68deb
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/AESObfuscator.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+import com.google.android.vending.licensing.util.Base64;
+import com.google.android.vending.licensing.util.Base64DecoderException;
+
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * An Obfuscator that uses AES to encrypt data.
+ */
+public class AESObfuscator implements Obfuscator {
+ private static final String UTF8 = "UTF-8";
+ private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC";
+ private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
+ private static final byte[] IV =
+ { 16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74 };
+ private static final String header = "com.android.vending.licensing.AESObfuscator-1|";
+
+ private Cipher mEncryptor;
+ private Cipher mDecryptor;
+
+ /**
+ * @param salt an array of random bytes to use for each (un)obfuscation
+ * @param applicationId application identifier, e.g. the package name
+ * @param deviceId device identifier. Use as many sources as possible to
+ * create this unique identifier.
+ */
+ public AESObfuscator(byte[] salt, String applicationId, String deviceId) {
+ try {
+ SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM);
+ KeySpec keySpec =
+ new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256);
+ SecretKey tmp = factory.generateSecret(keySpec);
+ SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
+ mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM);
+ mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV));
+ mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM);
+ mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV));
+ } catch (GeneralSecurityException e) {
+ // This can't happen on a compatible Android device.
+ throw new RuntimeException("Invalid environment", e);
+ }
+ }
+
+ public String obfuscate(String original, String key) {
+ if (original == null) {
+ return null;
+ }
+ try {
+ // Header is appended as an integrity check
+ return Base64.encode(mEncryptor.doFinal((header + key + original).getBytes(UTF8)));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Invalid environment", e);
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException("Invalid environment", e);
+ }
+ }
+
+ public String unobfuscate(String obfuscated, String key) throws ValidationException {
+ if (obfuscated == null) {
+ return null;
+ }
+ try {
+ String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8);
+ // Check for presence of header. This serves as a final integrity check, for cases
+ // where the block size is correct during decryption.
+ int headerIndex = result.indexOf(header+key);
+ if (headerIndex != 0) {
+ throw new ValidationException("Header not found (invalid data or key)" + ":" +
+ obfuscated);
+ }
+ return result.substring(header.length()+key.length(), result.length());
+ } catch (Base64DecoderException e) {
+ throw new ValidationException(e.getMessage() + ":" + obfuscated);
+ } catch (IllegalBlockSizeException e) {
+ throw new ValidationException(e.getMessage() + ":" + obfuscated);
+ } catch (BadPaddingException e) {
+ throw new ValidationException(e.getMessage() + ":" + obfuscated);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Invalid environment", e);
+ }
+ }
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/APKExpansionPolicy.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/APKExpansionPolicy.java
new file mode 100644
index 0000000000..17cc7a7cfd
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/APKExpansionPolicy.java
@@ -0,0 +1,397 @@
+
+package com.google.android.vending.licensing;
+
+/*
+ * 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.
+ */
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+/**
+ * Default policy. All policy decisions are based off of response data received
+ * from the licensing service. Specifically, the licensing server sends the
+ * following information: response validity period, error retry period, and
+ * error retry count.
+ * <p>
+ * These values will vary based on the the way the application is configured in
+ * the Android Market publishing console, such as whether the application is
+ * marked as free or is within its refund period, as well as how often an
+ * application is checking with the licensing service.
+ * <p>
+ * Developers who need more fine grained control over their application's
+ * licensing policy should implement a custom Policy.
+ */
+public class APKExpansionPolicy implements Policy {
+
+ private static final String TAG = "APKExpansionPolicy";
+ private static final String PREFS_FILE = "com.android.vending.licensing.APKExpansionPolicy";
+ private static final String PREF_LAST_RESPONSE = "lastResponse";
+ private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp";
+ private static final String PREF_RETRY_UNTIL = "retryUntil";
+ private static final String PREF_MAX_RETRIES = "maxRetries";
+ private static final String PREF_RETRY_COUNT = "retryCount";
+ private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
+ private static final String DEFAULT_RETRY_UNTIL = "0";
+ private static final String DEFAULT_MAX_RETRIES = "0";
+ private static final String DEFAULT_RETRY_COUNT = "0";
+
+ private static final long MILLIS_PER_MINUTE = 60 * 1000;
+
+ private long mValidityTimestamp;
+ private long mRetryUntil;
+ private long mMaxRetries;
+ private long mRetryCount;
+ private long mLastResponseTime = 0;
+ private int mLastResponse;
+ private PreferenceObfuscator mPreferences;
+ private Vector<String> mExpansionURLs = new Vector<String>();
+ private Vector<String> mExpansionFileNames = new Vector<String>();
+ private Vector<Long> mExpansionFileSizes = new Vector<Long>();
+
+ /**
+ * The design of the protocol supports n files. Currently the market can
+ * only deliver two files. To accommodate this, we have these two constants,
+ * but the order is the only relevant thing here.
+ */
+ public static final int MAIN_FILE_URL_INDEX = 0;
+ public static final int PATCH_FILE_URL_INDEX = 1;
+
+ /**
+ * @param context The context for the current application
+ * @param obfuscator An obfuscator to be used with preferences.
+ */
+ public APKExpansionPolicy(Context context, Obfuscator obfuscator) {
+ // Import old values
+ SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
+ mPreferences = new PreferenceObfuscator(sp, obfuscator);
+ mLastResponse = Integer.parseInt(
+ mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)));
+ mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP,
+ DEFAULT_VALIDITY_TIMESTAMP));
+ mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
+ mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
+ mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
+ }
+
+ /**
+ * We call this to guarantee that we fetch a fresh policy from the server.
+ * This is to be used if the URL is invalid.
+ */
+ public void resetPolicy() {
+ mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY));
+ setRetryUntil(DEFAULT_RETRY_UNTIL);
+ setMaxRetries(DEFAULT_MAX_RETRIES);
+ setRetryCount(Long.parseLong(DEFAULT_RETRY_COUNT));
+ setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
+ mPreferences.commit();
+ }
+
+ /**
+ * Process a new response from the license server.
+ * <p>
+ * This data will be used for computing future policy decisions. The
+ * following parameters are processed:
+ * <ul>
+ * <li>VT: the timestamp that the client should consider the response valid
+ * until
+ * <li>GT: the timestamp that the client should ignore retry errors until
+ * <li>GR: the number of retry errors that the client should ignore
+ * </ul>
+ *
+ * @param response the result from validating the server response
+ * @param rawData the raw server response data
+ */
+ public void processServerResponse(int response,
+ com.google.android.vending.licensing.ResponseData rawData) {
+
+ // Update retry counter
+ if (response != Policy.RETRY) {
+ setRetryCount(0);
+ } else {
+ setRetryCount(mRetryCount + 1);
+ }
+
+ if (response == Policy.LICENSED) {
+ // Update server policy data
+ Map<String, String> extras = decodeExtras(rawData.extra);
+ mLastResponse = response;
+ setValidityTimestamp(Long.toString(System.currentTimeMillis() + MILLIS_PER_MINUTE));
+ Set<String> keys = extras.keySet();
+ for (String key : keys) {
+ if (key.equals("VT")) {
+ setValidityTimestamp(extras.get(key));
+ } else if (key.equals("GT")) {
+ setRetryUntil(extras.get(key));
+ } else if (key.equals("GR")) {
+ setMaxRetries(extras.get(key));
+ } else if (key.startsWith("FILE_URL")) {
+ int index = Integer.parseInt(key.substring("FILE_URL".length())) - 1;
+ setExpansionURL(index, extras.get(key));
+ } else if (key.startsWith("FILE_NAME")) {
+ int index = Integer.parseInt(key.substring("FILE_NAME".length())) - 1;
+ setExpansionFileName(index, extras.get(key));
+ } else if (key.startsWith("FILE_SIZE")) {
+ int index = Integer.parseInt(key.substring("FILE_SIZE".length())) - 1;
+ setExpansionFileSize(index, Long.parseLong(extras.get(key)));
+ }
+ }
+ } else if (response == Policy.NOT_LICENSED) {
+ // Clear out stale policy data
+ setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
+ setRetryUntil(DEFAULT_RETRY_UNTIL);
+ setMaxRetries(DEFAULT_MAX_RETRIES);
+ }
+
+ setLastResponse(response);
+ mPreferences.commit();
+ }
+
+ /**
+ * Set the last license response received from the server and add to
+ * preferences. You must manually call PreferenceObfuscator.commit() to
+ * commit these changes to disk.
+ *
+ * @param l the response
+ */
+ private void setLastResponse(int l) {
+ mLastResponseTime = System.currentTimeMillis();
+ mLastResponse = l;
+ mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l));
+ }
+
+ /**
+ * Set the current retry count and add to preferences. You must manually
+ * call PreferenceObfuscator.commit() to commit these changes to disk.
+ *
+ * @param c the new retry count
+ */
+ private void setRetryCount(long c) {
+ mRetryCount = c;
+ mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c));
+ }
+
+ public long getRetryCount() {
+ return mRetryCount;
+ }
+
+ /**
+ * Set the last validity timestamp (VT) received from the server and add to
+ * preferences. You must manually call PreferenceObfuscator.commit() to
+ * commit these changes to disk.
+ *
+ * @param validityTimestamp the VT string received
+ */
+ private void setValidityTimestamp(String validityTimestamp) {
+ Long lValidityTimestamp;
+ try {
+ lValidityTimestamp = Long.parseLong(validityTimestamp);
+ } catch (NumberFormatException e) {
+ // No response or not parseable, expire in one minute.
+ Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute");
+ lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE;
+ validityTimestamp = Long.toString(lValidityTimestamp);
+ }
+
+ mValidityTimestamp = lValidityTimestamp;
+ mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp);
+ }
+
+ public long getValidityTimestamp() {
+ return mValidityTimestamp;
+ }
+
+ /**
+ * Set the retry until timestamp (GT) received from the server and add to
+ * preferences. You must manually call PreferenceObfuscator.commit() to
+ * commit these changes to disk.
+ *
+ * @param retryUntil the GT string received
+ */
+ private void setRetryUntil(String retryUntil) {
+ Long lRetryUntil;
+ try {
+ lRetryUntil = Long.parseLong(retryUntil);
+ } catch (NumberFormatException e) {
+ // No response or not parseable, expire immediately
+ Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled");
+ retryUntil = "0";
+ lRetryUntil = 0l;
+ }
+
+ mRetryUntil = lRetryUntil;
+ mPreferences.putString(PREF_RETRY_UNTIL, retryUntil);
+ }
+
+ public long getRetryUntil() {
+ return mRetryUntil;
+ }
+
+ /**
+ * Set the max retries value (GR) as received from the server and add to
+ * preferences. You must manually call PreferenceObfuscator.commit() to
+ * commit these changes to disk.
+ *
+ * @param maxRetries the GR string received
+ */
+ private void setMaxRetries(String maxRetries) {
+ Long lMaxRetries;
+ try {
+ lMaxRetries = Long.parseLong(maxRetries);
+ } catch (NumberFormatException e) {
+ // No response or not parseable, expire immediately
+ Log.w(TAG, "Licence retry count (GR) missing, grace period disabled");
+ maxRetries = "0";
+ lMaxRetries = 0l;
+ }
+
+ mMaxRetries = lMaxRetries;
+ mPreferences.putString(PREF_MAX_RETRIES, maxRetries);
+ }
+
+ public long getMaxRetries() {
+ return mMaxRetries;
+ }
+
+ /**
+ * Gets the count of expansion URLs. Since expansionURLs are not committed
+ * to preferences, this will return zero if there has been no LVL fetch
+ * in the current session.
+ *
+ * @return the number of expansion URLs. (0,1,2)
+ */
+ public int getExpansionURLCount() {
+ return mExpansionURLs.size();
+ }
+
+ /**
+ * Gets the expansion URL. Since these URLs are not committed to
+ * preferences, this will always return null if there has not been an LVL
+ * fetch in the current session.
+ *
+ * @param index the index of the URL to fetch. This value will be either
+ * MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX
+ * @param URL the URL to set
+ */
+ public String getExpansionURL(int index) {
+ if (index < mExpansionURLs.size()) {
+ return mExpansionURLs.elementAt(index);
+ }
+ return null;
+ }
+
+ /**
+ * Sets the expansion URL. Expansion URL's are not committed to preferences,
+ * but are instead intended to be stored when the license response is
+ * processed by the front-end.
+ *
+ * @param index the index of the expansion URL. This value will be either
+ * MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX
+ * @param URL the URL to set
+ */
+ public void setExpansionURL(int index, String URL) {
+ if (index >= mExpansionURLs.size()) {
+ mExpansionURLs.setSize(index + 1);
+ }
+ mExpansionURLs.set(index, URL);
+ }
+
+ public String getExpansionFileName(int index) {
+ if (index < mExpansionFileNames.size()) {
+ return mExpansionFileNames.elementAt(index);
+ }
+ return null;
+ }
+
+ public void setExpansionFileName(int index, String name) {
+ if (index >= mExpansionFileNames.size()) {
+ mExpansionFileNames.setSize(index + 1);
+ }
+ mExpansionFileNames.set(index, name);
+ }
+
+ public long getExpansionFileSize(int index) {
+ if (index < mExpansionFileSizes.size()) {
+ return mExpansionFileSizes.elementAt(index);
+ }
+ return -1;
+ }
+
+ public void setExpansionFileSize(int index, long size) {
+ if (index >= mExpansionFileSizes.size()) {
+ mExpansionFileSizes.setSize(index + 1);
+ }
+ mExpansionFileSizes.set(index, size);
+ }
+
+ /**
+ * {@inheritDoc} This implementation allows access if either:<br>
+ * <ol>
+ * <li>a LICENSED response was received within the validity period
+ * <li>a RETRY response was received in the last minute, and we are under
+ * the RETRY count or in the RETRY period.
+ * </ol>
+ */
+ public boolean allowAccess() {
+ long ts = System.currentTimeMillis();
+ if (mLastResponse == Policy.LICENSED) {
+ // Check if the LICENSED response occurred within the validity
+ // timeout.
+ if (ts <= mValidityTimestamp) {
+ // Cached LICENSED response is still valid.
+ return true;
+ }
+ } else if (mLastResponse == Policy.RETRY &&
+ ts < mLastResponseTime + MILLIS_PER_MINUTE) {
+ // Only allow access if we are within the retry period or we haven't
+ // used up our
+ // max retries.
+ return (ts <= mRetryUntil || mRetryCount <= mMaxRetries);
+ }
+ return false;
+ }
+
+ private Map<String, String> decodeExtras(String extras) {
+ Map<String, String> results = new HashMap<String, String>();
+ try {
+ URI rawExtras = new URI("?" + extras);
+ List<NameValuePair> extraList = URLEncodedUtils.parse(rawExtras, "UTF-8");
+ for (NameValuePair item : extraList) {
+ String name = item.getName();
+ int i = 0;
+ while (results.containsKey(name)) {
+ name = item.getName() + ++i;
+ }
+ results.put(name, item.getValue());
+ }
+ } catch (URISyntaxException e) {
+ Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
+ }
+ return results;
+ }
+
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/DeviceLimiter.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/DeviceLimiter.java
new file mode 100644
index 0000000000..e5c5e2d7ca
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/DeviceLimiter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+/**
+ * Allows the developer to limit the number of devices using a single license.
+ * <p>
+ * The LICENSED response from the server contains a user identifier unique to
+ * the &lt;application, user&gt; pair. The developer can send this identifier
+ * to their own server along with some device identifier (a random number
+ * generated and stored once per application installation,
+ * {@link android.telephony.TelephonyManager#getDeviceId getDeviceId},
+ * {@link android.provider.Settings.Secure#ANDROID_ID ANDROID_ID}, etc).
+ * The more sources used to identify the device, the harder it will be for an
+ * attacker to spoof.
+ * <p>
+ * The server can look at the &lt;application, user, device id&gt; tuple and
+ * restrict a user's application license to run on at most 10 different devices
+ * in a week (for example). We recommend not being too restrictive because a
+ * user might legitimately have multiple devices or be in the process of
+ * changing phones. This will catch egregious violations of multiple people
+ * sharing one license.
+ */
+public interface DeviceLimiter {
+
+ /**
+ * Checks if this device is allowed to use the given user's license.
+ *
+ * @param userId the user whose license the server responded with
+ * @return LICENSED if the device is allowed, NOT_LICENSED if not, RETRY if an error occurs
+ */
+ int isDeviceAllowed(String userId);
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicenseResultListener.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicenseResultListener.java
new file mode 100644
index 0000000000..d90d6eac7b
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicenseResultListener.java
@@ -0,0 +1,99 @@
+/*
+ * This file is auto-generated. DO NOT MODIFY.
+ * Original file: aidl/ILicenseResultListener.aidl
+ */
+package com.google.android.vending.licensing;
+import java.lang.String;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Binder;
+import android.os.Parcel;
+public interface ILicenseResultListener extends android.os.IInterface
+{
+/** Local-side IPC implementation stub class. */
+public static abstract class Stub extends android.os.Binder implements com.google.android.vending.licensing.ILicenseResultListener
+{
+private static final java.lang.String DESCRIPTOR = "com.android.vending.licensing.ILicenseResultListener";
+/** Construct the stub at attach it to the interface. */
+public Stub()
+{
+this.attachInterface(this, DESCRIPTOR);
+}
+/**
+ * Cast an IBinder object into an ILicenseResultListener interface,
+ * generating a proxy if needed.
+ */
+public static com.google.android.vending.licensing.ILicenseResultListener asInterface(android.os.IBinder obj)
+{
+if ((obj==null)) {
+return null;
+}
+android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
+if (((iin!=null)&&(iin instanceof com.google.android.vending.licensing.ILicenseResultListener))) {
+return ((com.google.android.vending.licensing.ILicenseResultListener)iin);
+}
+return new com.google.android.vending.licensing.ILicenseResultListener.Stub.Proxy(obj);
+}
+public android.os.IBinder asBinder()
+{
+return this;
+}
+public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+{
+switch (code)
+{
+case INTERFACE_TRANSACTION:
+{
+reply.writeString(DESCRIPTOR);
+return true;
+}
+case TRANSACTION_verifyLicense:
+{
+data.enforceInterface(DESCRIPTOR);
+int _arg0;
+_arg0 = data.readInt();
+java.lang.String _arg1;
+_arg1 = data.readString();
+java.lang.String _arg2;
+_arg2 = data.readString();
+this.verifyLicense(_arg0, _arg1, _arg2);
+return true;
+}
+}
+return super.onTransact(code, data, reply, flags);
+}
+private static class Proxy implements com.google.android.vending.licensing.ILicenseResultListener
+{
+private android.os.IBinder mRemote;
+Proxy(android.os.IBinder remote)
+{
+mRemote = remote;
+}
+public android.os.IBinder asBinder()
+{
+return mRemote;
+}
+public java.lang.String getInterfaceDescriptor()
+{
+return DESCRIPTOR;
+}
+public void verifyLicense(int responseCode, java.lang.String signedData, java.lang.String signature) throws android.os.RemoteException
+{
+android.os.Parcel _data = android.os.Parcel.obtain();
+try {
+_data.writeInterfaceToken(DESCRIPTOR);
+_data.writeInt(responseCode);
+_data.writeString(signedData);
+_data.writeString(signature);
+mRemote.transact(Stub.TRANSACTION_verifyLicense, _data, null, IBinder.FLAG_ONEWAY);
+}
+finally {
+_data.recycle();
+}
+}
+}
+static final int TRANSACTION_verifyLicense = (IBinder.FIRST_CALL_TRANSACTION + 0);
+}
+public void verifyLicense(int responseCode, java.lang.String signedData, java.lang.String signature) throws android.os.RemoteException;
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicensingService.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicensingService.java
new file mode 100644
index 0000000000..95599544e4
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicensingService.java
@@ -0,0 +1,99 @@
+/*
+ * This file is auto-generated. DO NOT MODIFY.
+ * Original file: aidl/ILicensingService.aidl
+ */
+package com.google.android.vending.licensing;
+import java.lang.String;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Binder;
+import android.os.Parcel;
+public interface ILicensingService extends android.os.IInterface
+{
+/** Local-side IPC implementation stub class. */
+public static abstract class Stub extends android.os.Binder implements com.google.android.vending.licensing.ILicensingService
+{
+private static final java.lang.String DESCRIPTOR = "com.android.vending.licensing.ILicensingService";
+/** Construct the stub at attach it to the interface. */
+public Stub()
+{
+this.attachInterface(this, DESCRIPTOR);
+}
+/**
+ * Cast an IBinder object into an ILicensingService interface,
+ * generating a proxy if needed.
+ */
+public static com.google.android.vending.licensing.ILicensingService asInterface(android.os.IBinder obj)
+{
+if ((obj==null)) {
+return null;
+}
+android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
+if (((iin!=null)&&(iin instanceof com.google.android.vending.licensing.ILicensingService))) {
+return ((com.google.android.vending.licensing.ILicensingService)iin);
+}
+return new com.google.android.vending.licensing.ILicensingService.Stub.Proxy(obj);
+}
+public android.os.IBinder asBinder()
+{
+return this;
+}
+public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+{
+switch (code)
+{
+case INTERFACE_TRANSACTION:
+{
+reply.writeString(DESCRIPTOR);
+return true;
+}
+case TRANSACTION_checkLicense:
+{
+data.enforceInterface(DESCRIPTOR);
+long _arg0;
+_arg0 = data.readLong();
+java.lang.String _arg1;
+_arg1 = data.readString();
+com.google.android.vending.licensing.ILicenseResultListener _arg2;
+_arg2 = com.google.android.vending.licensing.ILicenseResultListener.Stub.asInterface(data.readStrongBinder());
+this.checkLicense(_arg0, _arg1, _arg2);
+return true;
+}
+}
+return super.onTransact(code, data, reply, flags);
+}
+private static class Proxy implements com.google.android.vending.licensing.ILicensingService
+{
+private android.os.IBinder mRemote;
+Proxy(android.os.IBinder remote)
+{
+mRemote = remote;
+}
+public android.os.IBinder asBinder()
+{
+return mRemote;
+}
+public java.lang.String getInterfaceDescriptor()
+{
+return DESCRIPTOR;
+}
+public void checkLicense(long nonce, java.lang.String packageName, com.google.android.vending.licensing.ILicenseResultListener listener) throws android.os.RemoteException
+{
+android.os.Parcel _data = android.os.Parcel.obtain();
+try {
+_data.writeInterfaceToken(DESCRIPTOR);
+_data.writeLong(nonce);
+_data.writeString(packageName);
+_data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
+mRemote.transact(Stub.TRANSACTION_checkLicense, _data, null, IBinder.FLAG_ONEWAY);
+}
+finally {
+_data.recycle();
+}
+}
+}
+static final int TRANSACTION_checkLicense = (IBinder.FIRST_CALL_TRANSACTION + 0);
+}
+public void checkLicense(long nonce, java.lang.String packageName, com.google.android.vending.licensing.ILicenseResultListener listener) throws android.os.RemoteException;
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseChecker.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseChecker.java
new file mode 100644
index 0000000000..0b1c4b6cca
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseChecker.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+import com.google.android.vending.licensing.util.Base64;
+import com.google.android.vending.licensing.util.Base64DecoderException;
+
+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.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.provider.Settings.Secure;
+import android.util.Log;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.Set;
+
+/**
+ * Client library for Android Market license verifications.
+ * <p>
+ * The LicenseChecker is configured via a {@link Policy} which contains the
+ * logic to determine whether a user should have access to the application. For
+ * example, the Policy can define a threshold for allowable number of server or
+ * client failures before the library reports the user as not having access.
+ * <p>
+ * Must also provide the Base64-encoded RSA public key associated with your
+ * developer account. The public key is obtainable from the publisher site.
+ */
+public class LicenseChecker implements ServiceConnection {
+ private static final String TAG = "LicenseChecker";
+
+ private static final String KEY_FACTORY_ALGORITHM = "RSA";
+
+ // Timeout value (in milliseconds) for calls to service.
+ private static final int TIMEOUT_MS = 10 * 1000;
+
+ private static final SecureRandom RANDOM = new SecureRandom();
+ private static final boolean DEBUG_LICENSE_ERROR = false;
+
+ private ILicensingService mService;
+
+ private PublicKey mPublicKey;
+ private final Context mContext;
+ private final Policy mPolicy;
+ /**
+ * A handler for running tasks on a background thread. We don't want license
+ * processing to block the UI thread.
+ */
+ private Handler mHandler;
+ private final String mPackageName;
+ private final String mVersionCode;
+ private final Set<LicenseValidator> mChecksInProgress = new HashSet<LicenseValidator>();
+ private final Queue<LicenseValidator> mPendingChecks = new LinkedList<LicenseValidator>();
+
+ /**
+ * @param context a Context
+ * @param policy implementation of Policy
+ * @param encodedPublicKey Base64-encoded RSA public key
+ * @throws IllegalArgumentException if encodedPublicKey is invalid
+ */
+ public LicenseChecker(Context context, Policy policy, String encodedPublicKey) {
+ mContext = context;
+ mPolicy = policy;
+ mPublicKey = generatePublicKey(encodedPublicKey);
+ mPackageName = mContext.getPackageName();
+ mVersionCode = getVersionCode(context, mPackageName);
+ HandlerThread handlerThread = new HandlerThread("background thread");
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ }
+
+ /**
+ * Generates a PublicKey instance from a string containing the
+ * Base64-encoded public key.
+ *
+ * @param encodedPublicKey Base64-encoded public key
+ * @throws IllegalArgumentException if encodedPublicKey is invalid
+ */
+ private static PublicKey generatePublicKey(String encodedPublicKey) {
+ try {
+ byte[] decodedKey = Base64.decode(encodedPublicKey);
+ KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
+
+ return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
+ } catch (NoSuchAlgorithmException e) {
+ // This won't happen in an Android-compatible environment.
+ throw new RuntimeException(e);
+ } catch (Base64DecoderException e) {
+ Log.e(TAG, "Could not decode from Base64.");
+ throw new IllegalArgumentException(e);
+ } catch (InvalidKeySpecException e) {
+ Log.e(TAG, "Invalid key specification.");
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Checks if the user should have access to the app. Binds the service if necessary.
+ * <p>
+ * NOTE: This call uses a trivially obfuscated string (base64-encoded). For best security,
+ * we recommend obfuscating the string that is passed into bindService using another method
+ * of your own devising.
+ * <p>
+ * source string: "com.android.vending.licensing.ILicensingService"
+ * <p>
+ * @param callback
+ */
+ public synchronized void checkAccess(LicenseCheckerCallback callback) {
+ // If we have a valid recent LICENSED response, we can skip asking
+ // Market.
+ if (mPolicy.allowAccess()) {
+ Log.i(TAG, "Using cached license response");
+ callback.allow(Policy.LICENSED);
+ } else {
+ LicenseValidator validator = new LicenseValidator(mPolicy, new NullDeviceLimiter(),
+ callback, generateNonce(), mPackageName, mVersionCode);
+
+ if (mService == null) {
+ Log.i(TAG, "Binding to licensing service.");
+ try {
+ boolean bindResult = mContext
+ .bindService(
+ new Intent(
+ new String(
+ Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))),
+ this, // ServiceConnection.
+ Context.BIND_AUTO_CREATE);
+
+ if (bindResult) {
+ mPendingChecks.offer(validator);
+ } else {
+ Log.e(TAG, "Could not bind to service.");
+ handleServiceConnectionError(validator);
+ }
+ } catch (SecurityException e) {
+ callback.applicationError(LicenseCheckerCallback.ERROR_MISSING_PERMISSION);
+ } catch (Base64DecoderException e) {
+ e.printStackTrace();
+ }
+ } else {
+ mPendingChecks.offer(validator);
+ runChecks();
+ }
+ }
+ }
+
+ private void runChecks() {
+ LicenseValidator validator;
+ while ((validator = mPendingChecks.poll()) != null) {
+ try {
+ Log.i(TAG, "Calling checkLicense on service for " + validator.getPackageName());
+ mService.checkLicense(
+ validator.getNonce(), validator.getPackageName(),
+ new ResultListener(validator));
+ mChecksInProgress.add(validator);
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException in checkLicense call.", e);
+ handleServiceConnectionError(validator);
+ }
+ }
+ }
+
+ private synchronized void finishCheck(LicenseValidator validator) {
+ mChecksInProgress.remove(validator);
+ if (mChecksInProgress.isEmpty()) {
+ cleanupService();
+ }
+ }
+
+ private class ResultListener extends ILicenseResultListener.Stub {
+ private final LicenseValidator mValidator;
+ private Runnable mOnTimeout;
+
+ public ResultListener(LicenseValidator validator) {
+ mValidator = validator;
+ mOnTimeout = new Runnable() {
+ public void run() {
+ Log.i(TAG, "Check timed out.");
+ handleServiceConnectionError(mValidator);
+ finishCheck(mValidator);
+ }
+ };
+ startTimeout();
+ }
+
+ private static final int ERROR_CONTACTING_SERVER = 0x101;
+ private static final int ERROR_INVALID_PACKAGE_NAME = 0x102;
+ private static final int ERROR_NON_MATCHING_UID = 0x103;
+
+ // Runs in IPC thread pool. Post it to the Handler, so we can guarantee
+ // either this or the timeout runs.
+ public void verifyLicense(final int responseCode, final String signedData,
+ final String signature) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ Log.i(TAG, "Received response.");
+ // Make sure it hasn't already timed out.
+ if (mChecksInProgress.contains(mValidator)) {
+ clearTimeout();
+ mValidator.verify(mPublicKey, responseCode, signedData, signature);
+ finishCheck(mValidator);
+ }
+ if (DEBUG_LICENSE_ERROR) {
+ boolean logResponse;
+ String stringError = null;
+ switch (responseCode) {
+ case ERROR_CONTACTING_SERVER:
+ logResponse = true;
+ stringError = "ERROR_CONTACTING_SERVER";
+ break;
+ case ERROR_INVALID_PACKAGE_NAME:
+ logResponse = true;
+ stringError = "ERROR_INVALID_PACKAGE_NAME";
+ break;
+ case ERROR_NON_MATCHING_UID:
+ logResponse = true;
+ stringError = "ERROR_NON_MATCHING_UID";
+ break;
+ default:
+ logResponse = false;
+ }
+
+ if (logResponse) {
+ String android_id = Secure.getString(mContext.getContentResolver(),
+ Secure.ANDROID_ID);
+ Date date = new Date();
+ Log.d(TAG, "Server Failure: " + stringError);
+ Log.d(TAG, "Android ID: " + android_id);
+ Log.d(TAG, "Time: " + date.toGMTString());
+ }
+ }
+
+ }
+ });
+ }
+
+ private void startTimeout() {
+ Log.i(TAG, "Start monitoring timeout.");
+ mHandler.postDelayed(mOnTimeout, TIMEOUT_MS);
+ }
+
+ private void clearTimeout() {
+ Log.i(TAG, "Clearing timeout.");
+ mHandler.removeCallbacks(mOnTimeout);
+ }
+ }
+
+ public synchronized void onServiceConnected(ComponentName name, IBinder service) {
+ mService = ILicensingService.Stub.asInterface(service);
+ runChecks();
+ }
+
+ public synchronized void onServiceDisconnected(ComponentName name) {
+ // Called when the connection with the service has been
+ // unexpectedly disconnected. That is, Market crashed.
+ // If there are any checks in progress, the timeouts will handle them.
+ Log.w(TAG, "Service unexpectedly disconnected.");
+ mService = null;
+ }
+
+ /**
+ * Generates policy response for service connection errors, as a result of
+ * disconnections or timeouts.
+ */
+ private synchronized void handleServiceConnectionError(LicenseValidator validator) {
+ mPolicy.processServerResponse(Policy.RETRY, null);
+
+ if (mPolicy.allowAccess()) {
+ validator.getCallback().allow(Policy.RETRY);
+ } else {
+ validator.getCallback().dontAllow(Policy.RETRY);
+ }
+ }
+
+ /** Unbinds service if necessary and removes reference to it. */
+ private void cleanupService() {
+ if (mService != null) {
+ try {
+ mContext.unbindService(this);
+ } catch (IllegalArgumentException e) {
+ // Somehow we've already been unbound. This is a non-fatal
+ // error.
+ Log.e(TAG, "Unable to unbind from licensing service (already unbound)");
+ }
+ mService = null;
+ }
+ }
+
+ /**
+ * Inform the library that the context is about to be destroyed, so that any
+ * open connections can be cleaned up.
+ * <p>
+ * Failure to call this method can result in a crash under certain
+ * circumstances, such as during screen rotation if an Activity requests the
+ * license check or when the user exits the application.
+ */
+ public synchronized void onDestroy() {
+ cleanupService();
+ mHandler.getLooper().quit();
+ }
+
+ /** Generates a nonce (number used once). */
+ private int generateNonce() {
+ return RANDOM.nextInt();
+ }
+
+ /**
+ * Get version code for the application package name.
+ *
+ * @param context
+ * @param packageName application package name
+ * @return the version code or empty string if package not found
+ */
+ private static String getVersionCode(Context context, String packageName) {
+ try {
+ return String.valueOf(context.getPackageManager().getPackageInfo(packageName, 0).
+ versionCode);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Package not found. could not get version code.");
+ return "";
+ }
+ }
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseCheckerCallback.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseCheckerCallback.java
new file mode 100644
index 0000000000..b250a7147b
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseCheckerCallback.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+/**
+ * Callback for the license checker library.
+ * <p>
+ * Upon checking with the Market server and conferring with the {@link Policy},
+ * the library calls the appropriate callback method to communicate the result.
+ * <p>
+ * <b>The callback does not occur in the original checking thread.</b> Your
+ * application should post to the appropriate handling thread or lock
+ * accordingly.
+ * <p>
+ * The reason that is passed back with allow/dontAllow is the base status handed
+ * to the policy for allowed/disallowing the license. Policy.RETRY will call
+ * allow or dontAllow depending on other statistics associated with the policy,
+ * while in most cases Policy.NOT_LICENSED will call dontAllow and
+ * Policy.LICENSED will Allow.
+ */
+public interface LicenseCheckerCallback {
+
+ /**
+ * Allow use. App should proceed as normal.
+ *
+ * @param reason Policy.LICENSED or Policy.RETRY typically. (although in
+ * theory the policy can return Policy.NOT_LICENSED here as well)
+ */
+ public void allow(int reason);
+
+ /**
+ * Don't allow use. App should inform user and take appropriate action.
+ *
+ * @param reason Policy.NOT_LICENSED or Policy.RETRY. (although in theory
+ * the policy can return Policy.LICENSED here as well ---
+ * perhaps the call to the LVL took too long, for example)
+ */
+ public void dontAllow(int reason);
+
+ /** Application error codes. */
+ public static final int ERROR_INVALID_PACKAGE_NAME = 1;
+ public static final int ERROR_NON_MATCHING_UID = 2;
+ public static final int ERROR_NOT_MARKET_MANAGED = 3;
+ public static final int ERROR_CHECK_IN_PROGRESS = 4;
+ public static final int ERROR_INVALID_PUBLIC_KEY = 5;
+ public static final int ERROR_MISSING_PERMISSION = 6;
+
+ /**
+ * Error in application code. Caller did not call or set up license checker
+ * correctly. Should be considered fatal.
+ */
+ public void applicationError(int errorCode);
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseValidator.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseValidator.java
new file mode 100644
index 0000000000..61d3c7e79e
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseValidator.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+import com.google.android.vending.licensing.util.Base64;
+import com.google.android.vending.licensing.util.Base64DecoderException;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+
+/**
+ * Contains data related to a licensing request and methods to verify
+ * and process the response.
+ */
+class LicenseValidator {
+ private static final String TAG = "LicenseValidator";
+
+ // Server response codes.
+ private static final int LICENSED = 0x0;
+ private static final int NOT_LICENSED = 0x1;
+ private static final int LICENSED_OLD_KEY = 0x2;
+ private static final int ERROR_NOT_MARKET_MANAGED = 0x3;
+ private static final int ERROR_SERVER_FAILURE = 0x4;
+ private static final int ERROR_OVER_QUOTA = 0x5;
+
+ private static final int ERROR_CONTACTING_SERVER = 0x101;
+ private static final int ERROR_INVALID_PACKAGE_NAME = 0x102;
+ private static final int ERROR_NON_MATCHING_UID = 0x103;
+
+ private final Policy mPolicy;
+ private final LicenseCheckerCallback mCallback;
+ private final int mNonce;
+ private final String mPackageName;
+ private final String mVersionCode;
+ private final DeviceLimiter mDeviceLimiter;
+
+ LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback,
+ int nonce, String packageName, String versionCode) {
+ mPolicy = policy;
+ mDeviceLimiter = deviceLimiter;
+ mCallback = callback;
+ mNonce = nonce;
+ mPackageName = packageName;
+ mVersionCode = versionCode;
+ }
+
+ public LicenseCheckerCallback getCallback() {
+ return mCallback;
+ }
+
+ public int getNonce() {
+ return mNonce;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
+
+ /**
+ * Verifies the response from server and calls appropriate callback method.
+ *
+ * @param publicKey public key associated with the developer account
+ * @param responseCode server response code
+ * @param signedData signed data from server
+ * @param signature server signature
+ */
+ public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) {
+ String userId = null;
+ // Skip signature check for unsuccessful requests
+ ResponseData data = null;
+ if (responseCode == LICENSED || responseCode == NOT_LICENSED ||
+ responseCode == LICENSED_OLD_KEY) {
+ // Verify signature.
+ try {
+ Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
+ sig.initVerify(publicKey);
+ sig.update(signedData.getBytes());
+
+ if (!sig.verify(Base64.decode(signature))) {
+ Log.e(TAG, "Signature verification failed.");
+ handleInvalidResponse();
+ return;
+ }
+ } catch (NoSuchAlgorithmException e) {
+ // This can't happen on an Android compatible device.
+ throw new RuntimeException(e);
+ } catch (InvalidKeyException e) {
+ handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY);
+ return;
+ } catch (SignatureException e) {
+ throw new RuntimeException(e);
+ } catch (Base64DecoderException e) {
+ Log.e(TAG, "Could not Base64-decode signature.");
+ handleInvalidResponse();
+ return;
+ }
+
+ // Parse and validate response.
+ try {
+ data = ResponseData.parse(signedData);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Could not parse response.");
+ handleInvalidResponse();
+ return;
+ }
+
+ if (data.responseCode != responseCode) {
+ Log.e(TAG, "Response codes don't match.");
+ handleInvalidResponse();
+ return;
+ }
+
+ if (data.nonce != mNonce) {
+ Log.e(TAG, "Nonce doesn't match.");
+ handleInvalidResponse();
+ return;
+ }
+
+ if (!data.packageName.equals(mPackageName)) {
+ Log.e(TAG, "Package name doesn't match.");
+ handleInvalidResponse();
+ return;
+ }
+
+ if (!data.versionCode.equals(mVersionCode)) {
+ Log.e(TAG, "Version codes don't match.");
+ handleInvalidResponse();
+ return;
+ }
+
+ // Application-specific user identifier.
+ userId = data.userId;
+ if (TextUtils.isEmpty(userId)) {
+ Log.e(TAG, "User identifier is empty.");
+ handleInvalidResponse();
+ return;
+ }
+ }
+
+ switch (responseCode) {
+ case LICENSED:
+ case LICENSED_OLD_KEY:
+ int limiterResponse = mDeviceLimiter.isDeviceAllowed(userId);
+ handleResponse(limiterResponse, data);
+ break;
+ case NOT_LICENSED:
+ handleResponse(Policy.NOT_LICENSED, data);
+ break;
+ case ERROR_CONTACTING_SERVER:
+ Log.w(TAG, "Error contacting licensing server.");
+ handleResponse(Policy.RETRY, data);
+ break;
+ case ERROR_SERVER_FAILURE:
+ Log.w(TAG, "An error has occurred on the licensing server.");
+ handleResponse(Policy.RETRY, data);
+ break;
+ case ERROR_OVER_QUOTA:
+ Log.w(TAG, "Licensing server is refusing to talk to this device, over quota.");
+ handleResponse(Policy.RETRY, data);
+ break;
+ case ERROR_INVALID_PACKAGE_NAME:
+ handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME);
+ break;
+ case ERROR_NON_MATCHING_UID:
+ handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID);
+ break;
+ case ERROR_NOT_MARKET_MANAGED:
+ handleApplicationError(LicenseCheckerCallback.ERROR_NOT_MARKET_MANAGED);
+ break;
+ default:
+ Log.e(TAG, "Unknown response code for license check.");
+ handleInvalidResponse();
+ }
+ }
+
+ /**
+ * Confers with policy and calls appropriate callback method.
+ *
+ * @param response
+ * @param rawData
+ */
+ private void handleResponse(int response, ResponseData rawData) {
+ // Update policy data and increment retry counter (if needed)
+ mPolicy.processServerResponse(response, rawData);
+
+ // Given everything we know, including cached data, ask the policy if we should grant
+ // access.
+ if (mPolicy.allowAccess()) {
+ mCallback.allow(response);
+ } else {
+ mCallback.dontAllow(response);
+ }
+ }
+
+ private void handleApplicationError(int code) {
+ mCallback.applicationError(code);
+ }
+
+ private void handleInvalidResponse() {
+ mCallback.dontAllow(Policy.NOT_LICENSED);
+ }
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/NullDeviceLimiter.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/NullDeviceLimiter.java
new file mode 100644
index 0000000000..d87af3153f
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/NullDeviceLimiter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+/**
+ * A DeviceLimiter that doesn't limit the number of devices that can use a
+ * given user's license.
+ * <p>
+ * Unless you have reason to believe that your application is being pirated
+ * by multiple users using the same license (signing in to Market as the same
+ * user), we recommend you use this implementation.
+ */
+public class NullDeviceLimiter implements DeviceLimiter {
+
+ public int isDeviceAllowed(String userId) {
+ return Policy.LICENSED;
+ }
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Obfuscator.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Obfuscator.java
new file mode 100644
index 0000000000..b5d510d72d
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Obfuscator.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+/**
+ * Interface used as part of a {@link Policy} to allow application authors to obfuscate
+ * licensing data that will be stored into a SharedPreferences file.
+ * <p>
+ * Any transformation scheme must be reversable. Implementing classes may optionally implement an
+ * integrity check to further prevent modification to preference data. Implementing classes
+ * should use device-specific information as a key in the obfuscation algorithm to prevent
+ * obfuscated preferences from being shared among devices.
+ */
+public interface Obfuscator {
+
+ /**
+ * Obfuscate a string that is being stored into shared preferences.
+ *
+ * @param original The data that is to be obfuscated.
+ * @param key The key for the data that is to be obfuscated.
+ * @return A transformed version of the original data.
+ */
+ String obfuscate(String original, String key);
+
+ /**
+ * Undo the transformation applied to data by the obfuscate() method.
+ *
+ * @param original The data that is to be obfuscated.
+ * @param key The key for the data that is to be obfuscated.
+ * @return A transformed version of the original data.
+ * @throws ValidationException Optionally thrown if a data integrity check fails.
+ */
+ String unobfuscate(String obfuscated, String key) throws ValidationException;
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Policy.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Policy.java
new file mode 100644
index 0000000000..fa267fc71a
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Policy.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+/**
+ * Policy used by {@link LicenseChecker} to determine whether a user should have
+ * access to the application.
+ */
+public interface Policy {
+
+ /**
+ * Change these values to make it more difficult for tools to automatically
+ * strip LVL protection from your APK.
+ */
+
+ /**
+ * LICENSED means that the server returned back a valid license response
+ */
+ public static final int LICENSED = 0x0100;
+ /**
+ * NOT_LICENSED means that the server returned back a valid license response
+ * that indicated that the user definitively is not licensed
+ */
+ public static final int NOT_LICENSED = 0x0231;
+ /**
+ * RETRY means that the license response was unable to be determined ---
+ * perhaps as a result of faulty networking
+ */
+ public static final int RETRY = 0x0123;
+
+ /**
+ * Provide results from contact with the license server. Retry counts are
+ * incremented if the current value of response is RETRY. Results will be
+ * used for any future policy decisions.
+ *
+ * @param response the result from validating the server response
+ * @param rawData the raw server response data, can be null for RETRY
+ */
+ void processServerResponse(int response, ResponseData rawData);
+
+ /**
+ * Check if the user should be allowed access to the application.
+ */
+ boolean allowAccess();
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/PreferenceObfuscator.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/PreferenceObfuscator.java
new file mode 100644
index 0000000000..7c42bfc28a
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/PreferenceObfuscator.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+import android.content.SharedPreferences;
+import android.util.Log;
+
+/**
+ * An wrapper for SharedPreferences that transparently performs data obfuscation.
+ */
+public class PreferenceObfuscator {
+
+ private static final String TAG = "PreferenceObfuscator";
+
+ private final SharedPreferences mPreferences;
+ private final Obfuscator mObfuscator;
+ private SharedPreferences.Editor mEditor;
+
+ /**
+ * Constructor.
+ *
+ * @param sp A SharedPreferences instance provided by the system.
+ * @param o The Obfuscator to use when reading or writing data.
+ */
+ public PreferenceObfuscator(SharedPreferences sp, Obfuscator o) {
+ mPreferences = sp;
+ mObfuscator = o;
+ mEditor = null;
+ }
+
+ public void putString(String key, String value) {
+ if (mEditor == null) {
+ mEditor = mPreferences.edit();
+ }
+ String obfuscatedValue = mObfuscator.obfuscate(value, key);
+ mEditor.putString(key, obfuscatedValue);
+ }
+
+ public String getString(String key, String defValue) {
+ String result;
+ String value = mPreferences.getString(key, null);
+ if (value != null) {
+ try {
+ result = mObfuscator.unobfuscate(value, key);
+ } catch (ValidationException e) {
+ // Unable to unobfuscate, data corrupt or tampered
+ Log.w(TAG, "Validation error while reading preference: " + key);
+ result = defValue;
+ }
+ } else {
+ // Preference not found
+ result = defValue;
+ }
+ return result;
+ }
+
+ public void commit() {
+ if (mEditor != null) {
+ mEditor.commit();
+ mEditor = null;
+ }
+ }
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ResponseData.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ResponseData.java
new file mode 100644
index 0000000000..2adef3709e
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ResponseData.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+import java.util.regex.Pattern;
+
+import android.text.TextUtils;
+
+/**
+ * ResponseData from licensing server.
+ */
+public class ResponseData {
+
+ public int responseCode;
+ public int nonce;
+ public String packageName;
+ public String versionCode;
+ public String userId;
+ public long timestamp;
+ /** Response-specific data. */
+ public String extra;
+
+ /**
+ * Parses response string into ResponseData.
+ *
+ * @param responseData response data string
+ * @throws IllegalArgumentException upon parsing error
+ * @return ResponseData object
+ */
+ public static ResponseData parse(String responseData) {
+ // Must parse out main response data and response-specific data.
+ int index = responseData.indexOf(':');
+ String mainData, extraData;
+ if ( -1 == index ) {
+ mainData = responseData;
+ extraData = "";
+ } else {
+ mainData = responseData.substring(0, index);
+ extraData = index >= responseData.length() ? "" : responseData.substring(index+1);
+ }
+
+ String [] fields = TextUtils.split(mainData, Pattern.quote("|"));
+ if (fields.length < 6) {
+ throw new IllegalArgumentException("Wrong number of fields.");
+ }
+
+ ResponseData data = new ResponseData();
+ data.extra = extraData;
+ data.responseCode = Integer.parseInt(fields[0]);
+ data.nonce = Integer.parseInt(fields[1]);
+ data.packageName = fields[2];
+ data.versionCode = fields[3];
+ // Application-specific user identifier.
+ data.userId = fields[4];
+ data.timestamp = Long.parseLong(fields[5]);
+
+ return data;
+ }
+
+ @Override
+ public String toString() {
+ return TextUtils.join("|", new Object [] { responseCode, nonce, packageName, versionCode,
+ userId, timestamp });
+ }
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ServerManagedPolicy.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ServerManagedPolicy.java
new file mode 100644
index 0000000000..fbf8cf6d00
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ServerManagedPolicy.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+/**
+ * Default policy. All policy decisions are based off of response data received
+ * from the licensing service. Specifically, the licensing server sends the
+ * following information: response validity period, error retry period, and
+ * error retry count.
+ * <p>
+ * These values will vary based on the the way the application is configured in
+ * the Android Market publishing console, such as whether the application is
+ * marked as free or is within its refund period, as well as how often an
+ * application is checking with the licensing service.
+ * <p>
+ * Developers who need more fine grained control over their application's
+ * licensing policy should implement a custom Policy.
+ */
+public class ServerManagedPolicy implements Policy {
+
+ private static final String TAG = "ServerManagedPolicy";
+ private static final String PREFS_FILE = "com.android.vending.licensing.ServerManagedPolicy";
+ private static final String PREF_LAST_RESPONSE = "lastResponse";
+ private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp";
+ private static final String PREF_RETRY_UNTIL = "retryUntil";
+ private static final String PREF_MAX_RETRIES = "maxRetries";
+ private static final String PREF_RETRY_COUNT = "retryCount";
+ private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
+ private static final String DEFAULT_RETRY_UNTIL = "0";
+ private static final String DEFAULT_MAX_RETRIES = "0";
+ private static final String DEFAULT_RETRY_COUNT = "0";
+
+ private static final long MILLIS_PER_MINUTE = 60 * 1000;
+
+ private long mValidityTimestamp;
+ private long mRetryUntil;
+ private long mMaxRetries;
+ private long mRetryCount;
+ private long mLastResponseTime = 0;
+ private int mLastResponse;
+ private PreferenceObfuscator mPreferences;
+
+ /**
+ * @param context The context for the current application
+ * @param obfuscator An obfuscator to be used with preferences.
+ */
+ public ServerManagedPolicy(Context context, Obfuscator obfuscator) {
+ // Import old values
+ SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
+ mPreferences = new PreferenceObfuscator(sp, obfuscator);
+ mLastResponse = Integer.parseInt(
+ mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)));
+ mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP,
+ DEFAULT_VALIDITY_TIMESTAMP));
+ mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
+ mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
+ mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
+ }
+
+ /**
+ * Process a new response from the license server.
+ * <p>
+ * This data will be used for computing future policy decisions. The
+ * following parameters are processed:
+ * <ul>
+ * <li>VT: the timestamp that the client should consider the response
+ * valid until
+ * <li>GT: the timestamp that the client should ignore retry errors until
+ * <li>GR: the number of retry errors that the client should ignore
+ * </ul>
+ *
+ * @param response the result from validating the server response
+ * @param rawData the raw server response data
+ */
+ public void processServerResponse(int response, ResponseData rawData) {
+
+ // Update retry counter
+ if (response != Policy.RETRY) {
+ setRetryCount(0);
+ } else {
+ setRetryCount(mRetryCount + 1);
+ }
+
+ if (response == Policy.LICENSED) {
+ // Update server policy data
+ Map<String, String> extras = decodeExtras(rawData.extra);
+ mLastResponse = response;
+ setValidityTimestamp(extras.get("VT"));
+ setRetryUntil(extras.get("GT"));
+ setMaxRetries(extras.get("GR"));
+ } else if (response == Policy.NOT_LICENSED) {
+ // Clear out stale policy data
+ setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
+ setRetryUntil(DEFAULT_RETRY_UNTIL);
+ setMaxRetries(DEFAULT_MAX_RETRIES);
+ }
+
+ setLastResponse(response);
+ mPreferences.commit();
+ }
+
+ /**
+ * Set the last license response received from the server and add to
+ * preferences. You must manually call PreferenceObfuscator.commit() to
+ * commit these changes to disk.
+ *
+ * @param l the response
+ */
+ private void setLastResponse(int l) {
+ mLastResponseTime = System.currentTimeMillis();
+ mLastResponse = l;
+ mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l));
+ }
+
+ /**
+ * Set the current retry count and add to preferences. You must manually
+ * call PreferenceObfuscator.commit() to commit these changes to disk.
+ *
+ * @param c the new retry count
+ */
+ private void setRetryCount(long c) {
+ mRetryCount = c;
+ mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c));
+ }
+
+ public long getRetryCount() {
+ return mRetryCount;
+ }
+
+ /**
+ * Set the last validity timestamp (VT) received from the server and add to
+ * preferences. You must manually call PreferenceObfuscator.commit() to
+ * commit these changes to disk.
+ *
+ * @param validityTimestamp the VT string received
+ */
+ private void setValidityTimestamp(String validityTimestamp) {
+ Long lValidityTimestamp;
+ try {
+ lValidityTimestamp = Long.parseLong(validityTimestamp);
+ } catch (NumberFormatException e) {
+ // No response or not parsable, expire in one minute.
+ Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute");
+ lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE;
+ validityTimestamp = Long.toString(lValidityTimestamp);
+ }
+
+ mValidityTimestamp = lValidityTimestamp;
+ mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp);
+ }
+
+ public long getValidityTimestamp() {
+ return mValidityTimestamp;
+ }
+
+ /**
+ * Set the retry until timestamp (GT) received from the server and add to
+ * preferences. You must manually call PreferenceObfuscator.commit() to
+ * commit these changes to disk.
+ *
+ * @param retryUntil the GT string received
+ */
+ private void setRetryUntil(String retryUntil) {
+ Long lRetryUntil;
+ try {
+ lRetryUntil = Long.parseLong(retryUntil);
+ } catch (NumberFormatException e) {
+ // No response or not parsable, expire immediately
+ Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled");
+ retryUntil = "0";
+ lRetryUntil = 0l;
+ }
+
+ mRetryUntil = lRetryUntil;
+ mPreferences.putString(PREF_RETRY_UNTIL, retryUntil);
+ }
+
+ public long getRetryUntil() {
+ return mRetryUntil;
+ }
+
+ /**
+ * Set the max retries value (GR) as received from the server and add to
+ * preferences. You must manually call PreferenceObfuscator.commit() to
+ * commit these changes to disk.
+ *
+ * @param maxRetries the GR string received
+ */
+ private void setMaxRetries(String maxRetries) {
+ Long lMaxRetries;
+ try {
+ lMaxRetries = Long.parseLong(maxRetries);
+ } catch (NumberFormatException e) {
+ // No response or not parsable, expire immediately
+ Log.w(TAG, "Licence retry count (GR) missing, grace period disabled");
+ maxRetries = "0";
+ lMaxRetries = 0l;
+ }
+
+ mMaxRetries = lMaxRetries;
+ mPreferences.putString(PREF_MAX_RETRIES, maxRetries);
+ }
+
+ public long getMaxRetries() {
+ return mMaxRetries;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * This implementation allows access if either:<br>
+ * <ol>
+ * <li>a LICENSED response was received within the validity period
+ * <li>a RETRY response was received in the last minute, and we are under
+ * the RETRY count or in the RETRY period.
+ * </ol>
+ */
+ public boolean allowAccess() {
+ long ts = System.currentTimeMillis();
+ if (mLastResponse == Policy.LICENSED) {
+ // Check if the LICENSED response occurred within the validity timeout.
+ if (ts <= mValidityTimestamp) {
+ // Cached LICENSED response is still valid.
+ return true;
+ }
+ } else if (mLastResponse == Policy.RETRY &&
+ ts < mLastResponseTime + MILLIS_PER_MINUTE) {
+ // Only allow access if we are within the retry period or we haven't used up our
+ // max retries.
+ return (ts <= mRetryUntil || mRetryCount <= mMaxRetries);
+ }
+ return false;
+ }
+
+ private Map<String, String> decodeExtras(String extras) {
+ Map<String, String> results = new HashMap<String, String>();
+ try {
+ URI rawExtras = new URI("?" + extras);
+ List<NameValuePair> extraList = URLEncodedUtils.parse(rawExtras, "UTF-8");
+ for (NameValuePair item : extraList) {
+ results.put(item.getName(), item.getValue());
+ }
+ } catch (URISyntaxException e) {
+ Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
+ }
+ return results;
+ }
+
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/StrictPolicy.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/StrictPolicy.java
new file mode 100644
index 0000000000..d8d83b4e4b
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/StrictPolicy.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+/**
+ * Non-caching policy. All requests will be sent to the licensing service,
+ * and no local caching is performed.
+ * <p>
+ * Using a non-caching policy ensures that there is no local preference data
+ * for malicious users to tamper with. As a side effect, applications
+ * will not be permitted to run while offline. Developers should carefully
+ * weigh the risks of using this Policy over one which implements caching,
+ * such as ServerManagedPolicy.
+ * <p>
+ * Access to the application is only allowed if a LICESNED response is.
+ * received. All other responses (including RETRY) will deny access.
+ */
+public class StrictPolicy implements Policy {
+
+ private int mLastResponse;
+
+ public StrictPolicy() {
+ // Set default policy. This will force the application to check the policy on launch.
+ mLastResponse = Policy.RETRY;
+ }
+
+ /**
+ * Process a new response from the license server. Since we aren't
+ * performing any caching, this equates to reading the LicenseResponse.
+ * Any ResponseData provided is ignored.
+ *
+ * @param response the result from validating the server response
+ * @param rawData the raw server response data
+ */
+ public void processServerResponse(int response, ResponseData rawData) {
+ mLastResponse = response;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * This implementation allows access if and only if a LICENSED response
+ * was received the last time the server was contacted.
+ */
+ public boolean allowAccess() {
+ return (mLastResponse == Policy.LICENSED);
+ }
+
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ValidationException.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ValidationException.java
new file mode 100644
index 0000000000..ee4df47c68
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ValidationException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 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.licensing;
+
+/**
+ * Indicates that an error occurred while validating the integrity of data managed by an
+ * {@link Obfuscator}.}
+ */
+public class ValidationException extends Exception {
+ public ValidationException() {
+ super();
+ }
+
+ public ValidationException(String s) {
+ super(s);
+ }
+
+ private static final long serialVersionUID = 1L;
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64.java
new file mode 100644
index 0000000000..a0d2779af2
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64.java
@@ -0,0 +1,570 @@
+// Portions copyright 2002, Google, Inc.
+//
+// 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.licensing.util;
+
+// This code was converted from code at http://iharder.sourceforge.net/base64/
+// Lots of extraneous features were removed.
+/* The original code said:
+ * <p>
+ * I am placing this code in the Public Domain. Do with it as you will.
+ * This software comes with no guarantees or warranties but with
+ * plenty of well-wishing instead!
+ * Please visit
+ * <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
+ * periodically to check for updates or to contribute improvements.
+ * </p>
+ *
+ * @author Robert Harder
+ * @author rharder@usa.net
+ * @version 1.3
+ */
+
+/**
+ * Base64 converter class. This code is not a full-blown MIME encoder;
+ * it simply converts binary data to base64 data and back.
+ *
+ * <p>Note {@link CharBase64} is a GWT-compatible implementation of this
+ * class.
+ */
+public class Base64 {
+ /** Specify encoding (value is {@code true}). */
+ public final static boolean ENCODE = true;
+
+ /** Specify decoding (value is {@code false}). */
+ public final static boolean DECODE = false;
+
+ /** The equals sign (=) as a byte. */
+ private final static byte EQUALS_SIGN = (byte) '=';
+
+ /** The new line character (\n) as a byte. */
+ private final static byte NEW_LINE = (byte) '\n';
+
+ /**
+ * The 64 valid Base64 values.
+ */
+ private final static byte[] ALPHABET =
+ {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
+ (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
+ (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
+ (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
+ (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
+ (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
+ (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
+ (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
+ (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
+ (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
+ (byte) '9', (byte) '+', (byte) '/'};
+
+ /**
+ * The 64 valid web safe Base64 values.
+ */
+ private final static byte[] WEBSAFE_ALPHABET =
+ {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
+ (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
+ (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
+ (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
+ (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
+ (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
+ (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
+ (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
+ (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
+ (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
+ (byte) '9', (byte) '-', (byte) '_'};
+
+ /**
+ * Translates a Base64 value to either its 6-bit reconstruction value
+ * or a negative number indicating some other meaning.
+ **/
+ private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+ 62, // Plus sign at decimal 43
+ -9, -9, -9, // Decimal 44 - 46
+ 63, // Slash at decimal 47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
+ };
+
+ /** The web safe decodabet */
+ private final static byte[] WEBSAFE_DECODABET =
+ {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
+ 62, // Dash '-' sign at decimal 45
+ -9, -9, // Decimal 46-47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, // Decimal 91-94
+ 63, // Underscore '_' at decimal 95
+ -9, // Decimal 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
+ };
+
+ // Indicates white space in encoding
+ private final static byte WHITE_SPACE_ENC = -5;
+ // Indicates equals sign in encoding
+ private final static byte EQUALS_SIGN_ENC = -1;
+
+ /** Defeats instantiation. */
+ private Base64() {
+ }
+
+ /* ******** E N C O D I N G M E T H O D S ******** */
+
+ /**
+ * Encodes up to three bytes of the array <var>source</var>
+ * and writes the resulting four Base64 bytes to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accommodate <var>srcOffset</var> + 3 for
+ * the <var>source</var> array or <var>destOffset</var> + 4 for
+ * the <var>destination</var> array.
+ * The actual number of significant bytes in your array is
+ * given by <var>numSigBytes</var>.
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param numSigBytes the number of significant bytes in your array
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @param alphabet is the encoding alphabet
+ * @return the <var>destination</var> array
+ * @since 1.3
+ */
+ private static byte[] encode3to4(byte[] source, int srcOffset,
+ int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
+ // 1 2 3
+ // 01234567890123456789012345678901 Bit position
+ // --------000000001111111122222222 Array position from threeBytes
+ // --------| || || || | Six bit groups to index alphabet
+ // >>18 >>12 >> 6 >> 0 Right shift necessary
+ // 0x3f 0x3f 0x3f Additional AND
+
+ // Create buffer with zero-padding if there are only one or two
+ // significant bytes passed in the array.
+ // We have to shift left 24 in order to flush out the 1's that appear
+ // when Java treats a value as negative that is cast from a byte to an int.
+ int inBuff =
+ (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
+ | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
+ | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
+
+ switch (numSigBytes) {
+ case 3:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
+ return destination;
+ case 2:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+ case 1:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = EQUALS_SIGN;
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+ default:
+ return destination;
+ } // end switch
+ } // end encode3to4
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * Equivalent to calling
+ * {@code encodeBytes(source, 0, source.length)}
+ *
+ * @param source The data to convert
+ * @since 1.4
+ */
+ public static String encode(byte[] source) {
+ return encode(source, 0, source.length, ALPHABET, true);
+ }
+
+ /**
+ * Encodes a byte array into web safe Base64 notation.
+ *
+ * @param source The data to convert
+ * @param doPadding is {@code true} to pad result with '=' chars
+ * if it does not fall on 3 byte boundaries
+ */
+ public static String encodeWebSafe(byte[] source, boolean doPadding) {
+ return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
+ }
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @param alphabet is the encoding alphabet
+ * @param doPadding is {@code true} to pad result with '=' chars
+ * if it does not fall on 3 byte boundaries
+ * @since 1.4
+ */
+ public static String encode(byte[] source, int off, int len, byte[] alphabet,
+ boolean doPadding) {
+ byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
+ int outLen = outBuff.length;
+
+ // If doPadding is false, set length to truncate '='
+ // padding characters
+ while (doPadding == false && outLen > 0) {
+ if (outBuff[outLen - 1] != '=') {
+ break;
+ }
+ outLen -= 1;
+ }
+
+ return new String(outBuff, 0, outLen);
+ }
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ *
+ * @param source The data to convert
+ * @param off Offset in array where conversion should begin
+ * @param len Length of data to convert
+ * @param alphabet is the encoding alphabet
+ * @param maxLineLength maximum length of one line.
+ * @return the BASE64-encoded byte array
+ */
+ public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
+ int maxLineLength) {
+ int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
+ int len43 = lenDiv3 * 4;
+ byte[] outBuff = new byte[len43 // Main 4:3
+ + (len43 / maxLineLength)]; // New lines
+
+ int d = 0;
+ int e = 0;
+ int len2 = len - 2;
+ int lineLength = 0;
+ for (; d < len2; d += 3, e += 4) {
+
+ // The following block of code is the same as
+ // encode3to4( source, d + off, 3, outBuff, e, alphabet );
+ // but inlined for faster encoding (~20% improvement)
+ int inBuff =
+ ((source[d + off] << 24) >>> 8)
+ | ((source[d + 1 + off] << 24) >>> 16)
+ | ((source[d + 2 + off] << 24) >>> 24);
+ outBuff[e] = alphabet[(inBuff >>> 18)];
+ outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
+
+ lineLength += 4;
+ if (lineLength == maxLineLength) {
+ outBuff[e + 4] = NEW_LINE;
+ e++;
+ lineLength = 0;
+ } // end if: end of line
+ } // end for: each piece of array
+
+ if (d < len) {
+ encode3to4(source, d + off, len - d, outBuff, e, alphabet);
+
+ lineLength += 4;
+ if (lineLength == maxLineLength) {
+ // Add a last newline
+ outBuff[e + 4] = NEW_LINE;
+ e++;
+ }
+ e += 4;
+ }
+
+ assert (e == outBuff.length);
+ return outBuff;
+ }
+
+
+ /* ******** D E C O D I N G M E T H O D S ******** */
+
+
+ /**
+ * Decodes four bytes from array <var>source</var>
+ * and writes the resulting bytes (up to three of them)
+ * to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accommodate <var>srcOffset</var> + 4 for
+ * the <var>source</var> array or <var>destOffset</var> + 3 for
+ * the <var>destination</var> array.
+ * This method returns the actual number of bytes that
+ * were converted from the Base64 encoding.
+ *
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @param decodabet the decodabet for decoding Base64 content
+ * @return the number of decoded bytes converted
+ * @since 1.3
+ */
+ private static int decode4to3(byte[] source, int srcOffset,
+ byte[] destination, int destOffset, byte[] decodabet) {
+ // Example: Dk==
+ if (source[srcOffset + 2] == EQUALS_SIGN) {
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ return 1;
+ } else if (source[srcOffset + 3] == EQUALS_SIGN) {
+ // Example: DkL=
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
+ | ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ destination[destOffset + 1] = (byte) (outBuff >>> 8);
+ return 2;
+ } else {
+ // Example: DkLE
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
+ | ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
+ | ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
+
+ destination[destOffset] = (byte) (outBuff >> 16);
+ destination[destOffset + 1] = (byte) (outBuff >> 8);
+ destination[destOffset + 2] = (byte) (outBuff);
+ return 3;
+ }
+ } // end decodeToBytes
+
+
+ /**
+ * Decodes data from Base64 notation.
+ *
+ * @param s the string to decode (decoded in default encoding)
+ * @return the decoded data
+ * @since 1.4
+ */
+ public static byte[] decode(String s) throws Base64DecoderException {
+ byte[] bytes = s.getBytes();
+ return decode(bytes, 0, bytes.length);
+ }
+
+ /**
+ * Decodes data from web safe Base64 notation.
+ * Web safe encoding uses '-' instead of '+', '_' instead of '/'
+ *
+ * @param s the string to decode (decoded in default encoding)
+ * @return the decoded data
+ */
+ public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
+ byte[] bytes = s.getBytes();
+ return decodeWebSafe(bytes, 0, bytes.length);
+ }
+
+ /**
+ * Decodes Base64 content in byte array format and returns
+ * the decoded byte array.
+ *
+ * @param source The Base64 encoded data
+ * @return decoded data
+ * @since 1.3
+ * @throws Base64DecoderException
+ */
+ public static byte[] decode(byte[] source) throws Base64DecoderException {
+ return decode(source, 0, source.length);
+ }
+
+ /**
+ * Decodes web safe Base64 content in byte array format and returns
+ * the decoded data.
+ * Web safe encoding uses '-' instead of '+', '_' instead of '/'
+ *
+ * @param source the string to decode (decoded in default encoding)
+ * @return the decoded data
+ */
+ public static byte[] decodeWebSafe(byte[] source)
+ throws Base64DecoderException {
+ return decodeWebSafe(source, 0, source.length);
+ }
+
+ /**
+ * Decodes Base64 content in byte array format and returns
+ * the decoded byte array.
+ *
+ * @param source The Base64 encoded data
+ * @param off The offset of where to begin decoding
+ * @param len The length of characters to decode
+ * @return decoded data
+ * @since 1.3
+ * @throws Base64DecoderException
+ */
+ public static byte[] decode(byte[] source, int off, int len)
+ throws Base64DecoderException {
+ return decode(source, off, len, DECODABET);
+ }
+
+ /**
+ * Decodes web safe Base64 content in byte array format and returns
+ * the decoded byte array.
+ * Web safe encoding uses '-' instead of '+', '_' instead of '/'
+ *
+ * @param source The Base64 encoded data
+ * @param off The offset of where to begin decoding
+ * @param len The length of characters to decode
+ * @return decoded data
+ */
+ public static byte[] decodeWebSafe(byte[] source, int off, int len)
+ throws Base64DecoderException {
+ return decode(source, off, len, WEBSAFE_DECODABET);
+ }
+
+ /**
+ * Decodes Base64 content using the supplied decodabet and returns
+ * the decoded byte array.
+ *
+ * @param source The Base64 encoded data
+ * @param off The offset of where to begin decoding
+ * @param len The length of characters to decode
+ * @param decodabet the decodabet for decoding Base64 content
+ * @return decoded data
+ */
+ public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
+ throws Base64DecoderException {
+ int len34 = len * 3 / 4;
+ byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
+ int outBuffPosn = 0;
+
+ byte[] b4 = new byte[4];
+ int b4Posn = 0;
+ int i = 0;
+ byte sbiCrop = 0;
+ byte sbiDecode = 0;
+ for (i = 0; i < len; i++) {
+ sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
+ sbiDecode = decodabet[sbiCrop];
+
+ if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
+ if (sbiDecode >= EQUALS_SIGN_ENC) {
+ // An equals sign (for padding) must not occur at position 0 or 1
+ // and must be the last byte[s] in the encoded value
+ if (sbiCrop == EQUALS_SIGN) {
+ int bytesLeft = len - i;
+ byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
+ if (b4Posn == 0 || b4Posn == 1) {
+ throw new Base64DecoderException(
+ "invalid padding byte '=' at byte offset " + i);
+ } else if ((b4Posn == 3 && bytesLeft > 2)
+ || (b4Posn == 4 && bytesLeft > 1)) {
+ throw new Base64DecoderException(
+ "padding byte '=' falsely signals end of encoded value "
+ + "at offset " + i);
+ } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
+ throw new Base64DecoderException(
+ "encoded value has invalid trailing byte");
+ }
+ break;
+ }
+
+ b4[b4Posn++] = sbiCrop;
+ if (b4Posn == 4) {
+ outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
+ b4Posn = 0;
+ }
+ }
+ } else {
+ throw new Base64DecoderException("Bad Base64 input character at " + i
+ + ": " + source[i + off] + "(decimal)");
+ }
+ }
+
+ // Because web safe encoding allows non padding base64 encodes, we
+ // need to pad the rest of the b4 buffer with equal signs when
+ // b4Posn != 0. There can be at most 2 equal signs at the end of
+ // four characters, so the b4 buffer must have two or three
+ // characters. This also catches the case where the input is
+ // padded with EQUALS_SIGN
+ if (b4Posn != 0) {
+ if (b4Posn == 1) {
+ throw new Base64DecoderException("single trailing character at offset "
+ + (len - 1));
+ }
+ b4[b4Posn++] = EQUALS_SIGN;
+ outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
+ }
+
+ byte[] out = new byte[outBuffPosn];
+ System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
+ return out;
+ }
+}
diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64DecoderException.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64DecoderException.java
new file mode 100644
index 0000000000..1aef1b54b8
--- /dev/null
+++ b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64DecoderException.java
@@ -0,0 +1,32 @@
+// Copyright 2002, Google, Inc.
+//
+// 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.licensing.util;
+
+/**
+ * Exception thrown when encountering an invalid Base64 input character.
+ *
+ * @author nelson
+ */
+public class Base64DecoderException extends Exception {
+ public Base64DecoderException() {
+ super();
+ }
+
+ public Base64DecoderException(String s) {
+ super(s);
+ }
+
+ private static final long serialVersionUID = 1L;
+}
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 9d9da3750e..3a101515da 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -34,7 +34,7 @@
#include "drivers/unix/dir_access_unix.h"
#include "servers/visual/visual_server_raster.h"
-
+#include "servers/visual/visual_server_wrap_mt.h"
#include "main/main.h"
#include "core/globals.h"
@@ -128,6 +128,10 @@ void OS_Android::initialize(const VideoMode& p_desired,int p_video_driver,int p_
}
visual_server = memnew( VisualServerRaster(rasterizer) );
+ if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
+
+ visual_server = memnew(VisualServerWrapMT(visual_server, false));
+ };
visual_server->init();
visual_server->cursor_set_visible(false, 0);
@@ -672,8 +676,29 @@ String OS_Android::get_unique_ID() const {
return OS::get_unique_ID();
}
+Error OS_Android::native_video_play(String p_path) {
+ if (video_play_func)
+ video_play_func(p_path);
+ return OK;
+}
+
+bool OS_Android::native_video_is_playing() {
+ if (video_is_playing_func)
+ return video_is_playing_func();
+ return false;
+}
+
+void OS_Android::native_video_pause() {
+ if (video_pause_func)
+ video_pause_func();
+}
+
+void OS_Android::native_video_stop() {
+ if (video_stop_func)
+ video_stop_func();
+}
-OS_Android::OS_Android(GFXInitFunc p_gfx_init_func,void*p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetDataDirFunc p_get_data_dir_func,GetLocaleFunc p_get_locale_func,GetModelFunc p_get_model_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, SetScreenOrientationFunc p_screen_orient,GetUniqueIDFunc p_get_unique_id) {
+OS_Android::OS_Android(GFXInitFunc p_gfx_init_func,void*p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetDataDirFunc p_get_data_dir_func,GetLocaleFunc p_get_locale_func,GetModelFunc p_get_model_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, SetScreenOrientationFunc p_screen_orient,GetUniqueIDFunc p_get_unique_id, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func) {
default_videomode.width=800;
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index 93c672927e..76139c8a2d 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -41,16 +41,19 @@
#ifdef ANDROID_NATIVE_ACTIVITY
-#include "audio_driver_android.h"
+
#include <android/sensor.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#else
-#include "audio_driver_jandroid.h"
+
#endif
+#include "audio_driver_jandroid.h"
+#include "audio_driver_opensl.h"
+
typedef void (*GFXInitFunc)(void *ud,bool gl2);
typedef int (*OpenURIFunc)(const String&);
typedef String (*GetDataDirFunc)();
@@ -61,6 +64,11 @@ typedef void (*ShowVirtualKeyboardFunc)(const String&);
typedef void (*HideVirtualKeyboardFunc)();
typedef void (*SetScreenOrientationFunc)(int);
+typedef void (*VideoPlayFunc)(const String&);
+typedef bool (*VideoIsPlayingFunc)();
+typedef void (*VideoPauseFunc)();
+typedef void (*VideoStopFunc)();
+
class OS_Android : public OS_Unix {
public:
@@ -89,7 +97,12 @@ private:
SpatialSound2DServerSW *spatial_sound_2d_server;
PhysicsServer *physics_server;
Physics2DServer *physics_2d_server;
+#if 0
AudioDriverAndroid audio_driver_android;
+#else
+ AudioDriverOpenSL audio_driver_android;
+#endif
+
const char* gl_extensions;
InputDefault *input;
@@ -105,6 +118,11 @@ private:
SetScreenOrientationFunc set_screen_orientation_func;
GetUniqueIDFunc get_unique_id_func;
+ VideoPlayFunc video_play_func;
+ VideoIsPlayingFunc video_is_playing_func;
+ VideoPauseFunc video_pause_func;
+ VideoStopFunc video_stop_func;
+
public:
// functions used by main to initialize/deintialize the OS
@@ -189,7 +207,13 @@ public:
void process_touch(int p_what,int p_pointer, const Vector<TouchPos>& p_points);
void process_event(InputEvent p_event);
void init_video_mode(int p_video_width,int p_video_height);
- OS_Android(GFXInitFunc p_gfx_init_func,void*p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetDataDirFunc p_get_data_dir_func,GetLocaleFunc p_get_locale_func,GetModelFunc p_get_model_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, SetScreenOrientationFunc p_screen_orient,GetUniqueIDFunc p_get_unique_id);
+
+ virtual Error native_video_play(String p_path);
+ virtual bool native_video_is_playing();
+ virtual void native_video_pause();
+ virtual void native_video_stop();
+
+ OS_Android(GFXInitFunc p_gfx_init_func,void*p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetDataDirFunc p_get_data_dir_func,GetLocaleFunc p_get_locale_func,GetModelFunc p_get_model_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, SetScreenOrientationFunc p_screen_orient,GetUniqueIDFunc p_get_unique_id, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func);
~OS_Android();
};
diff --git a/platform/android/project.properties.template b/platform/android/project.properties.template
index 72bea9659c..d81b72b525 100644
--- a/platform/android/project.properties.template
+++ b/platform/android/project.properties.template
@@ -12,4 +12,4 @@
# Project target.
#android.library=true
-target=android-13
+target=android-15
diff --git a/platform/android/sign.sh b/platform/android/sign.sh
index 830da05a37..8f760e6312 100755
--- a/platform/android/sign.sh
+++ b/platform/android/sign.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore my-release-key.keystore "$1" reduz
+jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore /home/luis/Downloads/carnavalguachin.keystore -storepass 12345678 "$1" momoselacome
echo ""
echo ""
diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py
index ed935839d8..61ec87ccc1 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -82,7 +82,8 @@ def configure(env):
'-framework', 'AudioToolbox',
'-framework', 'SystemConfiguration',
'-framework', 'Security',
- '-framework', 'AdSupport',
+ #'-framework', 'AdSupport',
+ '-framework', 'MediaPlayer',
])
if env['game_center'] == 'yes':
diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h
index 58a040eb6d..df1d980c04 100644
--- a/platform/iphone/game_center.h
+++ b/platform/iphone/game_center.h
@@ -50,7 +50,6 @@ public:
bool is_connected();
Error post_score(Variant p_score);
-
Error award_achievement(Variant p_params);
int get_pending_event_count();
diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h
index 0d95af9ab1..3e6181ab90 100755
--- a/platform/iphone/gl_view.h
+++ b/platform/iphone/gl_view.h
@@ -31,6 +31,7 @@
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
+#import <MediaPlayer/MediaPlayer.h>
@protocol GLViewDelegate;
@@ -65,6 +66,9 @@
@property(nonatomic, assign) id<GLViewDelegate> delegate;
+@property(strong, nonatomic) MPMoviePlayerController *moviePlayerController;
+@property(strong, nonatomic) UIWindow *backgroundWindow;
+
-(void)startAnimation;
-(void)stopAnimation;
-(void)drawView;
diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm
index 07f5b39fac..a603e8dbf3 100755
--- a/platform/iphone/gl_view.mm
+++ b/platform/iphone/gl_view.mm
@@ -31,6 +31,7 @@
#import <OpenGLES/EAGLDrawable.h>
#include "os_iphone.h"
#include "core/os/keyboard.h"
+#include "core/globals.h"
#import "gl_view.h"
@@ -59,6 +60,48 @@ void _hide_keyboard() {
keyboard_text = "";
};
+bool _play_video(String p_path) {
+
+ p_path = Globals::get_singleton()->globalize_path(p_path);
+
+ // NSString *file_path = [NSString stringWithCString:p_path.utf8().get_data() encoding:NSASCIIStringEncoding];
+ NSString* file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease];
+ NSURL *file_url = [NSURL fileURLWithPath:file_path];
+
+ _instance.moviePlayerController = [[MPMoviePlayerController alloc] initWithContentURL:file_url];
+ _instance.moviePlayerController.controlStyle = MPMovieControlStyleNone;
+ [_instance.moviePlayerController setScalingMode:MPMovieScalingModeAspectFit];
+ // [_instance.moviePlayerController setScalingMode:MPMovieScalingModeAspectFill];
+
+ [[NSNotificationCenter defaultCenter] addObserver:_instance
+ selector:@selector(moviePlayBackDidFinish:)
+ name:MPMoviePlayerPlaybackDidFinishNotification
+ object:_instance.moviePlayerController];
+
+ [[_instance window] makeKeyAndVisible];
+ _instance.backgroundWindow = [[UIApplication sharedApplication] keyWindow];
+ [_instance.moviePlayerController.view setFrame:_instance.backgroundWindow.frame];
+ _instance.moviePlayerController.view.userInteractionEnabled = NO;
+ [_instance.backgroundWindow addSubview:_instance.moviePlayerController.view];
+ [_instance.moviePlayerController play];
+
+ return true;
+}
+
+bool _is_video_playing() {
+ NSInteger playback_state = _instance.moviePlayerController.playbackState;
+ return (playback_state == MPMoviePlaybackStatePlaying);
+}
+
+void _pause_video() {
+ [_instance.moviePlayerController pause];
+}
+
+void _stop_video() {
+ [_instance.moviePlayerController stop];
+ [_instance.moviePlayerController.view removeFromSuperview];
+}
+
@implementation GLView
@synthesize animationInterval;
@@ -189,8 +232,10 @@ static void clear_touches() {
// If our view is resized, we'll be asked to layout subviews.
// This is the perfect opportunity to also update the framebuffer so that it is
// the same size as our display area.
+
-(void)layoutSubviews
{
+ printf("HERE\n");
[EAGLContext setCurrentContext:context];
[self destroyFramebuffer];
[self createFramebuffer];
@@ -201,6 +246,7 @@ static void clear_touches() {
{
// Generate IDs for a framebuffer object and a color renderbuffer
UIScreen* mainscr = [UIScreen mainScreen];
+ printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height);
if (mainscr.currentMode.size.width == 640 || mainscr.currentMode.size.width == 960) // modern iphone, can go to 640x960
self.contentScaleFactor = 2.0;
@@ -454,4 +500,18 @@ static void clear_touches() {
[super dealloc];
}
+- (void)moviePlayBackDidFinish:(NSNotification*)notification {
+ MPMoviePlayerController *player = [notification object];
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:MPMoviePlayerPlaybackDidFinishNotification
+ object:player];
+
+ _stop_video();
+}
+
+- (void)handleTap:(UITapGestureRecognizer *)gesture {
+ NSLog(@"Gesture\n");
+}
+
@end
diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.cpp
index eb3f17736a..b86af007f8 100644
--- a/platform/iphone/godot_iphone.cpp
+++ b/platform/iphone/godot_iphone.cpp
@@ -37,6 +37,7 @@ static OSIPhone* os = NULL;
extern "C" {
int add_path(int p_argc, char** p_args);
+int add_cmdline(int p_argc, char** p_args);
};
int iphone_main(int width, int height, int argc, char** argv) {
@@ -67,6 +68,7 @@ int iphone_main(int width, int height, int argc, char** argv) {
};
fargv[argc] = NULL;
argc = add_path(argc, fargv);
+ argc = add_cmdline(argc, fargv);
printf("os created\n");
Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp
index 3b3b30cb86..756e8b575e 100644
--- a/platform/iphone/os_iphone.cpp
+++ b/platform/iphone/os_iphone.cpp
@@ -422,6 +422,8 @@ void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) c
bool OSIPhone::can_draw() const {
+ if (native_video_is_playing())
+ return false;
return true;
};
@@ -483,6 +485,31 @@ String OSIPhone::get_locale() const {
return locale_code;
}
+extern bool _play_video(String p_path);
+extern bool _is_video_playing();
+extern void _pause_video();
+extern void _stop_video();
+
+Error OSIPhone::native_video_play(String p_path) {
+ if ( _play_video(p_path) )
+ return OK;
+ return FAILED;
+}
+
+bool OSIPhone::native_video_is_playing() const {
+ return _is_video_playing();
+}
+
+void OSIPhone::native_video_pause() {
+ if (native_video_is_playing())
+ _pause_video();
+}
+
+void OSIPhone::native_video_stop() {
+ if (native_video_is_playing())
+ _stop_video();
+}
+
OSIPhone::OSIPhone(int width, int height) {
rasterizer_gles22 = NULL;
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index a8ccba02b5..643bf2b5e3 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -184,6 +184,11 @@ public:
void set_unique_ID(String p_ID);
String get_unique_ID() const;
+ virtual Error native_video_play(String p_path);
+ virtual bool native_video_is_playing() const;
+ virtual void native_video_pause();
+ virtual void native_video_stop();
+
OSIPhone(int width, int height);
~OSIPhone();
};
diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h
index ac94d7ed3a..7c4a107bb8 100644
--- a/platform/iphone/view_controller.h
+++ b/platform/iphone/view_controller.h
@@ -37,4 +37,6 @@
- (void)didReceiveMemoryWarning;
+- (BOOL)prefersStatusBarHidden;
+
@end
diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm
index 8af20358b9..eb331a61de 100644
--- a/platform/iphone/view_controller.mm
+++ b/platform/iphone/view_controller.mm
@@ -46,6 +46,26 @@ int add_path(int p_argc, char** p_args) {
return p_argc;
};
+int add_cmdline(int p_argc, char** p_args) {
+
+ NSArray* arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
+ if (!arr)
+ return p_argc;
+
+ for (int i=0; i < [arr count]; i++) {
+
+ NSString* str = [arr objectAtIndex:i];
+ if (!str)
+ continue;
+ [str retain]; // @todo delete these at some point
+ p_args[p_argc++] = (char*)[str cString];
+ };
+
+ p_args[p_argc] = NULL;
+
+ return p_argc;
+};
+
};
@interface ViewController ()
@@ -104,4 +124,10 @@ int add_path(int p_argc, char** p_args) {
}
};
+
+- (BOOL)prefersStatusBarHidden
+{
+ return YES;
+}
+
@end
diff --git a/platform/isim/detect.py b/platform/isim/detect.py
index 196d269deb..48f71bbef5 100644
--- a/platform/isim/detect.py
+++ b/platform/isim/detect.py
@@ -74,6 +74,7 @@ def configure(env):
'-framework', 'OpenGLES',
'-framework', 'QuartzCore',
'-framework', 'AudioToolbox',
+ '-framework', 'MediaPlayer',
'-F$ISIMSDK',
])
diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp
index 70b5a652d5..ad228662e4 100644
--- a/scene/2d/area_2d.cpp
+++ b/scene/2d/area_2d.cpp
@@ -317,6 +317,7 @@ Area2D::Area2D() : CollisionObject2D(Physics2DServer::get_singleton()->area_crea
density=0.1;
priority=0;
monitoring=false;
+ set_enable_monitoring(true);
}
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index acae3e62c4..fe6c9637e0 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -26,483 +26,501 @@
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "camera_2d.h"
-#include "scene/scene_string_names.h"
-#include "servers/visual_server.h"
-
-void Camera2D::_update_scroll() {
-
-
- if (!is_inside_scene())
- return;
-
- if (get_scene()->is_editor_hint()) {
- update(); //will just be drawn
- return;
- }
-
- if (current) {
- Matrix32 xform = get_camera_transform();
-
- RID vp = viewport->get_viewport();
- if (viewport) {
- viewport->set_canvas_transform( xform );
- }
- get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,group_name,"_camera_moved",xform);
- };
-
-}
-
-void Camera2D::set_zoom(const Vector2 &p_zoom) {
-
- zoom = p_zoom;
- _update_scroll();
-};
-
-Vector2 Camera2D::get_zoom() const {
-
- return zoom;
-};
-
-
-Matrix32 Camera2D::get_camera_transform() {
-
- if (!get_scene())
- return Matrix32();
-
- Size2 screen_size = get_viewport_rect().size;
- screen_size=get_viewport_rect().size;
-
-
- Point2 new_camera_pos = get_global_transform().get_origin();
- Point2 ret_camera_pos;
-
- if (!first) {
-
-
- if (centered) {
-
- if (h_drag_enabled) {
- camera_pos.x = MIN( camera_pos.x, (new_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_RIGHT]));
- camera_pos.x = MAX( camera_pos.x, (new_camera_pos.x - screen_size.x * 0.5 * drag_margin[MARGIN_LEFT]));
- } else {
-
- if (h_ofs<0) {
- camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_RIGHT] * h_ofs;
- } else {
- camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_LEFT] * h_ofs;
- }
- }
-
- if (v_drag_enabled) {
-
- camera_pos.y = MIN( camera_pos.y, (new_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_BOTTOM]));
- camera_pos.y = MAX( camera_pos.y, (new_camera_pos.y - screen_size.y * 0.5 * drag_margin[MARGIN_TOP]));
-
- } else {
-
- if (v_ofs<0) {
- camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_TOP] * v_ofs;
- } else {
- camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_BOTTOM] * v_ofs;
- }
- }
-
- }
-
-
- if (smoothing>0.0) {
-
- float c = smoothing*get_fixed_process_delta_time();
- smoothed_camera_pos = ((new_camera_pos-smoothed_camera_pos)*c)+smoothed_camera_pos;
- ret_camera_pos=smoothed_camera_pos;
-// camera_pos=camera_pos*(1.0-smoothing)+new_camera_pos*smoothing;
- } else {
-
- ret_camera_pos=smoothed_camera_pos=camera_pos;
-
- }
-
-
-
- } else {
- ret_camera_pos=smoothed_camera_pos=camera_pos=new_camera_pos;
- first=false;
- }
-
-
- Point2 screen_offset = (centered ? (screen_size * 0.5 * zoom) : Point2());;
- screen_offset+=offset;
-
- float angle = get_global_transform().get_rotation();
- if(rotating){
- screen_offset = screen_offset.rotated(angle);
- }
-
- Rect2 screen_rect(-screen_offset+ret_camera_pos,screen_size);
- if (screen_rect.pos.x + screen_rect.size.x > limit[MARGIN_RIGHT])
- screen_rect.pos.x = limit[MARGIN_RIGHT] - screen_rect.size.x;
-
- if (screen_rect.pos.y + screen_rect.size.y > limit[MARGIN_BOTTOM])
- screen_rect.pos.y = limit[MARGIN_BOTTOM] - screen_rect.size.y;
-
-
- if (screen_rect.pos.x < limit[MARGIN_LEFT])
- screen_rect.pos.x=limit[MARGIN_LEFT];
-
- if (screen_rect.pos.y < limit[MARGIN_TOP])
- screen_rect.pos.y =limit[MARGIN_TOP];
-
- camera_screen_center=screen_rect.pos+screen_rect.size*0.5;
-
- Matrix32 xform;
- if(rotating){
- xform.set_rotation(angle);
- }
- xform.scale_basis(zoom);
- xform.set_origin(screen_rect.pos/*.floor()*/);
-
-
-/*
- if (0) {
-
- xform = get_global_transform() * xform;
- } else {
-
- xform.elements[2]+=get_global_transform().get_origin();
- }
-*/
-
-
- return (xform).affine_inverse();
-}
-
-
-
-void Camera2D::_notification(int p_what) {
-
- switch(p_what) {
-
- case NOTIFICATION_FIXED_PROCESS: {
-
- _update_scroll();
-
- } break;
- case NOTIFICATION_TRANSFORM_CHANGED: {
-
-
- if (!is_fixed_processing())
- _update_scroll();
-
- } break;
- case NOTIFICATION_ENTER_SCENE: {
-
- viewport = NULL;
- Node *n=this;
- while(n){
-
- viewport = n->cast_to<Viewport>();
- if (viewport)
- break;
- n=n->get_parent();
- }
-
- canvas = get_canvas();
-
- RID vp = viewport->get_viewport();
-
- group_name = "__cameras_"+itos(vp.get_id());
- canvas_group_name ="__cameras_c"+itos(canvas.get_id());
- add_to_group(group_name);
- add_to_group(canvas_group_name);
-
- _update_scroll();
- first=true;
-
-
- } break;
- case NOTIFICATION_EXIT_SCENE: {
-
- if (is_current()) {
- if (viewport) {
- viewport->set_canvas_transform( Matrix32() );
- }
- }
- remove_from_group(group_name);
- remove_from_group(canvas_group_name);
- viewport=NULL;
-
- } break;
- }
-}
-
-void Camera2D::set_offset(const Vector2& p_offset) {
-
- offset=p_offset;
- _update_scroll();
-
-}
-
-Vector2 Camera2D::get_offset() const{
-
- return offset;
-}
-
-void Camera2D::set_centered(bool p_centered){
-
- centered=p_centered;
- _update_scroll();
-}
-
-bool Camera2D::is_centered() const {
-
- return centered;
-}
-
-void Camera2D::set_rotating(bool p_rotating){
-
- rotating=p_rotating;
- _update_scroll();
-}
-
-bool Camera2D::is_rotating() const {
-
- return rotating;
-}
-
-
-void Camera2D::_make_current(Object *p_which) {
-
- if (p_which==this) {
-
- current=true;
- _update_scroll();
- } else {
- current=false;
- }
-}
-
-
-void Camera2D::_set_current(bool p_current) {
-
- if (p_current)
- make_current();
-
- current=p_current;
-}
-
-bool Camera2D::is_current() const {
-
- return current;
-}
-
-void Camera2D::make_current() {
-
- if (!is_inside_scene()) {
- current=true;
- } else {
- get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,group_name,"_make_current",this);
- }
-}
-
-void Camera2D::set_limit(Margin p_margin,int p_limit) {
-
- ERR_FAIL_INDEX(p_margin,4);
- limit[p_margin]=p_limit;
-}
-
-int Camera2D::get_limit(Margin p_margin) const{
-
- ERR_FAIL_INDEX_V(p_margin,4,0);
- return limit[p_margin];
-
-}
-
-void Camera2D::set_drag_margin(Margin p_margin,float p_drag_margin) {
-
- ERR_FAIL_INDEX(p_margin,4);
- drag_margin[p_margin]=p_drag_margin;
-}
-
-float Camera2D::get_drag_margin(Margin p_margin) const{
-
- ERR_FAIL_INDEX_V(p_margin,4,0);
- return drag_margin[p_margin];
-
-}
-
-
-Vector2 Camera2D::get_camera_pos() const {
-
-
- return camera_pos;
-}
-
-void Camera2D::force_update_scroll() {
-
-
- _update_scroll();
-}
-
-
-void Camera2D::set_follow_smoothing(float p_speed) {
-
- smoothing=p_speed;
- if (smoothing>0)
- set_fixed_process(true);
- else
- set_fixed_process(false);
-}
-
-float Camera2D::get_follow_smoothing() const{
-
- return smoothing;
-}
-
-Point2 Camera2D::get_camera_screen_center() const {
-
- return camera_screen_center;
-}
-
-
-void Camera2D::set_h_drag_enabled(bool p_enabled) {
-
- h_drag_enabled=p_enabled;
-}
-
-bool Camera2D::is_h_drag_enabled() const{
-
- return h_drag_enabled;
-}
-
-void Camera2D::set_v_drag_enabled(bool p_enabled){
-
- v_drag_enabled=p_enabled;
-}
-
-bool Camera2D::is_v_drag_enabled() const{
-
- return v_drag_enabled;
-}
-
-void Camera2D::set_v_offset(float p_offset) {
-
- v_ofs=p_offset;
-}
-
-float Camera2D::get_v_offset() const{
-
- return v_ofs;
-}
-
-void Camera2D::set_h_offset(float p_offset){
-
- h_ofs=p_offset;
-}
-float Camera2D::get_h_offset() const{
-
- return h_ofs;
-}
-
-
-void Camera2D::_bind_methods() {
-
- ObjectTypeDB::bind_method(_MD("set_offset","offset"),&Camera2D::set_offset);
- ObjectTypeDB::bind_method(_MD("get_offset"),&Camera2D::get_offset);
-
- ObjectTypeDB::bind_method(_MD("set_centered","centered"),&Camera2D::set_centered);
- ObjectTypeDB::bind_method(_MD("is_centered"),&Camera2D::is_centered);
-
- ObjectTypeDB::bind_method(_MD("set_rotating","rotating"),&Camera2D::set_rotating);
- ObjectTypeDB::bind_method(_MD("is_rotating"),&Camera2D::is_rotating);
-
- ObjectTypeDB::bind_method(_MD("make_current"),&Camera2D::make_current);
- ObjectTypeDB::bind_method(_MD("_make_current"),&Camera2D::_make_current);
-
- ObjectTypeDB::bind_method(_MD("_update_scroll"),&Camera2D::_update_scroll);
-
-
- ObjectTypeDB::bind_method(_MD("_set_current","current"),&Camera2D::_set_current);
- ObjectTypeDB::bind_method(_MD("is_current"),&Camera2D::is_current);
-
- ObjectTypeDB::bind_method(_MD("set_limit","margin","limit"),&Camera2D::set_limit);
- ObjectTypeDB::bind_method(_MD("get_limit","margin"),&Camera2D::get_limit);
-
- ObjectTypeDB::bind_method(_MD("set_v_drag_enabled","enabled"),&Camera2D::set_v_drag_enabled);
- ObjectTypeDB::bind_method(_MD("is_v_drag_enabled"),&Camera2D::is_v_drag_enabled);
-
- ObjectTypeDB::bind_method(_MD("set_h_drag_enabled","enabled"),&Camera2D::set_h_drag_enabled);
- ObjectTypeDB::bind_method(_MD("is_h_drag_enabled"),&Camera2D::is_h_drag_enabled);
-
- ObjectTypeDB::bind_method(_MD("set_v_offset","ofs"),&Camera2D::set_v_offset);
- ObjectTypeDB::bind_method(_MD("get_v_offset"),&Camera2D::get_v_offset);
-
- ObjectTypeDB::bind_method(_MD("set_h_offset","ofs"),&Camera2D::set_h_offset);
- ObjectTypeDB::bind_method(_MD("get_h_offset"),&Camera2D::get_h_offset);
-
- ObjectTypeDB::bind_method(_MD("set_drag_margin","margin","drag_margin"),&Camera2D::set_drag_margin);
- ObjectTypeDB::bind_method(_MD("get_drag_margin","margin"),&Camera2D::get_drag_margin);
-
- ObjectTypeDB::bind_method(_MD("get_camera_pos"),&Camera2D::get_camera_pos);
- ObjectTypeDB::bind_method(_MD("get_camera_screen_center"),&Camera2D::get_camera_screen_center);
-
- ObjectTypeDB::bind_method(_MD("set_zoom"),&Camera2D::set_zoom);
- ObjectTypeDB::bind_method(_MD("get_zoom"),&Camera2D::get_zoom);
-
-
- ObjectTypeDB::bind_method(_MD("set_follow_smoothing","follow_smoothing"),&Camera2D::set_follow_smoothing);
- ObjectTypeDB::bind_method(_MD("get_follow_smoothing"),&Camera2D::get_follow_smoothing);
-
- ObjectTypeDB::bind_method(_MD("force_update_scroll"),&Camera2D::force_update_scroll);
-
-
- ADD_PROPERTYNZ( PropertyInfo(Variant::VECTOR2,"offset"),_SCS("set_offset"),_SCS("get_offset"));
- ADD_PROPERTY( PropertyInfo(Variant::BOOL,"centered"),_SCS("set_centered"),_SCS("is_centered"));
- ADD_PROPERTY( PropertyInfo(Variant::BOOL,"rotating"),_SCS("set_rotating"),_SCS("is_rotating"));
- ADD_PROPERTY( PropertyInfo(Variant::BOOL,"current"),_SCS("_set_current"),_SCS("is_current"));
- ADD_PROPERTY( PropertyInfo(Variant::REAL,"smoothing"),_SCS("set_follow_smoothing"),_SCS("get_follow_smoothing") );
- ADD_PROPERTY( PropertyInfo(Variant::VECTOR2,"zoom"),_SCS("set_zoom"),_SCS("get_zoom") );
-
- ADD_PROPERTYI( PropertyInfo(Variant::INT,"limit/left"),_SCS("set_limit"),_SCS("get_limit"),MARGIN_LEFT);
- ADD_PROPERTYI( PropertyInfo(Variant::INT,"limit/top"),_SCS("set_limit"),_SCS("get_limit"),MARGIN_TOP);
- ADD_PROPERTYI( PropertyInfo(Variant::INT,"limit/right"),_SCS("set_limit"),_SCS("get_limit"),MARGIN_RIGHT);
- ADD_PROPERTYI( PropertyInfo(Variant::INT,"limit/bottom"),_SCS("set_limit"),_SCS("get_limit"),MARGIN_BOTTOM);
-
- ADD_PROPERTY( PropertyInfo(Variant::BOOL,"drag_margin/h_enabled"),_SCS("set_h_drag_enabled"),_SCS("is_h_drag_enabled") );
- ADD_PROPERTY( PropertyInfo(Variant::BOOL,"drag_margin/v_enabled"),_SCS("set_v_drag_enabled"),_SCS("is_v_drag_enabled") );
-
- ADD_PROPERTYI( PropertyInfo(Variant::REAL,"drag_margin/left",PROPERTY_HINT_RANGE,"0,1,0.01"),_SCS("set_drag_margin"),_SCS("get_drag_margin"),MARGIN_LEFT);
- ADD_PROPERTYI( PropertyInfo(Variant::REAL,"drag_margin/top",PROPERTY_HINT_RANGE,"0,1,0.01"),_SCS("set_drag_margin"),_SCS("get_drag_margin"),MARGIN_TOP);
- ADD_PROPERTYI( PropertyInfo(Variant::REAL,"drag_margin/right",PROPERTY_HINT_RANGE,"0,1,0.01"),_SCS("set_drag_margin"),_SCS("get_drag_margin"),MARGIN_RIGHT);
- ADD_PROPERTYI( PropertyInfo(Variant::REAL,"drag_margin/bottom",PROPERTY_HINT_RANGE,"0,1,0.01"),_SCS("set_drag_margin"),_SCS("get_drag_margin"),MARGIN_BOTTOM);
-
-
-
-}
-
-Camera2D::Camera2D() {
-
-
-
- centered=true;
- rotating=false;
- current=false;
- limit[MARGIN_LEFT]=-10000000;
- limit[MARGIN_TOP]=-10000000;
- limit[MARGIN_RIGHT]=10000000;
- limit[MARGIN_BOTTOM]=10000000;
- drag_margin[MARGIN_LEFT]=0.2;
- drag_margin[MARGIN_TOP]=0.2;
- drag_margin[MARGIN_RIGHT]=0.2;
- drag_margin[MARGIN_BOTTOM]=0.2;
- camera_pos=Vector2();
-
- smoothing=0.0;
- zoom = Vector2(1, 1);
-
- h_drag_enabled=true;
- v_drag_enabled=true;
- h_ofs=0;
- v_ofs=0;
-
-}
+#include "camera_2d.h"
+#include "scene/scene_string_names.h"
+#include "servers/visual_server.h"
+
+void Camera2D::_update_scroll() {
+
+
+ if (!is_inside_scene())
+ return;
+
+ if (get_scene()->is_editor_hint()) {
+ update(); //will just be drawn
+ return;
+ }
+
+ if (current) {
+ Matrix32 xform = get_camera_transform();
+
+ RID vp = viewport->get_viewport();
+ if (viewport) {
+ viewport->set_canvas_transform( xform );
+ }
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,group_name,"_camera_moved",xform);
+ };
+
+}
+
+void Camera2D::set_zoom(const Vector2 &p_zoom) {
+
+ zoom = p_zoom;
+ _update_scroll();
+};
+
+Vector2 Camera2D::get_zoom() const {
+
+ return zoom;
+};
+
+
+Matrix32 Camera2D::get_camera_transform() {
+
+ if (!get_scene())
+ return Matrix32();
+
+ Size2 screen_size = get_viewport_rect().size;
+ screen_size=get_viewport_rect().size;
+
+
+ Point2 new_camera_pos = get_global_transform().get_origin();
+ Point2 ret_camera_pos;
+
+ if (!first) {
+
+
+ if (centered) {
+
+ if (h_drag_enabled) {
+ camera_pos.x = MIN( camera_pos.x, (new_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_RIGHT]));
+ camera_pos.x = MAX( camera_pos.x, (new_camera_pos.x - screen_size.x * 0.5 * drag_margin[MARGIN_LEFT]));
+ } else {
+
+ if (h_ofs<0) {
+ camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_RIGHT] * h_ofs;
+ } else {
+ camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_LEFT] * h_ofs;
+ }
+ }
+
+ if (v_drag_enabled) {
+
+ camera_pos.y = MIN( camera_pos.y, (new_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_BOTTOM]));
+ camera_pos.y = MAX( camera_pos.y, (new_camera_pos.y - screen_size.y * 0.5 * drag_margin[MARGIN_TOP]));
+
+ } else {
+
+ if (v_ofs<0) {
+ camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_TOP] * v_ofs;
+ } else {
+ camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_BOTTOM] * v_ofs;
+ }
+ }
+
+ }
+
+
+ if (smoothing>0.0) {
+
+ float c = smoothing*get_fixed_process_delta_time();
+ smoothed_camera_pos = ((new_camera_pos-smoothed_camera_pos)*c)+smoothed_camera_pos;
+ ret_camera_pos=smoothed_camera_pos;
+// camera_pos=camera_pos*(1.0-smoothing)+new_camera_pos*smoothing;
+ } else {
+
+ ret_camera_pos=smoothed_camera_pos=camera_pos;
+
+ }
+
+
+
+ } else {
+ ret_camera_pos=smoothed_camera_pos=camera_pos=new_camera_pos;
+ first=false;
+ }
+
+
+ Point2 screen_offset = (centered ? (screen_size * 0.5 * zoom) : Point2());;
+ screen_offset;
+
+ float angle = get_global_transform().get_rotation();
+ if(rotating){
+ screen_offset = screen_offset.rotated(angle);
+ }
+
+ Rect2 screen_rect(-screen_offset+ret_camera_pos,screen_size);
+ if (screen_rect.pos.x + screen_rect.size.x > limit[MARGIN_RIGHT])
+ screen_rect.pos.x = limit[MARGIN_RIGHT] - screen_rect.size.x;
+
+ if (screen_rect.pos.y + screen_rect.size.y > limit[MARGIN_BOTTOM])
+ screen_rect.pos.y = limit[MARGIN_BOTTOM] - screen_rect.size.y;
+
+
+ if (screen_rect.pos.x < limit[MARGIN_LEFT])
+ screen_rect.pos.x=limit[MARGIN_LEFT];
+
+ if (screen_rect.pos.y < limit[MARGIN_TOP])
+ screen_rect.pos.y =limit[MARGIN_TOP];
+
+ if (offset!=Vector2()) {
+
+ screen_rect.pos+=offset;
+ if (screen_rect.pos.x + screen_rect.size.x > limit[MARGIN_RIGHT])
+ screen_rect.pos.x = limit[MARGIN_RIGHT] - screen_rect.size.x;
+
+ if (screen_rect.pos.y + screen_rect.size.y > limit[MARGIN_BOTTOM])
+ screen_rect.pos.y = limit[MARGIN_BOTTOM] - screen_rect.size.y;
+
+
+ if (screen_rect.pos.x < limit[MARGIN_LEFT])
+ screen_rect.pos.x=limit[MARGIN_LEFT];
+
+ if (screen_rect.pos.y < limit[MARGIN_TOP])
+ screen_rect.pos.y =limit[MARGIN_TOP];
+
+ }
+
+ camera_screen_center=screen_rect.pos+screen_rect.size*0.5;
+
+ Matrix32 xform;
+ if(rotating){
+ xform.set_rotation(angle);
+ }
+ xform.scale_basis(zoom);
+ xform.set_origin(screen_rect.pos/*.floor()*/);
+
+
+/*
+ if (0) {
+
+ xform = get_global_transform() * xform;
+ } else {
+
+ xform.elements[2]+=get_global_transform().get_origin();
+ }
+*/
+
+
+ return (xform).affine_inverse();
+}
+
+
+
+void Camera2D::_notification(int p_what) {
+
+ switch(p_what) {
+
+ case NOTIFICATION_FIXED_PROCESS: {
+
+ _update_scroll();
+
+ } break;
+ case NOTIFICATION_TRANSFORM_CHANGED: {
+
+
+ if (!is_fixed_processing())
+ _update_scroll();
+
+ } break;
+ case NOTIFICATION_ENTER_SCENE: {
+
+ viewport = NULL;
+ Node *n=this;
+ while(n){
+
+ viewport = n->cast_to<Viewport>();
+ if (viewport)
+ break;
+ n=n->get_parent();
+ }
+
+ canvas = get_canvas();
+
+ RID vp = viewport->get_viewport();
+
+ group_name = "__cameras_"+itos(vp.get_id());
+ canvas_group_name ="__cameras_c"+itos(canvas.get_id());
+ add_to_group(group_name);
+ add_to_group(canvas_group_name);
+
+ _update_scroll();
+ first=true;
+
+
+ } break;
+ case NOTIFICATION_EXIT_SCENE: {
+
+ if (is_current()) {
+ if (viewport) {
+ viewport->set_canvas_transform( Matrix32() );
+ }
+ }
+ remove_from_group(group_name);
+ remove_from_group(canvas_group_name);
+ viewport=NULL;
+
+ } break;
+ }
+}
+
+void Camera2D::set_offset(const Vector2& p_offset) {
+
+ offset=p_offset;
+ _update_scroll();
+
+}
+
+Vector2 Camera2D::get_offset() const{
+
+ return offset;
+}
+
+void Camera2D::set_centered(bool p_centered){
+
+ centered=p_centered;
+ _update_scroll();
+}
+
+bool Camera2D::is_centered() const {
+
+ return centered;
+}
+
+void Camera2D::set_rotating(bool p_rotating){
+
+ rotating=p_rotating;
+ _update_scroll();
+}
+
+bool Camera2D::is_rotating() const {
+
+ return rotating;
+}
+
+
+void Camera2D::_make_current(Object *p_which) {
+
+ if (p_which==this) {
+
+ current=true;
+ _update_scroll();
+ } else {
+ current=false;
+ }
+}
+
+
+void Camera2D::_set_current(bool p_current) {
+
+ if (p_current)
+ make_current();
+
+ current=p_current;
+}
+
+bool Camera2D::is_current() const {
+
+ return current;
+}
+
+void Camera2D::make_current() {
+
+ if (!is_inside_scene()) {
+ current=true;
+ } else {
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,group_name,"_make_current",this);
+ }
+}
+
+void Camera2D::set_limit(Margin p_margin,int p_limit) {
+
+ ERR_FAIL_INDEX(p_margin,4);
+ limit[p_margin]=p_limit;
+}
+
+int Camera2D::get_limit(Margin p_margin) const{
+
+ ERR_FAIL_INDEX_V(p_margin,4,0);
+ return limit[p_margin];
+
+}
+
+void Camera2D::set_drag_margin(Margin p_margin,float p_drag_margin) {
+
+ ERR_FAIL_INDEX(p_margin,4);
+ drag_margin[p_margin]=p_drag_margin;
+}
+
+float Camera2D::get_drag_margin(Margin p_margin) const{
+
+ ERR_FAIL_INDEX_V(p_margin,4,0);
+ return drag_margin[p_margin];
+
+}
+
+
+Vector2 Camera2D::get_camera_pos() const {
+
+
+ return camera_pos;
+}
+
+void Camera2D::force_update_scroll() {
+
+
+ _update_scroll();
+}
+
+
+void Camera2D::set_follow_smoothing(float p_speed) {
+
+ smoothing=p_speed;
+ if (smoothing>0)
+ set_fixed_process(true);
+ else
+ set_fixed_process(false);
+}
+
+float Camera2D::get_follow_smoothing() const{
+
+ return smoothing;
+}
+
+Point2 Camera2D::get_camera_screen_center() const {
+
+ return camera_screen_center;
+}
+
+
+void Camera2D::set_h_drag_enabled(bool p_enabled) {
+
+ h_drag_enabled=p_enabled;
+}
+
+bool Camera2D::is_h_drag_enabled() const{
+
+ return h_drag_enabled;
+}
+
+void Camera2D::set_v_drag_enabled(bool p_enabled){
+
+ v_drag_enabled=p_enabled;
+}
+
+bool Camera2D::is_v_drag_enabled() const{
+
+ return v_drag_enabled;
+}
+
+void Camera2D::set_v_offset(float p_offset) {
+
+ v_ofs=p_offset;
+}
+
+float Camera2D::get_v_offset() const{
+
+ return v_ofs;
+}
+
+void Camera2D::set_h_offset(float p_offset){
+
+ h_ofs=p_offset;
+}
+float Camera2D::get_h_offset() const{
+
+ return h_ofs;
+}
+
+
+void Camera2D::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("set_offset","offset"),&Camera2D::set_offset);
+ ObjectTypeDB::bind_method(_MD("get_offset"),&Camera2D::get_offset);
+
+ ObjectTypeDB::bind_method(_MD("set_centered","centered"),&Camera2D::set_centered);
+ ObjectTypeDB::bind_method(_MD("is_centered"),&Camera2D::is_centered);
+
+ ObjectTypeDB::bind_method(_MD("set_rotating","rotating"),&Camera2D::set_rotating);
+ ObjectTypeDB::bind_method(_MD("is_rotating"),&Camera2D::is_rotating);
+
+ ObjectTypeDB::bind_method(_MD("make_current"),&Camera2D::make_current);
+ ObjectTypeDB::bind_method(_MD("_make_current"),&Camera2D::_make_current);
+
+ ObjectTypeDB::bind_method(_MD("_update_scroll"),&Camera2D::_update_scroll);
+
+
+ ObjectTypeDB::bind_method(_MD("_set_current","current"),&Camera2D::_set_current);
+ ObjectTypeDB::bind_method(_MD("is_current"),&Camera2D::is_current);
+
+ ObjectTypeDB::bind_method(_MD("set_limit","margin","limit"),&Camera2D::set_limit);
+ ObjectTypeDB::bind_method(_MD("get_limit","margin"),&Camera2D::get_limit);
+
+ ObjectTypeDB::bind_method(_MD("set_v_drag_enabled","enabled"),&Camera2D::set_v_drag_enabled);
+ ObjectTypeDB::bind_method(_MD("is_v_drag_enabled"),&Camera2D::is_v_drag_enabled);
+
+ ObjectTypeDB::bind_method(_MD("set_h_drag_enabled","enabled"),&Camera2D::set_h_drag_enabled);
+ ObjectTypeDB::bind_method(_MD("is_h_drag_enabled"),&Camera2D::is_h_drag_enabled);
+
+ ObjectTypeDB::bind_method(_MD("set_v_offset","ofs"),&Camera2D::set_v_offset);
+ ObjectTypeDB::bind_method(_MD("get_v_offset"),&Camera2D::get_v_offset);
+
+ ObjectTypeDB::bind_method(_MD("set_h_offset","ofs"),&Camera2D::set_h_offset);
+ ObjectTypeDB::bind_method(_MD("get_h_offset"),&Camera2D::get_h_offset);
+
+ ObjectTypeDB::bind_method(_MD("set_drag_margin","margin","drag_margin"),&Camera2D::set_drag_margin);
+ ObjectTypeDB::bind_method(_MD("get_drag_margin","margin"),&Camera2D::get_drag_margin);
+
+ ObjectTypeDB::bind_method(_MD("get_camera_pos"),&Camera2D::get_camera_pos);
+ ObjectTypeDB::bind_method(_MD("get_camera_screen_center"),&Camera2D::get_camera_screen_center);
+
+ ObjectTypeDB::bind_method(_MD("set_zoom"),&Camera2D::set_zoom);
+ ObjectTypeDB::bind_method(_MD("get_zoom"),&Camera2D::get_zoom);
+
+
+ ObjectTypeDB::bind_method(_MD("set_follow_smoothing","follow_smoothing"),&Camera2D::set_follow_smoothing);
+ ObjectTypeDB::bind_method(_MD("get_follow_smoothing"),&Camera2D::get_follow_smoothing);
+
+ ObjectTypeDB::bind_method(_MD("force_update_scroll"),&Camera2D::force_update_scroll);
+
+
+ ADD_PROPERTYNZ( PropertyInfo(Variant::VECTOR2,"offset"),_SCS("set_offset"),_SCS("get_offset"));
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL,"centered"),_SCS("set_centered"),_SCS("is_centered"));
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL,"rotating"),_SCS("set_rotating"),_SCS("is_rotating"));
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL,"current"),_SCS("_set_current"),_SCS("is_current"));
+ ADD_PROPERTY( PropertyInfo(Variant::REAL,"smoothing"),_SCS("set_follow_smoothing"),_SCS("get_follow_smoothing") );
+ ADD_PROPERTY( PropertyInfo(Variant::VECTOR2,"zoom"),_SCS("set_zoom"),_SCS("get_zoom") );
+
+ ADD_PROPERTYI( PropertyInfo(Variant::INT,"limit/left"),_SCS("set_limit"),_SCS("get_limit"),MARGIN_LEFT);
+ ADD_PROPERTYI( PropertyInfo(Variant::INT,"limit/top"),_SCS("set_limit"),_SCS("get_limit"),MARGIN_TOP);
+ ADD_PROPERTYI( PropertyInfo(Variant::INT,"limit/right"),_SCS("set_limit"),_SCS("get_limit"),MARGIN_RIGHT);
+ ADD_PROPERTYI( PropertyInfo(Variant::INT,"limit/bottom"),_SCS("set_limit"),_SCS("get_limit"),MARGIN_BOTTOM);
+
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL,"drag_margin/h_enabled"),_SCS("set_h_drag_enabled"),_SCS("is_h_drag_enabled") );
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL,"drag_margin/v_enabled"),_SCS("set_v_drag_enabled"),_SCS("is_v_drag_enabled") );
+
+ ADD_PROPERTYI( PropertyInfo(Variant::REAL,"drag_margin/left",PROPERTY_HINT_RANGE,"0,1,0.01"),_SCS("set_drag_margin"),_SCS("get_drag_margin"),MARGIN_LEFT);
+ ADD_PROPERTYI( PropertyInfo(Variant::REAL,"drag_margin/top",PROPERTY_HINT_RANGE,"0,1,0.01"),_SCS("set_drag_margin"),_SCS("get_drag_margin"),MARGIN_TOP);
+ ADD_PROPERTYI( PropertyInfo(Variant::REAL,"drag_margin/right",PROPERTY_HINT_RANGE,"0,1,0.01"),_SCS("set_drag_margin"),_SCS("get_drag_margin"),MARGIN_RIGHT);
+ ADD_PROPERTYI( PropertyInfo(Variant::REAL,"drag_margin/bottom",PROPERTY_HINT_RANGE,"0,1,0.01"),_SCS("set_drag_margin"),_SCS("get_drag_margin"),MARGIN_BOTTOM);
+
+
+
+}
+
+Camera2D::Camera2D() {
+
+
+
+ centered=true;
+ rotating=false;
+ current=false;
+ limit[MARGIN_LEFT]=-10000000;
+ limit[MARGIN_TOP]=-10000000;
+ limit[MARGIN_RIGHT]=10000000;
+ limit[MARGIN_BOTTOM]=10000000;
+ drag_margin[MARGIN_LEFT]=0.2;
+ drag_margin[MARGIN_TOP]=0.2;
+ drag_margin[MARGIN_RIGHT]=0.2;
+ drag_margin[MARGIN_BOTTOM]=0.2;
+ camera_pos=Vector2();
+
+ smoothing=0.0;
+ zoom = Vector2(1, 1);
+
+ h_drag_enabled=true;
+ v_drag_enabled=true;
+ h_ofs=0;
+ v_ofs=0;
+
+}
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index afdca4a3be..a1e7195b0a 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -1027,7 +1027,7 @@ void KinematicBody2D::_bind_methods() {
ObjectTypeDB::bind_method(_MD("get_collision_pos"),&KinematicBody2D::get_collision_pos);
ObjectTypeDB::bind_method(_MD("get_collision_normal"),&KinematicBody2D::get_collision_normal);
ObjectTypeDB::bind_method(_MD("get_collider_velocity"),&KinematicBody2D::get_collider_velocity);
- ObjectTypeDB::bind_method(_MD("get_collider:Object"),&KinematicBody2D::get_collider);
+ ObjectTypeDB::bind_method(_MD("get_collider:Object"),&KinematicBody2D::_get_collider);
ObjectTypeDB::bind_method(_MD("set_collide_with_static_bodies","enable"),&KinematicBody2D::set_collide_with_static_bodies);
@@ -1064,7 +1064,7 @@ KinematicBody2D::KinematicBody2D() : PhysicsBody2D(Physics2DServer::BODY_MODE_KI
colliding=false;
collider=0;
- margin=0.01;
+ margin=0.08;
}
KinematicBody2D::~KinematicBody2D() {
diff --git a/scene/2d/visibility_notifier_2d.cpp b/scene/2d/visibility_notifier_2d.cpp
index 323d02df4b..c76c18a595 100644
--- a/scene/2d/visibility_notifier_2d.cpp
+++ b/scene/2d/visibility_notifier_2d.cpp
@@ -37,11 +37,11 @@ void VisibilityNotifier2D::_enter_viewport(Viewport* p_viewport) {
ERR_FAIL_COND(viewports.has(p_viewport));
viewports.insert(p_viewport);
+
if (viewports.size()==1) {
emit_signal(SceneStringNames::get_singleton()->enter_screen);
- _screen_enter();
-
+ _screen_enter();
}
emit_signal(SceneStringNames::get_singleton()->enter_viewport,p_viewport);
diff --git a/scene/io/resource_format_image.cpp b/scene/io/resource_format_image.cpp
index 98453bcabb..b0fcf9717b 100644
--- a/scene/io/resource_format_image.cpp
+++ b/scene/io/resource_format_image.cpp
@@ -131,11 +131,11 @@ RES ResourceFormatLoaderImage::load(const String &p_path,const String& p_origina
uint32_t flags=0;
- if (bool(GLOBAL_DEF("texture_import/filter",true)))
+ if (bool(GLOBAL_DEF("image_loader/filter",true)))
flags|=Texture::FLAG_FILTER;
- if (bool(GLOBAL_DEF("texture_import/gen_mipmaps",true)))
+ if (bool(GLOBAL_DEF("image_loader/gen_mipmaps",true)))
flags|=Texture::FLAG_MIPMAPS;
- if (bool(GLOBAL_DEF("texture_import/repeat",false)))
+ if (bool(GLOBAL_DEF("image_loader/repeat",false)))
flags|=Texture::FLAG_REPEAT;
@@ -192,8 +192,8 @@ ResourceFormatLoaderImage::ResourceFormatLoaderImage() {
max_texture_size = GLOBAL_DEF("debug/max_texture_size",0);
GLOBAL_DEF("debug/max_texture_size_alert",false);
debug_load_times=GLOBAL_DEF("debug/image_load_times",false);
- GLOBAL_DEF("texture_import/filter",true);
- GLOBAL_DEF("texture_import/gen_mipmaps",true);
- GLOBAL_DEF("texture_import/repeat",true);
+ GLOBAL_DEF("image_loader/filter",true);
+ GLOBAL_DEF("image_loader/gen_mipmaps",true);
+ GLOBAL_DEF("image_loader/repeat",false);
}
diff --git a/scene/resources/audio_stream_resampled.cpp b/scene/resources/audio_stream_resampled.cpp
index 989e094edf..8e694a6110 100644
--- a/scene/resources/audio_stream_resampled.cpp
+++ b/scene/resources/audio_stream_resampled.cpp
@@ -180,7 +180,6 @@ bool AudioStreamResampled::mix(int32_t *p_dest, int p_frames) {
}
int todo = MIN( ((int64_t(rb_todo)<<MIX_FRAC_BITS)/increment)+1, p_frames );
-
#if 0
if (int(mix_rate)==get_mix_rate()) {
diff --git a/servers/visual/visual_server_wrap_mt.cpp b/servers/visual/visual_server_wrap_mt.cpp
index 85f7b8c3cb..919656fe04 100644
--- a/servers/visual/visual_server_wrap_mt.cpp
+++ b/servers/visual/visual_server_wrap_mt.cpp
@@ -28,7 +28,7 @@
/*************************************************************************/
#include "visual_server_wrap_mt.h"
#include "os/os.h"
-
+#include "globals.h"
void VisualServerWrapMT::thread_exit() {
exit=true;
@@ -160,8 +160,12 @@ void VisualServerWrapMT::finish() {
if (thread) {
command_queue.push( this, &VisualServerWrapMT::thread_exit);
- Thread::wait_to_finish( thread );
+ Thread::wait_to_finish( thread );
memdelete(thread);
+
+
+ texture_free_cached_ids();
+
thread=NULL;
} else {
visual_server->finish();
@@ -181,6 +185,8 @@ VisualServerWrapMT::VisualServerWrapMT(VisualServer* p_contained,bool p_create_t
draw_mutex=NULL;
draw_pending=0;
draw_thread_up=false;
+ alloc_mutex=Mutex::create();
+ texture_pool_max_size=GLOBAL_DEF("render/thread_textures_prealloc",20);
if (!p_create_thread) {
server_thread=Thread::get_caller_ID();
} else {
@@ -192,6 +198,7 @@ VisualServerWrapMT::VisualServerWrapMT(VisualServer* p_contained,bool p_create_t
VisualServerWrapMT::~VisualServerWrapMT() {
memdelete(visual_server);
+ memdelete(alloc_mutex);
//finish();
}
diff --git a/servers/visual/visual_server_wrap_mt.h b/servers/visual/visual_server_wrap_mt.h
index 3d227cfdbc..3a6a3e509a 100644
--- a/servers/visual/visual_server_wrap_mt.h
+++ b/servers/visual/visual_server_wrap_mt.h
@@ -60,65 +60,102 @@ class VisualServerWrapMT : public VisualServer {
void thread_exit();
+ Mutex*alloc_mutex;
+
+
+ int texture_pool_max_size;
+ List<RID> texture_id_pool;
+
+
public:
-#define FUNC0R(m_r,m_func)\
- virtual m_r m_func() { \
+#define FUNC0R(m_r,m_type)\
+ virtual m_r m_type() { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,&ret);\
return ret;\
} else {\
- return visual_server->m_func();\
+ return visual_server->m_type();\
+ }\
+ }
+
+#define FUNCRID(m_type)\
+ int m_type##allocn() {\
+ for(int i=0;i<m_type##_pool_max_size;i++) {\
+ m_type##_id_pool.push_back( visual_server->m_type##_create() );\
+ }\
+ return 0;\
+ }\
+ void m_type##_free_cached_ids() {\
+ while (m_type##_id_pool.size()) {\
+ free(m_type##_id_pool.front()->get());\
+ m_type##_id_pool.pop_front();\
+ }\
+ }\
+ virtual RID m_type##_create() { \
+ if (Thread::get_caller_ID()!=server_thread) {\
+ RID rid;\
+ alloc_mutex->lock();\
+ if (m_type##_id_pool.size()==0) {\
+ int ret;\
+ command_queue.push_and_ret( this, &VisualServerWrapMT::m_type##allocn,&ret);\
+ }\
+ rid=m_type##_id_pool.front()->get();\
+ m_type##_id_pool.pop_front();\
+ alloc_mutex->unlock();\
+ return rid;\
+ } else {\
+ return visual_server->m_type##_create();\
}\
}
-#define FUNC0RC(m_r,m_func)\
- virtual m_r m_func() const { \
+#define FUNC0RC(m_r,m_type)\
+ virtual m_r m_type() const { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,&ret);\
return ret;\
} else {\
- return visual_server->m_func();\
+ return visual_server->m_type();\
}\
}
-#define FUNC0(m_func)\
- virtual void m_func() { \
+#define FUNC0(m_type)\
+ virtual void m_type() { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func);\
+ command_queue.push( visual_server, &VisualServer::m_type);\
} else {\
- visual_server->m_func();\
+ visual_server->m_type();\
}\
}
-#define FUNC0C(m_func)\
- virtual void m_func() const { \
+#define FUNC0C(m_type)\
+ virtual void m_type() const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func);\
+ command_queue.push( visual_server, &VisualServer::m_type);\
} else {\
- visual_server->m_func();\
+ visual_server->m_type();\
}\
}
-#define FUNC0S(m_func)\
- virtual void m_func() { \
+#define FUNC0S(m_type)\
+ virtual void m_type() { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type);\
} else {\
- visual_server->m_func();\
+ visual_server->m_type();\
}\
}
-#define FUNC0SC(m_func)\
- virtual void m_func() const { \
+#define FUNC0SC(m_type)\
+ virtual void m_type() const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type);\
} else {\
- visual_server->m_func();\
+ visual_server->m_type();\
}\
}
@@ -126,448 +163,449 @@ public:
///////////////////////////////////////////////
-#define FUNC1R(m_r,m_func,m_arg1)\
- virtual m_r m_func(m_arg1 p1) { \
+#define FUNC1R(m_r,m_type,m_arg1)\
+ virtual m_r m_type(m_arg1 p1) { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1);\
+ return visual_server->m_type(p1);\
}\
}
-#define FUNC1RC(m_r,m_func,m_arg1)\
- virtual m_r m_func(m_arg1 p1) const { \
+#define FUNC1RC(m_r,m_type,m_arg1)\
+ virtual m_r m_type(m_arg1 p1) const { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1);\
+ return visual_server->m_type(p1);\
}\
}
-#define FUNC1S(m_func,m_arg1)\
- virtual void m_func(m_arg1 p1) { \
+#define FUNC1S(m_type,m_arg1)\
+ virtual void m_type(m_arg1 p1) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1);\
} else {\
- visual_server->m_func(p1);\
+ visual_server->m_type(p1);\
}\
}
-#define FUNC1SC(m_func,m_arg1)\
- virtual void m_func(m_arg1 p1) const { \
+#define FUNC1SC(m_type,m_arg1)\
+ virtual void m_type(m_arg1 p1) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1);\
} else {\
- visual_server->m_func(p1);\
+ visual_server->m_type(p1);\
}\
}
-#define FUNC1(m_func,m_arg1)\
- virtual void m_func(m_arg1 p1) { \
+#define FUNC1(m_type,m_arg1)\
+ virtual void m_type(m_arg1 p1) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1);\
} else {\
- visual_server->m_func(p1);\
+ visual_server->m_type(p1);\
}\
}
-#define FUNC1C(m_func,m_arg1)\
- virtual void m_func(m_arg1 p1) const { \
+#define FUNC1C(m_type,m_arg1)\
+ virtual void m_type(m_arg1 p1) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1);\
} else {\
- visual_server->m_func(p1);\
+ visual_server->m_type(p1);\
}\
}
-#define FUNC2R(m_r,m_func,m_arg1, m_arg2)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2) { \
+#define FUNC2R(m_r,m_type,m_arg1, m_arg2)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2) { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2);\
+ return visual_server->m_type(p1, p2);\
}\
}
-#define FUNC2RC(m_r,m_func,m_arg1, m_arg2)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2) const { \
+#define FUNC2RC(m_r,m_type,m_arg1, m_arg2)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2) const { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2);\
+ return visual_server->m_type(p1, p2);\
}\
}
-#define FUNC2S(m_func,m_arg1, m_arg2)\
- virtual void m_func(m_arg1 p1, m_arg2 p2) { \
+#define FUNC2S(m_type,m_arg1, m_arg2)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2);\
} else {\
- visual_server->m_func(p1, p2);\
+ visual_server->m_type(p1, p2);\
}\
}
-#define FUNC2SC(m_func,m_arg1, m_arg2)\
- virtual void m_func(m_arg1 p1, m_arg2 p2) const { \
+#define FUNC2SC(m_type,m_arg1, m_arg2)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2);\
} else {\
- visual_server->m_func(p1, p2);\
+ visual_server->m_type(p1, p2);\
}\
}
-#define FUNC2(m_func,m_arg1, m_arg2)\
- virtual void m_func(m_arg1 p1, m_arg2 p2) { \
+#define FUNC2(m_type,m_arg1, m_arg2)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2);\
} else {\
- visual_server->m_func(p1, p2);\
+ visual_server->m_type(p1, p2);\
}\
}
-#define FUNC2C(m_func,m_arg1, m_arg2)\
- virtual void m_func(m_arg1 p1, m_arg2 p2) const { \
+#define FUNC2C(m_type,m_arg1, m_arg2)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2);\
} else {\
- visual_server->m_func(p1, p2);\
+ visual_server->m_type(p1, p2);\
}\
}
-#define FUNC3R(m_r,m_func,m_arg1, m_arg2, m_arg3)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3) { \
+#define FUNC3R(m_r,m_type,m_arg1, m_arg2, m_arg3)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3) { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2, p3,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2, p3,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2, p3);\
+ return visual_server->m_type(p1, p2, p3);\
}\
}
-#define FUNC3RC(m_r,m_func,m_arg1, m_arg2, m_arg3)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3) const { \
+#define FUNC3RC(m_r,m_type,m_arg1, m_arg2, m_arg3)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3) const { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2, p3,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2, p3,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2, p3);\
+ return visual_server->m_type(p1, p2, p3);\
}\
}
-#define FUNC3S(m_func,m_arg1, m_arg2, m_arg3)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3) { \
+#define FUNC3S(m_type,m_arg1, m_arg2, m_arg3)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2, p3);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2, p3);\
} else {\
- visual_server->m_func(p1, p2, p3);\
+ visual_server->m_type(p1, p2, p3);\
}\
}
-#define FUNC3SC(m_func,m_arg1, m_arg2, m_arg3)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3) const { \
+#define FUNC3SC(m_type,m_arg1, m_arg2, m_arg3)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2, p3);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2, p3);\
} else {\
- visual_server->m_func(p1, p2, p3);\
+ visual_server->m_type(p1, p2, p3);\
}\
}
-#define FUNC3(m_func,m_arg1, m_arg2, m_arg3)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3) { \
+#define FUNC3(m_type,m_arg1, m_arg2, m_arg3)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2, p3);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2, p3);\
} else {\
- visual_server->m_func(p1, p2, p3);\
+ visual_server->m_type(p1, p2, p3);\
}\
}
-#define FUNC3C(m_func,m_arg1, m_arg2, m_arg3)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3) const { \
+#define FUNC3C(m_type,m_arg1, m_arg2, m_arg3)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2, p3);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2, p3);\
} else {\
- visual_server->m_func(p1, p2, p3);\
+ visual_server->m_type(p1, p2, p3);\
}\
}
-#define FUNC4R(m_r,m_func,m_arg1, m_arg2, m_arg3, m_arg4)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) { \
+#define FUNC4R(m_r,m_type,m_arg1, m_arg2, m_arg3, m_arg4)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2, p3, p4,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2, p3, p4,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2, p3, p4);\
+ return visual_server->m_type(p1, p2, p3, p4);\
}\
}
-#define FUNC4RC(m_r,m_func,m_arg1, m_arg2, m_arg3, m_arg4)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) const { \
+#define FUNC4RC(m_r,m_type,m_arg1, m_arg2, m_arg3, m_arg4)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) const { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2, p3, p4,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2, p3, p4,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2, p3, p4);\
+ return visual_server->m_type(p1, p2, p3, p4);\
}\
}
-#define FUNC4S(m_func,m_arg1, m_arg2, m_arg3, m_arg4)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) { \
+#define FUNC4S(m_type,m_arg1, m_arg2, m_arg3, m_arg4)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2, p3, p4);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2, p3, p4);\
} else {\
- visual_server->m_func(p1, p2, p3, p4);\
+ visual_server->m_type(p1, p2, p3, p4);\
}\
}
-#define FUNC4SC(m_func,m_arg1, m_arg2, m_arg3, m_arg4)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) const { \
+#define FUNC4SC(m_type,m_arg1, m_arg2, m_arg3, m_arg4)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2, p3, p4);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2, p3, p4);\
} else {\
- visual_server->m_func(p1, p2, p3, p4);\
+ visual_server->m_type(p1, p2, p3, p4);\
}\
}
-#define FUNC4(m_func,m_arg1, m_arg2, m_arg3, m_arg4)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) { \
+#define FUNC4(m_type,m_arg1, m_arg2, m_arg3, m_arg4)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2, p3, p4);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2, p3, p4);\
} else {\
- visual_server->m_func(p1, p2, p3, p4);\
+ visual_server->m_type(p1, p2, p3, p4);\
}\
}
-#define FUNC4C(m_func,m_arg1, m_arg2, m_arg3, m_arg4)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) const { \
+#define FUNC4C(m_type,m_arg1, m_arg2, m_arg3, m_arg4)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2, p3, p4);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2, p3, p4);\
} else {\
- visual_server->m_func(p1, p2, p3, p4);\
+ visual_server->m_type(p1, p2, p3, p4);\
}\
}
-#define FUNC5R(m_r,m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) { \
+#define FUNC5R(m_r,m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2, p3, p4, p5);\
+ return visual_server->m_type(p1, p2, p3, p4, p5);\
}\
}
-#define FUNC5RC(m_r,m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) const { \
+#define FUNC5RC(m_r,m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) const { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2, p3, p4, p5);\
+ return visual_server->m_type(p1, p2, p3, p4, p5);\
}\
}
-#define FUNC5S(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) { \
+#define FUNC5S(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5);\
+ visual_server->m_type(p1, p2, p3, p4, p5);\
}\
}
-#define FUNC5SC(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) const { \
+#define FUNC5SC(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5);\
+ visual_server->m_type(p1, p2, p3, p4, p5);\
}\
}
-#define FUNC5(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) { \
+#define FUNC5(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5);\
+ visual_server->m_type(p1, p2, p3, p4, p5);\
}\
}
-#define FUNC5C(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) const { \
+#define FUNC5C(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5);\
+ visual_server->m_type(p1, p2, p3, p4, p5);\
}\
}
-#define FUNC6R(m_r,m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) { \
+#define FUNC6R(m_r,m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2, p3, p4, p5, p6);\
+ return visual_server->m_type(p1, p2, p3, p4, p5, p6);\
}\
}
-#define FUNC6RC(m_r,m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) const { \
+#define FUNC6RC(m_r,m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) const { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2, p3, p4, p5, p6);\
+ return visual_server->m_type(p1, p2, p3, p4, p5, p6);\
}\
}
-#define FUNC6S(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) { \
+#define FUNC6S(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5, p6);\
+ visual_server->m_type(p1, p2, p3, p4, p5, p6);\
}\
}
-#define FUNC6SC(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) const { \
+#define FUNC6SC(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5, p6);\
+ visual_server->m_type(p1, p2, p3, p4, p5, p6);\
}\
}
-#define FUNC6(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) { \
+#define FUNC6(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5, p6);\
+ visual_server->m_type(p1, p2, p3, p4, p5, p6);\
}\
}
-#define FUNC6C(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) const { \
+#define FUNC6C(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5, p6);\
+ visual_server->m_type(p1, p2, p3, p4, p5, p6);\
}\
}
-#define FUNC7R(m_r,m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) { \
+#define FUNC7R(m_r,m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6, p7,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6, p7,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2, p3, p4, p5, p6, p7);\
+ return visual_server->m_type(p1, p2, p3, p4, p5, p6, p7);\
}\
}
-#define FUNC7RC(m_r,m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
- virtual m_r m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) const { \
+#define FUNC7RC(m_r,m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
+ virtual m_r m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) const { \
if (Thread::get_caller_ID()!=server_thread) {\
m_r ret;\
- command_queue.push_and_ret( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6, p7,&ret);\
+ command_queue.push_and_ret( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6, p7,&ret);\
return ret;\
} else {\
- return visual_server->m_func(p1, p2, p3, p4, p5, p6, p7);\
+ return visual_server->m_type(p1, p2, p3, p4, p5, p6, p7);\
}\
}
-#define FUNC7S(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) { \
+#define FUNC7S(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6, p7);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6, p7);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5, p6, p7);\
+ visual_server->m_type(p1, p2, p3, p4, p5, p6, p7);\
}\
}
-#define FUNC7SC(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) const { \
+#define FUNC7SC(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push_and_sync( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6, p7);\
+ command_queue.push_and_sync( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6, p7);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5, p6, p7);\
+ visual_server->m_type(p1, p2, p3, p4, p5, p6, p7);\
}\
}
-#define FUNC7(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) { \
+#define FUNC7(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6, p7);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6, p7);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5, p6, p7);\
+ visual_server->m_type(p1, p2, p3, p4, p5, p6, p7);\
}\
}
-#define FUNC7C(m_func,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
- virtual void m_func(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) const { \
+#define FUNC7C(m_type,m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7)\
+ virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7) const { \
if (Thread::get_caller_ID()!=server_thread) {\
- command_queue.push( visual_server, &VisualServer::m_func,p1, p2, p3, p4, p5, p6, p7);\
+ command_queue.push( visual_server, &VisualServer::m_type,p1, p2, p3, p4, p5, p6, p7);\
} else {\
- visual_server->m_func(p1, p2, p3, p4, p5, p6, p7);\
+ visual_server->m_type(p1, p2, p3, p4, p5, p6, p7);\
}\
}
- FUNC0R(RID,texture_create);
+ //FUNC0R(RID,texture_create);
+ FUNCRID(texture);
FUNC5(texture_allocate,RID,int,int,Image::Format,uint32_t);
FUNC3(texture_set_data,RID,const Image&,CubeMapSide);
FUNC2RC(Image,texture_get_data,RID,CubeMapSide);
@@ -594,7 +632,7 @@ public:
if (Thread::get_caller_ID()!=server_thread) {
command_queue.push_and_sync( visual_server, &VisualServer::shader_get_param_list,p_shader,p_param_list);
} else {
- visual_server->m_func(p1, p2, p3, p4, p5);
+ visual_server->m_type(p1, p2, p3, p4, p5);
}
}*/
diff --git a/tools/collada/collada.cpp b/tools/collada/collada.cpp
index 66e5e3f0e6..0d02c32d00 100644
--- a/tools/collada/collada.cpp
+++ b/tools/collada/collada.cpp
@@ -344,6 +344,9 @@ void Collada::_parse_image(XMLParser& parser) {
// path is relative to file being loaded, so convert to a resource path
path=Globals::get_singleton()->localize_path(state.local_path.get_base_dir()+"/"+path);
+ } else if (path.find("file:///")==0) {
+ path=path.replace_first("file:///","");
+ path=Globals::get_singleton()->localize_path(path);
}
image.path=path;
diff --git a/tools/editor/editor_file_system.cpp b/tools/editor/editor_file_system.cpp
index 52b195b232..861b6a9e3c 100644
--- a/tools/editor/editor_file_system.cpp
+++ b/tools/editor/editor_file_system.cpp
@@ -227,7 +227,7 @@ EditorFileSystem::DirItem* EditorFileSystem::_scan_dir(DirAccess *da,Set<String>
DirCache *dc = dir_cache.getptr(path);
- if (dc && dc->modification_time==mtime) {
+ if (false && dc && dc->modification_time==mtime) {
//use the cached files, since directory did not change
for (Set<String>::Element *E=dc->subdirs.front();E;E=E->next()) {
dirs.push_back(E->get());
@@ -542,6 +542,7 @@ bool EditorFileSystem::_check_meta_sources(EditorFileSystemDirectory::ImportMeta
for(int j=0;j<p_meta.sources.size();j++) {
+
String src = EditorImportPlugin::expand_source_path(p_meta.sources[j].path);
if (!FileAccess::exists(src)) {
@@ -556,6 +557,7 @@ bool EditorFileSystem::_check_meta_sources(EditorFileSystemDirectory::ImportMeta
if (mt!=p_meta.sources[j].modified_time) {
//scan
String md5 = FileAccess::get_md5(src);
+ print_line("checking: "+src);
print_line("md5: "+md5);
print_line("vs: "+p_meta.sources[j].md5);
if (md5!=p_meta.sources[j].md5) {
diff --git a/tools/editor/editor_import_export.cpp b/tools/editor/editor_import_export.cpp
index 1a5dd73040..18c0010904 100644
--- a/tools/editor/editor_import_export.cpp
+++ b/tools/editor/editor_import_export.cpp
@@ -447,12 +447,64 @@ void EditorExportPlatformPC::_get_property_list( List<PropertyInfo> *p_list) con
+static void _exp_add_dep(Map<StringName,List<StringName> > &deps,const StringName& p_path) {
+
+
+ if (deps.has(p_path))
+ return; //already done
+
+ deps.insert(p_path,List<StringName>());
+
+ List<StringName> &deplist=deps[p_path];
+ Set<StringName> depset;
+
+ List<String> dl;
+ ResourceLoader::get_dependencies(p_path,&dl);
+
+ //added in order so child dependencies are always added bfore parent dependencies
+ for (List<String>::Element *E=dl.front();E;E=E->next()) {
+
+
+ if (!deps.has(E->get()))
+ _exp_add_dep(deps,E->get());
+
+ for(List<StringName>::Element *F=deps[E->get()].front();F;F=F->next()) {
+
+
+ if (!depset.has(F->get())) {
+ depset.insert(F->get());
+ deplist.push_back(F->get());
+ }
+ }
+
+ if (!depset.has(E->get())) {
+ depset.insert(E->get());
+ deplist.push_back(E->get());
+ }
+
+ }
+}
+
+
+
Error EditorExportPlatform::export_project_files(EditorExportSaveFunction p_func, void* p_udata,bool p_make_bundles) {
/* ALL FILES AND DEPENDENCIES */
Vector<StringName> files=get_dependencies(p_make_bundles);
+ Map<StringName,List<StringName> > deps;
+
+ if (false) {
+ for(int i=0;i<files.size();i++) {
+
+ _exp_add_dep(deps,files[i]);
+
+ }
+ }
+
+
+
/* GROUP ATLAS */
@@ -643,7 +695,7 @@ Error EditorExportPlatform::export_project_files(EditorExportSaveFunction p_func
for (List<StringName>::Element *F=atlas_images.front();F;F=F->next()) {
- imd->add_source(EditorImportPlugin::validate_source_path(F->get()));
+ imd->add_source(EditorImportPlugin::validate_source_path(F->get()),FileAccess::get_md5(F->get()));
}
@@ -683,7 +735,6 @@ Error EditorExportPlatform::export_project_files(EditorExportSaveFunction p_func
rects.resize(r_rects.size());
for(int i=0;i<r_rects.size();i++) {
//get back region and margins
-
rects[i]=r_rects[i];
}
@@ -788,18 +839,49 @@ Error EditorExportPlatform::export_project_files(EditorExportSaveFunction p_func
{
//make binary engine.cfg config
-
Map<String,Variant> custom;
+
if (remap_files.size()) {
Vector<String> remapsprop;
for(Map<StringName,StringName>::Element *E=remap_files.front();E;E=E->next()) {
+ print_line("REMAP: "+String(E->key())+" -> "+E->get());
remapsprop.push_back(E->key());
remapsprop.push_back(E->get());
}
custom["remap/all"]=remapsprop;
}
+
+ //add presaved dependencies
+ for(Map<StringName,List<StringName> >::Element *E=deps.front();E;E=E->next()) {
+
+ if (E->get().size()==0)
+ continue; //no deps
+ String key;
+ Vector<StringName> deps;
+ //if bundle continue (when bundles supported obviously)
+
+ if (remap_files.has(E->key())) {
+ key=remap_files[E->key()];
+ } else {
+ key=E->key();
+ }
+
+ deps.resize(E->get().size());
+ int i=0;
+
+ for(List<StringName>::Element *F=E->get().front();F;F=F->next()) {
+ deps[i++]=F->get();
+ print_line(" -"+String(F->get()));
+ }
+
+ NodePath prop(deps,true,String()); //seems best to use this for performance
+
+ custom["deps/"+key.md5_text()]=prop;
+
+ }
+
String remap_file="engine.cfb";
String engine_cfb =EditorSettings::get_singleton()->get_settings_path()+"/tmp/tmp"+remap_file;
Globals::get_singleton()->save_custom(engine_cfb,custom);
diff --git a/tools/editor/editor_node.cpp b/tools/editor/editor_node.cpp
index d8f9dcc947..af61022af2 100644
--- a/tools/editor/editor_node.cpp
+++ b/tools/editor/editor_node.cpp
@@ -723,8 +723,6 @@ void EditorNode::_save_scene(String p_file) {
flg|=ResourceSaver::FLAG_COMPRESS;
if (EditorSettings::get_singleton()->get("on_save/save_paths_as_relative"))
flg|=ResourceSaver::FLAG_RELATIVE_PATHS;
- if (EditorSettings::get_singleton()->get("on_save/save_paths_without_extension"))
- flg|=ResourceSaver::FLAG_NO_EXTENSION;
err = ResourceSaver::save(p_file,sdata,flg);
@@ -1005,8 +1003,6 @@ void EditorNode::_dialog_action(String p_file) {
flg|=ResourceSaver::FLAG_COMPRESS;
if (EditorSettings::get_singleton()->get("on_save/save_paths_as_relative"))
flg|=ResourceSaver::FLAG_RELATIVE_PATHS;
- if (EditorSettings::get_singleton()->get("on_save/save_paths_without_extension"))
- flg|=ResourceSaver::FLAG_NO_EXTENSION;
err = ResourceSaver::save(p_file,sdata,flg);
@@ -3455,7 +3451,7 @@ EditorNode::EditorNode() {
p->add_item("Undo",EDIT_UNDO,KEY_MASK_CMD+KEY_Z);
p->add_item("Redo",EDIT_REDO,KEY_MASK_CMD+KEY_MASK_SHIFT+KEY_Z);
p->add_separator();
- p->add_item("Run Script",FILE_RUN_SCRIPT,KEY_MASK_CMD+KEY_R);
+ p->add_item("Run Script",FILE_RUN_SCRIPT,KEY_MASK_SHIFT+KEY_MASK_CMD+KEY_R);
p->add_separator();
p->add_item("Project Settings",RUN_SETTINGS);
p->add_separator();
diff --git a/tools/editor/io_plugins/editor_font_import_plugin.cpp b/tools/editor/io_plugins/editor_font_import_plugin.cpp
index 4de68c7f4c..4b3e052907 100644
--- a/tools/editor/io_plugins/editor_font_import_plugin.cpp
+++ b/tools/editor/io_plugins/editor_font_import_plugin.cpp
@@ -88,6 +88,7 @@ public:
Color color;
Color gradient_begin;
Color gradient_end;
+ bool color_use_monochrome;
String gradient_image;
@@ -148,6 +149,8 @@ public:
gradient_end=p_value;
else if (n=="color/image")
gradient_image=p_value;
+ else if (n=="color/monochrome")
+ color_use_monochrome=p_value;
else if (n=="advanced/round_advance")
round_advance=p_value;
else
@@ -210,6 +213,8 @@ public:
r_ret=gradient_end;
else if (n=="color/image")
r_ret=gradient_image;
+ else if (n=="color/monochrome")
+ r_ret=color_use_monochrome;
else if (n=="advanced/round_advance")
r_ret=round_advance;
else
@@ -258,6 +263,7 @@ public:
if (color_type==COLOR_GRADIENT_IMAGE) {
p_list->push_back(PropertyInfo(Variant::STRING,"color/image",PROPERTY_HINT_FILE));
}
+ p_list->push_back(PropertyInfo(Variant::BOOL,"color/monochrome"));
p_list->push_back(PropertyInfo(Variant::BOOL,"advanced/round_advance"));
}
@@ -270,6 +276,35 @@ public:
}
+ void reset() {
+
+ char_extra_spacing=0;
+ top_extra_spacing=0;
+ bottom_extra_spacing=0;
+ space_extra_spacing=0;
+
+ character_set=CHARSET_LATIN;
+
+ shadow=false;
+ shadow_radius=2;
+ shadow_color=Color(0,0,0,0.3);
+ shadow_transition=1.0;
+
+ shadow2=false;
+ shadow2_radius=2;
+ shadow2_color=Color(0,0,0,0.3);
+ shadow2_transition=1.0;
+
+ color_type=COLOR_WHITE;
+ color=Color(1,1,1,1);
+ gradient_begin=Color(1,1,1,1);
+ gradient_end=Color(0.5,0.5,0.5,1);
+ color_use_monochrome=false;
+
+ round_advance=true;
+
+ }
+
_EditorFontImportOptions() {
char_extra_spacing=0;
@@ -293,6 +328,7 @@ public:
color=Color(1,1,1,1);
gradient_begin=Color(1,1,1,1);
gradient_end=Color(0.5,0.5,0.5,1);
+ color_use_monochrome=false;
round_advance=true;
}
@@ -503,6 +539,7 @@ public:
dest->get_line_edit()->set_text(p_path);
List<String> opts;
rimd->get_options(&opts);
+ options->reset();
for(List<String>::Element *E=opts.front();E;E=E->next()) {
options->_set(E->get(),rimd->get_option(E->get()));
@@ -1200,6 +1237,12 @@ Ref<Font> EditorFontImportPlugin::generate_font(const Ref<ResourceImportMetadata
}
+
+ if (from->has_option("color/monochrome") && bool(from->get_option("color/monochrome"))) {
+
+ atlas.convert(Image::FORMAT_GRAYSCALE_ALPHA);
+ }
+
if (0) {
//debug the texture
Ref<ImageTexture> atlast = memnew( ImageTexture );
diff --git a/tools/editor/io_plugins/editor_texture_import_plugin.cpp b/tools/editor/io_plugins/editor_texture_import_plugin.cpp
index 90dcbb97e0..916bd59360 100644
--- a/tools/editor/io_plugins/editor_texture_import_plugin.cpp
+++ b/tools/editor/io_plugins/editor_texture_import_plugin.cpp
@@ -747,8 +747,10 @@ Error EditorTextureImportPlugin::import2(const String& p_path, const Ref<Resourc
for(int i=0;i<from->get_source_count();i++) {
String path = EditorImportPlugin::expand_source_path(from->get_source_path(i));
+ String md5 = FileAccess::get_md5(path);
+ from->set_source_md5(i,FileAccess::get_md5(path));
ep.step("Loading Image: "+path,i);
- print_line("source path: "+path);
+ print_line("source path: "+path+" md5 "+md5);
Image src;
Error err = ImageLoader::load_image(path,&src);
if (err) {
@@ -894,7 +896,7 @@ Error EditorTextureImportPlugin::import2(const String& p_path, const Ref<Resourc
EditorNode::add_io_error("Couldn't save atlas image: "+apath);
return err;
}
- from->set_source_md5(i,FileAccess::get_md5(apath));
+ //from->set_source_md5(i,FileAccess::get_md5(apath));
}
}
}
@@ -953,7 +955,7 @@ Error EditorTextureImportPlugin::import2(const String& p_path, const Ref<Resourc
} else {
- print_line("compress...");
+
Image image=texture->get_data();
ERR_FAIL_COND_V(image.empty(),ERR_INVALID_DATA);
@@ -990,7 +992,6 @@ Error EditorTextureImportPlugin::import2(const String& p_path, const Ref<Resourc
}
- print_line("COMPRESSED TO: "+itos(image.get_format()));
texture->create_from_image(image,tex_flags);
@@ -1075,7 +1076,7 @@ Vector<uint8_t> EditorTextureImportPlugin::custom_export(const String& p_path, c
rimd->set_option("quality",group_lossy_quality);
rimd->set_option("atlas",false);
rimd->set_option("shrink",group_shrink);
- rimd->add_source(EditorImportPlugin::validate_source_path(p_path));
+ rimd->add_source(EditorImportPlugin::validate_source_path(p_path),FileAccess::get_md5(p_path));
} else if (EditorImportExport::get_singleton()->get_image_formats().has(p_path.extension().to_lower()) && EditorImportExport::get_singleton()->get_export_image_action()!=EditorImportExport::IMAGE_ACTION_NONE) {
//handled by general image export settings
@@ -1102,7 +1103,7 @@ Vector<uint8_t> EditorTextureImportPlugin::custom_export(const String& p_path, c
rimd->set_option("flags",flags);
rimd->set_option("quality",EditorImportExport::get_singleton()->get_export_image_quality());
rimd->set_option("atlas",false);
- rimd->add_source(EditorImportPlugin::validate_source_path(p_path));
+ rimd->add_source(EditorImportPlugin::validate_source_path(p_path),FileAccess::get_md5(p_path));
} else {
return Vector<uint8_t>();
@@ -1117,7 +1118,7 @@ Vector<uint8_t> EditorTextureImportPlugin::custom_export(const String& p_path, c
}
uint32_t flags = rimd->get_option("flags");
- uint8_t shrink = rimd->has_option("shrink") ? rimd->get_option("shrink"): Variant(1);
+ uint8_t shrink = rimd->has_option("shrink") ? rimd->get_option("shrink"): Variant(1);
uint8_t format = rimd->get_option("format");
uint8_t comp = (format==EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM)?uint8_t(p_platform->get_image_compression()):uint8_t(255);
@@ -1214,6 +1215,9 @@ EditorTextureImportPlugin::EditorTextureImportPlugin(EditorNode *p_editor, Mode
if (rimd.is_valid()) {
if (rimd->get_editor()!="") {
+ int compression = rimd->get_option("format");
+ if (compression!=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM)
+ return Vector<uint8_t>(); //only useful for RAM compression to reconvert
Ref<EditorImportPlugin> pl = EditorImportExport::get_singleton()->get_import_plugin_by_name(rimd->get_editor());
if (pl.is_valid()) {
Vector<uint8_t> ce = pl->custom_export(p_path,p_platform);
diff --git a/tools/editor/project_export.cpp b/tools/editor/project_export.cpp
index eae5dacb27..f571aba434 100644
--- a/tools/editor/project_export.cpp
+++ b/tools/editor/project_export.cpp
@@ -276,7 +276,7 @@ void ProjectExportDialog::_notification(int p_what) {
image_action->select(EditorImportExport::get_singleton()->get_export_image_action());
image_quality->set_val(EditorImportExport::get_singleton()->get_export_image_quality());
- image_shrink->set_val(EditorImportExport::get_singleton()->get_export_image_quality());
+ image_shrink->set_val(EditorImportExport::get_singleton()->get_export_image_shrink());
image_quality->connect("value_changed",this,"_quality_edited");
image_shrink->connect("value_changed",this,"_shrink_edited");
image_action->connect("item_selected",this,"_image_export_edited");
@@ -349,7 +349,7 @@ void ProjectExportDialog::_validate_platform() {
List<String> pl;
EditorFileSystem::get_singleton()->get_changed_sources(&pl);
- if (pl.size()) {
+ if (false && pl.size()) {
if (pl.size()==1)
platform_error_string->set_text(" -One Resource is pending re-import.");
else
diff --git a/tools/editor/resources_dock.cpp b/tools/editor/resources_dock.cpp
index 37a3469578..eb2e526d71 100644
--- a/tools/editor/resources_dock.cpp
+++ b/tools/editor/resources_dock.cpp
@@ -156,8 +156,6 @@ void ResourcesDock::save_resource(const String& p_path,const Ref<Resource>& p_re
flg|=ResourceSaver::FLAG_COMPRESS;
if (EditorSettings::get_singleton()->get("on_save/save_paths_as_relative"))
flg|=ResourceSaver::FLAG_RELATIVE_PATHS;
- if (EditorSettings::get_singleton()->get("on_save/save_paths_without_extension"))
- flg|=ResourceSaver::FLAG_NO_EXTENSION;
String path = Globals::get_singleton()->localize_path(p_path);
Error err = ResourceSaver::save(path,p_resource,flg);
diff --git a/tools/editor/scene_tree_editor.cpp b/tools/editor/scene_tree_editor.cpp
index 89b7e54195..e2ae897fe9 100644
--- a/tools/editor/scene_tree_editor.cpp
+++ b/tools/editor/scene_tree_editor.cpp
@@ -73,6 +73,12 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item,int p_column,int p_id)
undo_redo->commit_action();
} else if (n->is_type("CanvasItem")) {
+ CanvasItem *ci = n->cast_to<CanvasItem>();
+ if (!ci->is_visible() && ci->get_parent_item() && !ci->get_parent_item()->is_visible()) {
+ error->set_text("This item cannot be made visible because the parent is hidden. Unhide the parent first.");
+ error->popup_centered_minsize(Size2(400,80));
+ return;
+ }
bool v = !bool(n->call("is_hidden"));
undo_redo->create_action("Toggle CanvasItem Visible");
undo_redo->add_do_method(n,v?"hide":"show");
@@ -663,6 +669,9 @@ SceneTreeEditor::SceneTreeEditor(bool p_label,bool p_can_rename, bool p_can_open
tree->connect("button_pressed",this,"_cell_button_pressed");
// tree->connect("item_edited", this,"_renamed",Vector<Variant>(),true);
+ error = memnew( AcceptDialog );
+ add_child(error);
+
last_hash=0;
pending_test_update=false;
updating_tree=false;
diff --git a/tools/editor/scene_tree_editor.h b/tools/editor/scene_tree_editor.h
index 9a2bdb7ef9..edd67a4047 100644
--- a/tools/editor/scene_tree_editor.h
+++ b/tools/editor/scene_tree_editor.h
@@ -52,6 +52,8 @@ class SceneTreeEditor : public Control {
Tree *tree;
Node *selected;
+ AcceptDialog *error;
+
int blocked;
void _compute_hash(Node *p_node,uint64_t &hash);
diff --git a/tools/ios_xcode_template/godot_ios.xcodeproj/project.xcworkspace/xcuserdata/punto.xcuserdatad/UserInterfaceState.xcuserstate b/tools/ios_xcode_template/godot_ios.xcodeproj/project.xcworkspace/xcuserdata/punto.xcuserdatad/UserInterfaceState.xcuserstate
index bc65cadf59..7c338929ed 100644
--- a/tools/ios_xcode_template/godot_ios.xcodeproj/project.xcworkspace/xcuserdata/punto.xcuserdatad/UserInterfaceState.xcuserstate
+++ b/tools/ios_xcode_template/godot_ios.xcodeproj/project.xcworkspace/xcuserdata/punto.xcuserdatad/UserInterfaceState.xcuserstate
Binary files differ