diff options
Diffstat (limited to 'platform')
282 files changed, 11100 insertions, 11448 deletions
diff --git a/platform/android/AndroidManifest.xml.template b/platform/android/AndroidManifest.xml.template deleted file mode 100644 index 81f4c15849..0000000000 --- a/platform/android/AndroidManifest.xml.template +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.godot.game" - android:versionCode="1" - android:versionName="1.0" - android:installLocation="auto" - > -<supports-screens android:smallScreens="true" - android:normalScreens="true" - android:largeScreens="true" - android:xlargeScreens="true"/> - - <application android:label="@string/godot_project_name_string" android:icon="@drawable/icon" android:allowBackup="false" $$ADD_APPATTRIBUTE_CHUNKS$$ > - <activity android:name="org.godotengine.godot.Godot" - android:label="@string/godot_project_name_string" - android:theme="@android:style/Theme.NoTitleBar.Fullscreen" - android:launchMode="singleTask" - android:screenOrientation="landscape" - android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize" - android:resizeableActivity="false"> - - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - <service android:name="org.godotengine.godot.GodotDownloaderService" /> - - - - -$$ADD_APPLICATION_CHUNKS$$ - - </application> - <uses-feature android:glEsVersion="0x00020000" android:required="true" /> - -$$ADD_PERMISSION_CHUNKS$$ - -<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="27"/> - -</manifest> diff --git a/platform/android/SCsub b/platform/android/SCsub index 6d5af99bc5..22ed476c6f 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -2,7 +2,6 @@ Import('env') -import shutil from compat import open_utf8 from distutils.version import LooseVersion from detect import get_ndk_version @@ -16,16 +15,13 @@ android_files = [ 'dir_access_jandroid.cpp', 'thread_jandroid.cpp', 'audio_driver_jandroid.cpp', - 'java_glue.cpp', + 'java_godot_lib_jni.cpp', 'java_class_wrapper.cpp', + 'java_godot_wrapper.cpp', + 'java_godot_io_wrapper.cpp', # 'power_android.cpp' ] -thirdparty_files = [ - 'ifaddrs_android.cpp', - 'cpu-features.c', -] - env_android = env.Clone() if env['target'] == "profile": env_android.Append(CPPFLAGS=['-DPROFILER_ENABLED']) @@ -36,119 +32,7 @@ for x in android_files: env_thirdparty = env_android.Clone() env_thirdparty.disable_warnings() -for x in thirdparty_files: - android_objects.append(env_thirdparty.SharedObject(x)) - -prog = None - -abspath = env.Dir(".").abspath - - -with open_utf8(abspath + "/build.gradle.template", "r") as gradle_basein: - gradle_text = gradle_basein.read() - -gradle_maven_flat_text = "" -if len(env.android_flat_dirs) > 0: - gradle_maven_flat_text += "flatDir {\n" - gradle_maven_flat_text += "\tdirs " - for x in env.android_flat_dirs: - gradle_maven_flat_text += "'" + x + "'," - - gradle_maven_flat_text = gradle_maven_flat_text[:-1] - gradle_maven_flat_text += "\n\t}\n" - -gradle_maven_repos_text = "" -gradle_maven_repos_text += gradle_maven_flat_text - -if len(env.android_maven_repos) > 0: - gradle_maven_repos_text += "" - for x in env.android_maven_repos: - gradle_maven_repos_text += "\tmaven {\n" - gradle_maven_repos_text += "\t" + x + "\n" - gradle_maven_repos_text += "\t}\n" - -gradle_maven_dependencies_text = "" - -for x in env.android_dependencies: - gradle_maven_dependencies_text += x + "\n\t" - -gradle_java_dirs_text = "" - -for x in env.android_java_dirs: - gradle_java_dirs_text += ",'" + x.replace("\\", "/") + "'" - -gradle_plugins = "" -for x in env.android_gradle_plugins: - gradle_plugins += "apply plugin: \"" + x + "\"\n" - -gradle_classpath = "" -for x in env.android_gradle_classpath: - gradle_classpath += "\t\tclasspath \"" + x + "\"\n" - -gradle_res_dirs_text = "" - -for x in env.android_res_dirs: - gradle_res_dirs_text += ",'" + x.replace("\\", "/") + "'" - -gradle_aidl_dirs_text = "" - -for x in env.android_aidl_dirs: - gradle_aidl_dirs_text += ",'" + x.replace("\\", "/") + "'" - -gradle_jni_dirs_text = "" - -for x in env.android_jni_dirs: - gradle_jni_dirs_text += ",'" + x.replace("\\", "/") + "'" - -gradle_asset_dirs_text = "" - -for x in env.android_asset_dirs: - gradle_asset_dirs_text += ",'" + x.replace("\\", "/") + "'" - -gradle_default_config_text = "" - -minSdk = 18 -targetSdk = 27 - -for x in env.android_default_config: - if x.startswith("minSdkVersion") and int(x.split(" ")[-1]) < minSdk: - x = "minSdkVersion " + str(minSdk) - if x.startswith("targetSdkVersion") and int(x.split(" ")[-1]) > targetSdk: - x = "targetSdkVersion " + str(targetSdk) - - gradle_default_config_text += x + "\n\t\t" - -if "minSdkVersion" not in gradle_default_config_text: - gradle_default_config_text += ("minSdkVersion " + str(minSdk) + "\n\t\t") - -if "targetSdkVersion" not in gradle_default_config_text: - gradle_default_config_text += ("targetSdkVersion " + str(targetSdk) + "\n\t\t") - -gradle_text = gradle_text.replace("$$GRADLE_REPOSITORY_URLS$$", gradle_maven_repos_text) -gradle_text = gradle_text.replace("$$GRADLE_DEPENDENCIES$$", gradle_maven_dependencies_text) -gradle_text = gradle_text.replace("$$GRADLE_JAVA_DIRS$$", gradle_java_dirs_text) -gradle_text = gradle_text.replace("$$GRADLE_RES_DIRS$$", gradle_res_dirs_text) -gradle_text = gradle_text.replace("$$GRADLE_ASSET_DIRS$$", gradle_asset_dirs_text) -gradle_text = gradle_text.replace("$$GRADLE_AIDL_DIRS$$", gradle_aidl_dirs_text) -gradle_text = gradle_text.replace("$$GRADLE_JNI_DIRS$$", gradle_jni_dirs_text) -gradle_text = gradle_text.replace("$$GRADLE_DEFAULT_CONFIG$$", gradle_default_config_text) -gradle_text = gradle_text.replace("$$GRADLE_PLUGINS$$", gradle_plugins) -gradle_text = gradle_text.replace("$$GRADLE_CLASSPATH$$", gradle_classpath) - -with open_utf8(abspath + "/java/build.gradle", "w") as gradle_baseout: - gradle_baseout.write(gradle_text) - - -with open_utf8(abspath + "/AndroidManifest.xml.template", "r") as pp_basein: - manifest = pp_basein.read() - -manifest = manifest.replace("$$ADD_APPLICATION_CHUNKS$$", env.android_manifest_chunk) -manifest = manifest.replace("$$ADD_PERMISSION_CHUNKS$$", env.android_permission_chunk) -manifest = manifest.replace("$$ADD_APPATTRIBUTE_CHUNKS$$", env.android_appattributes_chunk) - -with open_utf8(abspath + "/java/AndroidManifest.xml", "w") as pp_baseout: - pp_baseout.write(manifest) - +android_objects.append(env_thirdparty.SharedObject('#thirdparty/misc/ifaddrs-android.cc')) lib = env_android.add_shared_library("#bin/libgodot", [android_objects], SHLIBSUFFIX=env["SHLIBSUFFIX"]) @@ -161,6 +45,8 @@ elif env['android_arch'] == 'arm64v8': lib_arch_dir = 'arm64-v8a' elif env['android_arch'] == 'x86': lib_arch_dir = 'x86' +elif env['android_arch'] == 'x86_64': + lib_arch_dir = 'x86_64' else: print('WARN: Architecture not suitable for embedding into APK; keeping .so at \\bin') diff --git a/platform/android/audio_driver_jandroid.cpp b/platform/android/audio_driver_jandroid.cpp index b75a4a3869..bcef5b0c85 100644 --- a/platform/android/audio_driver_jandroid.cpp +++ b/platform/android/audio_driver_jandroid.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/audio_driver_jandroid.h b/platform/android/audio_driver_jandroid.h index 3c51ed746d..f92ef06052 100644 --- a/platform/android/audio_driver_jandroid.h +++ b/platform/android/audio_driver_jandroid.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -33,7 +33,7 @@ #include "servers/audio_server.h" -#include "java_glue.h" +#include "java_godot_lib_jni.h" class AudioDriverAndroid : public AudioDriver { diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp index 21c61f6ca0..1232fc7453 100644 --- a/platform/android/audio_driver_opensl.cpp +++ b/platform/android/audio_driver_opensl.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -53,7 +53,7 @@ void AudioDriverOpenSL::_buffer_callback( } else { int32_t *src_buff = mixdown_buffer; - for (int i = 0; i < buffer_size * 2; i++) { + for (unsigned int i = 0; i < buffer_size * 2; i++) { src_buff[i] = 0; } } @@ -66,7 +66,7 @@ void AudioDriverOpenSL::_buffer_callback( int16_t *ptr = (int16_t *)buffers[last_free]; last_free = (last_free + 1) % BUFFER_COUNT; - for (int i = 0; i < buffer_size * 2; i++) { + for (unsigned int i = 0; i < buffer_size * 2; i++) { ptr[i] = src_buff[i] >> 16; } @@ -211,9 +211,122 @@ void AudioDriverOpenSL::start() { active = true; } +void AudioDriverOpenSL::_record_buffer_callback(SLAndroidSimpleBufferQueueItf queueItf) { + + for (int i = 0; i < rec_buffer.size(); i++) { + int32_t sample = rec_buffer[i] << 16; + input_buffer_write(sample); + input_buffer_write(sample); // call twice to convert to Stereo + } + + SLresult res = (*recordBufferQueueItf)->Enqueue(recordBufferQueueItf, rec_buffer.ptrw(), rec_buffer.size() * sizeof(int16_t)); + ERR_FAIL_COND(res != SL_RESULT_SUCCESS); +} + +void AudioDriverOpenSL::_record_buffer_callbacks(SLAndroidSimpleBufferQueueItf queueItf, void *pContext) { + + AudioDriverOpenSL *ad = (AudioDriverOpenSL *)pContext; + + ad->_record_buffer_callback(queueItf); +} + +Error AudioDriverOpenSL::capture_init_device() { + + SLDataLocator_IODevice loc_dev = { + SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, + NULL + }; + SLDataSource recSource = { &loc_dev, NULL }; + + SLDataLocator_AndroidSimpleBufferQueue loc_bq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + 2 + }; + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, + 1, + SL_SAMPLINGRATE_44_1, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN + }; + SLDataSink recSnk = { &loc_bq, &format_pcm }; + + const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }; + const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; + + SLresult res = (*EngineItf)->CreateAudioRecorder(EngineItf, &recorder, &recSource, &recSnk, 2, ids, req); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + res = (*recorder)->Realize(recorder, SL_BOOLEAN_FALSE); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + res = (*recorder)->GetInterface(recorder, SL_IID_RECORD, (void *)&recordItf); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + res = (*recorder)->GetInterface(recorder, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, (void *)&recordBufferQueueItf); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + res = (*recordBufferQueueItf)->RegisterCallback(recordBufferQueueItf, _record_buffer_callbacks, this); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + SLuint32 state; + res = (*recordItf)->GetRecordState(recordItf, &state); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + if (state != SL_RECORDSTATE_STOPPED) { + res = (*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_STOPPED); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + res = (*recordBufferQueueItf)->Clear(recordBufferQueueItf); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + } + + const int rec_buffer_frames = 2048; + rec_buffer.resize(rec_buffer_frames); + input_buffer_init(rec_buffer_frames); + + res = (*recordBufferQueueItf)->Enqueue(recordBufferQueueItf, rec_buffer.ptrw(), rec_buffer.size() * sizeof(int16_t)); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + res = (*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_RECORDING); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + return OK; +} + +Error AudioDriverOpenSL::capture_start() { + + if (OS::get_singleton()->request_permission("RECORD_AUDIO")) { + return capture_init_device(); + } + + return OK; +} + +Error AudioDriverOpenSL::capture_stop() { + + SLuint32 state; + SLresult res = (*recordItf)->GetRecordState(recordItf, &state); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + if (state != SL_RECORDSTATE_STOPPED) { + res = (*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_STOPPED); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + + res = (*recordBufferQueueItf)->Clear(recordBufferQueueItf); + ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); + } + + return OK; +} + int AudioDriverOpenSL::get_mix_rate() const { - return 44100; + return 44100; // hardcoded for Android, as selected by SL_SAMPLINGRATE_44_1 } AudioDriver::SpeakerMode AudioDriverOpenSL::get_speaker_mode() const { diff --git a/platform/android/audio_driver_opensl.h b/platform/android/audio_driver_opensl.h index 39e1315a02..2981073cec 100644 --- a/platform/android/audio_driver_opensl.h +++ b/platform/android/audio_driver_opensl.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -54,13 +54,18 @@ class AudioDriverOpenSL : public AudioDriver { int32_t *mixdown_buffer; int last_free; + Vector<int16_t> rec_buffer; + SLPlayItf playItf; + SLRecordItf recordItf; SLObjectItf sl; SLEngineItf EngineItf; SLObjectItf OutputMix; SLVolumeItf volumeItf; SLObjectItf player; + SLObjectItf recorder; SLAndroidSimpleBufferQueueItf bufferQueueItf; + SLAndroidSimpleBufferQueueItf recordBufferQueueItf; SLDataSource audioSource; SLDataFormat_PCM pcm; SLDataSink audioSink; @@ -76,6 +81,15 @@ class AudioDriverOpenSL : public AudioDriver { SLAndroidSimpleBufferQueueItf queueItf, void *pContext); + void _record_buffer_callback( + SLAndroidSimpleBufferQueueItf queueItf); + + static void _record_buffer_callbacks( + SLAndroidSimpleBufferQueueItf queueItf, + void *pContext); + + virtual Error capture_init_device(); + public: void set_singleton(); @@ -91,6 +105,9 @@ public: virtual void set_pause(bool p_pause); + virtual Error capture_start(); + virtual Error capture_stop(); + AudioDriverOpenSL(); }; diff --git a/platform/android/build.gradle.template b/platform/android/build.gradle.template deleted file mode 100644 index 18ffc74fc3..0000000000 --- a/platform/android/build.gradle.template +++ /dev/null @@ -1,87 +0,0 @@ -buildscript { - repositories { - google() - jcenter() - $$GRADLE_REPOSITORY_URLS$$ - } - dependencies { - classpath 'com.android.tools.build:gradle:3.2.0' - $$GRADLE_CLASSPATH$$ - } -} - -apply plugin: 'com.android.application' - -allprojects { - repositories { - mavenCentral() - google() - jcenter() - $$GRADLE_REPOSITORY_URLS$$ - } -} - -dependencies { - $$GRADLE_DEPENDENCIES$$ -} - -android { - - lintOptions { - abortOnError false - disable 'MissingTranslation' - } - - compileSdkVersion 27 - buildToolsVersion "28.0.3" - useLibrary 'org.apache.http.legacy' - - packagingOptions { - exclude 'META-INF/LICENSE' - exclude 'META-INF/NOTICE' - } - defaultConfig { - $$GRADLE_DEFAULT_CONFIG$$ - } - // Both signing and zip-aligning will be done at export time - buildTypes.all { buildType -> - buildType.zipAlignEnabled false - buildType.signingConfig null - } - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src' - $$GRADLE_JAVA_DIRS$$ - ] - res.srcDirs = [ - 'res' - $$GRADLE_RES_DIRS$$ - ] - aidl.srcDirs = [ - 'aidl' - $$GRADLE_AIDL_DIRS$$ - ] - assets.srcDirs = [ - 'assets' - $$GRADLE_ASSET_DIRS$$ - ] - } - debug.jniLibs.srcDirs = [ - 'libs/debug' - $$GRADLE_JNI_DIRS$$ - ] - release.jniLibs.srcDirs = [ - 'libs/release' - $$GRADLE_JNI_DIRS$$ - ] - } - - applicationVariants.all { variant -> - variant.outputs.all { output -> - output.outputFileName = "../../../../../../../bin/android_${variant.name}.apk" - } - } -} - -$$GRADLE_PLUGINS$$ diff --git a/platform/android/cpu-features.c b/platform/android/cpu-features.c deleted file mode 100644 index 9cdadd5407..0000000000 --- a/platform/android/cpu-features.c +++ /dev/null @@ -1,1089 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* ChangeLog for this library: - * - * NDK r8d: Add android_setCpu(). - * - * NDK r8c: Add new ARM CPU features: VFPv2, VFP_D32, VFP_FP16, - * VFP_FMA, NEON_FMA, IDIV_ARM, IDIV_THUMB2 and iWMMXt. - * - * Rewrite the code to parse /proc/self/auxv instead of - * the "Features" field in /proc/cpuinfo. - * - * Dynamically allocate the buffer that hold the content - * of /proc/cpuinfo to deal with newer hardware. - * - * NDK r7c: Fix CPU count computation. The old method only reported the - * number of _active_ CPUs when the library was initialized, - * which could be less than the real total. - * - * NDK r5: Handle buggy kernels which report a CPU Architecture number of 7 - * for an ARMv6 CPU (see below). - * - * Handle kernels that only report 'neon', and not 'vfpv3' - * (VFPv3 is mandated by the ARM architecture is Neon is implemented) - * - * Handle kernels that only report 'vfpv3d16', and not 'vfpv3' - * - * Fix x86 compilation. Report ANDROID_CPU_FAMILY_X86 in - * android_getCpuFamily(). - * - * NDK r4: Initial release - */ - -#if defined(__le32__) - -// When users enter this, we should only provide interface and -// libportable will give the implementations. - -#else // !__le32__ - -#include <sys/system_properties.h> -#include <pthread.h> -#include "cpu-features.h" -#include <stdio.h> -#include <stdlib.h> -#include <fcntl.h> -#include <errno.h> - -static pthread_once_t g_once; -static int g_inited; -static AndroidCpuFamily g_cpuFamily; -static uint64_t g_cpuFeatures; -static int g_cpuCount; - -#ifdef __arm__ -static uint32_t g_cpuIdArm; -#endif - -static const int android_cpufeatures_debug = 0; - -#ifdef __arm__ -# define DEFAULT_CPU_FAMILY ANDROID_CPU_FAMILY_ARM -#elif defined __i386__ -# define DEFAULT_CPU_FAMILY ANDROID_CPU_FAMILY_X86 -#else -# define DEFAULT_CPU_FAMILY ANDROID_CPU_FAMILY_UNKNOWN -#endif - -#define D(...) \ - do { \ - if (android_cpufeatures_debug) { \ - printf(__VA_ARGS__); fflush(stdout); \ - } \ - } while (0) - -#ifdef __i386__ -static __inline__ void x86_cpuid(int func, int values[4]) -{ - int a, b, c, d; - /* We need to preserve ebx since we're compiling PIC code */ - /* this means we can't use "=b" for the second output register */ - __asm__ __volatile__ ( \ - "push %%ebx\n" - "cpuid\n" \ - "mov %%ebx, %1\n" - "pop %%ebx\n" - : "=a" (a), "=r" (b), "=c" (c), "=d" (d) \ - : "a" (func) \ - ); - values[0] = a; - values[1] = b; - values[2] = c; - values[3] = d; -} -#endif - -/* Get the size of a file by reading it until the end. This is needed - * because files under /proc do not always return a valid size when - * using fseek(0, SEEK_END) + ftell(). Nor can they be mmap()-ed. - */ -static int -get_file_size(const char* pathname) -{ - int fd, result = 0; - char buffer[256]; - - fd = open(pathname, O_RDONLY); - if (fd < 0) { - D("Can't open %s: %s\n", pathname, strerror(errno)); - return -1; - } - - for (;;) { - int ret = read(fd, buffer, sizeof buffer); - if (ret < 0) { - if (errno == EINTR) - continue; - D("Error while reading %s: %s\n", pathname, strerror(errno)); - break; - } - if (ret == 0) - break; - - result += ret; - } - close(fd); - return result; -} - -/* Read the content of /proc/cpuinfo into a user-provided buffer. - * Return the length of the data, or -1 on error. Does *not* - * zero-terminate the content. Will not read more - * than 'buffsize' bytes. - */ -static int -read_file(const char* pathname, char* buffer, size_t buffsize) -{ - int fd, count; - - fd = open(pathname, O_RDONLY); - if (fd < 0) { - D("Could not open %s: %s\n", pathname, strerror(errno)); - return -1; - } - count = 0; - while (count < (int)buffsize) { - int ret = read(fd, buffer + count, buffsize - count); - if (ret < 0) { - if (errno == EINTR) - continue; - D("Error while reading from %s: %s\n", pathname, strerror(errno)); - if (count == 0) - count = -1; - break; - } - if (ret == 0) - break; - count += ret; - } - close(fd); - return count; -} - -/* Extract the content of a the first occurence of a given field in - * the content of /proc/cpuinfo and return it as a heap-allocated - * string that must be freed by the caller. - * - * Return NULL if not found - */ -static char* -extract_cpuinfo_field(const char* buffer, int buflen, const char* field) -{ - int fieldlen = strlen(field); - const char* bufend = buffer + buflen; - char* result = NULL; - int len, ignore; - const char *p, *q; - - /* Look for first field occurence, and ensures it starts the line. */ - p = buffer; - for (;;) { - p = memmem(p, bufend-p, field, fieldlen); - if (p == NULL) - goto EXIT; - - if (p == buffer || p[-1] == '\n') - break; - - p += fieldlen; - } - - /* Skip to the first column followed by a space */ - p += fieldlen; - p = memchr(p, ':', bufend-p); - if (p == NULL || p[1] != ' ') - goto EXIT; - - /* Find the end of the line */ - p += 2; - q = memchr(p, '\n', bufend-p); - if (q == NULL) - q = bufend; - - /* Copy the line into a heap-allocated buffer */ - len = q-p; - result = malloc(len+1); - if (result == NULL) - goto EXIT; - - memcpy(result, p, len); - result[len] = '\0'; - -EXIT: - return result; -} - -/* Checks that a space-separated list of items contains one given 'item'. - * Returns 1 if found, 0 otherwise. - */ -static int -has_list_item(const char* list, const char* item) -{ - const char* p = list; - int itemlen = strlen(item); - - if (list == NULL) - return 0; - - while (*p) { - const char* q; - - /* skip spaces */ - while (*p == ' ' || *p == '\t') - p++; - - /* find end of current list item */ - q = p; - while (*q && *q != ' ' && *q != '\t') - q++; - - if (itemlen == q-p && !memcmp(p, item, itemlen)) - return 1; - - /* skip to next item */ - p = q; - } - return 0; -} - -/* Parse a number starting from 'input', but not going further - * than 'limit'. Return the value into '*result'. - * - * NOTE: Does not skip over leading spaces, or deal with sign characters. - * NOTE: Ignores overflows. - * - * The function returns NULL in case of error (bad format), or the new - * position after the decimal number in case of success (which will always - * be <= 'limit'). - */ -static const char* -parse_number(const char* input, const char* limit, int base, int* result) -{ - const char* p = input; - int val = 0; - while (p < limit) { - int d = (*p - '0'); - if ((unsigned)d >= 10U) { - d = (*p - 'a'); - if ((unsigned)d >= 6U) - d = (*p - 'A'); - if ((unsigned)d >= 6U) - break; - d += 10; - } - if (d >= base) - break; - val = val*base + d; - p++; - } - if (p == input) - return NULL; - - *result = val; - return p; -} - -static const char* -parse_decimal(const char* input, const char* limit, int* result) -{ - return parse_number(input, limit, 10, result); -} - -static const char* -parse_hexadecimal(const char* input, const char* limit, int* result) -{ - return parse_number(input, limit, 16, result); -} - -/* This small data type is used to represent a CPU list / mask, as read - * from sysfs on Linux. See http://www.kernel.org/doc/Documentation/cputopology.txt - * - * For now, we don't expect more than 32 cores on mobile devices, so keep - * everything simple. - */ -typedef struct { - uint32_t mask; -} CpuList; - -static __inline__ void -cpulist_init(CpuList* list) { - list->mask = 0; -} - -static __inline__ void -cpulist_and(CpuList* list1, CpuList* list2) { - list1->mask &= list2->mask; -} - -static __inline__ void -cpulist_set(CpuList* list, int index) { - if ((unsigned)index < 32) { - list->mask |= (uint32_t)(1U << index); - } -} - -static __inline__ int -cpulist_count(CpuList* list) { - return __builtin_popcount(list->mask); -} - -/* Parse a textual list of cpus and store the result inside a CpuList object. - * Input format is the following: - * - comma-separated list of items (no spaces) - * - each item is either a single decimal number (cpu index), or a range made - * of two numbers separated by a single dash (-). Ranges are inclusive. - * - * Examples: 0 - * 2,4-127,128-143 - * 0-1 - */ -static void -cpulist_parse(CpuList* list, const char* line, int line_len) -{ - const char* p = line; - const char* end = p + line_len; - const char* q; - - /* NOTE: the input line coming from sysfs typically contains a - * trailing newline, so take care of it in the code below - */ - while (p < end && *p != '\n') - { - int val, start_value, end_value; - - /* Find the end of current item, and put it into 'q' */ - q = memchr(p, ',', end-p); - if (q == NULL) { - q = end; - } - - /* Get first value */ - p = parse_decimal(p, q, &start_value); - if (p == NULL) - goto BAD_FORMAT; - - end_value = start_value; - - /* If we're not at the end of the item, expect a dash and - * and integer; extract end value. - */ - if (p < q && *p == '-') { - p = parse_decimal(p+1, q, &end_value); - if (p == NULL) - goto BAD_FORMAT; - } - - /* Set bits CPU list bits */ - for (val = start_value; val <= end_value; val++) { - cpulist_set(list, val); - } - - /* Jump to next item */ - p = q; - if (p < end) - p++; - } - -BAD_FORMAT: - ; -} - -/* Read a CPU list from one sysfs file */ -static void -cpulist_read_from(CpuList* list, const char* filename) -{ - char file[64]; - int filelen; - - cpulist_init(list); - - filelen = read_file(filename, file, sizeof file); - if (filelen < 0) { - D("Could not read %s: %s\n", filename, strerror(errno)); - return; - } - - cpulist_parse(list, file, filelen); -} - -// See <asm/hwcap.h> kernel header. -#define HWCAP_VFP (1 << 6) -#define HWCAP_IWMMXT (1 << 9) -#define HWCAP_NEON (1 << 12) -#define HWCAP_VFPv3 (1 << 13) -#define HWCAP_VFPv3D16 (1 << 14) -#define HWCAP_VFPv4 (1 << 16) -#define HWCAP_IDIVA (1 << 17) -#define HWCAP_IDIVT (1 << 18) - -#define AT_HWCAP 16 - -#if defined(__arm__) -/* Compute the ELF HWCAP flags. - */ -static uint32_t -get_elf_hwcap(const char* cpuinfo, int cpuinfo_len) -{ - /* IMPORTANT: - * Accessing /proc/self/auxv doesn't work anymore on all - * platform versions. More specifically, when running inside - * a regular application process, most of /proc/self/ will be - * non-readable, including /proc/self/auxv. This doesn't - * happen however if the application is debuggable, or when - * running under the "shell" UID, which is why this was not - * detected appropriately. - */ -#if 0 - uint32_t result = 0; - const char filepath[] = "/proc/self/auxv"; - int fd = open(filepath, O_RDONLY); - if (fd < 0) { - D("Could not open %s: %s\n", filepath, strerror(errno)); - return 0; - } - - struct { uint32_t tag; uint32_t value; } entry; - - for (;;) { - int ret = read(fd, (char*)&entry, sizeof entry); - if (ret < 0) { - if (errno == EINTR) - continue; - D("Error while reading %s: %s\n", filepath, strerror(errno)); - break; - } - // Detect end of list. - if (ret == 0 || (entry.tag == 0 && entry.value == 0)) - break; - if (entry.tag == AT_HWCAP) { - result = entry.value; - break; - } - } - close(fd); - return result; -#else - // Recreate ELF hwcaps by parsing /proc/cpuinfo Features tag. - uint32_t hwcaps = 0; - - char* cpuFeatures = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "Features"); - - if (cpuFeatures != NULL) { - D("Found cpuFeatures = '%s'\n", cpuFeatures); - - if (has_list_item(cpuFeatures, "vfp")) - hwcaps |= HWCAP_VFP; - if (has_list_item(cpuFeatures, "vfpv3")) - hwcaps |= HWCAP_VFPv3; - if (has_list_item(cpuFeatures, "vfpv3d16")) - hwcaps |= HWCAP_VFPv3D16; - if (has_list_item(cpuFeatures, "vfpv4")) - hwcaps |= HWCAP_VFPv4; - if (has_list_item(cpuFeatures, "neon")) - hwcaps |= HWCAP_NEON; - if (has_list_item(cpuFeatures, "idiva")) - hwcaps |= HWCAP_IDIVA; - if (has_list_item(cpuFeatures, "idivt")) - hwcaps |= HWCAP_IDIVT; - if (has_list_item(cpuFeatures, "idiv")) - hwcaps |= HWCAP_IDIVA | HWCAP_IDIVT; - if (has_list_item(cpuFeatures, "iwmmxt")) - hwcaps |= HWCAP_IWMMXT; - - free(cpuFeatures); - } - return hwcaps; -#endif -} -#endif /* __arm__ */ - -/* Return the number of cpus present on a given device. - * - * To handle all weird kernel configurations, we need to compute the - * intersection of the 'present' and 'possible' CPU lists and count - * the result. - */ -static int -get_cpu_count(void) -{ - CpuList cpus_present[1]; - CpuList cpus_possible[1]; - - cpulist_read_from(cpus_present, "/sys/devices/system/cpu/present"); - cpulist_read_from(cpus_possible, "/sys/devices/system/cpu/possible"); - - /* Compute the intersection of both sets to get the actual number of - * CPU cores that can be used on this device by the kernel. - */ - cpulist_and(cpus_present, cpus_possible); - - return cpulist_count(cpus_present); -} - -static void -android_cpuInitFamily(void) -{ -#if defined(__arm__) - g_cpuFamily = ANDROID_CPU_FAMILY_ARM; -#elif defined(__i386__) - g_cpuFamily = ANDROID_CPU_FAMILY_X86; -#elif defined(__mips64) -/* Needs to be before __mips__ since the compiler defines both */ - g_cpuFamily = ANDROID_CPU_FAMILY_MIPS64; -#elif defined(__mips__) - g_cpuFamily = ANDROID_CPU_FAMILY_MIPS; -#elif defined(__aarch64__) - g_cpuFamily = ANDROID_CPU_FAMILY_ARM64; -#elif defined(__x86_64__) - g_cpuFamily = ANDROID_CPU_FAMILY_X86_64; -#else - g_cpuFamily = ANDROID_CPU_FAMILY_UNKNOWN; -#endif -} - -static void -android_cpuInit(void) -{ - char* cpuinfo = NULL; - int cpuinfo_len; - - android_cpuInitFamily(); - - g_cpuFeatures = 0; - g_cpuCount = 1; - g_inited = 1; - - cpuinfo_len = get_file_size("/proc/cpuinfo"); - if (cpuinfo_len < 0) { - D("cpuinfo_len cannot be computed!"); - return; - } - cpuinfo = malloc(cpuinfo_len); - if (cpuinfo == NULL) { - D("cpuinfo buffer could not be allocated"); - return; - } - cpuinfo_len = read_file("/proc/cpuinfo", cpuinfo, cpuinfo_len); - D("cpuinfo_len is (%d):\n%.*s\n", cpuinfo_len, - cpuinfo_len >= 0 ? cpuinfo_len : 0, cpuinfo); - - if (cpuinfo_len < 0) /* should not happen */ { - free(cpuinfo); - return; - } - - /* Count the CPU cores, the value may be 0 for single-core CPUs */ - g_cpuCount = get_cpu_count(); - if (g_cpuCount == 0) { - g_cpuCount = 1; - } - - D("found cpuCount = %d\n", g_cpuCount); - -#ifdef __arm__ - { - char* features = NULL; - char* architecture = NULL; - - /* Extract architecture from the "CPU Architecture" field. - * The list is well-known, unlike the the output of - * the 'Processor' field which can vary greatly. - * - * See the definition of the 'proc_arch' array in - * $KERNEL/arch/arm/kernel/setup.c and the 'c_show' function in - * same file. - */ - char* cpuArch = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "CPU architecture"); - - if (cpuArch != NULL) { - char* end; - long archNumber; - int hasARMv7 = 0; - - D("found cpuArch = '%s'\n", cpuArch); - - /* read the initial decimal number, ignore the rest */ - archNumber = strtol(cpuArch, &end, 10); - - /* Here we assume that ARMv8 will be upwards compatible with v7 - * in the future. Unfortunately, there is no 'Features' field to - * indicate that Thumb-2 is supported. - */ - if (end > cpuArch && archNumber >= 7) { - hasARMv7 = 1; - } - - /* Unfortunately, it seems that certain ARMv6-based CPUs - * report an incorrect architecture number of 7! - * - * See http://code.google.com/p/android/issues/detail?id=10812 - * - * We try to correct this by looking at the 'elf_format' - * field reported by the 'Processor' field, which is of the - * form of "(v7l)" for an ARMv7-based CPU, and "(v6l)" for - * an ARMv6-one. - */ - if (hasARMv7) { - char* cpuProc = extract_cpuinfo_field(cpuinfo, cpuinfo_len, - "Processor"); - if (cpuProc != NULL) { - D("found cpuProc = '%s'\n", cpuProc); - if (has_list_item(cpuProc, "(v6l)")) { - D("CPU processor and architecture mismatch!!\n"); - hasARMv7 = 0; - } - free(cpuProc); - } - } - - if (hasARMv7) { - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_ARMv7; - } - - /* The LDREX / STREX instructions are available from ARMv6 */ - if (archNumber >= 6) { - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_LDREX_STREX; - } - - free(cpuArch); - } - - /* Extract the list of CPU features from ELF hwcaps */ - uint32_t hwcaps = get_elf_hwcap(cpuinfo, cpuinfo_len); - - if (hwcaps != 0) { - int has_vfp = (hwcaps & HWCAP_VFP); - int has_vfpv3 = (hwcaps & HWCAP_VFPv3); - int has_vfpv3d16 = (hwcaps & HWCAP_VFPv3D16); - int has_vfpv4 = (hwcaps & HWCAP_VFPv4); - int has_neon = (hwcaps & HWCAP_NEON); - int has_idiva = (hwcaps & HWCAP_IDIVA); - int has_idivt = (hwcaps & HWCAP_IDIVT); - int has_iwmmxt = (hwcaps & HWCAP_IWMMXT); - - // The kernel does a poor job at ensuring consistency when - // describing CPU features. So lots of guessing is needed. - - // 'vfpv4' implies VFPv3|VFP_FMA|FP16 - if (has_vfpv4) - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3 | - ANDROID_CPU_ARM_FEATURE_VFP_FP16 | - ANDROID_CPU_ARM_FEATURE_VFP_FMA; - - // 'vfpv3' or 'vfpv3d16' imply VFPv3. Note that unlike GCC, - // a value of 'vfpv3' doesn't necessarily mean that the D32 - // feature is present, so be conservative. All CPUs in the - // field that support D32 also support NEON, so this should - // not be a problem in practice. - if (has_vfpv3 || has_vfpv3d16) - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3; - - // 'vfp' is super ambiguous. Depending on the kernel, it can - // either mean VFPv2 or VFPv3. Make it depend on ARMv7. - if (has_vfp) { - if (g_cpuFeatures & ANDROID_CPU_ARM_FEATURE_ARMv7) - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3; - else - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv2; - } - - // Neon implies VFPv3|D32, and if vfpv4 is detected, NEON_FMA - if (has_neon) { - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3 | - ANDROID_CPU_ARM_FEATURE_NEON | - ANDROID_CPU_ARM_FEATURE_VFP_D32; - if (has_vfpv4) - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_NEON_FMA; - } - - // VFPv3 implies VFPv2 and ARMv7 - if (g_cpuFeatures & ANDROID_CPU_ARM_FEATURE_VFPv3) - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv2 | - ANDROID_CPU_ARM_FEATURE_ARMv7; - - if (has_idiva) - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_IDIV_ARM; - if (has_idivt) - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2; - - if (has_iwmmxt) - g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_iWMMXt; - } - - /* Extract the cpuid value from various fields */ - // The CPUID value is broken up in several entries in /proc/cpuinfo. - // This table is used to rebuild it from the entries. - static const struct CpuIdEntry { - const char* field; - char format; - char bit_lshift; - char bit_length; - } cpu_id_entries[] = { - { "CPU implementer", 'x', 24, 8 }, - { "CPU variant", 'x', 20, 4 }, - { "CPU part", 'x', 4, 12 }, - { "CPU revision", 'd', 0, 4 }, - }; - size_t i; - D("Parsing /proc/cpuinfo to recover CPUID\n"); - for (i = 0; - i < sizeof(cpu_id_entries)/sizeof(cpu_id_entries[0]); - ++i) { - const struct CpuIdEntry* entry = &cpu_id_entries[i]; - char* value = extract_cpuinfo_field(cpuinfo, - cpuinfo_len, - entry->field); - if (value == NULL) - continue; - - D("field=%s value='%s'\n", entry->field, value); - char* value_end = value + strlen(value); - int val = 0; - const char* start = value; - const char* p; - if (value[0] == '0' && (value[1] == 'x' || value[1] == 'X')) { - start += 2; - p = parse_hexadecimal(start, value_end, &val); - } else if (entry->format == 'x') - p = parse_hexadecimal(value, value_end, &val); - else - p = parse_decimal(value, value_end, &val); - - if (p > (const char*)start) { - val &= ((1 << entry->bit_length)-1); - val <<= entry->bit_lshift; - g_cpuIdArm |= (uint32_t) val; - } - - free(value); - } - - // Handle kernel configuration bugs that prevent the correct - // reporting of CPU features. - static const struct CpuFix { - uint32_t cpuid; - uint64_t or_flags; - } cpu_fixes[] = { - /* The Nexus 4 (Qualcomm Krait) kernel configuration - * forgets to report IDIV support. */ - { 0x510006f2, ANDROID_CPU_ARM_FEATURE_IDIV_ARM | - ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2 }, - { 0x510006f3, ANDROID_CPU_ARM_FEATURE_IDIV_ARM | - ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2 }, - }; - size_t n; - for (n = 0; n < sizeof(cpu_fixes)/sizeof(cpu_fixes[0]); ++n) { - const struct CpuFix* entry = &cpu_fixes[n]; - - if (g_cpuIdArm == entry->cpuid) - g_cpuFeatures |= entry->or_flags; - } - - } -#endif /* __arm__ */ - -#ifdef __i386__ - int regs[4]; - -/* According to http://en.wikipedia.org/wiki/CPUID */ -#define VENDOR_INTEL_b 0x756e6547 -#define VENDOR_INTEL_c 0x6c65746e -#define VENDOR_INTEL_d 0x49656e69 - - x86_cpuid(0, regs); - int vendorIsIntel = (regs[1] == VENDOR_INTEL_b && - regs[2] == VENDOR_INTEL_c && - regs[3] == VENDOR_INTEL_d); - - x86_cpuid(1, regs); - if ((regs[2] & (1 << 9)) != 0) { - g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_SSSE3; - } - if ((regs[2] & (1 << 23)) != 0) { - g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_POPCNT; - } - if (vendorIsIntel && (regs[2] & (1 << 22)) != 0) { - g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_MOVBE; - } -#endif - - free(cpuinfo); -} - - -AndroidCpuFamily -android_getCpuFamily(void) -{ - pthread_once(&g_once, android_cpuInit); - return g_cpuFamily; -} - - -uint64_t -android_getCpuFeatures(void) -{ - pthread_once(&g_once, android_cpuInit); - return g_cpuFeatures; -} - - -int -android_getCpuCount(void) -{ - pthread_once(&g_once, android_cpuInit); - return g_cpuCount; -} - -static void -android_cpuInitDummy(void) -{ - g_inited = 1; -} - -int -android_setCpu(int cpu_count, uint64_t cpu_features) -{ - /* Fail if the library was already initialized. */ - if (g_inited) - return 0; - - android_cpuInitFamily(); - g_cpuCount = (cpu_count <= 0 ? 1 : cpu_count); - g_cpuFeatures = cpu_features; - pthread_once(&g_once, android_cpuInitDummy); - - return 1; -} - -#ifdef __arm__ -uint32_t -android_getCpuIdArm(void) -{ - pthread_once(&g_once, android_cpuInit); - return g_cpuIdArm; -} - -int -android_setCpuArm(int cpu_count, uint64_t cpu_features, uint32_t cpu_id) -{ - if (!android_setCpu(cpu_count, cpu_features)) - return 0; - - g_cpuIdArm = cpu_id; - return 1; -} -#endif /* __arm__ */ - -/* - * Technical note: Making sense of ARM's FPU architecture versions. - * - * FPA was ARM's first attempt at an FPU architecture. There is no Android - * device that actually uses it since this technology was already obsolete - * when the project started. If you see references to FPA instructions - * somewhere, you can be sure that this doesn't apply to Android at all. - * - * FPA was followed by "VFP", soon renamed "VFPv1" due to the emergence of - * new versions / additions to it. ARM considers this obsolete right now, - * and no known Android device implements it either. - * - * VFPv2 added a few instructions to VFPv1, and is an *optional* extension - * supported by some ARMv5TE, ARMv6 and ARMv6T2 CPUs. Note that a device - * supporting the 'armeabi' ABI doesn't necessarily support these. - * - * VFPv3-D16 adds a few instructions on top of VFPv2 and is typically used - * on ARMv7-A CPUs which implement a FPU. Note that it is also mandated - * by the Android 'armeabi-v7a' ABI. The -D16 suffix in its name means - * that it provides 16 double-precision FPU registers (d0-d15) and 32 - * single-precision ones (s0-s31) which happen to be mapped to the same - * register banks. - * - * VFPv3-D32 is the name of an extension to VFPv3-D16 that provides 16 - * additional double precision registers (d16-d31). Note that there are - * still only 32 single precision registers. - * - * VFPv3xD is a *subset* of VFPv3-D16 that only provides single-precision - * registers. It is only used on ARMv7-M (i.e. on micro-controllers) which - * are not supported by Android. Note that it is not compatible with VFPv2. - * - * NOTE: The term 'VFPv3' usually designate either VFPv3-D16 or VFPv3-D32 - * depending on context. For example GCC uses it for VFPv3-D32, but - * the Linux kernel code uses it for VFPv3-D16 (especially in - * /proc/cpuinfo). Always try to use the full designation when - * possible. - * - * NEON, a.k.a. "ARM Advanced SIMD" is an extension that provides - * instructions to perform parallel computations on vectors of 8, 16, - * 32, 64 and 128 bit quantities. NEON requires VFPv32-D32 since all - * NEON registers are also mapped to the same register banks. - * - * VFPv4-D16, adds a few instructions on top of VFPv3-D16 in order to - * perform fused multiply-accumulate on VFP registers, as well as - * half-precision (16-bit) conversion operations. - * - * VFPv4-D32 is VFPv4-D16 with 32, instead of 16, FPU double precision - * registers. - * - * VPFv4-NEON is VFPv4-D32 with NEON instructions. It also adds fused - * multiply-accumulate instructions that work on the NEON registers. - * - * NOTE: Similarly, "VFPv4" might either reference VFPv4-D16 or VFPv4-D32 - * depending on context. - * - * The following information was determined by scanning the binutils-2.22 - * sources: - * - * Basic VFP instruction subsets: - * - * #define FPU_VFP_EXT_V1xD 0x08000000 // Base VFP instruction set. - * #define FPU_VFP_EXT_V1 0x04000000 // Double-precision insns. - * #define FPU_VFP_EXT_V2 0x02000000 // ARM10E VFPr1. - * #define FPU_VFP_EXT_V3xD 0x01000000 // VFPv3 single-precision. - * #define FPU_VFP_EXT_V3 0x00800000 // VFPv3 double-precision. - * #define FPU_NEON_EXT_V1 0x00400000 // Neon (SIMD) insns. - * #define FPU_VFP_EXT_D32 0x00200000 // Registers D16-D31. - * #define FPU_VFP_EXT_FP16 0x00100000 // Half-precision extensions. - * #define FPU_NEON_EXT_FMA 0x00080000 // Neon fused multiply-add - * #define FPU_VFP_EXT_FMA 0x00040000 // VFP fused multiply-add - * - * FPU types (excluding NEON) - * - * FPU_VFP_V1xD (EXT_V1xD) - * | - * +--------------------------+ - * | | - * FPU_VFP_V1 (+EXT_V1) FPU_VFP_V3xD (+EXT_V2+EXT_V3xD) - * | | - * | | - * FPU_VFP_V2 (+EXT_V2) FPU_VFP_V4_SP_D16 (+EXT_FP16+EXT_FMA) - * | - * FPU_VFP_V3D16 (+EXT_Vx3D+EXT_V3) - * | - * +--------------------------+ - * | | - * FPU_VFP_V3 (+EXT_D32) FPU_VFP_V4D16 (+EXT_FP16+EXT_FMA) - * | | - * | FPU_VFP_V4 (+EXT_D32) - * | - * FPU_VFP_HARD (+EXT_FMA+NEON_EXT_FMA) - * - * VFP architectures: - * - * ARCH_VFP_V1xD (EXT_V1xD) - * | - * +------------------+ - * | | - * | ARCH_VFP_V3xD (+EXT_V2+EXT_V3xD) - * | | - * | ARCH_VFP_V3xD_FP16 (+EXT_FP16) - * | | - * | ARCH_VFP_V4_SP_D16 (+EXT_FMA) - * | - * ARCH_VFP_V1 (+EXT_V1) - * | - * ARCH_VFP_V2 (+EXT_V2) - * | - * ARCH_VFP_V3D16 (+EXT_V3xD+EXT_V3) - * | - * +-------------------+ - * | | - * | ARCH_VFP_V3D16_FP16 (+EXT_FP16) - * | - * +-------------------+ - * | | - * | ARCH_VFP_V4_D16 (+EXT_FP16+EXT_FMA) - * | | - * | ARCH_VFP_V4 (+EXT_D32) - * | | - * | ARCH_NEON_VFP_V4 (+EXT_NEON+EXT_NEON_FMA) - * | - * ARCH_VFP_V3 (+EXT_D32) - * | - * +-------------------+ - * | | - * | ARCH_VFP_V3_FP16 (+EXT_FP16) - * | - * ARCH_VFP_V3_PLUS_NEON_V1 (+EXT_NEON) - * | - * ARCH_NEON_FP16 (+EXT_FP16) - * - * -fpu=<name> values and their correspondance with FPU architectures above: - * - * {"vfp", FPU_ARCH_VFP_V2}, - * {"vfp9", FPU_ARCH_VFP_V2}, - * {"vfp3", FPU_ARCH_VFP_V3}, // For backwards compatbility. - * {"vfp10", FPU_ARCH_VFP_V2}, - * {"vfp10-r0", FPU_ARCH_VFP_V1}, - * {"vfpxd", FPU_ARCH_VFP_V1xD}, - * {"vfpv2", FPU_ARCH_VFP_V2}, - * {"vfpv3", FPU_ARCH_VFP_V3}, - * {"vfpv3-fp16", FPU_ARCH_VFP_V3_FP16}, - * {"vfpv3-d16", FPU_ARCH_VFP_V3D16}, - * {"vfpv3-d16-fp16", FPU_ARCH_VFP_V3D16_FP16}, - * {"vfpv3xd", FPU_ARCH_VFP_V3xD}, - * {"vfpv3xd-fp16", FPU_ARCH_VFP_V3xD_FP16}, - * {"neon", FPU_ARCH_VFP_V3_PLUS_NEON_V1}, - * {"neon-fp16", FPU_ARCH_NEON_FP16}, - * {"vfpv4", FPU_ARCH_VFP_V4}, - * {"vfpv4-d16", FPU_ARCH_VFP_V4D16}, - * {"fpv4-sp-d16", FPU_ARCH_VFP_V4_SP_D16}, - * {"neon-vfpv4", FPU_ARCH_NEON_VFP_V4}, - * - * - * Simplified diagram that only includes FPUs supported by Android: - * Only ARCH_VFP_V3D16 is actually mandated by the armeabi-v7a ABI, - * all others are optional and must be probed at runtime. - * - * ARCH_VFP_V3D16 (EXT_V1xD+EXT_V1+EXT_V2+EXT_V3xD+EXT_V3) - * | - * +-------------------+ - * | | - * | ARCH_VFP_V3D16_FP16 (+EXT_FP16) - * | - * +-------------------+ - * | | - * | ARCH_VFP_V4_D16 (+EXT_FP16+EXT_FMA) - * | | - * | ARCH_VFP_V4 (+EXT_D32) - * | | - * | ARCH_NEON_VFP_V4 (+EXT_NEON+EXT_NEON_FMA) - * | - * ARCH_VFP_V3 (+EXT_D32) - * | - * +-------------------+ - * | | - * | ARCH_VFP_V3_FP16 (+EXT_FP16) - * | - * ARCH_VFP_V3_PLUS_NEON_V1 (+EXT_NEON) - * | - * ARCH_NEON_FP16 (+EXT_FP16) - * - */ - -#endif // defined(__le32__) diff --git a/platform/android/cpu-features.h b/platform/android/cpu-features.h deleted file mode 100644 index 01b7fe207c..0000000000 --- a/platform/android/cpu-features.h +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -#ifndef CPU_FEATURES_H -#define CPU_FEATURES_H - -#include <sys/cdefs.h> -#include <stdint.h> - -__BEGIN_DECLS - -typedef enum { - ANDROID_CPU_FAMILY_UNKNOWN = 0, - ANDROID_CPU_FAMILY_ARM, - ANDROID_CPU_FAMILY_X86, - ANDROID_CPU_FAMILY_MIPS, - ANDROID_CPU_FAMILY_ARM64, - ANDROID_CPU_FAMILY_X86_64, - ANDROID_CPU_FAMILY_MIPS64, - - ANDROID_CPU_FAMILY_MAX /* do not remove */ - -} AndroidCpuFamily; - -/* Return family of the device's CPU */ -extern AndroidCpuFamily android_getCpuFamily(void); - -/* The list of feature flags for ARM CPUs that can be recognized by the - * library. Value details are: - * - * VFPv2: - * CPU supports the VFPv2 instruction set. Many, but not all, ARMv6 CPUs - * support these instructions. VFPv2 is a subset of VFPv3 so this will - * be set whenever VFPv3 is set too. - * - * ARMv7: - * CPU supports the ARMv7-A basic instruction set. - * This feature is mandated by the 'armeabi-v7a' ABI. - * - * VFPv3: - * CPU supports the VFPv3-D16 instruction set, providing hardware FPU - * support for single and double precision floating point registers. - * Note that only 16 FPU registers are available by default, unless - * the D32 bit is set too. This feature is also mandated by the - * 'armeabi-v7a' ABI. - * - * VFP_D32: - * CPU VFP optional extension that provides 32 FPU registers, - * instead of 16. Note that ARM mandates this feature is the 'NEON' - * feature is implemented by the CPU. - * - * NEON: - * CPU FPU supports "ARM Advanced SIMD" instructions, also known as - * NEON. Note that this mandates the VFP_D32 feature as well, per the - * ARM Architecture specification. - * - * VFP_FP16: - * Half-width floating precision VFP extension. If set, the CPU - * supports instructions to perform floating-point operations on - * 16-bit registers. This is part of the VFPv4 specification, but - * not mandated by any Android ABI. - * - * VFP_FMA: - * Fused multiply-accumulate VFP instructions extension. Also part of - * the VFPv4 specification, but not mandated by any Android ABI. - * - * NEON_FMA: - * Fused multiply-accumulate NEON instructions extension. Optional - * extension from the VFPv4 specification, but not mandated by any - * Android ABI. - * - * IDIV_ARM: - * Integer division available in ARM mode. Only available - * on recent CPUs (e.g. Cortex-A15). - * - * IDIV_THUMB2: - * Integer division available in Thumb-2 mode. Only available - * on recent CPUs (e.g. Cortex-A15). - * - * iWMMXt: - * Optional extension that adds MMX registers and operations to an - * ARM CPU. This is only available on a few XScale-based CPU designs - * sold by Marvell. Pretty rare in practice. - * - * If you want to tell the compiler to generate code that targets one of - * the feature set above, you should probably use one of the following - * flags (for more details, see technical note at the end of this file): - * - * -mfpu=vfp - * -mfpu=vfpv2 - * These are equivalent and tell GCC to use VFPv2 instructions for - * floating-point operations. Use this if you want your code to - * run on *some* ARMv6 devices, and any ARMv7-A device supported - * by Android. - * - * Generated code requires VFPv2 feature. - * - * -mfpu=vfpv3-d16 - * Tell GCC to use VFPv3 instructions (using only 16 FPU registers). - * This should be generic code that runs on any CPU that supports the - * 'armeabi-v7a' Android ABI. Note that no ARMv6 CPU supports this. - * - * Generated code requires VFPv3 feature. - * - * -mfpu=vfpv3 - * Tell GCC to use VFPv3 instructions with 32 FPU registers. - * Generated code requires VFPv3|VFP_D32 features. - * - * -mfpu=neon - * Tell GCC to use VFPv3 instructions with 32 FPU registers, and - * also support NEON intrinsics (see <arm_neon.h>). - * Generated code requires VFPv3|VFP_D32|NEON features. - * - * -mfpu=vfpv4-d16 - * Generated code requires VFPv3|VFP_FP16|VFP_FMA features. - * - * -mfpu=vfpv4 - * Generated code requires VFPv3|VFP_FP16|VFP_FMA|VFP_D32 features. - * - * -mfpu=neon-vfpv4 - * Generated code requires VFPv3|VFP_FP16|VFP_FMA|VFP_D32|NEON|NEON_FMA - * features. - * - * -mcpu=cortex-a7 - * -mcpu=cortex-a15 - * Generated code requires VFPv3|VFP_FP16|VFP_FMA|VFP_D32| - * NEON|NEON_FMA|IDIV_ARM|IDIV_THUMB2 - * This flag implies -mfpu=neon-vfpv4. - * - * -mcpu=iwmmxt - * Allows the use of iWMMXt instrinsics with GCC. - */ -enum { - ANDROID_CPU_ARM_FEATURE_ARMv7 = (1 << 0), - ANDROID_CPU_ARM_FEATURE_VFPv3 = (1 << 1), - ANDROID_CPU_ARM_FEATURE_NEON = (1 << 2), - ANDROID_CPU_ARM_FEATURE_LDREX_STREX = (1 << 3), - ANDROID_CPU_ARM_FEATURE_VFPv2 = (1 << 4), - ANDROID_CPU_ARM_FEATURE_VFP_D32 = (1 << 5), - ANDROID_CPU_ARM_FEATURE_VFP_FP16 = (1 << 6), - ANDROID_CPU_ARM_FEATURE_VFP_FMA = (1 << 7), - ANDROID_CPU_ARM_FEATURE_NEON_FMA = (1 << 8), - ANDROID_CPU_ARM_FEATURE_IDIV_ARM = (1 << 9), - ANDROID_CPU_ARM_FEATURE_IDIV_THUMB2 = (1 << 10), - ANDROID_CPU_ARM_FEATURE_iWMMXt = (1 << 11), -}; - -enum { - ANDROID_CPU_X86_FEATURE_SSSE3 = (1 << 0), - ANDROID_CPU_X86_FEATURE_POPCNT = (1 << 1), - ANDROID_CPU_X86_FEATURE_MOVBE = (1 << 2), -}; - -extern uint64_t android_getCpuFeatures(void); -#define android_getCpuFeaturesExt android_getCpuFeatures - -/* Return the number of CPU cores detected on this device. */ -extern int android_getCpuCount(void); - -/* The following is used to force the CPU count and features - * mask in sandboxed processes. Under 4.1 and higher, these processes - * cannot access /proc, which is the only way to get information from - * the kernel about the current hardware (at least on ARM). - * - * It _must_ be called only once, and before any android_getCpuXXX - * function, any other case will fail. - * - * This function return 1 on success, and 0 on failure. - */ -extern int android_setCpu(int cpu_count, - uint64_t cpu_features); - -#ifdef __arm__ -/* Retrieve the ARM 32-bit CPUID value from the kernel. - * Note that this cannot work on sandboxed processes under 4.1 and - * higher, unless you called android_setCpuArm() before. - */ -extern uint32_t android_getCpuIdArm(void); - -/* An ARM-specific variant of android_setCpu() that also allows you - * to set the ARM CPUID field. - */ -extern int android_setCpuArm(int cpu_count, - uint64_t cpu_features, - uint32_t cpu_id); -#endif - -__END_DECLS - -#endif /* CPU_FEATURES_H */ diff --git a/platform/android/detect.py b/platform/android/detect.py index 7a728e4ef1..b7641172e4 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -1,6 +1,5 @@ import os import sys -import string import platform from distutils.version import LooseVersion @@ -27,7 +26,7 @@ def get_opts(): return [ ('ANDROID_NDK_ROOT', 'Path to the Android NDK', os.environ.get("ANDROID_NDK_ROOT", 0)), ('ndk_platform', 'Target platform (android-<api>, e.g. "android-18")', "android-18"), - EnumVariable('android_arch', 'Target architecture', "armv7", ('armv7', 'armv6', 'arm64v8', 'x86')), + EnumVariable('android_arch', 'Target architecture', "armv7", ('armv7', 'armv6', 'arm64v8', 'x86', 'x86_64')), BoolVariable('android_neon', 'Enable NEON support (armv7 only)', True), BoolVariable('android_stl', 'Enable Android STL support (for modules)', True) ] @@ -94,7 +93,7 @@ def configure(env): ## Architecture - if env['android_arch'] not in ['armv7', 'armv6', 'arm64v8', 'x86']: + if env['android_arch'] not in ['armv7', 'armv6', 'arm64v8', 'x86', 'x86_64']: env['android_arch'] = 'armv7' neon_text = "" @@ -110,6 +109,16 @@ def configure(env): abi_subpath = "i686-linux-android" arch_subpath = "x86" env["x86_libtheora_opt_gcc"] = True + if env['android_arch'] == 'x86_64': + if get_platform(env["ndk_platform"]) < 21: + print("WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21") + env["ndk_platform"] = "android-21" + env['ARCH'] = 'arch-x86_64' + env.extra_suffix = ".x86_64" + env.extra_suffix + target_subpath = "x86_64-4.9" + abi_subpath = "x86_64-linux-android" + arch_subpath = "x86_64" + env["x86_libtheora_opt_gcc"] = True elif env['android_arch'] == 'armv6': env['ARCH'] = 'arch-arm' env.extra_suffix = ".armv6" + env.extra_suffix @@ -141,19 +150,21 @@ def configure(env): if (env["target"].startswith("release")): if (env["optimize"] == "speed"): #optimize for speed (default) env.Append(LINKFLAGS=['-O2']) - env.Append(CPPFLAGS=['-O2', '-DNDEBUG', '-ffast-math', '-funsafe-math-optimizations', '-fomit-frame-pointer']) + env.Append(CCFLAGS=['-O2', '-fomit-frame-pointer']) + env.Append(CPPFLAGS=['-DNDEBUG']) else: #optimize for size - env.Append(CPPFLAGS=['-Os', '-DNDEBUG']) + env.Append(CCFLAGS=['-Os']) + env.Append(CPPFLAGS=['-DNDEBUG']) env.Append(LINKFLAGS=['-Os']) if (can_vectorize): - env.Append(CPPFLAGS=['-ftree-vectorize']) + env.Append(CCFLAGS=['-ftree-vectorize']) if (env["target"] == "release_debug"): env.Append(CPPFLAGS=['-DDEBUG_ENABLED']) elif (env["target"] == "debug"): env.Append(LINKFLAGS=['-O0']) - env.Append(CPPFLAGS=['-O0', '-D_DEBUG', '-UNDEBUG', '-DDEBUG_ENABLED', - '-DDEBUG_MEMORY_ENABLED', '-g', '-fno-limit-debug-info']) + env.Append(CCFLAGS=['-O0', '-g', '-fno-limit-debug-info']) + env.Append(CPPFLAGS=['-D_DEBUG', '-UNDEBUG', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) ## Compiler configuration @@ -203,19 +214,21 @@ def configure(env): lib_sysroot = env["ANDROID_NDK_ROOT"] + "/platforms/" + env['ndk_platform'] + "/" + env['ARCH'] ## Compile flags - - if env['android_stl']: + # Disable exceptions and rtti on non-tools (template) builds + if env['tools'] or env['android_stl']: env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/include"]) env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++abi/include"]) - env.Append(CXXFLAGS=['-frtti',"-std=gnu++14"]) + env.Append(CXXFLAGS=['-frtti', "-std=gnu++14"]) else: - env.Append(CXXFLAGS=['-fno-rtti', '-fno-exceptions', '-DNO_SAFE_CAST']) + env.Append(CXXFLAGS=['-fno-rtti', '-fno-exceptions']) + # Don't use dynamic_cast, necessary with no-rtti. + env.Append(CPPFLAGS=['-DNO_SAFE_CAST']) ndk_version = get_ndk_version(env["ANDROID_NDK_ROOT"]) if ndk_version != None and LooseVersion(ndk_version) >= LooseVersion("15.0.4075724"): print("Using NDK unified headers") sysroot = env["ANDROID_NDK_ROOT"] + "/sysroot" - env.Append(CPPFLAGS=["--sysroot="+sysroot]) + env.Append(CPPFLAGS=["--sysroot=" + sysroot]) env.Append(CPPFLAGS=["-isystem", sysroot + "/usr/include/" + abi_subpath]) env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/android/support/include"]) # For unified headers this define has to be set manually @@ -224,45 +237,51 @@ def configure(env): print("Using NDK deprecated headers") env.Append(CPPFLAGS=["-isystem", lib_sysroot + "/usr/include"]) - env.Append(CPPFLAGS='-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing'.split()) + env.Append(CCFLAGS='-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing'.split()) env.Append(CPPFLAGS='-DNO_STATVFS -DGLES_ENABLED'.split()) env['neon_enabled'] = False if env['android_arch'] == 'x86': target_opts = ['-target', 'i686-none-linux-android'] # The NDK adds this if targeting API < 21, so we can drop it when Godot targets it at least - env.Append(CPPFLAGS=['-mstackrealign']) + env.Append(CCFLAGS=['-mstackrealign']) + + elif env['android_arch'] == 'x86_64': + target_opts = ['-target', 'x86_64-none-linux-android'] elif env["android_arch"] == "armv6": target_opts = ['-target', 'armv6-none-linux-androideabi'] - env.Append(CPPFLAGS='-D__ARM_ARCH_6__ -march=armv6 -mfpu=vfp -mfloat-abi=softfp'.split()) + env.Append(CCFLAGS='-march=armv6 -mfpu=vfp -mfloat-abi=softfp'.split()) + env.Append(CPPFLAGS=['-D__ARM_ARCH_6__']) elif env["android_arch"] == "armv7": target_opts = ['-target', 'armv7-none-linux-androideabi'] - env.Append(CPPFLAGS='-D__ARM_ARCH_7__ -D__ARM_ARCH_7A__ -march=armv7-a -mfloat-abi=softfp'.split()) + env.Append(CCFLAGS='-march=armv7-a -mfloat-abi=softfp'.split()) + env.Append(CPPFLAGS='-D__ARM_ARCH_7__ -D__ARM_ARCH_7A__'.split()) if env['android_neon']: env['neon_enabled'] = True - env.Append(CPPFLAGS=['-mfpu=neon', '-D__ARM_NEON__']) + env.Append(CCFLAGS=['-mfpu=neon']) + env.Append(CPPFLAGS=['-D__ARM_NEON__']) else: - env.Append(CPPFLAGS=['-mfpu=vfpv3-d16']) + env.Append(CCFLAGS=['-mfpu=vfpv3-d16']) elif env["android_arch"] == "arm64v8": target_opts = ['-target', 'aarch64-none-linux-android'] + env.Append(CCFLAGS=['-mfix-cortex-a53-835769']) env.Append(CPPFLAGS=['-D__ARM_ARCH_8A__']) - env.Append(CPPFLAGS=['-mfix-cortex-a53-835769']) - env.Append(CPPFLAGS=target_opts) - env.Append(CPPFLAGS=common_opts) + env.Append(CCFLAGS=target_opts) + env.Append(CCFLAGS=common_opts) ## Link flags if ndk_version != None and LooseVersion(ndk_version) >= LooseVersion("15.0.4075724"): if LooseVersion(ndk_version) >= LooseVersion("17.1.4828580"): - env.Append(LINKFLAGS=['-Wl,--exclude-libs,libgcc.a','-Wl,--exclude-libs,libatomic.a','-nostdlib++']) + env.Append(LINKFLAGS=['-Wl,--exclude-libs,libgcc.a', '-Wl,--exclude-libs,libatomic.a', '-nostdlib++']) else: - env.Append(LINKFLAGS=[env["ANDROID_NDK_ROOT"] +"/sources/cxx-stl/llvm-libc++/libs/"+arch_subpath+"/libandroid_support.a"]) + env.Append(LINKFLAGS=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libandroid_support.a"]) env.Append(LINKFLAGS=['-shared', '--sysroot=' + lib_sysroot, '-Wl,--warn-shared-textrel']) - env.Append(LIBPATH=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/"+arch_subpath+"/"]) - env.Append(LINKFLAGS=[env["ANDROID_NDK_ROOT"] +"/sources/cxx-stl/llvm-libc++/libs/"+arch_subpath+"/libc++_shared.so"]) + env.Append(LIBPATH=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/"]) + env.Append(LINKFLAGS=[env["ANDROID_NDK_ROOT"] +"/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libc++_shared.so"]) else: env.Append(LINKFLAGS=['-shared', '--sysroot=' + lib_sysroot, '-Wl,--warn-shared-textrel']) if mt_link: @@ -281,15 +300,9 @@ def configure(env): env.Append(LIBPATH=[env["ANDROID_NDK_ROOT"] + '/toolchains/' + target_subpath + '/prebuilt/' + host_subpath + '/' + abi_subpath + '/lib']) - env.Append(CPPPATH=['#platform/android']) - env.Append(CPPFLAGS=['-DANDROID_ENABLED', '-DUNIX_ENABLED', '-DNO_FCNTL', '-DMPC_FIXED_POINT']) - env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'android', 'log', 'z', 'dl']) - - # TODO: Move that to opus module's config - if 'module_opus_enabled' in env and env['module_opus_enabled']: - if (env["android_arch"] == "armv6" or env["android_arch"] == "armv7"): - env.Append(CFLAGS=["-DOPUS_ARM_OPT"]) - env.opus_fixed_point = "yes" + env.Prepend(CPPPATH=['#platform/android']) + env.Append(CPPFLAGS=['-DANDROID_ENABLED', '-DUNIX_ENABLED', '-DNO_FCNTL']) + env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'GLESv2', 'android', 'log', 'z', 'dl']) # Return NDK version string in source.properties (adapted from the Chromium project). def get_ndk_version(path): diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 679a13217f..8c464465ca 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,6 +31,7 @@ #include "dir_access_jandroid.h" #include "core/print_string.h" #include "file_access_jandroid.h" +#include "string_android.h" #include "thread_jandroid.h" jobject DirAccessJAndroid::io = NULL; @@ -69,7 +70,7 @@ String DirAccessJAndroid::get_next() { if (!str) return ""; - String ret = String::utf8(env->GetStringUTFChars((jstring)str, NULL)); + String ret = jstring_to_string((jstring)str, env); env->DeleteLocalRef((jobject)str); return ret; } @@ -212,6 +213,11 @@ Error DirAccessJAndroid::remove(String p_name) { ERR_FAIL_V(ERR_UNAVAILABLE); } +String DirAccessJAndroid::get_filesystem_type() const { + + return "APK"; +} + //FileType get_file_type() const; size_t DirAccessJAndroid::get_space_left() { diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index ea1e11a4f1..cdea93ff4c 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,7 +32,7 @@ #define DIR_ACCESS_JANDROID_H #include "core/os/dir_access.h" -#include "java_glue.h" +#include "java_godot_lib_jni.h" #include <stdio.h> class DirAccessJAndroid : public DirAccess { @@ -75,6 +75,8 @@ public: virtual Error rename(String p_from, String p_to); virtual Error remove(String p_name); + virtual String get_filesystem_type() const; + //virtual FileType get_file_type() const; size_t get_space_left(); diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index a3b5b6dd58..b987b3aebd 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -206,9 +206,9 @@ static const LauncherIcon launcher_icons[] = { { "launcher_icons/mdpi_48x48", "res/drawable-mdpi-v4/icon.png" } }; -class EditorExportAndroid : public EditorExportPlatform { +class EditorExportPlatformAndroid : public EditorExportPlatform { - GDCLASS(EditorExportAndroid, EditorExportPlatform) + GDCLASS(EditorExportPlatformAndroid, EditorExportPlatform) Ref<ImageTexture> logo; Ref<ImageTexture> run_icon; @@ -235,7 +235,7 @@ class EditorExportAndroid : public EditorExportPlatform { static void _device_poll_thread(void *ud) { - EditorExportAndroid *ea = (EditorExportAndroid *)ud; + EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud; while (!ea->quit_request) { @@ -301,10 +301,10 @@ class EditorExportAndroid : public EditorExportPlatform { args.push_back(d.id); args.push_back("shell"); args.push_back("getprop"); - int ec; + int ec2; String dp; - OS::get_singleton()->execute(adb, args, true, NULL, &dp, &ec); + OS::get_singleton()->execute(adb, args, true, NULL, &dp, &ec2); Vector<String> props = dp.split("\n"); String vendor; @@ -417,6 +417,7 @@ class EditorExportAndroid : public EditorExportPlatform { name = "noname"; pname = pname.replace("$genname", name); + return pname; } @@ -426,7 +427,7 @@ class EditorExportAndroid : public EditorExportPlatform { if (pname.length() == 0) { if (r_error) { - *r_error = "Package name is missing."; + *r_error = TTR("Package name is missing."); } return false; } @@ -437,7 +438,7 @@ class EditorExportAndroid : public EditorExportPlatform { CharType c = pname[i]; if (first && c == '.') { if (r_error) { - *r_error = "Package segments must be of non-zero length."; + *r_error = TTR("Package segments must be of non-zero length."); } return false; } @@ -448,19 +449,19 @@ class EditorExportAndroid : public EditorExportPlatform { } if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) { if (r_error) { - *r_error = "The character '" + String::chr(c) + "' is not allowed in Android application package names."; + *r_error = vformat(TTR("The character '%s' is not allowed in Android application package names."), String::chr(c)); } return false; } if (first && (c >= '0' && c <= '9')) { if (r_error) { - *r_error = "A digit cannot be the first character in a package segment."; + *r_error = TTR("A digit cannot be the first character in a package segment."); } return false; } if (first && c == '_') { if (r_error) { - *r_error = "The character '" + String::chr(c) + "' cannot be the first character in a package segment."; + *r_error = vformat(TTR("The character '%s' cannot be the first character in a package segment."), String::chr(c)); } return false; } @@ -469,14 +470,14 @@ class EditorExportAndroid : public EditorExportPlatform { if (segments == 0) { if (r_error) { - *r_error = "The package must have at least one '.' separator."; + *r_error = TTR("The package must have at least one '.' separator."); } return false; } if (first) { if (r_error) { - *r_error = "Package segments must be of non-zero length."; + *r_error = TTR("Package segments must be of non-zero length."); } return false; } @@ -551,8 +552,10 @@ class EditorExportAndroid : public EditorExportPlatform { } static Vector<String> get_abis() { - // mips and armv6 are dead (especially for games), so not including them Vector<String> abis; + // We can still build armv6 in theory, but it doesn't make much + // sense for games, so disabling for now. + //abis.push_back("armeabi"); abis.push_back("armeabi-v7a"); abis.push_back("arm64-v8a"); abis.push_back("x86"); @@ -582,7 +585,7 @@ class EditorExportAndroid : public EditorExportPlatform { static Error save_apk_so(void *p_userdata, const SharedObject &p_so) { if (!p_so.path.get_file().begins_with("lib")) { String err = "Android .so file names must start with \"lib\", but got: " + p_so.path; - ERR_PRINT(err.utf8().get_data()); + ERR_PRINTS(err); return FAILED; } APKExportData *ed = (APKExportData *)p_userdata; @@ -603,7 +606,7 @@ class EditorExportAndroid : public EditorExportPlatform { if (!exported) { String abis_string = String(" ").join(abis); String err = "Cannot determine ABI for library \"" + p_so.path + "\". One of the supported ABIs must be used as a tag: " + abis_string; - ERR_PRINT(err.utf8().get_data()); + ERR_PRINTS(err); return FAILED; } return OK; @@ -614,7 +617,9 @@ class EditorExportAndroid : public EditorExportPlatform { String dst_path = p_path.replace_first("res://", "assets/"); store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0); - ed->ep->step("File: " + p_path, 3 + p_file * 100 / p_total); + if (ed->ep->step("File: " + p_path, 3 + p_file * 100 / p_total)) { + return ERR_SKIP; + } return OK; } @@ -656,6 +661,8 @@ class EditorExportAndroid : public EditorExportPlatform { int orientation = p_preset->get("screen/orientation"); + bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" && + !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2"); bool screen_support_small = p_preset->get("screen/support_small"); bool screen_support_normal = p_preset->get("screen/support_normal"); bool screen_support_large = p_preset->get("screen/support_large"); @@ -813,6 +820,11 @@ class EditorExportAndroid : public EditorExportPlatform { } } + if (tname == "uses-feature" && attrname == "glEsVersion") { + + encode_uint32(min_gles3 ? 0x00030000 : 0x00020000, &p_manifest.write[iofs + 16]); + } + iofs += 20; } @@ -1112,16 +1124,14 @@ public: public: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { - // Re-enable when a GLES 2.0 backend is read - /*int api = p_preset->get("graphics/api"); - if (api == 0) - r_features->push_back("etc"); - else*/ String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); if (driver == "GLES2") { r_features->push_back("etc"); - } else { + } else if (driver == "GLES3") { r_features->push_back("etc2"); + if (ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2")) { + r_features->push_back("etc"); + } } Vector<String> abis = get_enabled_abis(p_preset); @@ -1136,11 +1146,12 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_package/use_custom_build"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.$genname"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "screen/orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait"), 0)); @@ -1154,6 +1165,9 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icons[i].option_id, PROPERTY_HINT_FILE, "*.png"), "")); } + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), "")); @@ -1164,7 +1178,7 @@ public: Vector<String> abis = get_abis(); for (int i = 0; i < abis.size(); ++i) { String abi = abis[i]; - bool is_default = (abi == "armeabi-v7a"); + bool is_default = (abi == "armeabi-v7a" || abi == "arm64-v8a"); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + abi), is_default)); } @@ -1243,7 +1257,9 @@ public: } //export_temp - ep.step("Exporting APK", 0); + if (ep.step("Exporting APK", 0)) { + return ERR_SKIP; + } const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); const bool use_reverse = devices[p_device].api_level >= 21; @@ -1266,7 +1282,9 @@ public: String package_name = p_preset->get("package/unique_name"); if (remove_prev) { - ep.step("Uninstalling...", 1); + if (ep.step("Uninstalling...", 1)) { + return ERR_SKIP; + } print_line("Uninstalling previous version: " + devices[p_device].name); @@ -1279,7 +1297,9 @@ public: } print_line("Installing to device (please wait...): " + devices[p_device].name); - ep.step("Installing to device (please wait...)", 2); + if (ep.step("Installing to device (please wait...)", 2)) { + return ERR_SKIP; + } args.clear(); args.push_back("-s"); @@ -1345,7 +1365,9 @@ public: } } - ep.step("Running on Device...", 3); + if (ep.step("Running on Device...", 3)) { + return ERR_SKIP; + } args.clear(); args.push_back("-s"); args.push_back(devices[p_device].id); @@ -1378,21 +1400,25 @@ public: virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { String err; - r_missing_templates = find_export_template("android_debug.apk") == String() || find_export_template("android_release.apk") == String(); - if (p_preset->get("custom_package/debug") != "") { - if (FileAccess::exists(p_preset->get("custom_package/debug"))) { - r_missing_templates = false; - } else { - err += "Custom debug package not found.\n"; + if (!bool(p_preset->get("custom_package/use_custom_build"))) { + + r_missing_templates = find_export_template("android_debug.apk") == String() || find_export_template("android_release.apk") == String(); + + if (p_preset->get("custom_package/debug") != "") { + if (FileAccess::exists(p_preset->get("custom_package/debug"))) { + r_missing_templates = false; + } else { + err += TTR("Custom debug template not found.") + "\n"; + } } - } - if (p_preset->get("custom_package/release") != "") { - if (FileAccess::exists(p_preset->get("custom_package/release"))) { - r_missing_templates = false; - } else { - err += "Custom release package not found.\n"; + if (p_preset->get("custom_package/release") != "") { + if (FileAccess::exists(p_preset->get("custom_package/release"))) { + r_missing_templates = false; + } else { + err += TTR("Custom release template not found.") + "\n"; + } } } @@ -1403,7 +1429,7 @@ public: if (!FileAccess::exists(adb)) { valid = false; - err += "ADB executable not configured in the Editor Settings.\n"; + err += TTR("ADB executable not configured in the Editor Settings.") + "\n"; } String js = EditorSettings::get_singleton()->get("export/android/jarsigner"); @@ -1411,34 +1437,54 @@ public: if (!FileAccess::exists(js)) { valid = false; - err += "OpenJDK 8 jarsigner not configured in the Editor Settings.\n"; + err += TTR("OpenJDK jarsigner not configured in the Editor Settings.") + "\n"; } - String dk = EditorSettings::get_singleton()->get("export/android/debug_keystore"); + String dk = p_preset->get("keystore/debug"); if (!FileAccess::exists(dk)) { - valid = false; - err += "Debug keystore not configured in the Editor Settings.\n"; + dk = EditorSettings::get_singleton()->get("export/android/debug_keystore"); + if (!FileAccess::exists(dk)) { + valid = false; + err += TTR("Debug keystore not configured in the Editor Settings nor in the preset.") + "\n"; + } + } + + if (bool(p_preset->get("custom_package/use_custom_build"))) { + String sdk_path = EditorSettings::get_singleton()->get("export/android/custom_build_sdk_path"); + if (sdk_path == "") { + err += TTR("Custom build requires a valid Android SDK path in Editor Settings.") + "\n"; + valid = false; + } else { + Error errn; + DirAccess *da = DirAccess::open(sdk_path.plus_file("tools"), &errn); + if (errn != OK) { + err += TTR("Invalid Android SDK path for custom build in Editor Settings.") + "\n"; + valid = false; + } + if (da) { + memdelete(da); + } + } + + if (!FileAccess::exists("res://android/build/build.gradle")) { + + err += TTR("Android project is not installed for compiling. Install from Editor menu.") + "\n"; + valid = false; + } } bool apk_expansion = p_preset->get("apk_expansion/enable"); if (apk_expansion) { - /* - if (apk_expansion_salt=="") { - valid=false; - err+="Invalid SALT for apk expansion.\n"; - } - */ - String apk_expansion_pkey = p_preset->get("apk_expansion/public_key"); if (apk_expansion_pkey == "") { valid = false; - err += "Invalid public key for APK expansion.\n"; + err += TTR("Invalid public key for APK expansion.") + "\n"; } } @@ -1448,7 +1494,13 @@ public: if (!is_package_name_valid(get_package_name(pn), &pn_err)) { valid = false; - err += "Invalid package name - " + pn_err + "\n"; + err += TTR("Invalid package name:") + " " + pn_err + "\n"; + } + + String etc_error = test_etc2(); + if (etc_error != String()) { + valid = false; + err += etc_error; } r_error = err; @@ -1461,36 +1513,361 @@ public: return list; } + void _update_custom_build_project() { + + DirAccessRef da = DirAccess::open("res://android"); + + ERR_FAIL_COND(!da); + Map<String, List<String> > directory_paths; + Map<String, List<String> > manifest_sections; + Map<String, List<String> > gradle_sections; + da->list_dir_begin(); + String d = da->get_next(); + while (d != String()) { + + if (!d.begins_with(".") && d != "build" && da->current_is_dir()) { //a dir and not the build dir + //add directories found + DirAccessRef ds = DirAccess::open(String("res://android").plus_file(d)); + if (ds) { + ds->list_dir_begin(); + String sd = ds->get_next(); + while (sd != String()) { + + if (!sd.begins_with(".") && ds->current_is_dir()) { + String key = sd.to_upper(); + if (!directory_paths.has(key)) { + directory_paths[key] = List<String>(); + } + String path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android").plus_file(d).plus_file(sd); + directory_paths[key].push_back(path); + print_line("Add: " + sd + ":" + path); + } + + sd = ds->get_next(); + } + ds->list_dir_end(); + } + //parse manifest + { + FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("AndroidManifest.conf"), FileAccess::READ); + if (f) { + + String section; + while (!f->eof_reached()) { + String l = f->get_line(); + String k = l.strip_edges(); + if (k.begins_with("[")) { + section = k.substr(1, k.length() - 2).strip_edges().to_upper(); + print_line("Section: " + section); + } else if (k != String()) { + if (!manifest_sections.has(section)) { + manifest_sections[section] = List<String>(); + } + manifest_sections[section].push_back(l); + } + } + + f->close(); + } + } + //parse gradle + { + FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("gradle.conf"), FileAccess::READ); + if (f) { + + String section; + while (!f->eof_reached()) { + String l = f->get_line().strip_edges(); + String k = l.strip_edges(); + if (k.begins_with("[")) { + section = k.substr(1, k.length() - 2).strip_edges().to_upper(); + print_line("Section: " + section); + } else if (k != String()) { + if (!gradle_sections.has(section)) { + gradle_sections[section] = List<String>(); + } + gradle_sections[section].push_back(l); + } + } + } + } + } + d = da->get_next(); + } + da->list_dir_end(); + + { //fix gradle build + + String new_file; + { + FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::READ); + if (f) { + + while (!f->eof_reached()) { + String l = f->get_line(); + + if (l.begins_with("//CHUNK_")) { + String text = l.replace_first("//CHUNK_", ""); + int begin_pos = text.find("_BEGIN"); + if (begin_pos != -1) { + text = text.substr(0, begin_pos); + text = text.to_upper(); //just in case + + String end_marker = "//CHUNK_" + text + "_END"; + size_t pos = f->get_position(); + bool found = false; + while (!f->eof_reached()) { + l = f->get_line(); + if (l.begins_with(end_marker)) { + found = true; + break; + } + } + + new_file += "//CHUNK_" + text + "_BEGIN\n"; + + if (!found) { + ERR_PRINTS("No end marker found in build.gradle for chunk: " + text); + f->seek(pos); + } else { + + //add chunk lines + if (gradle_sections.has(text)) { + for (List<String>::Element *E = gradle_sections[text].front(); E; E = E->next()) { + new_file += E->get() + "\n"; + } + } + new_file += end_marker + "\n"; + } + } else { + new_file += l + "\n"; //pass line by + } + } else if (l.begins_with("//DIR_")) { + String text = l.replace_first("//DIR_", ""); + int begin_pos = text.find("_BEGIN"); + if (begin_pos != -1) { + text = text.substr(0, begin_pos); + text = text.to_upper(); //just in case + + String end_marker = "//DIR_" + text + "_END"; + size_t pos = f->get_position(); + bool found = false; + while (!f->eof_reached()) { + l = f->get_line(); + if (l.begins_with(end_marker)) { + found = true; + break; + } + } + + new_file += "//DIR_" + text + "_BEGIN\n"; + + if (!found) { + ERR_PRINTS("No end marker found in build.gradle for dir: " + text); + f->seek(pos); + } else { + //add chunk lines + if (directory_paths.has(text)) { + for (List<String>::Element *E = directory_paths[text].front(); E; E = E->next()) { + new_file += ",'" + E->get().replace("'", "\'") + "'"; + new_file += "\n"; + } + } + new_file += end_marker + "\n"; + } + } else { + new_file += l + "\n"; //pass line by + } + + } else { + new_file += l + "\n"; + } + } + } + } + + FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::WRITE); + f->store_string(new_file); + f->close(); + } + + { //fix manifest + + String new_file; + { + FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::READ); + if (f) { + + while (!f->eof_reached()) { + String l = f->get_line(); + + if (l.begins_with("<!--CHUNK_")) { + String text = l.replace_first("<!--CHUNK_", ""); + int begin_pos = text.find("_BEGIN-->"); + if (begin_pos != -1) { + text = text.substr(0, begin_pos); + text = text.to_upper(); //just in case + + String end_marker = "<!--CHUNK_" + text + "_END-->"; + size_t pos = f->get_position(); + bool found = false; + while (!f->eof_reached()) { + l = f->get_line(); + if (l.begins_with(end_marker)) { + found = true; + break; + } + } + + new_file += "<!--CHUNK_" + text + "_BEGIN-->\n"; + + if (!found) { + ERR_PRINTS("No end marker found in AndroidManifest.conf for chunk: " + text); + f->seek(pos); + } else { + //add chunk lines + if (manifest_sections.has(text)) { + for (List<String>::Element *E = manifest_sections[text].front(); E; E = E->next()) { + new_file += E->get() + "\n"; + } + } + new_file += end_marker + "\n"; + } + } else { + new_file += l + "\n"; //pass line by + } + + } else if (l.strip_edges().begins_with("<application")) { + String last_tag = "android:icon=\"@drawable/icon\""; + int last_tag_pos = l.find(last_tag); + if (last_tag_pos == -1) { + WARN_PRINTS("No adding of application tags because could not find last tag for <application: " + last_tag); + new_file += l + "\n"; + } else { + String base = l.substr(0, last_tag_pos + last_tag.length()); + if (manifest_sections.has("application_attribs")) { + for (List<String>::Element *E = manifest_sections["application_attribs"].front(); E; E = E->next()) { + String to_add = E->get().strip_edges(); + base += " " + to_add + " "; + } + } + base += ">\n"; + new_file += base; + } + } else { + new_file += l + "\n"; + } + } + } + } + + FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::WRITE); + f->store_string(new_file); + f->close(); + } + } + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); String src_apk; - EditorProgress ep("export", "Exporting for Android", 105); + EditorProgress ep("export", "Exporting for Android", 105, true); + + if (bool(p_preset->get("custom_package/use_custom_build"))) { //custom build + //re-generate build.gradle and AndroidManifest.xml + + { //test that installed build version is alright + FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ); + if (!f) { + EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); + return ERR_UNCONFIGURED; + } + String version = f->get_line().strip_edges(); + if (version != VERSION_FULL_CONFIG) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); + return ERR_UNCONFIGURED; + } + } + //build project if custom build is enabled + String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path"); + + ERR_FAIL_COND_V(sdk_path == "", ERR_UNCONFIGURED); + + _update_custom_build_project(); + + OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required - if (p_debug) - src_apk = p_preset->get("custom_package/debug"); - else - src_apk = p_preset->get("custom_package/release"); + String build_command; +#ifdef WINDOWS_ENABLED + build_command = "gradlew.bat"; +#else + build_command = "gradlew"; +#endif - src_apk = src_apk.strip_edges(); - if (src_apk == "") { + String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build"); + + build_command = build_path.plus_file(build_command); + + List<String> cmdline; + cmdline.push_back("build"); + cmdline.push_back("-p"); + cmdline.push_back(build_path); + /*{ used for debug + int ec; + String pipe; + OS::get_singleton()->execute(build_command, cmdline, true, NULL, NULL, &ec); + print_line("exit code: " + itos(ec)); + } + */ + int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline); + if (result != 0) { + EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation.")); + return ERR_CANT_CREATE; + } if (p_debug) { - src_apk = find_export_template("android_debug.apk"); + src_apk = build_path.plus_file("build/outputs/apk/debug/build-debug-unsigned.apk"); } else { - src_apk = find_export_template("android_release.apk"); + src_apk = build_path.plus_file("build/outputs/apk/release/build-release-unsigned.apk"); + } + + if (!FileAccess::exists(src_apk)) { + EditorNode::get_singleton()->show_warning(TTR("No build apk generated at: ") + "\n" + src_apk); + return ERR_CANT_CREATE; } + + } else { + + if (p_debug) + src_apk = p_preset->get("custom_package/debug"); + else + src_apk = p_preset->get("custom_package/release"); + + src_apk = src_apk.strip_edges(); if (src_apk == "") { - EditorNode::add_io_error("Package not found: " + src_apk); - return ERR_FILE_NOT_FOUND; + if (p_debug) { + src_apk = find_export_template("android_debug.apk"); + } else { + src_apk = find_export_template("android_release.apk"); + } + if (src_apk == "") { + EditorNode::add_io_error("Package not found: " + src_apk); + return ERR_FILE_NOT_FOUND; + } } } + if (!DirAccess::exists(p_path.get_base_dir())) { + return ERR_FILE_BAD_PATH; + } + FileAccess *src_f = NULL; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - ep.step("Creating APK", 0); + if (ep.step("Creating APK", 0)) { + return ERR_SKIP; + } unzFile pkg = unzOpen2(src_apk.utf8().get_data(), &io); if (!pkg) { @@ -1499,7 +1876,6 @@ public: return ERR_FILE_NOT_FOUND; } - ERR_FAIL_COND_V(!pkg, ERR_CANT_OPEN); int ret = unzGoToFirstFile(pkg); zlib_filefunc_def io2 = io; @@ -1561,7 +1937,7 @@ public: _fix_resources(p_preset, data); } - if (file == "res/drawable/icon.png") { + if (file == "res/drawable-nodpi-v4/icon.png") { bool found = false; for (unsigned int i = 0; i < sizeof(launcher_icons) / sizeof(launcher_icons[0]); ++i) { String icon_path = String(p_preset->get(launcher_icons[i].option_id)).strip_edges(); @@ -1633,7 +2009,9 @@ public: ret = unzGoToNextFile(pkg); } - ep.step("Adding Files...", 1); + if (ep.step("Adding Files...", 1)) { + return ERR_SKIP; + } Error err = OK; Vector<String> cl = cmdline.strip_edges().split(" "); for (int i = 0; i < cl.size(); i++) { @@ -1647,16 +2025,6 @@ public: if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { - /*String host = EditorSettings::get_singleton()->get("filesystem/file_server/host"); - int port = EditorSettings::get_singleton()->get("filesystem/file_server/post"); - String passwd = EditorSettings::get_singleton()->get("filesystem/file_server/password"); - cl.push_back("--remote-fs"); - cl.push_back(host+":"+itos(port)); - if (passwd!="") { - cl.push_back("--remote-fs-password"); - cl.push_back(passwd); - }*/ - APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; @@ -1769,18 +2137,30 @@ public: String password; String user; if (p_debug) { - keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore"); - password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass"); - user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); - ep.step("Signing debug APK...", 103); + keystore = p_preset->get("keystore/debug"); + password = p_preset->get("keystore/debug_password"); + user = p_preset->get("keystore/debug_user"); + + if (keystore.empty()) { + + keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore"); + password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass"); + user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); + } + + if (ep.step("Signing debug APK...", 103)) { + return ERR_SKIP; + } } else { keystore = release_keystore; password = release_password; user = release_username; - ep.step("Signing release APK...", 103); + if (ep.step("Signing release APK...", 103)) { + return ERR_SKIP; + } } if (!FileAccess::exists(keystore)) { @@ -1812,7 +2192,9 @@ public: return ERR_CANT_CREATE; } - ep.step("Verifying APK...", 104); + if (ep.step("Verifying APK...", 104)) { + return ERR_SKIP; + } args.clear(); args.push_back("-verify"); @@ -1832,7 +2214,9 @@ public: static const int ZIP_ALIGNMENT = 4; - ep.step("Aligning APK...", 105); + if (ep.step("Aligning APK...", 105)) { + return ERR_SKIP; + } unzFile tmp_unaligned = unzOpen2(unaligned_path.utf8().get_data(), &io); if (!tmp_unaligned) { @@ -1841,7 +2225,6 @@ public: return ERR_FILE_NOT_FOUND; } - ERR_FAIL_COND_V(!tmp_unaligned, ERR_CANT_OPEN); ret = unzGoToFirstFile(tmp_unaligned); io2 = io; @@ -1909,10 +2292,6 @@ public: zipClose(final_apk, NULL); unzClose(tmp_unaligned); - if (err) { - return err; - } - return OK; } @@ -1925,7 +2304,7 @@ public: virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { } - EditorExportAndroid() { + EditorExportPlatformAndroid() { Ref<Image> img = memnew(Image(_android_logo)); logo.instance(); @@ -1941,7 +2320,7 @@ public: device_thread = Thread::create(_device_poll_thread, this); } - ~EditorExportAndroid() { + ~EditorExportPlatformAndroid() { quit_request = true; Thread::wait_to_finish(device_thread); memdelete(device_lock); @@ -1965,10 +2344,12 @@ void register_android_exporter() { EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey"); EDITOR_DEF("export/android/debug_keystore_pass", "android"); EDITOR_DEF("export/android/force_system_user", false); + EDITOR_DEF("export/android/custom_build_sdk_path", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/custom_build_sdk_path", PROPERTY_HINT_GLOBAL_DIR, "*.keystore")); EDITOR_DEF("export/android/timestamping_authority_url", ""); EDITOR_DEF("export/android/shutdown_adb_on_exit", true); - Ref<EditorExportAndroid> exporter = Ref<EditorExportAndroid>(memnew(EditorExportAndroid)); + Ref<EditorExportPlatformAndroid> exporter = Ref<EditorExportPlatformAndroid>(memnew(EditorExportPlatformAndroid)); EditorExport::get_singleton()->add_export_platform(exporter); } diff --git a/platform/android/export/export.h b/platform/android/export/export.h index 9d66626866..42f3e70450 100644 --- a/platform/android/export/export.h +++ b/platform/android/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index 4c7436a5dc..f8a2c73a1e 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index 1ee8697fa4..b8e78627ec 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -70,6 +70,8 @@ public: virtual bool file_exists(const String &p_path); ///< return true if a file exists virtual uint64_t _get_modified_time(const String &p_file) { return 0; } + virtual uint32_t _get_unix_permissions(const String &p_file) { return 0; } + virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) { return FAILED; } //static void make_default(); diff --git a/platform/android/file_access_jandroid.cpp b/platform/android/file_access_jandroid.cpp index bba45ffc1d..63bc5f69d1 100644 --- a/platform/android/file_access_jandroid.cpp +++ b/platform/android/file_access_jandroid.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/file_access_jandroid.h b/platform/android/file_access_jandroid.h index 98486702ab..9429100d65 100644 --- a/platform/android/file_access_jandroid.h +++ b/platform/android/file_access_jandroid.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,7 +32,7 @@ #define FILE_ACCESS_JANDROID_H #include "core/os/file_access.h" -#include "java_glue.h" +#include "java_godot_lib_jni.h" class FileAccessJAndroid : public FileAccess { static jobject io; @@ -74,6 +74,8 @@ public: static void setup(jobject p_io); virtual uint64_t _get_modified_time(const String &p_file) { return 0; } + virtual uint32_t _get_unix_permissions(const String &p_file) { return 0; } + virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) { return FAILED; } FileAccessJAndroid(); ~FileAccessJAndroid(); diff --git a/platform/android/globals/global_defaults.h b/platform/android/globals/global_defaults.h deleted file mode 100644 index 99da2dd527..0000000000 --- a/platform/android/globals/global_defaults.h +++ /dev/null @@ -1,31 +0,0 @@ -/*************************************************************************/ -/* global_defaults.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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_android_global_defaults(); diff --git a/platform/android/ifaddrs_android.cpp b/platform/android/ifaddrs_android.cpp deleted file mode 100644 index f6d5cdbe77..0000000000 --- a/platform/android/ifaddrs_android.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/* - * libjingle - * Copyright 2012, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "ifaddrs_android.h" -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/utsname.h> -#include <sys/ioctl.h> -#include <netinet/in.h> -#include <net/if.h> -#include <unistd.h> -#include <errno.h> -#include <linux/netlink.h> -#include <linux/rtnetlink.h> - -struct netlinkrequest { - nlmsghdr header; - ifaddrmsg msg; -}; - -namespace { -const int kMaxReadSize = 4096; -} - -static int set_ifname(struct ifaddrs* ifaddr, int interface) { - char buf[IFNAMSIZ] = {0}; - char* name = if_indextoname(interface, buf); - if (name == NULL) { - return -1; - } - ifaddr->ifa_name = new char[strlen(name) + 1]; - strncpy(ifaddr->ifa_name, name, strlen(name) + 1); - return 0; -} - -static int set_flags(struct ifaddrs* ifaddr) { - int fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd == -1) { - return -1; - } - ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, ifaddr->ifa_name, IFNAMSIZ - 1); - int rc = ioctl(fd, SIOCGIFFLAGS, &ifr); - close(fd); - if (rc == -1) { - return -1; - } - ifaddr->ifa_flags = ifr.ifr_flags; - return 0; -} - -static int set_addresses(struct ifaddrs* ifaddr, ifaddrmsg* msg, void* data, - size_t len) { - if (msg->ifa_family == AF_INET) { - sockaddr_in* sa = new sockaddr_in; - sa->sin_family = AF_INET; - memcpy(&sa->sin_addr, data, len); - ifaddr->ifa_addr = reinterpret_cast<sockaddr*>(sa); - } else if (msg->ifa_family == AF_INET6) { - sockaddr_in6* sa = new sockaddr_in6; - sa->sin6_family = AF_INET6; - sa->sin6_scope_id = msg->ifa_index; - memcpy(&sa->sin6_addr, data, len); - ifaddr->ifa_addr = reinterpret_cast<sockaddr*>(sa); - } else { - return -1; - } - return 0; -} - -static int make_prefixes(struct ifaddrs* ifaddr, int family, int prefixlen) { - char* prefix = NULL; - if (family == AF_INET) { - sockaddr_in* mask = new sockaddr_in; - mask->sin_family = AF_INET; - memset(&mask->sin_addr, 0, sizeof(in_addr)); - ifaddr->ifa_netmask = reinterpret_cast<sockaddr*>(mask); - if (prefixlen > 32) { - prefixlen = 32; - } - prefix = reinterpret_cast<char*>(&mask->sin_addr); - } else if (family == AF_INET6) { - sockaddr_in6* mask = new sockaddr_in6; - mask->sin6_family = AF_INET6; - memset(&mask->sin6_addr, 0, sizeof(in6_addr)); - ifaddr->ifa_netmask = reinterpret_cast<sockaddr*>(mask); - if (prefixlen > 128) { - prefixlen = 128; - } - prefix = reinterpret_cast<char*>(&mask->sin6_addr); - } else { - return -1; - } - for (int i = 0; i < (prefixlen / 8); i++) { - *prefix++ = 0xFF; - } - char remainder = 0xff; - remainder <<= (8 - prefixlen % 8); - *prefix = remainder; - return 0; -} - -static int populate_ifaddrs(struct ifaddrs* ifaddr, ifaddrmsg* msg, void* bytes, - size_t len) { - if (set_ifname(ifaddr, msg->ifa_index) != 0) { - return -1; - } - if (set_flags(ifaddr) != 0) { - return -1; - } - if (set_addresses(ifaddr, msg, bytes, len) != 0) { - return -1; - } - if (make_prefixes(ifaddr, msg->ifa_family, msg->ifa_prefixlen) != 0) { - return -1; - } - return 0; -} - -int getifaddrs(struct ifaddrs** result) { - int fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (fd < 0) { - return -1; - } - netlinkrequest ifaddr_request; - memset(&ifaddr_request, 0, sizeof(ifaddr_request)); - ifaddr_request.header.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST; - ifaddr_request.header.nlmsg_type = RTM_GETADDR; - ifaddr_request.header.nlmsg_len = NLMSG_LENGTH(sizeof(ifaddrmsg)); - ssize_t count = send(fd, &ifaddr_request, ifaddr_request.header.nlmsg_len, 0); - if (static_cast<size_t>(count) != ifaddr_request.header.nlmsg_len) { - close(fd); - return -1; - } - struct ifaddrs* start = NULL; - struct ifaddrs* current = NULL; - char buf[kMaxReadSize]; - ssize_t amount_read = recv(fd, &buf, kMaxReadSize, 0); - while (amount_read > 0) { - nlmsghdr* header = reinterpret_cast<nlmsghdr*>(&buf[0]); - size_t header_size = static_cast<size_t>(amount_read); - for ( ; NLMSG_OK(header, header_size); - header = NLMSG_NEXT(header, header_size)) { - switch (header->nlmsg_type) { - case NLMSG_DONE: - // Success. Return. - *result = start; - close(fd); - return 0; - case NLMSG_ERROR: - close(fd); - freeifaddrs(start); - return -1; - case RTM_NEWADDR: { - ifaddrmsg* address_msg = - reinterpret_cast<ifaddrmsg*>(NLMSG_DATA(header)); - rtattr* rta = IFA_RTA(address_msg); - ssize_t payload_len = IFA_PAYLOAD(header); - while (RTA_OK(rta, payload_len)) { - if (rta->rta_type == IFA_ADDRESS) { - int family = address_msg->ifa_family; - if (family == AF_INET || family == AF_INET6) { - ifaddrs* newest = new ifaddrs; - memset(newest, 0, sizeof(ifaddrs)); - if (current) { - current->ifa_next = newest; - } else { - start = newest; - } - if (populate_ifaddrs(newest, address_msg, RTA_DATA(rta), - RTA_PAYLOAD(rta)) != 0) { - freeifaddrs(start); - *result = NULL; - return -1; - } - current = newest; - } - } - rta = RTA_NEXT(rta, payload_len); - } - break; - } - } - } - amount_read = recv(fd, &buf, kMaxReadSize, 0); - } - close(fd); - freeifaddrs(start); - return -1; -} - -void freeifaddrs(struct ifaddrs* addrs) { - struct ifaddrs* last = NULL; - struct ifaddrs* cursor = addrs; - while (cursor) { - delete[] cursor->ifa_name; - delete cursor->ifa_addr; - delete cursor->ifa_netmask; - last = cursor; - cursor = cursor->ifa_next; - delete last; - } -} diff --git a/platform/android/ifaddrs_android.h b/platform/android/ifaddrs_android.h deleted file mode 100644 index 539fa40455..0000000000 --- a/platform/android/ifaddrs_android.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * libjingle - * Copyright 2013, Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#ifndef TALK_BASE_IFADDRS_ANDROID_H_ -#define TALK_BASE_IFADDRS_ANDROID_H_ -#include <stdio.h> -#include <sys/socket.h> -// Implementation of getifaddrs for Android. -// Fills out a list of ifaddr structs (see below) which contain information -// about every network interface available on the host. -// See 'man getifaddrs' on Linux or OS X (nb: it is not a POSIX function). -struct ifaddrs { - struct ifaddrs* ifa_next; - char* ifa_name; - unsigned int ifa_flags; - struct sockaddr* ifa_addr; - struct sockaddr* ifa_netmask; - // Real ifaddrs has broadcast, point to point and data members. - // We don't need them (yet?). -}; -int getifaddrs(struct ifaddrs** result); -void freeifaddrs(struct ifaddrs* addrs); -#endif // TALK_BASE_IFADDRS_ANDROID_H_ diff --git a/platform/android/java/AndroidManifest.xml b/platform/android/java/AndroidManifest.xml new file mode 100644 index 0000000000..9997950137 --- /dev/null +++ b/platform/android/java/AndroidManifest.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.godot.game" + android:versionCode="1" + android:versionName="1.0" + android:installLocation="auto" + > +<supports-screens android:smallScreens="true" + android:normalScreens="true" + android:largeScreens="true" + android:xlargeScreens="true"/> + +<!--glEsVersion is modified by the exporter, changing this value here has no effect--> + <uses-feature android:glEsVersion="0x00020000" android:required="true" /> +<!--Adding custom text to manifest is fine, but do it outside the custom user and application BEGIN/ENDregions, as that gets rewritten--> + +<!--Custom permissions XML added by add-ons. It's recommended to add them from the export preset, though--> +<!--CHUNK_USER_PERMISSIONS_BEGIN--> +<!--CHUNK_USER_PERMISSIONS_END--> + +<!--Anything in this line after the icon will be erased when doing custom build. If you want to add tags manually, do before it.--> + <application android:label="@string/godot_project_name_string" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" android:icon="@drawable/icon"> + +<!--The following values are replaced when Godot exports, modifying them here has no effect. Do these changes in the--> +<!--export preset. Adding new ones is fine.--> + + <activity android:name="org.godotengine.godot.Godot" + android:label="@string/godot_project_name_string" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen" + android:launchMode="singleTask" + android:screenOrientation="landscape" + android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize" + android:resizeableActivity="false" + tools:ignore="UnusedAttribute"> + + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <service android:name="org.godotengine.godot.GodotDownloaderService" /> + +<!--Custom application XML added by add-ons--> +<!--CHUNK_APPLICATION_BEGIN--> +<!--CHUNK_APPLICATION_END--> + + </application> + + <instrumentation android:icon="@drawable/icon" + android:label="@string/godot_project_name_string" + android:name="org.godotengine.godot.GodotInstrumentation" + android:targetPackage="org.godotengine.game" /> + +</manifest> diff --git a/platform/android/java/README.md b/platform/android/java/README.md new file mode 100644 index 0000000000..58d2b10706 --- /dev/null +++ b/platform/android/java/README.md @@ -0,0 +1,47 @@ +# Third party libraries + + +## Google's vending library + +- Upstream: https://github.com/google/play-licensing/tree/master/lvl_library/src/main/java/com/google/android/vending +- Version: git (eb57657, 2018) with modifications +- License: Apache 2.0 + +Overwrite all files under `com/google/android/vending` + +### Modify some files to avoid compile error and lint warning + +#### com/google/android/vending/licensing/util/Base64.java +``` +@@ -338,7 +338,8 @@ public class Base64 { + e += 4; + } + +- assert (e == outBuff.length); ++ if (BuildConfig.DEBUG && e != outBuff.length) ++ throw new RuntimeException(); + return outBuff; + } +``` + +#### com/google/android/vending/licensing/LicenseChecker.java +``` +@@ -29,8 +29,8 @@ import android.os.RemoteException; + import android.provider.Settings.Secure; + import android.util.Log; + +-import com.android.vending.licensing.ILicenseResultListener; +-import com.android.vending.licensing.ILicensingService; ++import com.google.android.vending.licensing.ILicenseResultListener; ++import com.google.android.vending.licensing.ILicensingService; + import com.google.android.vending.licensing.util.Base64; + import com.google.android.vending.licensing.util.Base64DecoderException; +``` +``` +@@ -287,13 +287,15 @@ public class LicenseChecker implements ServiceConnection { + if (logResponse) { +- String android_id = Secure.getString(mContext.getContentResolver(), +- Secure.ANDROID_ID); ++ String android_id = Secure.ANDROID_ID; + Date date = new Date(); +``` diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle new file mode 100644 index 0000000000..c468277daa --- /dev/null +++ b/platform/android/java/build.gradle @@ -0,0 +1,113 @@ +//Gradle project for Godot Engine Android port. +//Do not modify code between the BEGIN/END sections, as it's autogenerated by add-ons + +buildscript { + repositories { + google() + jcenter() +//CHUNK_BUILDSCRIPT_REPOSITORIES_BEGIN +//CHUNK_BUILDSCRIPT_REPOSITORIES_END + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' +//CHUNK_BUILDSCRIPT_DEPENDENCIES_BEGIN +//CHUNK_BUILDSCRIPT_DEPENDENCIES_END + } +} + +apply plugin: 'com.android.application' + +allprojects { + repositories { + mavenCentral() + google() + jcenter() +//CHUNK_ALLPROJECTS_REPOSITORIES_BEGIN +//CHUNK_ALLPROJECTS_REPOSITORIES_END + + } +} + +dependencies { + implementation "com.android.support:support-core-utils:28.0.0" +//CHUNK_DEPENDENCIES_BEGIN +//CHUNK_DEPENDENCIES_END +} + +android { + + lintOptions { + abortOnError false + disable 'MissingTranslation','UnusedResources' + } + + compileSdkVersion 28 + buildToolsVersion "28.0.3" + useLibrary 'org.apache.http.legacy' + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + } + defaultConfig { + minSdkVersion 18 + targetSdkVersion 28 +//CHUNK_ANDROID_DEFAULTCONFIG_BEGIN +//CHUNK_ANDROID_DEFAULTCONFIG_END + } + // Both signing and zip-aligning will be done at export time + buildTypes.all { buildType -> + buildType.zipAlignEnabled false + buildType.signingConfig null + } + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src' +//DIR_SRC_BEGIN +//DIR_SRC_END + ] + res.srcDirs = [ + 'res' +//DIR_RES_BEGIN +//DIR_RES_END + ] + aidl.srcDirs = [ + 'aidl' +//DIR_AIDL_BEGIN +//DIR_AIDL_END + ] + assets.srcDirs = [ + 'assets' +//DIR_ASSETS_BEGIN +//DIR_ASSETS_END + + ] + } + debug.jniLibs.srcDirs = [ + 'libs/debug' +//DIR_JNI_DEBUG_BEGIN +//DIR_JNI_DEBUG_END + ] + release.jniLibs.srcDirs = [ + 'libs/release' +//DIR_JNI_RELEASE_BEGIN +//DIR_JNI_RELEASE_END + ] + } +// No longer used, as it's not useful for build source template +// applicationVariants.all { variant -> +// variant.outputs.all { output -> +// output.outputFileName = "../../../../../../../bin/android_${variant.name}.apk" +// } +// } + +} + +//CHUNK_GLOBAL_BEGIN +//CHUNK_GLOBAL_END + + + + + diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.jar b/platform/android/java/gradle/wrapper/gradle-wrapper.jar Binary files differindex 13372aef5e..f6b961fd5a 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.jar +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.jar diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties index 6fb3a79546..bf3de21830 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Sat Jul 29 16:10:03 ICT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/platform/android/java/gradlew b/platform/android/java/gradlew index 9d82f78915..cccdd3d517 100755 --- a/platform/android/java/gradlew +++ b/platform/android/java/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/platform/android/java/gradlew.bat b/platform/android/java/gradlew.bat index 8a0b282aa6..f9553162f1 100644 --- a/platform/android/java/gradlew.bat +++ b/platform/android/java/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png Binary files differindex 372b763ec5..2c246b04a4 100644 --- a/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png Binary files differindex c61c440636..8bcd464bed 100644 --- a/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/res/drawable/icon.png b/platform/android/java/res/drawable-nodpi/icon.png Binary files differindex 6ad9b43117..6ad9b43117 100644 --- a/platform/android/java/res/drawable/icon.png +++ b/platform/android/java/res/drawable-nodpi/icon.png diff --git a/platform/android/java/res/drawable-xhdpi/notify_panel_notification_icon_bg.png b/platform/android/java/res/drawable-xhdpi/notify_panel_notification_icon_bg.png Binary files differnew file mode 100644 index 0000000000..372b763ec5 --- /dev/null +++ b/platform/android/java/res/drawable-xhdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png b/platform/android/java/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png Binary files differnew file mode 100644 index 0000000000..b458ff3057 --- /dev/null +++ b/platform/android/java/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/res/layout/downloading_expansion.xml b/platform/android/java/res/layout/downloading_expansion.xml index d678d94eac..4a9700965f 100644 --- a/platform/android/java/res/layout/downloading_expansion.xml +++ b/platform/android/java/res/layout/downloading_expansion.xml @@ -15,7 +15,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" - android:layout_marginLeft="5dp" + android:layout_marginStart="5dp" android:layout_marginTop="10dp" android:textStyle="bold" /> @@ -23,12 +23,11 @@ android:id="@+id/downloaderDashboard" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_below="@id/statusText" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="0dp" android:layout_weight="1" > <TextView @@ -36,18 +35,15 @@ style="@android:style/TextAppearance.Small" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_marginLeft="5dp" - android:text="0MB / 0MB" > - </TextView> + android:layout_alignParentStart="true" + android:layout_marginStart="5dp" /> <TextView android:id="@+id/progressAsPercentage" style="@android:style/TextAppearance.Small" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignRight="@+id/progressBar" - android:text="0%" /> + android:layout_alignEnd="@+id/progressBar" /> <ProgressBar android:id="@+id/progressBar" @@ -58,24 +54,23 @@ android:layout_marginBottom="10dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" - android:layout_marginTop="10dp" - android:layout_weight="1" /> + android:layout_marginTop="10dp" /> <TextView android:id="@+id/progressAverageSpeed" style="@android:style/TextAppearance.Small" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" android:layout_below="@+id/progressBar" - android:layout_marginLeft="5dp" /> + android:layout_marginStart="5dp" /> <TextView android:id="@+id/progressTimeRemaining" style="@android:style/TextAppearance.Small" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignRight="@+id/progressBar" + android:layout_alignEnd="@+id/progressBar" android:layout_below="@+id/progressBar" /> </RelativeLayout> @@ -86,33 +81,35 @@ android:orientation="horizontal" > <Button - android:id="@+id/pauseButton" + android:id="@+id/cancelButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginBottom="10dp" - android:layout_marginLeft="10dp" + android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:layout_marginTop="10dp" android:layout_weight="0" android:minHeight="40dp" android:minWidth="94dp" - android:text="@string/text_button_pause" /> + android:text="@string/text_button_cancel" + android:visibility="gone" + style="?android:attr/buttonBarButtonStyle" /> <Button - android:id="@+id/cancelButton" + android:id="@+id/pauseButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginBottom="10dp" - android:layout_marginLeft="5dp" - android:layout_marginRight="5dp" + android:layout_marginStart="10dp" + android:layout_marginEnd="5dp" android:layout_marginTop="10dp" android:layout_weight="0" android:minHeight="40dp" android:minWidth="94dp" - android:text="@string/text_button_cancel" - android:visibility="gone" /> + android:text="@string/text_button_pause" + style="?android:attr/buttonBarButtonStyle" /> </LinearLayout> </LinearLayout> </LinearLayout> @@ -151,7 +148,8 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="10dp" - android:text="@string/text_button_resume_cellular" /> + android:text="@string/text_button_resume_cellular" + style="?android:attr/buttonBarButtonStyle" /> <Button android:id="@+id/wifiSettingsButton" @@ -159,7 +157,8 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="10dp" - android:text="@string/text_button_wifi_settings" /> + android:text="@string/text_button_wifi_settings" + style="?android:attr/buttonBarButtonStyle" /> </LinearLayout> </LinearLayout> diff --git a/platform/android/java/res/layout/status_bar_ongoing_event_progress_bar.xml b/platform/android/java/res/layout/status_bar_ongoing_event_progress_bar.xml index 104993da7e..fae1faeb60 100644 --- a/platform/android/java/res/layout/status_bar_ongoing_event_progress_bar.xml +++ b/platform/android/java/res/layout/status_bar_ongoing_event_progress_bar.xml @@ -17,7 +17,8 @@ */ --> -<LinearLayout android:layout_width="match_parent" +<LinearLayout xmlns:tools="http://schemas.android.com/tools" + 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"> @@ -33,16 +34,17 @@ android:layout_width="fill_parent" android:layout_height="25dp" android:scaleType="centerInside" - android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" android:layout_alignParentTop="true" - android:src="@android:drawable/stat_sys_download" /> + android:src="@android:drawable/stat_sys_download" + android:contentDescription="@string/godot_project_name_string" /> <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_alignParentStart="true" android:layout_alignParentBottom="true" android:layout_gravity="center_horizontal" android:singleLine="true" @@ -56,15 +58,16 @@ android:clickable="true" android:focusable="true" android:paddingTop="10dp" - android:paddingRight="8dp" - android:paddingBottom="8dp" > + android:paddingEnd="8dp" + android:paddingBottom="8dp" + tools:ignore="RtlSymmetry"> <TextView android:id="@+id/title" style="@style/NotificationTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" android:singleLine="true"/> <TextView @@ -72,8 +75,9 @@ style="@style/NotificationText" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:singleLine="true"/> + android:layout_alignParentEnd="true" + android:singleLine="true" + tools:ignore="RelativeOverlap" /> <!-- Only one of progress_bar and paused_text will be visible. --> <FrameLayout @@ -87,7 +91,7 @@ style="?android:attr/progressBarStyleHorizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:paddingRight="25dp" /> + android:paddingEnd="25dp" /> <TextView android:id="@+id/description" @@ -95,7 +99,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:paddingRight="25dp" + android:paddingEnd="25dp" android:singleLine="true" /> </FrameLayout> diff --git a/platform/android/java/res/values-ko/strings.xml b/platform/android/java/res/values-ko/strings.xml index b997b934b2..fab0bdd753 100644 --- a/platform/android/java/res/values-ko/strings.xml +++ b/platform/android/java/res/values-ko/strings.xml @@ -30,7 +30,7 @@ <string name="notification_download_failed">다운로드 실패</string> - <string name="state_unknown">시작중...</string> + <string name="state_unknown">시작중…</string> <string name="state_idle">다운로드 시작을 기다리는 중</string> <string name="state_fetching_url">다운로드할 항목을 찾는 중</string> <string name="state_connecting">다운로드 서버에 연결 중</string> diff --git a/platform/android/java/res/values-v11/styles.xml b/platform/android/java/res/values-v11/styles.xml deleted file mode 100644 index f2013bc0bf..0000000000 --- a/platform/android/java/res/values-v11/styles.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?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/java/res/values-v9/styles.xml b/platform/android/java/res/values-v9/styles.xml deleted file mode 100644 index 736e77a5d6..0000000000 --- a/platform/android/java/res/values-v9/styles.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?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/java/res/values/strings.xml b/platform/android/java/res/values/strings.xml index f0ea56148f..a1b81a6186 100644 --- a/platform/android/java/res/values/strings.xml +++ b/platform/android/java/res/values/strings.xml @@ -30,7 +30,7 @@ <string name="notification_download_failed">Download unsuccessful</string> - <string name="state_unknown">Starting...</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> diff --git a/platform/android/java/src/com/android/vending/licensing/AESObfuscator.java b/platform/android/java/src/com/android/vending/licensing/AESObfuscator.java deleted file mode 100644 index ee12c68deb..0000000000 --- a/platform/android/java/src/com/android/vending/licensing/AESObfuscator.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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/java/src/com/android/vending/licensing/APKExpansionPolicy.java b/platform/android/java/src/com/android/vending/licensing/APKExpansionPolicy.java deleted file mode 100644 index 17cc7a7cfd..0000000000 --- a/platform/android/java/src/com/android/vending/licensing/APKExpansionPolicy.java +++ /dev/null @@ -1,397 +0,0 @@ - -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/java/src/com/android/vending/licensing/ILicenseResultListener.java b/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.java deleted file mode 100644 index 63720999a7..0000000000 --- a/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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. -*/ - -/* - * 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/java/src/com/android/vending/licensing/ILicensingService.java b/platform/android/java/src/com/android/vending/licensing/ILicensingService.java deleted file mode 100644 index 36afc0537d..0000000000 --- a/platform/android/java/src/com/android/vending/licensing/ILicensingService.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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. -*/ - -/* - * 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/java/src/com/android/vending/licensing/LicenseChecker.java b/platform/android/java/src/com/android/vending/licensing/LicenseChecker.java deleted file mode 100644 index 531cb22f8c..0000000000 --- a/platform/android/java/src/com/android/vending/licensing/LicenseChecker.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * 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 { - Intent serviceIntent = new Intent(new String(Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))); - serviceIntent.setPackage("com.android.vending"); - boolean bindResult = mContext - .bindService( - serviceIntent, - 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/java/src/com/android/vending/licensing/LicenseValidator.java b/platform/android/java/src/com/android/vending/licensing/LicenseValidator.java deleted file mode 100644 index 61d3c7e79e..0000000000 --- a/platform/android/java/src/com/android/vending/licensing/LicenseValidator.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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/java/src/com/android/vending/licensing/PreferenceObfuscator.java b/platform/android/java/src/com/android/vending/licensing/PreferenceObfuscator.java deleted file mode 100644 index 7c42bfc28a..0000000000 --- a/platform/android/java/src/com/android/vending/licensing/PreferenceObfuscator.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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/java/src/com/android/vending/licensing/ResponseData.java b/platform/android/java/src/com/android/vending/licensing/ResponseData.java deleted file mode 100644 index 2adef3709e..0000000000 --- a/platform/android/java/src/com/android/vending/licensing/ResponseData.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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/java/src/com/android/vending/licensing/ServerManagedPolicy.java b/platform/android/java/src/com/android/vending/licensing/ServerManagedPolicy.java deleted file mode 100644 index fbf8cf6d00..0000000000 --- a/platform/android/java/src/com/android/vending/licensing/ServerManagedPolicy.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * 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/java/src/com/android/vending/licensing/util/Base64.java b/platform/android/java/src/com/android/vending/licensing/util/Base64.java deleted file mode 100644 index a0d2779af2..0000000000 --- a/platform/android/java/src/com/android/vending/licensing/util/Base64.java +++ /dev/null @@ -1,570 +0,0 @@ -// 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/java/src/com/google/android/vending/expansion/downloader/Constants.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java index 2af33b96b9..ff1eee528f 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java @@ -18,119 +18,113 @@ 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"; +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; - - // save to private app's data on Android 6.0 to skip requesting permission. - public static final String EXP_PATH_API23 = File.separator + "Android" - + File.separator + "data" + 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"; + 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 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 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"; + /** 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 = "-"; + public static final String FILENAME_SEQUENCE_SEPARATOR = "-"; - /** The default user agent used for downloads */ - public static final String DEFAULT_USER_AGENT = "Android.LVLDM"; + /** 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 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 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 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 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 = 10; + 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 + 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 + 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. + 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; + public static final int RETRY_FIRST_DELAY = 30; + + /** Enable separate connectivity logging */ + public static final boolean LOGX = true; - /** Enable separate connectivity logging */ - public static final boolean LOGX = true; + /** Enable verbose logging */ + public static final boolean LOGV = false; - /** 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; - - /** + /** 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; + 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; + 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; + 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 @@ -139,102 +133,101 @@ public class Constants { * accurately (therefore making it impossible to know when a download * completes). */ - public static final int STATUS_LENGTH_REQUIRED = 411; + 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; + 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; + 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; + 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; + public static final int STATUS_CANNOT_RESUME = 489; - /** + /** * This download was canceled */ - public static final int STATUS_CANCELED = 490; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; - + public static final long ACTIVE_THREAD_WATCHDOG = 5 * 1000; }
\ No newline at end of file diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java index 9cb294d721..9a78a6d3df 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java @@ -19,7 +19,6 @@ 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). * @@ -31,50 +30,49 @@ import android.os.Parcelable; * 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; - } + public long mOverallTotal; + public long mOverallProgress; + public long mTimeRemaining; // time remaining + public float mCurrentSpeed; // speed in KB/S - @Override - public void writeToParcel(Parcel p, int i) { - p.writeLong(mOverallTotal); - p.writeLong(mOverallProgress); - p.writeLong(mTimeRemaining); - p.writeFloat(mCurrentSpeed); - } + @Override + public int describeContents() { + return 0; + } - public DownloadProgressInfo(Parcel p) { - mOverallTotal = p.readLong(); - mOverallProgress = p.readLong(); - mTimeRemaining = p.readLong(); - mCurrentSpeed = p.readFloat(); - } + @Override + public void writeToParcel(Parcel p, int i) { + p.writeLong(mOverallTotal); + p.writeLong(mOverallProgress); + p.writeLong(mTimeRemaining); + p.writeFloat(mCurrentSpeed); + } - public DownloadProgressInfo(long overallTotal, long overallProgress, - long timeRemaining, - float currentSpeed) { - this.mOverallTotal = overallTotal; - this.mOverallProgress = overallProgress; - this.mTimeRemaining = timeRemaining; - this.mCurrentSpeed = currentSpeed; - } + public DownloadProgressInfo(Parcel p) { + mOverallTotal = p.readLong(); + mOverallProgress = p.readLong(); + mTimeRemaining = p.readLong(); + mCurrentSpeed = p.readFloat(); + } - public static final Creator<DownloadProgressInfo> CREATOR = new Creator<DownloadProgressInfo>() { - @Override - public DownloadProgressInfo createFromParcel(Parcel parcel) { - return new DownloadProgressInfo(parcel); - } + public DownloadProgressInfo(long overallTotal, long overallProgress, + long timeRemaining, + float currentSpeed) { + this.mOverallTotal = overallTotal; + this.mOverallProgress = overallProgress; + this.mTimeRemaining = timeRemaining; + this.mCurrentSpeed = currentSpeed; + } - @Override - public DownloadProgressInfo[] newArray(int i) { - return new DownloadProgressInfo[i]; - } - }; + 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/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java index 2201751254..146426ef83 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java @@ -32,13 +32,13 @@ import android.os.Messenger; import android.os.RemoteException; import android.util.Log; - +import java.lang.ref.WeakReference; /** * 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 @@ -58,162 +58,176 @@ import android.util.Log; * 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 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 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; - 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; - 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 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); + } - @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(); + } + } - 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; - } + public Proxy(Messenger msg) { + mServiceMessenger = msg; + } - @Override - public void onServiceConnected(Messenger m) { - /** + @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; - /** + 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; - } - } - }); + private final MessengerHandlerClient mMsgHandler = new MessengerHandlerClient(this); + final Messenger mMessenger = new Messenger(mMsgHandler); + + private static class MessengerHandlerClient extends Handler { + private final WeakReference<Stub> mDownloader; + public MessengerHandlerClient(Stub downloader) { + mDownloader = new WeakReference<>(downloader); + } + + @Override + public void handleMessage(Message msg) { + Stub downloader = mDownloader.get(); + if (downloader != null) { + downloader.handleMessage(msg); + } + } + } - public Stub(IDownloaderClient itf, Class<?> downloaderService) { - mItf = itf; - mDownloaderServiceClass = downloaderService; - } + private 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); - } + 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; - } - }; + 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 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 void disconnect(Context c) { + if (mBound) { + c.unbindService(mConnection); + mBound = false; + } + mContext = null; + } - @Override - public Messenger getMessenger() { - return mMessenger; - } - } + @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); - } + 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 @@ -221,11 +235,11 @@ public class DownloaderClientMarshaller { * @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); - } - - /** + 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, @@ -237,7 +251,7 @@ public class DownloaderClientMarshaller { * 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 @@ -248,30 +262,29 @@ public class DownloaderClientMarshaller { * #DOWNLOAD_REQUIRED}. * @throws NameNotFoundException */ - public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, - Class<?> serviceClass) - throws NameNotFoundException { - return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, - serviceClass); - } - - /** + 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 + * <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); - } - + public static int startDownloadServiceIfRequired(Context context, Intent notificationClient, + Class<?> serviceClass) + throws NameNotFoundException { + return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, + serviceClass); + } } diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java index 054eaa9895..f75debe32d 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java @@ -25,7 +25,7 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; - +import java.lang.ref.WeakReference; /** * This class is used by the client activity to proxy requests to the Downloader @@ -38,144 +38,156 @@ import android.os.RemoteException; */ 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) { - - } - } - - /** + 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; + private final MessengerHandlerServer mMsgHandler = new MessengerHandlerServer(this); + final Messenger mMessenger = new Messenger(mMsgHandler); + + private static class MessengerHandlerServer extends Handler { + private final WeakReference<Stub> mDownloader; + public MessengerHandlerServer(Stub downloader) { + mDownloader = new WeakReference<>(downloader); + } + + @Override + public void handleMessage(Message msg) { + Stub downloader = mDownloader.get(); + if (downloader != null) { + downloader.handleMessage(msg); + } + } + } + + private 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); - } + 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); - } - + public static IStub CreateStub(IDownloaderService itf) { + return new Stub(itf); + } } diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java index fb56f917be..cd8726533f 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java @@ -16,8 +16,7 @@ package com.google.android.vending.expansion.downloader; -import com.godot.game.R; - +import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.os.Environment; @@ -25,6 +24,8 @@ import android.os.StatFs; import android.os.SystemClock; import android.util.Log; +import com.godot.game.R; + import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; @@ -39,273 +40,316 @@ import java.util.regex.Pattern; */ public class Helpers { - public static Random sRandom = new Random(SystemClock.uptimeMillis()); + 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*\"([^\"]*)\""); + /** Regex used to parse content-disposition headers */ + private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern + .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""); - private Helpers() { - } + 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. + /* + * 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; - } + 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 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; - } + 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 + /** + * @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; - } + 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()); - } + 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); - } - } + /* 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. - * + /** + * 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"; - } + 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(Locale.ENGLISH, "%.2f", + (float)overallProgress / (1024.0f * 1024.0f)) + + "MB /" + + String.format(Locale.ENGLISH, "%.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) + ")"; - } + 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 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 getSpeedString(float bytesPerMillisecond) { + return String.format(Locale.ENGLISH, "%.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())); - } + 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. - * + /** + * 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"; - } + 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 + /** + * 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 generateSaveFileName(Context c, String fileName) { + String path = getSaveFilePath(c) + File.separator + fileName; + return path; + } - static public String getSaveFilePath(Context c) { - File root = Environment.getExternalStorageDirectory(); - // this makes several issues with Android SDK >= 23 devices. - // https://github.com/danikula/Google-Play-Expansion-File/commit/93a03bd34acad67c6ea34cfb6c3f02c93bdcea85 - // https://issuetracker.google.com/issues/37075181 - //String path = Build.VERSION.SDK_INT >= 23 ? Constants.EXP_PATH_API23 : Constants.EXP_PATH; - String path = Constants.EXP_PATH; - return root.toString() + path + c.getPackageName(); - } + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + static public String getSaveFilePath(Context c) { + // This technically existed since Honeycomb, but it is critical + // on KitKat and greater versions since it will create the + // directory if needed + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + return c.getObbDir().toString(); + } else { + 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 - * + /** + * 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 + * @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 Play --- 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; + } + + public static final int FS_READABLE = 0; + public static final int FS_DOES_NOT_EXIST = 1; + public static final int FS_CANNOT_READ = 2; + + /** + * Helper function to ascertain whether a file can be read. + * + * @param c the app/activity/service context + * @param fileName the name (sans path) of the file to query * @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; - } + static public int getFileStatus(Context c, String fileName) { + // the file may have been delivered by Play --- let's make sure + // it's the size we expect + File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); + int returnValue; + if (fileForNewFile.exists()) { + if (fileForNewFile.canRead()) { + returnValue = FS_READABLE; + } else { + returnValue = FS_CANNOT_READ; + } + } else { + returnValue = FS_DOES_NOT_EXIST; + } + return returnValue; + } + + /** + * Helper function to ascertain whether the application has the correct access to the OBB + * directory to allow an OBB file to be written. + * + * @param c the app/activity/service context + * @return true if the application can write an OBB file, false otherwise + */ + static public boolean canWriteOBBFile(Context c) { + String path = getSaveFilePath(c); + File fileForNewFile = new File(path); + boolean canWrite; + if (fileForNewFile.exists()) { + canWrite = fileForNewFile.isDirectory() && fileForNewFile.canWrite(); + } else { + canWrite = fileForNewFile.mkdirs(); + } + return canWrite; + } - /** - * 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. + /** + * 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; - } - } - + 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/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java index b8511a62a0..bae93f633a 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java @@ -23,26 +23,26 @@ import android.os.Messenger; * 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_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; + 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; + 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 @@ -53,27 +53,27 @@ public interface IDownloaderClient { * 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_WIFI_DISABLED = 10; + static final int STATE_PAUSED_NEED_WIFI = 11; - static final int STATE_PAUSED_ROAMING = 12; + 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_NETWORK_SETUP_FAILURE = 13; - static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14; + 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_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; + 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 @@ -86,13 +86,13 @@ public interface IDownloaderClient { * 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); + 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. @@ -109,18 +109,18 @@ public interface IDownloaderClient { * 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); + 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); + void onDownloadProgress(DownloadProgressInfo progress); } diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java index 4789afe19c..a84fb32728 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java @@ -31,53 +31,53 @@ import android.os.Messenger; * 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; + 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(); + 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(); + 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(); + void requestContinueDownload(); - /** + /** * Set the flags for this download (e.g. * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}). - * + * * @param flags */ - void setDownloadFlags(int flags); + void setDownloadFlags(int flags); - /** + /** * Requests that the download status be sent to the client. */ - void requestDownloadStatus(); + 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); + void onClientUpdated(Messenger clientMessenger); } diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java index d5bc3a843e..dcdef1bfcf 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java @@ -33,9 +33,9 @@ import android.os.Messenger; * {@link IDownloaderService#onClientUpdated}. */ public interface IStub { - Messenger getMessenger(); + Messenger getMessenger(); - void connect(Context c); + void connect(Context c); - void disconnect(Context c); + void disconnect(Context c); } diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java index 12edd97ab2..c5577d4c2a 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java @@ -16,6 +16,7 @@ package com.google.android.vending.expansion.downloader; +import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.content.Context; @@ -30,94 +31,96 @@ 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) { - /** + 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; + } + + @SuppressLint("MissingPermission") + 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; + } + + @SuppressLint("MissingPermission") + 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); - } + mNotificationManager.notify((int)id, notification); + } - public void cancelNotification(long id) { - mNotificationManager.cancel((int) id); - } + public void cancelNotification(long id) { + mNotificationManager.cancel((int)id); + } - public void cancelAllNotifications() { - mNotificationManager.cancelAll(); - } + public void cancelAllNotifications() { + mNotificationManager.cancelAll(); + } - public void startThread(Thread thread) { - thread.start(); - } + public void startThread(Thread thread) { + thread.start(); + } } diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java deleted file mode 100644 index 4667acce67..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java +++ /dev/null @@ -1,536 +0,0 @@ -/* - * 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/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java index b77af7e085..6346d7703a 100755 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java @@ -32,81 +32,80 @@ import android.util.Log; * 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; + private String mName; + private boolean mRedelivery; + private volatile ServiceHandler mServiceHandler; + private volatile Looper mServiceLooper; + private static final String LOG_TAG = "CustomIntentService"; + private static final int WHAT_MESSAGE = -10; - public CustomIntentService(String paramString) { - this.mName = paramString; - } + public CustomIntentService(String paramString) { + this.mName = paramString; + } - @Override - public IBinder onBind(Intent paramIntent) { - return null; - } + @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 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"); - } + @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 void onHandleIntent(Intent paramIntent); - protected abstract boolean shouldStop(); + 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 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; - } + @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; - } + public void setIntentRedelivery(boolean enabled) { + this.mRedelivery = enabled; + } - private final class ServiceHandler extends Handler { - public ServiceHandler(Looper looper) { - super(looper); - } + 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"); - } - } - } + @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/java/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java deleted file mode 100644 index e2673a9dd7..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 - throw new RuntimeException(); - } -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java index 45111b16a3..0e72b7ae77 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java @@ -25,68 +25,68 @@ 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; + 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; + boolean mInitialized; - public int mFuzz; + public int mFuzz; - public DownloadInfo(int index, String fileName, String pkg) { - mFuzz = Helpers.sRandom.nextInt(1001); - mFileName = fileName; - mIndex = index; - } + 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; - } + 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 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); - } + 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/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java index a9f674803c..099e3f05b3 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java @@ -22,11 +22,12 @@ import com.google.android.vending.expansion.downloader.DownloaderClientMarshalle 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.Build; import android.os.Messenger; +import android.support.v4.app.NotificationCompat; /** * This class handles displaying the notification associated with the download @@ -41,190 +42,183 @@ import android.os.Messenger; */ 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.Builder mNotificationBuilder; - private Notification.Builder mCurrentNotificationBuilder; - 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(); - mCurrentNotificationBuilder.setTicker(mLabel + ": " + mCurrentText); - mCurrentNotificationBuilder.setSmallIcon(iconResource); - mCurrentNotificationBuilder.setContentTitle(mCurrentTitle); - mCurrentNotificationBuilder.setContentText(mCurrentText); - mCurrentNotificationBuilder.setContentIntent(mContentIntent); - mCurrentNotificationBuilder.setOngoing(ongoingEvent); - mCurrentNotificationBuilder.setAutoCancel(!ongoingEvent); - mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotificationBuilder.build()); - } - } - - @Override - public void onDownloadProgress(DownloadProgressInfo progress) { - mProgressInfo = progress; - if (null != mClientProxy) { - mClientProxy.onDownloadProgress(progress); - } - if (progress.mOverallTotal <= 0) { - // we just show the text - mNotificationBuilder.setTicker(mCurrentTitle); - mNotificationBuilder.setSmallIcon(android.R.drawable.stat_sys_download); - mNotificationBuilder.setContentTitle(mCurrentTitle); - mNotificationBuilder.setContentText(mCurrentText); - mNotificationBuilder.setContentIntent(mContentIntent); - mCurrentNotificationBuilder = mNotificationBuilder; - } 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); - mCurrentNotificationBuilder = mCustomNotification.updateNotification(mContext); - } - mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotificationBuilder.build()); - } - - 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.Builder updateNotification(Context c); - } - - /** + private int mState; + private final Context mContext; + private final NotificationManager mNotificationManager; + private CharSequence mCurrentTitle; + + private IDownloaderClient mClientProxy; + private NotificationCompat.Builder mActiveDownloadBuilder; + private NotificationCompat.Builder mBuilder; + private NotificationCompat.Builder mCurrentBuilder; + private CharSequence mLabel; + private String mCurrentText; + private DownloadProgressInfo mProgressInfo; + private PendingIntent mContentIntent; + + static final String LOGTAG = "DownloadNotification"; + static final int NOTIFICATION_ID = LOGTAG.hashCode(); + + public PendingIntent getClientIntent() { + return mContentIntent; + } + + public void setClientIntent(PendingIntent clientIntent) { + this.mBuilder.setContentIntent(clientIntent); + this.mActiveDownloadBuilder.setContentIntent(clientIntent); + this.mContentIntent = clientIntent; + } + + 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; + mCurrentBuilder.setTicker(mLabel + ": " + mCurrentText); + mCurrentBuilder.setSmallIcon(iconResource); + mCurrentBuilder.setContentTitle(mCurrentTitle); + mCurrentBuilder.setContentText(mCurrentText); + if (ongoingEvent) { + mCurrentBuilder.setOngoing(true); + } else { + mCurrentBuilder.setOngoing(false); + mCurrentBuilder.setAutoCancel(true); + } + mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build()); + } + } + + @Override + public void onDownloadProgress(DownloadProgressInfo progress) { + mProgressInfo = progress; + if (null != mClientProxy) { + mClientProxy.onDownloadProgress(progress); + } + if (progress.mOverallTotal <= 0) { + // we just show the text + mBuilder.setTicker(mCurrentTitle); + mBuilder.setSmallIcon(android.R.drawable.stat_sys_download); + mBuilder.setContentTitle(mCurrentTitle); + mBuilder.setContentText(mCurrentText); + mCurrentBuilder = mBuilder; + } else { + mActiveDownloadBuilder.setProgress((int)progress.mOverallTotal, (int)progress.mOverallProgress, false); + mActiveDownloadBuilder.setContentText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal)); + mActiveDownloadBuilder.setSmallIcon(android.R.drawable.stat_sys_download); + mActiveDownloadBuilder.setTicker(mLabel + ": " + mCurrentText); + mActiveDownloadBuilder.setContentTitle(mLabel); + mActiveDownloadBuilder.setContentInfo(mContext.getString(R.string.time_remaining_notification, + Helpers.getTimeRemaining(progress.mTimeRemaining))); + mCurrentBuilder = mActiveDownloadBuilder; + } + mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build()); + } + + /** * 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); - } - } - - /** + 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(); - mNotificationBuilder = new Notification.Builder(ctx); - mCurrentNotificationBuilder = mNotificationBuilder; - - } - - @Override - public void onServiceConnected(Messenger m) { - } - + DownloadNotification(Context ctx, CharSequence applicationLabel) { + mState = -1; + mContext = ctx; + mLabel = applicationLabel; + mNotificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mActiveDownloadBuilder = new NotificationCompat.Builder(ctx); + mBuilder = new NotificationCompat.Builder(ctx); + + // Set Notification category and priorities to something that makes sense for a long + // lived background task. + mActiveDownloadBuilder.setPriority(NotificationCompat.PRIORITY_LOW); + mActiveDownloadBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS); + + mBuilder.setPriority(NotificationCompat.PRIORITY_LOW); + mBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS); + + mCurrentBuilder = mBuilder; + } + + @Override + public void onServiceConnected(Messenger m) { + } } diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java index 056d1eca0b..2fa146408b 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2015 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. @@ -20,14 +20,7 @@ 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; @@ -38,8 +31,8 @@ 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.net.HttpURLConnection; +import java.net.URL; import java.util.Locale; /** @@ -47,917 +40,794 @@ import java.util.Locale; */ 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(); - } - - /** + 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; - } + 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); - } - } - - /** + 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; - } - - /** + 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; - } - } - - /** + 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; - } - - /** + private class RetryDownload extends Throwable { + + private static final long serialVersionUID = 6196036036517540229L; + } + + /** * 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); - } - } - - /** + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + + State state = new State(mInfo, mService); + 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, "org.godot.game:wakelock"); + wakeLock.acquire(20 * 60 * 1000L /*20 minutes*/); + + if (Constants.LOGV) { + Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName); + Log.v(Constants.TAG, " at " + mInfo.mUri); + } + + 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. + URL url = new URL(state.mRequestUri); + HttpURLConnection request = (HttpURLConnection)url.openConnection(); + request.setRequestProperty("User-Agent", userAgent()); + try { + executeDownload(state, request); + finished = true; + } catch (RetryDownload exc) { + // fall through + } finally { + request.disconnect(); + 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; + } + 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]; + private void executeDownload(State state, HttpURLConnection request) + throws StopRequest, RetryDownload { + InnerState innerState = new InnerState(); + byte data[] = new byte[Constants.BUFFER_SIZE]; - checkPausedOrCanceled(state); + checkPausedOrCanceled(state); - setupDestinationFile(state, innerState); - addRequestHeaders(innerState, request); + setupDestinationFile(state, innerState); + addRequestHeaders(innerState, request); - // check just before sending the request to avoid using an invalid - // connection at all - checkConnectivity(state); + // 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); + mNotification.onDownloadStateChanged(IDownloaderClient.STATE_CONNECTING); + int responseCode = sendRequest(state, request); + handleExceptionalStatus(state, innerState, request, responseCode); - if (Constants.LOGV) { - Log.v(Constants.TAG, "received response for " + mInfo.mUri); - } + 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); - } + processResponseHeaders(state, innerState, request); + InputStream entityStream = openResponseEntity(state, request); + 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"); - } - } - - /** + 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); - } - } - - /** + 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"); - } - } - } - - /** + 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; - } - } - - /** + 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); - } - } - } - } - - /** + 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 - } - } - - /** + 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"); - } - } - } - - /** + 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); - } - } - - /** + 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); - } - } - } - - /** + 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; - } - - /** + 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); - } - } - } - - /** + 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")); - } - } - - /** + private InputStream openResponseEntity(State state, HttpURLConnection response) + throws StopRequest { + try { + return response.getInputStream(); + } 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); - } - - /** + private void processResponseHeaders(State state, InnerState innerState, HttpURLConnection 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); - } + 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"); - } - } - - /** + private void readResponseHeaders(State state, InnerState innerState, HttpURLConnection response) + throws StopRequest { + String value = response.getHeaderField("Content-Disposition"); + if (value != null) { + innerState.mHeaderContentDisposition = value; + } + value = response.getHeaderField("Content-Location"); + if (value != null) { + innerState.mHeaderContentLocation = value; + } + value = response.getHeaderField("ETag"); + if (value != null) { + innerState.mHeaderETag = value; + } + String headerTransferEncoding = null; + value = response.getHeaderField("Transfer-Encoding"); + if (value != null) { + headerTransferEncoding = value; + } + String headerContentType = null; + value = response.getHeaderField("Content-Type"); + if (value != null) { + headerContentType = value; + if (!headerContentType.equals("application/vnd.android.obb")) { + throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY, + "file delivered with incorrect Mime type"); + } + } + + if (headerTransferEncoding == null) { + long contentLength = response.getContentLength(); + if (value != null) { + // this is always set from Market + 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 { + innerState.mHeaderContentLength = Long.toString(contentLength); + } + } + } 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; - } - } - - /** + private void handleExceptionalStatus(State state, InnerState innerState, HttpURLConnection connection, int responseCode) + throws StopRequest, RetryDownload { + if (responseCode == 503 && mInfo.mNumFailed < Constants.MAX_RETRIES) { + handleServiceUnavailable(state, connection); + } + int expectedStatus = innerState.mContinuingDownload ? 206 : DownloaderService.STATUS_SUCCESS; + if (responseCode != expectedStatus) { + handleOtherStatus(state, innerState, responseCode); + } 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(); - } - - /** + 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); + } + + /** * 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 + "-"); - } - } - - /** + private void addRequestHeaders(InnerState innerState, HttpURLConnection request) { + if (innerState.mContinuingDownload) { + if (innerState.mHeaderETag != null) { + request.setRequestProperty("If-Match", innerState.mHeaderETag); + } + request.setRequestProperty("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"); - } - - /** + private void handleServiceUnavailable(State state, HttpURLConnection connection) throws StopRequest { + if (Constants.LOGVV) { + Log.v(Constants.TAG, "got HTTP response code 503"); + } + state.mCountRetry = true; + String retryAfterValue = connection.getHeaderField("Retry-After"); + if (retryAfterValue != null) { + try { + if (Constants.LOGVV) { + Log.v(Constants.TAG, "Retry-After :" + retryAfterValue); + } + state.mRetryAfter = Integer.parseInt(retryAfterValue); + 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; - } - } - - /** + private int sendRequest(State state, HttpURLConnection request) + throws StopRequest { + try { + return request.getResponseCode(); + } 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); - } - } - - /** + 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); - } - + 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/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java index e83faa2756..25a561ccd4 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java @@ -29,6 +29,7 @@ import com.google.android.vending.licensing.LicenseChecker; import com.google.android.vending.licensing.LicenseCheckerCallback; import com.google.android.vending.licensing.Policy; +import android.annotation.SuppressLint; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; @@ -61,82 +62,82 @@ import java.io.File; */ public abstract class DownloaderService extends CustomIntentService implements IDownloaderService { - public DownloaderService() { - super("LVLDownloadService"); - } + public DownloaderService() { + super("LVLDownloadService"); + } - private static final String LOG_TAG = "LVLDL"; + 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 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; + public static final int NETWORK_OK = 1; - /** + /** * There is no network connectivity. */ - public static final int NETWORK_NO_CONNECTION = 2; + 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; + 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; + 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; + 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; + 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"; + 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 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"; + 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"; + 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"; + 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 @@ -144,503 +145,498 @@ public abstract class DownloaderService extends CustomIntentService implements I * errors */ - /** + /** * Returns whether the status is informational (i.e. 1xx). */ - public static boolean isStatusInformational(int status) { - return (status >= 100 && status < 200); - } + 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); - } + 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); - } + 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); - } + 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); - } + 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); - } + 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; + public static final int STATUS_PENDING = 190; - /** + /** * This download has started */ - public static final int STATUS_RUNNING = 192; + 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; + 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; + 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; + 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; + 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; + 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; + public static final int STATUS_SUCCESS = 200; - /** + /** * The requested URL is no longer available */ - public static final int STATUS_FORBIDDEN = 403; + public static final int STATUS_FORBIDDEN = 403; - /** + /** * The file was delivered incorrectly */ - public static final int STATUS_FILE_DELIVERED_INCORRECTLY = 487; + 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; + 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; + public static final int STATUS_CANNOT_RESUME = 489; - /** + /** * This download was canceled - * + * * @hide */ - public static final int STATUS_CANCELED = 490; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; - /** + /** * This download is allowed to run. - * + * * @hide */ - public static final int CONTROL_RUN = 0; + public static final int CONTROL_RUN = 0; - /** + /** * This download must pause at the first opportunity. - * + * * @hide */ - public static final int CONTROL_PAUSED = 1; + 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; + 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; + 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; + public static final int VISIBILITY_HIDDEN = 2; - /** - * Bit flag for {@link #setAllowedNetworkTypes} corresponding to + /** + * Bit flag for setAllowedNetworkTypes corresponding to * {@link ConnectivityManager#TYPE_MOBILE}. */ - public static final int NETWORK_MOBILE = 1 << 0; + public static final int NETWORK_MOBILE = 1 << 0; - /** - * Bit flag for {@link #setAllowedNetworkTypes} corresponding to + /** + * Bit flag for setAllowedNetworkTypes corresponding to * {@link ConnectivityManager#TYPE_WIFI}. */ - public static final int NETWORK_WIFI = 1 << 1; + public static final int NETWORK_WIFI = 1 << 1; - private final static String TEMP_EXT = ".tmp"; + private final static String TEMP_EXT = ".tmp"; - /** + /** * Service thread status */ - private static boolean sIsRunning; + private static boolean sIsRunning; - @Override - public IBinder onBind(Intent paramIntent) { - Log.d(Constants.TAG, "Service Bound"); - return this.mServiceMessenger.getBinder(); - } + @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; + 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; + private int mControl; + private int mStatus; - public boolean isWiFi() { - return mIsConnected && !mIsCellularConnection; - } + public boolean isWiFi() { + return mIsConnected && !mIsCellularConnection; + } - /** + /** * Bindings to important services */ - private ConnectivityManager mConnectivityManager; - private WifiManager mWifiManager; + private ConnectivityManager mConnectivityManager; + private WifiManager mWifiManager; - /** + /** * Package we are downloading for (defaults to package of application) */ - private PackageInfo mPackageInfo; + private PackageInfo mPackageInfo; - /** + /** * Byte counts */ - long mBytesSoFar; - long mTotalLength; - int mFileCount; + long mBytesSoFar; + long mTotalLength; + int mFileCount; - /** + /** * Used for calculating time remaining and speed */ - long mBytesAtSample; - long mMillisecondsAtSample; - float mAverageDownloadSpeed; + 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; + 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; - } - } - } - - } - } - } - - /** + 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) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); - } - if (null == mWifiManager) { - mWifiManager = (WifiManager) getApplicationContext().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"; - - /** + void pollNetworkState() { + if (null == mConnectivityManager) { + mConnectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); + } + if (null == mWifiManager) { + mWifiManager = (WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVICE); + } + if (mConnectivityManager == null) { + Log.w(Constants.TAG, + "couldn't get connectivity manager to poll network state"); + } else { + @SuppressLint("MissingPermission") + 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; - } + 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); - } - - /** + 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 @@ -652,690 +648,673 @@ public abstract class DownloaderService extends CustomIntentService implements I * 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 + * @param pendingIntent * @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); - } - } - - }); - - } - - }; - - /** + 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.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)); - } + 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; - } - } - - /** + 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); - } - } - }; - - /** + 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) { - - /** + @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(); - } - } - - /** + 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 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; - } - } + 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; - } + 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; - } - - /** + 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"; + 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_UNUSABLE_DUE_TO_SIZE: + return "download size exceeds limit for mobile network"; - case NETWORK_NO_CONNECTION: - return "no network connection available"; + 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_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"; + case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: + return "download was requested to not use the current network type"; - default: - return "unknown error with network connectivity"; - } - } + default: + return "unknown error with network connectivity"; + } + } - public int getControl() { - return mControl; - } + public int getControl() { + return mControl; + } - public int getStatus() { - return mStatus; - } + 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); - } - + 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/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java index 250299c400..5d8dce0bac 100755 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java @@ -27,484 +27,443 @@ 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(','); - } - } - - /** + 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[][][] sSchemas = { + DownloadColumns.SCHEMA, MetadataColumns.SCHEMA + }; - static final private String[] sTables = { - DownloadColumns.TABLE_NAME, MetadataColumns.TABLE_NAME - }; + 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(); - } - } - } - - /** + 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; - - /** + 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(); - } - } - } - + 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/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java index 3f440e9893..02bd1f27f6 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java @@ -27,7 +27,7 @@ import java.util.regex.Pattern; */ 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 @@ -37,164 +37,155 @@ public final class HttpDateTime { * (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_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})"; + 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'); - } - } - - /* + 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); - } + 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/java/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java deleted file mode 100644 index 56b2331e31..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.godot.game.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.Builder 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; - } - - @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/java/src/com/google/android/vending/licensing/AESObfuscator.java b/platform/android/java/src/com/google/android/vending/licensing/AESObfuscator.java new file mode 100644 index 0000000000..feba3034c3 --- /dev/null +++ b/platform/android/java/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.google.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/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java b/platform/android/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java new file mode 100644 index 0000000000..2c60e7e4b8 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java @@ -0,0 +1,413 @@ + +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 android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import com.google.android.vending.licensing.util.URIQueryDecoder; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +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, + * error retry count and a URL for restoring app access in unlicensed cases. + * <p> + * These values will vary based on the the way the application is configured in + * the Google Play 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.google.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 PREF_LICENSING_URL = "licensingUrl"; + 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 String mLicensingUrl; + 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)); + mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null); + } + + /** + * 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 + * <li>LU: a deep link URL that can enable access for unlicensed apps (e.g. + * buy app on the Play Store) + * </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); + } + + // Update server policy data + Map<String, String> extras = decodeExtras(rawData); + if (response == Policy.LICENSED) { + mLastResponse = response; + // Reset the licensing URL since it is only applicable for NOT_LICENSED responses. + setLicensingUrl(null); + 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 retry params + setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); + setRetryUntil(DEFAULT_RETRY_UNTIL); + setMaxRetries(DEFAULT_MAX_RETRIES); + // Update the licensing URL + setLicensingUrl(extras.get("LU")); + } + + 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; + } + + /** + * Set the licensing URL that displays a Play Store UI for the user to regain app access. + * + * @param url the LU string received + */ + private void setLicensingUrl(String url) { + mLicensingUrl = url; + mPreferences.putString(PREF_LICENSING_URL, url); + } + + public String getLicensingUrl() { + return mLicensingUrl; + } + + /** + * 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 + */ + 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( + com.google.android.vending.licensing.ResponseData rawData) { + Map<String, String> results = new HashMap<String, String>(); + if (rawData == null) { + return results; + } + + try { + URI rawExtras = new URI("?" + rawData.extra); + URIQueryDecoder.DecodeQuery(rawExtras, results); + } catch (URISyntaxException e) { + Log.w(TAG, "Invalid syntax error while decoding extras data from server."); + } + return results; + } +} diff --git a/platform/android/java/src/com/android/vending/licensing/DeviceLimiter.java b/platform/android/java/src/com/google/android/vending/licensing/DeviceLimiter.java index e5c5e2d7ca..2384b8b82f 100644 --- a/platform/android/java/src/com/android/vending/licensing/DeviceLimiter.java +++ b/platform/android/java/src/com/google/android/vending/licensing/DeviceLimiter.java @@ -37,11 +37,11 @@ package com.google.android.vending.licensing; */ 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); + int isDeviceAllowed(String userId); } diff --git a/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.aidl b/platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.aidl index c816558afc..c816558afc 100644 --- a/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.aidl +++ b/platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.aidl diff --git a/platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.java b/platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.java new file mode 100644 index 0000000000..89edeae1b4 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.java @@ -0,0 +1,100 @@ +/* + * 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. +*/ + +/* + * 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/java/src/com/android/vending/licensing/ILicensingService.aidl b/platform/android/java/src/com/google/android/vending/licensing/ILicensingService.aidl index 664510ce0c..664510ce0c 100644 --- a/platform/android/java/src/com/android/vending/licensing/ILicensingService.aidl +++ b/platform/android/java/src/com/google/android/vending/licensing/ILicensingService.aidl diff --git a/platform/android/java/src/com/google/android/vending/licensing/ILicensingService.java b/platform/android/java/src/com/google/android/vending/licensing/ILicensingService.java new file mode 100644 index 0000000000..8b7cc83541 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/licensing/ILicensingService.java @@ -0,0 +1,100 @@ +/* + * 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. +*/ + +/* + * 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/java/src/com/google/android/vending/licensing/LicenseChecker.java b/platform/android/java/src/com/google/android/vending/licensing/LicenseChecker.java new file mode 100644 index 0000000000..38aab9f4f5 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/licensing/LicenseChecker.java @@ -0,0 +1,387 @@ +/* + * 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.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +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 com.google.android.vending.licensing.ILicenseResultListener; +import com.google.android.vending.licensing.ILicensingService; +import com.google.android.vending.licensing.util.Base64; +import com.google.android.vending.licensing.util.Base64DecoderException; + +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 Google Play 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 encoded - + // com.android.vending.licensing.ILicensingService + // Consider encoding this in another way in your + // code to improve security + Base64.decode( + "Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))) + // As of Android 5.0, implicit + // Service Intents are no longer + // allowed because it's not + // possible for the user to + // participate in disambiguating + // them. This does mean we break + // compatibility with Android + // Cupcake devices with this + // release, since setPackage was + // added in Donut. + .setPackage( + new String( + // Base64 + // encoded - + // com.android.vending + Base64.decode( + "Y29tLmFuZHJvaWQudmVuZGluZw=="))), + 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(); + } + } + } + + /** + * Triggers the last deep link licensing URL returned from the server, which redirects users to a + * page which enables them to gain access to the app. If no such URL is returned by the server, it + * will go to the details page of the app in the Play Store. + */ + public void followLastLicensingUrl(Context context) { + String licensingUrl = mPolicy.getLicensingUrl(); + if (licensingUrl == null) { + licensingUrl = "https://play.google.com/store/apps/details?id=" + context.getPackageName(); + } + Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(licensingUrl)); + context.startActivity(marketIntent); + } + + 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.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/java/src/com/android/vending/licensing/LicenseCheckerCallback.java b/platform/android/java/src/com/google/android/vending/licensing/LicenseCheckerCallback.java index b250a7147b..dc2c2d70bf 100644 --- a/platform/android/java/src/com/android/vending/licensing/LicenseCheckerCallback.java +++ b/platform/android/java/src/com/google/android/vending/licensing/LicenseCheckerCallback.java @@ -34,34 +34,34 @@ package com.google.android.vending.licensing; */ 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); + 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); + 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; + /** 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); + public void applicationError(int errorCode); } diff --git a/platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java b/platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java new file mode 100644 index 0000000000..77f7dc7295 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java @@ -0,0 +1,232 @@ +/* + * 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 { + if (TextUtils.isEmpty(signedData)) { + Log.e(TAG, "Signature verification failed: signedData is empty. " + + + "(Device not signed-in to any Google accounts?)"); + handleInvalidResponse(); + return; + } + + 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/java/src/com/android/vending/licensing/NullDeviceLimiter.java b/platform/android/java/src/com/google/android/vending/licensing/NullDeviceLimiter.java index d87af3153f..a43e454228 100644 --- a/platform/android/java/src/com/android/vending/licensing/NullDeviceLimiter.java +++ b/platform/android/java/src/com/google/android/vending/licensing/NullDeviceLimiter.java @@ -26,7 +26,7 @@ package com.google.android.vending.licensing; */ public class NullDeviceLimiter implements DeviceLimiter { - public int isDeviceAllowed(String userId) { - return Policy.LICENSED; - } + public int isDeviceAllowed(String userId) { + return Policy.LICENSED; + } } diff --git a/platform/android/java/src/com/android/vending/licensing/Obfuscator.java b/platform/android/java/src/com/google/android/vending/licensing/Obfuscator.java index 88891728e6..8731d03aa6 100644 --- a/platform/android/java/src/com/android/vending/licensing/Obfuscator.java +++ b/platform/android/java/src/com/google/android/vending/licensing/Obfuscator.java @@ -20,29 +20,29 @@ 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 reversible. Implementing classes may optionally implement an + * 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); + 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. + * @param obfuscated The data that is to be un-obfuscated. + * @param key The key for the data that is to be un-obfuscated. + * @return The original data transformed by the obfuscate() method. * @throws ValidationException Optionally thrown if a data integrity check fails. */ - String unobfuscate(String obfuscated, String key) throws ValidationException; + String unobfuscate(String obfuscated, String key) throws ValidationException; } diff --git a/platform/android/java/src/com/android/vending/licensing/Policy.java b/platform/android/java/src/com/google/android/vending/licensing/Policy.java index fa267fc71a..65202aceb9 100644 --- a/platform/android/java/src/com/android/vending/licensing/Policy.java +++ b/platform/android/java/src/com/google/android/vending/licensing/Policy.java @@ -22,38 +22,44 @@ package com.google.android.vending.licensing; */ 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; - /** + 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; - /** + 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; + 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); + void processServerResponse(int response, ResponseData rawData); - /** + /** * Check if the user should be allowed access to the application. */ - boolean allowAccess(); + boolean allowAccess(); + + /** + * Gets the licensing URL returned by the server that can enable access for unlicensed apps (e.g. + * buy app on the Play Store). + */ + String getLicensingUrl(); } diff --git a/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java b/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java new file mode 100644 index 0000000000..099bb1c48b --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java @@ -0,0 +1,78 @@ +/* + * 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(); + mEditor.apply(); + } + 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/java/src/com/google/android/vending/licensing/ResponseData.java b/platform/android/java/src/com/google/android/vending/licensing/ResponseData.java new file mode 100644 index 0000000000..1c802f8e45 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/licensing/ResponseData.java @@ -0,0 +1,80 @@ +/* + * 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.text.TextUtils; + +import java.util.regex.Pattern; + +/** + * 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/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java b/platform/android/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java new file mode 100644 index 0000000000..b9a50c1104 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java @@ -0,0 +1,299 @@ +/* + * 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.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import com.google.android.vending.licensing.util.URIQueryDecoder; + +/** + * 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, + * error retry count and a URL for restoring app access in unlicensed cases. + * <p> + * These values will vary based on the the way the application is configured in + * the Google Play 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.google.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 PREF_LICENSING_URL = "licensingUrl"; + 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 String mLicensingUrl; + 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)); + mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null); + } + + /** + * 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 + * <li>LU: a deep link URL that can enable access for unlicensed apps (e.g. + * buy app on the Play Store) + * </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); + } + + // Update server policy data + Map<String, String> extras = decodeExtras(rawData); + if (response == Policy.LICENSED) { + mLastResponse = response; + // Reset the licensing URL since it is only applicable for NOT_LICENSED responses. + setLicensingUrl(null); + setValidityTimestamp(extras.get("VT")); + setRetryUntil(extras.get("GT")); + setMaxRetries(extras.get("GR")); + } else if (response == Policy.NOT_LICENSED) { + // Clear out stale retry params + setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); + setRetryUntil(DEFAULT_RETRY_UNTIL); + setMaxRetries(DEFAULT_MAX_RETRIES); + // Update the licensing URL + setLicensingUrl(extras.get("LU")); + } + + 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; + } + + /** + * Set the license URL value (LU) as received from the server and add to preferences. You must + * manually call PreferenceObfuscator.commit() to commit these changes to disk. + * + * @param url the LU string received + */ + private void setLicensingUrl(String url) { + mLicensingUrl = url; + mPreferences.putString(PREF_LICENSING_URL, url); + } + + public String getLicensingUrl() { + return mLicensingUrl; + } + + /** + * {@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( + com.google.android.vending.licensing.ResponseData rawData) { + Map<String, String> results = new HashMap<String, String>(); + if (rawData == null) { + return results; + } + + try { + URI rawExtras = new URI("?" + rawData.extra); + URIQueryDecoder.DecodeQuery(rawExtras, results); + } catch (URISyntaxException e) { + Log.w(TAG, "Invalid syntax error while decoding extras data from server."); + } + return results; + } +} diff --git a/platform/android/java/src/com/android/vending/licensing/StrictPolicy.java b/platform/android/java/src/com/google/android/vending/licensing/StrictPolicy.java index d8d83b4e4b..9849730c38 100644 --- a/platform/android/java/src/com/android/vending/licensing/StrictPolicy.java +++ b/platform/android/java/src/com/google/android/vending/licensing/StrictPolicy.java @@ -16,6 +16,13 @@ package com.google.android.vending.licensing; +import android.util.Log; +import com.google.android.vending.licensing.util.URIQueryDecoder; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + /** * Non-caching policy. All requests will be sent to the licensing service, * and no local caching is performed. @@ -26,38 +33,67 @@ package com.google.android.vending.licensing; * 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. + * Access to the application is only allowed if a LICENSED response is. * received. All other responses (including RETRY) will deny access. */ public class StrictPolicy implements Policy { - private int mLastResponse; + private static final String TAG = "StrictPolicy"; + + private int mLastResponse; + private String mLicensingUrl; - public StrictPolicy() { - // Set default policy. This will force the application to check the policy on launch. - mLastResponse = Policy.RETRY; - } + public StrictPolicy() { + // Set default policy. This will force the application to check the policy on launch. + mLastResponse = Policy.RETRY; + mLicensingUrl = null; + } - /** + /** * 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. + * Any cache-related ResponseData is ignored, but the licensing URL + * extra is still extracted in cases where the app is unlicensed. * * @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; - } + public void processServerResponse(int response, ResponseData rawData) { + mLastResponse = response; + + if (response == Policy.NOT_LICENSED) { + Map<String, String> extras = decodeExtras(rawData); + mLicensingUrl = extras.get("LU"); + } + } - /** + /** * {@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); - } + public boolean allowAccess() { + return (mLastResponse == Policy.LICENSED); + } + + public String getLicensingUrl() { + return mLicensingUrl; + } + + private Map<String, String> decodeExtras( + com.google.android.vending.licensing.ResponseData rawData) { + Map<String, String> results = new HashMap<String, String>(); + if (rawData == null) { + return results; + } + try { + URI rawExtras = new URI("?" + rawData.extra); + URIQueryDecoder.DecodeQuery(rawExtras, results); + } catch (URISyntaxException e) { + Log.w(TAG, "Invalid syntax error while decoding extras data from server."); + } + return results; + } } diff --git a/platform/android/java/src/com/android/vending/licensing/ValidationException.java b/platform/android/java/src/com/google/android/vending/licensing/ValidationException.java index ee4df47c68..79b70e6804 100644 --- a/platform/android/java/src/com/android/vending/licensing/ValidationException.java +++ b/platform/android/java/src/com/google/android/vending/licensing/ValidationException.java @@ -21,13 +21,13 @@ package com.google.android.vending.licensing; * {@link Obfuscator}.} */ public class ValidationException extends Exception { - public ValidationException() { - super(); - } + public ValidationException() { + super(); + } - public ValidationException(String s) { - super(s); - } + public ValidationException(String s) { + super(s); + } - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; } diff --git a/platform/android/java/src/com/google/android/vending/licensing/util/Base64.java b/platform/android/java/src/com/google/android/vending/licensing/util/Base64.java new file mode 100644 index 0000000000..bd711aadf5 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/licensing/util/Base64.java @@ -0,0 +1,556 @@ +// 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 + */ + +import com.godot.game.BuildConfig; + +/** + * 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; + } + + if (BuildConfig.DEBUG && e != outBuff.length) + throw new RuntimeException(); + 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/java/src/com/android/vending/licensing/util/Base64DecoderException.java b/platform/android/java/src/com/google/android/vending/licensing/util/Base64DecoderException.java index 1aef1b54b8..50724a9b05 100644 --- a/platform/android/java/src/com/android/vending/licensing/util/Base64DecoderException.java +++ b/platform/android/java/src/com/google/android/vending/licensing/util/Base64DecoderException.java @@ -20,13 +20,13 @@ package com.google.android.vending.licensing.util; * @author nelson */ public class Base64DecoderException extends Exception { - public Base64DecoderException() { - super(); - } + public Base64DecoderException() { + super(); + } - public Base64DecoderException(String s) { - super(s); - } + public Base64DecoderException(String s) { + super(s); + } - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; } diff --git a/platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java b/platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java new file mode 100644 index 0000000000..4f908b472c --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 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.util; + +import android.util.Log; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; +import java.util.Map; +import java.util.Scanner; + +public class URIQueryDecoder { + private static final String TAG = "URIQueryDecoder"; + + /** + * Decodes the query portion of the passed-in URI. + * + * @param encodedURI the URI containing the query to decode + * @param results a map containing all query parameters. Query parameters that do not have a + * value will map to a null string + */ + static public void DecodeQuery(URI encodedURI, Map<String, String> results) { + Scanner scanner = new Scanner(encodedURI.getRawQuery()); + scanner.useDelimiter("&"); + try { + while (scanner.hasNext()) { + String param = scanner.next(); + String[] valuePair = param.split("="); + String name, value; + if (valuePair.length == 1) { + value = null; + } else if (valuePair.length == 2) { + value = URLDecoder.decode(valuePair[1], "UTF-8"); + } else { + throw new IllegalArgumentException("query parameter invalid"); + } + name = URLDecoder.decode(valuePair[0], "UTF-8"); + results.put(name, value); + } + } catch (UnsupportedEncodingException e) { + // This should never happen. + Log.e(TAG, "UTF-8 Not Recognized as a charset. Device configuration Error."); + } + } +} diff --git a/platform/android/java/src/org/godotengine/godot/Dictionary.java b/platform/android/java/src/org/godotengine/godot/Dictionary.java index de6b4af568..588d9ae646 100644 --- a/platform/android/java/src/org/godotengine/godot/Dictionary.java +++ b/platform/android/java/src/org/godotengine/godot/Dictionary.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java/src/org/godotengine/godot/Godot.java b/platform/android/java/src/org/godotengine/godot/Godot.java index 88194f00d1..0eeaf0701c 100644 --- a/platform/android/java/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/src/org/godotengine/godot/Godot.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,59 +30,51 @@ package org.godotengine.godot; -import android.R; +//import android.R; + +import android.Manifest; import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; import android.content.pm.ConfigurationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Build; import android.os.Bundle; +import android.os.Environment; +import android.os.Messenger; +import android.provider.Settings.Secure; +import android.support.v4.content.ContextCompat; +import android.util.Log; +import android.view.Display; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowManager; import android.widget.Button; +import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; -import android.widget.LinearLayout; import android.widget.TextView; -import android.view.ViewGroup.LayoutParams; -import android.app.*; -import android.content.*; -import android.content.SharedPreferences.Editor; -import android.view.*; -import android.view.inputmethod.InputMethodManager; -import android.os.*; -import android.util.Log; -import android.graphics.*; -import android.text.method.*; -import android.text.*; -import android.media.*; -import android.hardware.*; -import android.content.*; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.Uri; -import android.media.MediaPlayer; - -import android.content.ClipboardManager; -import android.content.ClipData; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.ArrayList; - -import org.godotengine.godot.payments.PaymentsManager; - -import java.io.IOException; - -import android.provider.Settings.Secure; -import android.widget.FrameLayout; - -import org.godotengine.godot.input.*; - -import java.io.InputStream; -import javax.microedition.khronos.opengles.GL10; -import java.security.MessageDigest; -import java.io.File; -import java.io.FileInputStream; -import java.util.LinkedList; - -import com.google.android.vending.expansion.downloader.Constants; import com.google.android.vending.expansion.downloader.DownloadProgressInfo; import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller; @@ -90,14 +82,24 @@ 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 android.os.Bundle; -import android.os.Messenger; -import android.os.SystemClock; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import javax.microedition.khronos.opengles.GL10; +import org.godotengine.godot.input.GodotEditText; +import org.godotengine.godot.payments.PaymentsManager; public class Godot extends Activity implements SensorEventListener, IDownloaderClient { static final int MAX_SINGLETONS = 64; + static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; + static final int REQUEST_CAMERA_PERMISSION = 2; private IStub mDownloaderClientStub; private IDownloaderService mRemoteService; private TextView mStatusText; @@ -119,7 +121,6 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC private boolean use_debug_opengl = false; private boolean mStatePaused; private int mState; - private boolean keep_screen_on = true; static private Intent mCurrentIntent; @@ -222,9 +223,8 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC private Sensor mGyroscope; public FrameLayout layout; - public RelativeLayout adLayout; - static public GodotIO io; + public static GodotIO io; public static void setWindowTitle(String title) { //setTitle(title); @@ -260,27 +260,30 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC for (int i = 0; i < singleton_count; i++) { singletons[i].onMainRequestPermissionsResult(requestCode, permissions, grantResults); } + + for (int i = 0; i < permissions.length; i++) { + GodotLib.requestPermissionResult(permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED); + } }; public void onVideoInit() { - boolean use_gl3 = getGLESVersionCode() >= 0x00030000; //mView = new GodotView(getApplication(),io,use_gl3); //setContentView(mView); layout = new FrameLayout(this); - layout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); + layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); setContentView(layout); // GodotEditText layout GodotEditText edittext = new GodotEditText(this); - edittext.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); + edittext.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); // ...add to FrameLayout layout.addView(edittext); mView = new GodotView(getApplication(), io, use_gl3, use_32_bits, use_debug_opengl, this); - layout.addView(mView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); + layout.addView(mView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); edittext.setView(mView); io.setEdit(edittext); @@ -298,44 +301,52 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC } }); - // Ad layout - adLayout = new RelativeLayout(this); - adLayout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); - layout.addView(adLayout); - final String[] current_command_line = command_line; - final GodotView view = mView; mView.queueEvent(new Runnable() { @Override public void run() { GodotLib.setup(current_command_line); - runOnUiThread(new Runnable() { - @Override - public void run() { - view.setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); - } - }); + setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); } }); } public void setKeepScreenOn(final boolean p_enabled) { - keep_screen_on = p_enabled; - if (mView != null) { - runOnUiThread(new Runnable() { - @Override - public void run() { - mView.setKeepScreenOn(p_enabled); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (p_enabled) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } - }); - } + } + }); + } + + public void restart() { + // HACK: + // + // Currently it's very hard to properly deinitialize Godot on Android to restart the game + // from scratch. Therefore, we need to kill the whole app process and relaunch it. + // + // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including + // releasing and reloading native libs or resetting their state somehow and clearing statics). + // + // Using instrumentation is a way of making the whole app process restart, because Android + // will kill any process of the same package which was already running. + // + Bundle args = new Bundle(); + args.putParcelable("intent", mCurrentIntent); + startInstrumentation(new ComponentName(Godot.this, GodotInstrumentation.class), null, args); } public void alert(final String message, final String title) { + final Activity activity = this; runOnUiThread(new Runnable() { @Override public void run() { - AlertDialog.Builder builder = new AlertDialog.Builder(getInstance()); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setMessage(message).setTitle(title); builder.setPositiveButton( "OK", @@ -350,14 +361,8 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC }); } - private static Godot _self; - - public static Godot getInstance() { - return Godot._self; - } - public int getGLESVersionCode() { - ActivityManager am = (ActivityManager)Godot.getInstance().getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE); ConfigurationInfo deviceInfo = am.getDeviceConfigurationInfo(); return deviceInfo.reqGlEsVersion; } @@ -433,7 +438,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME); - GodotLib.initialize(this, io.needsReloadHooks(), getAssets(), use_apk_expansion); + GodotLib.initialize(this, getAssets(), use_apk_expansion); result_callback = null; @@ -452,7 +457,6 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC 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); window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); @@ -476,7 +480,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC use_debug_opengl = true; } else if (command_line[i].equals("--use_immersive")) { use_immersive = true; - if (Build.VERSION.SDK_INT >= 19.0) { // check if the application runs on an android 4.4+ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | @@ -498,7 +502,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC Editor editor = prefs.edit(); editor.putString("store_public_key", main_pack_key); - editor.commit(); + editor.apply(); i++; } else if (command_line[i].trim().length() != 0) { new_args.add(command_line[i]); @@ -599,6 +603,9 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC for (int i = 0; i < singleton_count; i++) { singletons[i].onMainDestroy(); } + + GodotLib.ondestroy(this); + super.onDestroy(); } @@ -665,7 +672,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME); mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME); - if (use_immersive && Build.VERSION.SDK_INT >= 19.0) { // check if the application runs on an android 4.4+ + if (use_immersive && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ Window window = getWindow(); window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | @@ -688,13 +695,15 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC @Override public void onSystemUiVisibilityChange(int visibility) { if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { - decorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + decorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } } } }); @@ -935,13 +944,33 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC } */ - // Audio + public boolean requestPermission(String p_name) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + // Not necessary, asked on install already + return true; + } + + if (p_name.equals("RECORD_AUDIO")) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION); + return false; + } + } + + if (p_name.equals("CAMERA")) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION); + return false; + } + } + return true; + } /** - * The download state should trigger changes in the UI --- it may be useful - * to show the state as being indeterminate at times. This sample can be - * considered a guideline. - */ + * The download state should trigger changes in the UI --- it may be useful + * to show the state as being indeterminate at times. This sample can be + * considered a guideline. + */ @Override public void onDownloadStateChanged(int newState) { setState(newState); @@ -1024,13 +1053,13 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC mTimeRemaining.setText(getString(com.godot.game.R.string.time_remaining, Helpers.getTimeRemaining(progress.mTimeRemaining))); - progress.mOverallTotal = progress.mOverallTotal; mPB.setMax((int)(progress.mOverallTotal >> 8)); mPB.setProgress((int)(progress.mOverallProgress >> 8)); - mProgressPercent.setText(Long.toString(progress.mOverallProgress * 100 / - progress.mOverallTotal) + - "%"); + mProgressPercent.setText(String.format(Locale.ENGLISH, "%d %%", progress.mOverallProgress * 100 / progress.mOverallTotal)); mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal)); } + public void initInputDevices() { + mView.initInputDevices(); + } } diff --git a/platform/android/java/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java b/platform/android/java/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java index 4701bac9df..e7e2a3f808 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java +++ b/platform/android/java/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,13 +30,12 @@ package org.godotengine.godot; -import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; import android.util.Log; +import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; /** * You should start your derived downloader class when this receiver gets the message diff --git a/platform/android/java/src/org/godotengine/godot/GodotDownloaderService.java b/platform/android/java/src/org/godotengine/godot/GodotDownloaderService.java index 3a94354843..8e10710c9f 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotDownloaderService.java +++ b/platform/android/java/src/org/godotengine/godot/GodotDownloaderService.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -33,7 +33,6 @@ package org.godotengine.godot; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; - import com.google.android.vending.expansion.downloader.impl.DownloaderService; /** diff --git a/platform/android/java/src/org/godotengine/godot/GodotIO.java b/platform/android/java/src/org/godotengine/godot/GodotIO.java index a95c508d21..98174157ec 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/src/org/godotengine/godot/GodotIO.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,27 +29,27 @@ /*************************************************************************/ package org.godotengine.godot; -import java.util.HashMap; -import java.util.Locale; -import android.net.Uri; -import android.content.Intent; -import android.content.res.AssetManager; -import java.io.InputStream; -import java.io.IOException; import android.app.*; import android.content.*; -import android.view.*; -import android.view.inputmethod.InputMethodManager; -import android.os.*; -import android.util.Log; -import android.util.DisplayMetrics; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.AssetManager; import android.graphics.*; -import android.text.method.*; -import android.text.*; -import android.media.*; import android.hardware.*; -import android.content.*; -import android.content.pm.ActivityInfo; +import android.media.*; +import android.net.Uri; +import android.os.*; +import android.text.*; +import android.text.method.*; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.SparseArray; +import android.view.*; +import android.view.inputmethod.InputMethodManager; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Locale; import org.godotengine.godot.input.*; //android.os.Build @@ -61,7 +61,6 @@ public class GodotIO { Godot activity; GodotEditText edit; - Context applicationContext; MediaPlayer mediaPlayer; final int SCREEN_LANDSCAPE = 0; @@ -87,7 +86,7 @@ public class GodotIO { public int pos; } - HashMap<Integer, AssetData> streams; + SparseArray<AssetData> streams; public int file_open(String path, boolean write) { @@ -125,7 +124,7 @@ public class GodotIO { } public int file_get_size(int id) { - if (!streams.containsKey(id)) { + if (streams.get(id) == null) { System.out.printf("file_get_size: Invalid file id: %d\n", id); return -1; } @@ -134,7 +133,7 @@ public class GodotIO { } public void file_seek(int id, int bytes) { - if (!streams.containsKey(id)) { + if (streams.get(id) == null) { System.out.printf("file_get_size: Invalid file id: %d\n", id); return; } @@ -174,7 +173,7 @@ public class GodotIO { public int file_tell(int id) { - if (!streams.containsKey(id)) { + if (streams.get(id) == null) { System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id); return 0; } @@ -184,7 +183,7 @@ public class GodotIO { } public boolean file_eof(int id) { - if (!streams.containsKey(id)) { + if (streams.get(id) == null) { System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id); return false; } @@ -195,7 +194,7 @@ public class GodotIO { public byte[] file_read(int id, int bytes) { - if (!streams.containsKey(id)) { + if (streams.get(id) == null) { System.out.printf("file_read: Can't read invalid file id: %d\n", id); return new byte[0]; } @@ -243,7 +242,7 @@ public class GodotIO { public void file_close(int id) { - if (!streams.containsKey(id)) { + if (streams.get(id) == null) { System.out.printf("file_close: Can't close invalid file id: %d\n", id); return; } @@ -264,7 +263,7 @@ public class GodotIO { public int last_dir_id = 1; - HashMap<Integer, AssetDir> dirs; + SparseArray<AssetDir> dirs; public int dir_open(String path) { @@ -293,7 +292,7 @@ public class GodotIO { } public boolean dir_is_dir(int id) { - if (!dirs.containsKey(id)) { + if (dirs.get(id) == null) { System.out.printf("dir_next: invalid dir id: %d\n", id); return false; } @@ -320,7 +319,7 @@ public class GodotIO { public String dir_next(int id) { - if (!dirs.containsKey(id)) { + if (dirs.get(id) == null) { System.out.printf("dir_next: invalid dir id: %d\n", id); return ""; } @@ -339,7 +338,7 @@ public class GodotIO { public void dir_close(int id) { - if (!dirs.containsKey(id)) { + if (dirs.get(id) == null) { System.out.printf("dir_close: invalid dir id: %d\n", id); return; } @@ -351,9 +350,9 @@ public class GodotIO { am = p_activity.getAssets(); activity = p_activity; - streams = new HashMap<Integer, AssetData>(); - dirs = new HashMap<Integer, AssetDir>(); - applicationContext = activity.getApplicationContext(); + //streams = new HashMap<Integer, AssetData>(); + streams = new SparseArray<AssetData>(); + dirs = new SparseArray<AssetDir>(); } ///////////////////////// @@ -365,7 +364,7 @@ public class GodotIO { private AudioTrack mAudioTrack; public Object audioInit(int sampleRate, int desiredFrames) { - int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO; + int channelConfig = AudioFormat.CHANNEL_OUT_STEREO; int audioFormat = AudioFormat.ENCODING_PCM_16BIT; int frameSize = 4; @@ -496,15 +495,10 @@ public class GodotIO { } public int getScreenDPI() { - DisplayMetrics metrics = applicationContext.getResources().getDisplayMetrics(); + DisplayMetrics metrics = activity.getApplicationContext().getResources().getDisplayMetrics(); return (int)(metrics.density * 160f); } - public boolean needsReloadHooks() { - - return android.os.Build.VERSION.SDK_INT < 11; - } - public void showKeyboard(String p_existing_text) { if (edit != null) edit.showKeyboard(p_existing_text); @@ -516,14 +510,6 @@ public class GodotIO { public void hideKeyboard() { if (edit != null) edit.hideKeyboard(); - - InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE); - View v = activity.getCurrentFocus(); - if (v != null) { - inputMgr.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); - } else { - inputMgr.hideSoftInputFromWindow(new View(activity).getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); - } }; public void setScreenOrientation(int p_orientation) { @@ -564,7 +550,7 @@ public class GodotIO { try { mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - mediaPlayer.setDataSource(applicationContext, filePath); + mediaPlayer.setDataSource(activity.getApplicationContext(), filePath); mediaPlayer.prepare(); mediaPlayer.start(); } catch (IOException e) { diff --git a/platform/iphone/globals/global_defaults.cpp b/platform/android/java/src/org/godotengine/godot/GodotInstrumentation.java index 423f50995e..0466f380e8 100644 --- a/platform/iphone/globals/global_defaults.cpp +++ b/platform/android/java/src/org/godotengine/godot/GodotInstrumentation.java @@ -1,12 +1,12 @@ /*************************************************************************/ -/* global_defaults.cpp */ +/* GodotInstrumentation.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,8 +28,23 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "global_defaults.h" -#include "core/project_settings.h" +package org.godotengine.godot; -void register_iphone_global_defaults() { +import android.app.Instrumentation; +import android.content.Intent; +import android.os.Bundle; + +public class GodotInstrumentation extends Instrumentation { + private Intent intent; + + @Override + public void onCreate(Bundle arguments) { + intent = arguments.getParcelable("intent"); + start(); + } + + @Override + public void onStart() { + startActivitySync(intent); + } } diff --git a/platform/android/java/src/org/godotengine/godot/GodotLib.java b/platform/android/java/src/org/godotengine/godot/GodotLib.java index 45eb188327..81c98bcc79 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/src/org/godotengine/godot/GodotLib.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -45,9 +45,10 @@ public class GodotLib { * @param height the current view height */ - public static native void initialize(Godot p_instance, boolean need_reload_hook, Object p_asset_manager, boolean use_apk_expansion); + public static native void initialize(Godot p_instance, Object p_asset_manager, boolean use_apk_expansion); + public static native void ondestroy(Godot p_instance); public static native void setup(String[] p_cmdline); - public static native void resize(int width, int height, boolean reload); + public static native void resize(int width, int height); public static native void newcontext(boolean p_32_bits); public static native void back(); public static native void step(); @@ -67,8 +68,9 @@ public class GodotLib { public static native void singleton(String p_name, Object p_object); public static native void method(String p_sname, String p_name, String p_ret, String[] p_params); public static native String getGlobal(String p_key); - public static native void callobject(int p_ID, String p_method, Object[] p_params); - public static native void calldeferred(int p_ID, String p_method, Object[] p_params); + public static native void callobject(int p_id, String p_method, Object[] p_params); + public static native void calldeferred(int p_id, String p_method, Object[] p_params); + public static native void requestPermissionResult(String p_permission, boolean p_result); public static native void setVirtualKeyboardHeight(int p_height); } diff --git a/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java b/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java index bde4221644..1432cd3a67 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java +++ b/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,14 +32,12 @@ package org.godotengine.godot; import android.app.Activity; import android.util.Log; - -import org.godotengine.godot.payments.PaymentsManager; -import org.json.JSONException; -import org.json.JSONObject; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.godotengine.godot.payments.PaymentsManager; +import org.json.JSONException; +import org.json.JSONObject; public class GodotPaymentV3 extends Godot.SingletonBase { diff --git a/platform/android/java/src/org/godotengine/godot/GodotView.java b/platform/android/java/src/org/godotengine/godot/GodotView.java index 4cb4db33de..ab28d9ec33 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotView.java +++ b/platform/android/java/src/org/godotengine/godot/GodotView.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,17 +29,17 @@ /*************************************************************************/ package org.godotengine.godot; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.ContextWrapper; import android.graphics.PixelFormat; +import android.hardware.input.InputManager; import android.opengl.GLSurfaceView; import android.util.AttributeSet; import android.util.Log; +import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; -import android.content.ContextWrapper; -import android.view.InputDevice; -import android.hardware.input.InputManager; - import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -50,7 +50,6 @@ import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.opengles.GL10; - import org.godotengine.godot.input.InputManagerCompat; import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener; /** @@ -75,10 +74,9 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener { private static String TAG = "GodotView"; private static final boolean DEBUG = false; - private static Context ctx; + private Context ctx; - private static GodotIO io; - private static boolean firsttime = true; + private GodotIO io; private static boolean use_gl3 = false; private static boolean use_32 = false; private static boolean use_debug_opengl = false; @@ -96,29 +94,45 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener { activity = p_activity; - if (!p_io.needsReloadHooks()) { - //will only work on SDK 11+!! - setPreserveEGLContextOnPause(true); - } + setPreserveEGLContextOnPause(true); + mInputManager = InputManagerCompat.Factory.getInputManager(this.getContext()); mInputManager.registerInputDeviceListener(this, null); init(false, 16, 0); } + public GodotView(Context context) { + super(context); + ctx = context; + } + public GodotView(Context context, boolean translucent, int depth, int stencil) { super(context); init(translucent, depth, stencil); } + public void initInputDevices() { + /* initially add input devices*/ + int[] deviceIds = mInputManager.getInputDeviceIds(); + for (int deviceId : deviceIds) { + InputDevice device = mInputManager.getInputDevice(deviceId); + if (DEBUG) { + Log.v("GodotView", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName())); + } + onInputDeviceAdded(deviceId); + } + } + + @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); return activity.gotTouchEvent(event); - }; + } public int get_godot_button(int keyCode) { - int button = 0; + int button; switch (keyCode) { case KeyEvent.KEYCODE_BUTTON_A: // Android A is SNES B button = 0; @@ -178,7 +192,7 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener { default: button = keyCode - KeyEvent.KEYCODE_BUTTON_1 + 20; break; - }; + } return button; }; @@ -215,36 +229,42 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener { // Check if the device has not been already added if (id < 0) { InputDevice device = mInputManager.getInputDevice(deviceId); + //device can be null if deviceId is not found + if (device != null) { + int sources = device.getSources(); + if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || + ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) { + id = joy_devices.size(); + + joystick joy = new joystick(); + joy.device_id = deviceId; + joy.name = device.getName(); + joy.axes = new ArrayList<InputDevice.MotionRange>(); + joy.hats = new ArrayList<InputDevice.MotionRange>(); + + List<InputDevice.MotionRange> ranges = device.getMotionRanges(); + Collections.sort(ranges, new RangeComparator()); + + for (InputDevice.MotionRange range : ranges) { + if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) { + joy.hats.add(range); + } else { + joy.axes.add(range); + } + } - id = joy_devices.size(); - - joystick joy = new joystick(); - joy.device_id = deviceId; - joy.name = device.getName(); - joy.axes = new ArrayList<InputDevice.MotionRange>(); - joy.hats = new ArrayList<InputDevice.MotionRange>(); - - List<InputDevice.MotionRange> ranges = device.getMotionRanges(); - Collections.sort(ranges, new RangeComparator()); + joy_devices.add(joy); - for (InputDevice.MotionRange range : ranges) { - if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) { - joy.hats.add(range); - } else { - joy.axes.add(range); + final int device_id = id; + final String name = joy.name; + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyconnectionchanged(device_id, true, name); + } + }); } } - - joy_devices.add(joy); - - final int device_id = id; - final String name = joy.name; - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyconnectionchanged(device_id, true, name); - } - }); } } @@ -267,6 +287,8 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener { @Override public void onInputDeviceChanged(int deviceId) { + onInputDeviceRemoved(deviceId); + onInputDeviceAdded(deviceId); } @Override public boolean onKeyUp(final int keyCode, KeyEvent event) { @@ -440,6 +462,10 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener { private static class ContextFactory implements GLSurfaceView.EGLContextFactory { private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { + String driver_name = GodotLib.getGlobal("rendering/quality/driver/driver_name"); + if (use_gl3 && !driver_name.equals("GLES3")) { + use_gl3 = false; + } if (use_gl3) Log.w(TAG, "creating OpenGL ES 3.0 context :"); else @@ -508,26 +534,24 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener { * perform actual matching in chooseConfig() below. */ private static int EGL_OPENGL_ES2_BIT = 4; - private static int[] s_configAttribs2 = - { - EGL10.EGL_RED_SIZE, 4, - EGL10.EGL_GREEN_SIZE, 4, - EGL10.EGL_BLUE_SIZE, 4, - // EGL10.EGL_DEPTH_SIZE, 16, - // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_NONE - }; - private static int[] s_configAttribs3 = - { - EGL10.EGL_RED_SIZE, 4, - EGL10.EGL_GREEN_SIZE, 4, - EGL10.EGL_BLUE_SIZE, 4, - // EGL10.EGL_DEPTH_SIZE, 16, - // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT - EGL10.EGL_NONE - }; + private static int[] s_configAttribs2 = { + EGL10.EGL_RED_SIZE, 4, + EGL10.EGL_GREEN_SIZE, 4, + EGL10.EGL_BLUE_SIZE, 4, + // EGL10.EGL_DEPTH_SIZE, 16, + // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_NONE + }; + private static int[] s_configAttribs3 = { + EGL10.EGL_RED_SIZE, 4, + EGL10.EGL_GREEN_SIZE, 4, + EGL10.EGL_BLUE_SIZE, 4, + // EGL10.EGL_DEPTH_SIZE, 16, + // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT + EGL10.EGL_NONE + }; public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { @@ -709,8 +733,7 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener { public void onSurfaceChanged(GL10 gl, int width, int height) { - GodotLib.resize(width, height, !firsttime); - firsttime = false; + GodotLib.resize(width, height); for (int i = 0; i < Godot.singleton_count; i++) { Godot.singletons[i].onGLSurfaceChanged(gl, width, height); } diff --git a/platform/android/java/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/src/org/godotengine/godot/input/GodotEditText.java index 53fcf5ef70..45b739baa0 100644 --- a/platform/android/java/src/org/godotengine/godot/input/GodotEditText.java +++ b/platform/android/java/src/org/godotengine/godot/input/GodotEditText.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,14 +30,15 @@ package org.godotengine.godot.input; import android.content.Context; +import android.os.Handler; +import android.os.Message; import android.util.AttributeSet; import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; import android.widget.EditText; +import java.lang.ref.WeakReference; import org.godotengine.godot.*; -import android.os.Handler; -import android.os.Message; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.EditorInfo; public class GodotEditText extends EditText { // =========================================================== @@ -51,9 +52,24 @@ public class GodotEditText extends EditText { // =========================================================== private GodotView mView; private GodotTextInputWrapper mInputWrapper; - private static Handler sHandler; + private EditHandler sHandler = new EditHandler(this); private String mOriginText; + private static class EditHandler extends Handler { + private final WeakReference<GodotEditText> mEdit; + public EditHandler(GodotEditText edit) { + mEdit = new WeakReference<>(edit); + } + + @Override + public void handleMessage(Message msg) { + GodotEditText edit = mEdit.get(); + if (edit != null) { + edit.handleMessage(msg); + } + } + } + // =========================================================== // Constructors // =========================================================== @@ -75,36 +91,33 @@ public class GodotEditText extends EditText { protected void initView() { this.setPadding(0, 0, 0, 0); this.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); + } - sHandler = new Handler() { - @Override - public void handleMessage(final Message msg) { - switch (msg.what) { - case HANDLER_OPEN_IME_KEYBOARD: { - GodotEditText edit = (GodotEditText)msg.obj; - String text = edit.mOriginText; - if (edit.requestFocus()) { - edit.removeTextChangedListener(edit.mInputWrapper); - edit.setText(""); - edit.append(text); - edit.mInputWrapper.setOriginText(text); - edit.addTextChangedListener(edit.mInputWrapper); - final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(edit, 0); - } - } break; - - case HANDLER_CLOSE_IME_KEYBOARD: { - GodotEditText edit = (GodotEditText)msg.obj; - - edit.removeTextChangedListener(mInputWrapper); - final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(edit.getWindowToken(), 0); - edit.mView.requestFocus(); - } break; + private void handleMessage(final Message msg) { + switch (msg.what) { + case HANDLER_OPEN_IME_KEYBOARD: { + GodotEditText edit = (GodotEditText)msg.obj; + String text = edit.mOriginText; + if (edit.requestFocus()) { + edit.removeTextChangedListener(edit.mInputWrapper); + edit.setText(""); + edit.append(text); + edit.mInputWrapper.setOriginText(text); + edit.addTextChangedListener(edit.mInputWrapper); + final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(edit, 0); } - } - }; + } break; + + case HANDLER_CLOSE_IME_KEYBOARD: { + GodotEditText edit = (GodotEditText)msg.obj; + + edit.removeTextChangedListener(mInputWrapper); + final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(edit.getWindowToken(), 0); + edit.mView.requestFocus(); + } break; + } } // =========================================================== diff --git a/platform/android/java/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/src/org/godotengine/godot/input/GodotTextInputWrapper.java index 5d13f17ffb..d6e7ad5b18 100644 --- a/platform/android/java/src/org/godotengine/godot/input/GodotTextInputWrapper.java +++ b/platform/android/java/src/org/godotengine/godot/input/GodotTextInputWrapper.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java b/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java index 0a876d2b7f..4042c42e9d 100644 --- a/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java +++ b/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java @@ -17,7 +17,6 @@ package org.godotengine.godot.input; import android.content.Context; -import android.os.Build; import android.os.Handler; import android.view.InputDevice; import android.view.MotionEvent; @@ -130,11 +129,7 @@ public interface InputManagerCompat { * @return a compatible implementation of InputManager */ public static InputManagerCompat getInputManager(Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - return new InputManagerV16(context); - } else { - return new InputManagerV9(); - } + return new InputManagerV16(context); } } } diff --git a/platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java b/platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java index 3b88609cc9..e4bafa7ff9 100644 --- a/platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java +++ b/platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java @@ -23,7 +23,6 @@ import android.os.Build; import android.os.Handler; import android.view.InputDevice; import android.view.MotionEvent; - import java.util.HashMap; import java.util.Map; diff --git a/platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java b/platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java deleted file mode 100644 index a1418c5899..0000000000 --- a/platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2013 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 org.godotengine.godot.input; - -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.util.Log; -import android.util.SparseArray; -import android.view.InputDevice; -import android.view.MotionEvent; - -import java.lang.ref.WeakReference; -import java.util.ArrayDeque; -import java.util.HashMap; -import java.util.Map; -import java.util.Queue; - -public class InputManagerV9 implements InputManagerCompat { - private static final String LOG_TAG = "InputManagerV9"; - private static final int MESSAGE_TEST_FOR_DISCONNECT = 101; - private static final long CHECK_ELAPSED_TIME = 3000L; - - private static final int ON_DEVICE_ADDED = 0; - private static final int ON_DEVICE_CHANGED = 1; - private static final int ON_DEVICE_REMOVED = 2; - - private final SparseArray<long[]> mDevices; - private final Map<InputDeviceListener, Handler> mListeners; - private final Handler mDefaultHandler; - - private static class PollingMessageHandler extends Handler { - private final WeakReference<InputManagerV9> mInputManager; - - PollingMessageHandler(InputManagerV9 im) { - mInputManager = new WeakReference<InputManagerV9>(im); - } - - @Override - public void handleMessage(Message msg) { - super.handleMessage(msg); - switch (msg.what) { - case MESSAGE_TEST_FOR_DISCONNECT: - InputManagerV9 imv = mInputManager.get(); - if (null != imv) { - long time = SystemClock.elapsedRealtime(); - int size = imv.mDevices.size(); - for (int i = 0; i < size; i++) { - long[] lastContact = imv.mDevices.valueAt(i); - if (null != lastContact) { - if (time - lastContact[0] > CHECK_ELAPSED_TIME) { - // check to see if the device has been - // disconnected - int id = imv.mDevices.keyAt(i); - if (null == InputDevice.getDevice(id)) { - // disconnected! - imv.notifyListeners(ON_DEVICE_REMOVED, id); - imv.mDevices.remove(id); - } else { - lastContact[0] = time; - } - } - } - } - sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, - CHECK_ELAPSED_TIME); - } - break; - } - } - } - - public InputManagerV9() { - mDevices = new SparseArray<long[]>(); - mListeners = new HashMap<InputDeviceListener, Handler>(); - mDefaultHandler = new PollingMessageHandler(this); - // as a side-effect, populates our collection of watched - // input devices - getInputDeviceIds(); - } - - @Override - public InputDevice getInputDevice(int id) { - return InputDevice.getDevice(id); - } - - @Override - public int[] getInputDeviceIds() { - // add any hitherto unknown devices to our - // collection of watched input devices - int[] activeDevices = InputDevice.getDeviceIds(); - long time = SystemClock.elapsedRealtime(); - for (int id : activeDevices) { - long[] lastContact = mDevices.get(id); - if (null == lastContact) { - // we have a new device - mDevices.put(id, new long[] { time }); - } - } - return activeDevices; - } - - @Override - public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) { - mListeners.remove(listener); - if (handler == null) { - handler = mDefaultHandler; - } - mListeners.put(listener, handler); - } - - @Override - public void unregisterInputDeviceListener(InputDeviceListener listener) { - mListeners.remove(listener); - } - - private void notifyListeners(int why, int deviceId) { - // the state of some device has changed - if (!mListeners.isEmpty()) { - // yes... this will cause an object to get created... hopefully - // it won't happen very often - for (InputDeviceListener listener : mListeners.keySet()) { - Handler handler = mListeners.get(listener); - DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId, listener); - handler.post(odc); - } - } - } - - private static class DeviceEvent implements Runnable { - private int mMessageType; - private int mId; - private InputDeviceListener mListener; - private static Queue<DeviceEvent> sEventQueue = new ArrayDeque<DeviceEvent>(); - - private DeviceEvent() { - } - - static DeviceEvent getDeviceEvent(int messageType, int id, - InputDeviceListener listener) { - DeviceEvent curChanged = sEventQueue.poll(); - if (null == curChanged) { - curChanged = new DeviceEvent(); - } - curChanged.mMessageType = messageType; - curChanged.mId = id; - curChanged.mListener = listener; - return curChanged; - } - - @Override - public void run() { - switch (mMessageType) { - case ON_DEVICE_ADDED: - mListener.onInputDeviceAdded(mId); - break; - case ON_DEVICE_CHANGED: - mListener.onInputDeviceChanged(mId); - break; - case ON_DEVICE_REMOVED: - mListener.onInputDeviceRemoved(mId); - break; - default: - Log.e(LOG_TAG, "Unknown Message Type"); - break; - } - // dump this runnable back in the queue - sEventQueue.offer(this); - } - } - - @Override - public void onGenericMotionEvent(MotionEvent event) { - // detect new devices - int id = event.getDeviceId(); - long[] timeArray = mDevices.get(id); - if (null == timeArray) { - notifyListeners(ON_DEVICE_ADDED, id); - timeArray = new long[1]; - mDevices.put(id, timeArray); - } - long time = SystemClock.elapsedRealtime(); - timeArray[0] = time; - } - - @Override - public void onPause() { - mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT); - } - - @Override - public void onResume() { - mDefaultHandler.sendEmptyMessage(MESSAGE_TEST_FOR_DISCONNECT); - } -} diff --git a/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java b/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java index 5d94e77cd7..f872e7af56 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java +++ b/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,66 +30,86 @@ package org.godotengine.godot.payments; -import com.android.vending.billing.IInAppBillingService; - import android.content.Context; import android.os.AsyncTask; import android.os.RemoteException; import android.util.Log; +import com.android.vending.billing.IInAppBillingService; +import java.lang.ref.WeakReference; abstract public class ConsumeTask { private Context context; - private IInAppBillingService mService; + + private String mSku; + private String mToken; + + private static class ConsumeAsyncTask extends AsyncTask<String, String, String> { + + private WeakReference<ConsumeTask> mTask; + + ConsumeAsyncTask(ConsumeTask consume) { + mTask = new WeakReference<>(consume); + } + + @Override + protected String doInBackground(String... strings) { + ConsumeTask consume = mTask.get(); + if (consume != null) { + return consume.doInBackground(strings); + } + return null; + } + + @Override + protected void onPostExecute(String param) { + ConsumeTask consume = mTask.get(); + if (consume != null) { + consume.onPostExecute(param); + } + } + } + public ConsumeTask(IInAppBillingService mService, Context context) { this.context = context; this.mService = mService; } public void consume(final String sku) { - //Log.d("XXX", "Consuming product " + sku); + mSku = sku; PaymentsCache pc = new PaymentsCache(context); Boolean isBlocked = pc.getConsumableFlag("block", sku); - String _token = pc.getConsumableValue("token", sku); - //Log.d("XXX", "token " + _token); - if (!isBlocked && _token == null) { - //_token = "inapp:"+context.getPackageName()+":android.test.purchased"; - //Log.d("XXX", "Consuming product " + sku + " with token " + _token); + mToken = pc.getConsumableValue("token", sku); + if (!isBlocked && mToken == null) { + // Consuming task is processing } else if (!isBlocked) { - //Log.d("XXX", "It is not blocked ¿?"); return; - } else if (_token == null) { - //Log.d("XXX", "No token available"); + } else if (mToken == null) { this.error("No token for sku:" + sku); return; } - final String token = _token; - new AsyncTask<String, String, String>() { - @Override - protected String doInBackground(String... params) { - try { - //Log.d("XXX", "Requesting to release item."); - int response = mService.consumePurchase(3, context.getPackageName(), token); - //Log.d("XXX", "release response code: " + response); - if (response == 0 || response == 8) { - return null; - } - } catch (RemoteException e) { - return e.getMessage(); - } - return "Some error"; - } + new ConsumeAsyncTask(this).execute(); + } - protected void onPostExecute(String param) { - if (param == null) { - success(new PaymentsCache(context).getConsumableValue("ticket", sku)); - } else { - error(param); - } + private String doInBackground(String... params) { + try { + int response = mService.consumePurchase(3, context.getPackageName(), mToken); + if (response == 0 || response == 8) { + return null; } + } catch (RemoteException e) { + return e.getMessage(); + } + return "Some error"; + } + + private void onPostExecute(String param) { + if (param == null) { + success(new PaymentsCache(context).getConsumableValue("ticket", mSku)); + } else { + error(param); } - .execute(); } abstract protected void success(String ticket); diff --git a/platform/android/java/src/org/godotengine/godot/payments/GenericConsumeTask.java b/platform/android/java/src/org/godotengine/godot/payments/GenericConsumeTask.java deleted file mode 100644 index 8b48193ae2..0000000000 --- a/platform/android/java/src/org/godotengine/godot/payments/GenericConsumeTask.java +++ /dev/null @@ -1,79 +0,0 @@ -/*************************************************************************/ -/* GenericConsumeTask.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.payments; - -import com.android.vending.billing.IInAppBillingService; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.RemoteException; -import android.util.Log; - -abstract public class GenericConsumeTask extends AsyncTask<String, String, String> { - - private Context context; - private IInAppBillingService mService; - - public GenericConsumeTask(Context context, IInAppBillingService mService, String sku, String receipt, String signature, String token) { - this.context = context; - this.mService = mService; - this.sku = sku; - this.receipt = receipt; - this.signature = signature; - this.token = token; - } - - private String sku; - private String receipt; - private String signature; - private String token; - - @Override - protected String doInBackground(String... params) { - try { - //Log.d("godot", "Requesting to consume an item with token ." + token); - int response = mService.consumePurchase(3, context.getPackageName(), token); - //Log.d("godot", "consumePurchase response: " + response); - if (response == 0 || response == 8) { - return null; - } - } catch (Exception e) { - Log.d("godot", "Error " + e.getClass().getName() + ":" + e.getMessage()); - } - return null; - } - - protected void onPostExecute(String sarasa) { - onSuccess(sku, receipt, signature, token); - } - - abstract public void onSuccess(String sku, String receipt, String signature, String token); -} diff --git a/platform/android/java/src/org/godotengine/godot/payments/HandlePurchaseTask.java b/platform/android/java/src/org/godotengine/godot/payments/HandlePurchaseTask.java index aaf18c74bf..5424ebb49d 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/HandlePurchaseTask.java +++ b/platform/android/java/src/org/godotengine/godot/payments/HandlePurchaseTask.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,13 +30,6 @@ package org.godotengine.godot.payments; -import org.json.JSONException; -import org.json.JSONObject; - -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.utils.Crypt; -import com.android.vending.billing.IInAppBillingService; - import android.app.Activity; import android.app.PendingIntent; import android.app.ProgressDialog; @@ -47,6 +40,11 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; +import com.android.vending.billing.IInAppBillingService; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.utils.Crypt; +import org.json.JSONException; +import org.json.JSONObject; abstract public class HandlePurchaseTask { diff --git a/platform/android/java/src/org/godotengine/godot/payments/PaymentsCache.java b/platform/android/java/src/org/godotengine/godot/payments/PaymentsCache.java index 40cdeea72e..8a2facbcfb 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/PaymentsCache.java +++ b/platform/android/java/src/org/godotengine/godot/payments/PaymentsCache.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -46,7 +46,7 @@ public class PaymentsCache { SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPref.edit(); editor.putBoolean(sku, flag); - editor.commit(); + editor.apply(); } public boolean getConsumableFlag(String set, String sku) { @@ -60,7 +60,7 @@ public class PaymentsCache { SharedPreferences.Editor editor = sharedPref.edit(); editor.putString(sku, value); //Log.d("XXX", "Setting asset: consumables_" + set + ":" + sku); - editor.commit(); + editor.apply(); } public String getConsumableValue(String set, String sku) { diff --git a/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java b/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java index d4c7380424..a0dbc432c1 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java +++ b/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -40,17 +40,14 @@ import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; - import com.android.vending.billing.IInAppBillingService; - +import java.util.ArrayList; +import java.util.Arrays; import org.godotengine.godot.Godot; import org.godotengine.godot.GodotPaymentV3; import org.json.JSONException; import org.json.JSONObject; -import java.util.ArrayList; -import java.util.Arrays; - public class PaymentsManager { public static final int BILLING_RESPONSE_RESULT_OK = 0; @@ -112,7 +109,7 @@ public class PaymentsManager { }; public void requestPurchase(final String sku, String transactionId) { - new PurchaseTask(mService, Godot.getInstance()) { + new PurchaseTask(mService, activity) { @Override protected void error(String message) { godotPaymentV3.callbackFail(message); @@ -159,7 +156,7 @@ public class PaymentsManager { public void requestPurchased() { try { - PaymentsCache pc = new PaymentsCache(Godot.getInstance()); + PaymentsCache pc = new PaymentsCache(activity); String continueToken = null; diff --git a/platform/android/java/src/org/godotengine/godot/payments/PurchaseTask.java b/platform/android/java/src/org/godotengine/godot/payments/PurchaseTask.java index e1d9bcee65..650c5178f0 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/PurchaseTask.java +++ b/platform/android/java/src/org/godotengine/godot/payments/PurchaseTask.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,13 +30,6 @@ package org.godotengine.godot.payments; -import org.json.JSONException; -import org.json.JSONObject; - -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.utils.Crypt; -import com.android.vending.billing.IInAppBillingService; - import android.app.Activity; import android.app.PendingIntent; import android.app.ProgressDialog; @@ -47,6 +40,11 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; +import com.android.vending.billing.IInAppBillingService; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.utils.Crypt; +import org.json.JSONException; +import org.json.JSONObject; abstract public class PurchaseTask { diff --git a/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java b/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java index eccc6f671b..daca6ef5ae 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java +++ b/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,26 +30,56 @@ package org.godotengine.godot.payments; -import java.util.ArrayList; - -import org.json.JSONException; -import org.json.JSONObject; - -import org.godotengine.godot.Dictionary; -import org.godotengine.godot.Godot; -import com.android.vending.billing.IInAppBillingService; - import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; -import android.os.RemoteException; import android.util.Log; +import com.android.vending.billing.IInAppBillingService; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import org.json.JSONException; +import org.json.JSONObject; abstract public class ReleaseAllConsumablesTask { private Context context; private IInAppBillingService mService; + private static class ReleaseAllConsumablesAsyncTask extends AsyncTask<String, String, String> { + + private WeakReference<ReleaseAllConsumablesTask> mTask; + private String mSku; + private String mReceipt; + private String mSignature; + private String mToken; + + ReleaseAllConsumablesAsyncTask(ReleaseAllConsumablesTask task, String sku, String receipt, String signature, String token) { + mTask = new WeakReference<ReleaseAllConsumablesTask>(task); + + mSku = sku; + mReceipt = receipt; + mSignature = signature; + mToken = token; + } + + @Override + protected String doInBackground(String... params) { + ReleaseAllConsumablesTask consume = mTask.get(); + if (consume != null) { + return consume.doInBackground(mToken); + } + return null; + } + + @Override + protected void onPostExecute(String param) { + ReleaseAllConsumablesTask consume = mTask.get(); + if (consume != null) { + consume.success(mSku, mReceipt, mSignature, mToken); + } + } + } + public ReleaseAllConsumablesTask(IInAppBillingService mService, Context context) { this.context = context; this.mService = mService; @@ -60,12 +90,6 @@ abstract public class ReleaseAllConsumablesTask { //Log.d("godot", "consumeItall for " + context.getPackageName()); Bundle bundle = mService.getPurchases(3, context.getPackageName(), "inapp", null); - for (String key : bundle.keySet()) { - Object value = bundle.get(key); - //Log.d("godot", String.format("%s %s (%s)", key, - //value.toString(), value.getClass().getName())); - } - if (bundle.getInt("RESPONSE_CODE") == 0) { final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); @@ -87,14 +111,7 @@ abstract public class ReleaseAllConsumablesTask { String token = inappPurchaseData.getString("purchaseToken"); String signature = mySignatures.get(i); //Log.d("godot", "A punto de consumir un item con token:" + token + "\n" + receipt); - new GenericConsumeTask(context, mService, sku, receipt, signature, token) { - @Override - public void onSuccess(String sku, String receipt, String signature, String token) { - ReleaseAllConsumablesTask.this.success(sku, receipt, signature, token); - } - } - .execute(); - + new ReleaseAllConsumablesAsyncTask(this, sku, receipt, signature, token).execute(); } catch (JSONException e) { } } @@ -104,6 +121,20 @@ abstract public class ReleaseAllConsumablesTask { } } + private String doInBackground(String token) { + try { + //Log.d("godot", "Requesting to consume an item with token ." + token); + int response = mService.consumePurchase(3, context.getPackageName(), token); + //Log.d("godot", "consumePurchase response: " + response); + if (response == 0 || response == 8) { + return null; + } + } catch (Exception e) { + Log.d("godot", "Error " + e.getClass().getName() + ":" + e.getMessage()); + } + return null; + } + abstract protected void success(String sku, String receipt, String signature, String token); abstract protected void error(String message); abstract protected void notRequired(); diff --git a/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java b/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java index 0626e50bb1..d32c80e8e0 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java +++ b/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,17 +30,6 @@ package org.godotengine.godot.payments; -import org.json.JSONException; -import org.json.JSONObject; - -import org.godotengine.godot.Godot; -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.GodotPaymentV3; -import org.godotengine.godot.utils.Crypt; -import org.godotengine.godot.utils.HttpRequester; -import org.godotengine.godot.utils.RequestParams; -import com.android.vending.billing.IInAppBillingService; - import android.app.Activity; import android.app.PendingIntent; import android.app.ProgressDialog; @@ -51,70 +40,113 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; +import com.android.vending.billing.IInAppBillingService; +import java.lang.ref.WeakReference; +import org.godotengine.godot.Godot; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.GodotPaymentV3; +import org.godotengine.godot.utils.Crypt; +import org.godotengine.godot.utils.HttpRequester; +import org.godotengine.godot.utils.RequestParams; +import org.json.JSONException; +import org.json.JSONObject; abstract public class ValidateTask { private Activity context; private GodotPaymentV3 godotPaymentsV3; + private ProgressDialog dialog; + private String mSku; + + private static class ValidateAsyncTask extends AsyncTask<String, String, String> { + private WeakReference<ValidateTask> mTask; + + ValidateAsyncTask(ValidateTask task) { + mTask = new WeakReference<>(task); + } + + @Override + protected void onPreExecute() { + ValidateTask task = mTask.get(); + if (task != null) { + task.onPreExecute(); + } + } + + @Override + protected String doInBackground(String... params) { + ValidateTask task = mTask.get(); + if (task != null) { + return task.doInBackground(params); + } + return null; + } + + @Override + protected void onPostExecute(String response) { + ValidateTask task = mTask.get(); + if (task != null) { + task.onPostExecute(response); + } + } + } + public ValidateTask(Activity context, GodotPaymentV3 godotPaymentsV3) { this.context = context; this.godotPaymentsV3 = godotPaymentsV3; } public void validatePurchase(final String sku) { - new AsyncTask<String, String, String>() { - private ProgressDialog dialog; + mSku = sku; + new ValidateAsyncTask(this).execute(); + } - @Override - protected void onPreExecute() { - dialog = ProgressDialog.show(context, null, "Please wait..."); - } + private void onPreExecute() { + dialog = ProgressDialog.show(context, null, "Please wait..."); + } - @Override - protected String doInBackground(String... params) { - PaymentsCache pc = new PaymentsCache(context); - String url = godotPaymentsV3.getPurchaseValidationUrlPrefix(); - RequestParams param = new RequestParams(); - param.setUrl(url); - param.put("ticket", pc.getConsumableValue("ticket", sku)); - param.put("purchaseToken", pc.getConsumableValue("token", sku)); - param.put("sku", sku); - //Log.d("XXX", "Haciendo request a " + url); - //Log.d("XXX", "ticket: " + pc.getConsumableValue("ticket", sku)); - //Log.d("XXX", "purchaseToken: " + pc.getConsumableValue("token", sku)); - //Log.d("XXX", "sku: " + sku); - param.put("package", context.getApplicationContext().getPackageName()); - HttpRequester requester = new HttpRequester(); - String jsonResponse = requester.post(param); - //Log.d("XXX", "Validation response:\n"+jsonResponse); - return jsonResponse; - } + private String doInBackground(String... params) { + PaymentsCache pc = new PaymentsCache(context); + String url = godotPaymentsV3.getPurchaseValidationUrlPrefix(); + RequestParams param = new RequestParams(); + param.setUrl(url); + param.put("ticket", pc.getConsumableValue("ticket", mSku)); + param.put("purchaseToken", pc.getConsumableValue("token", mSku)); + param.put("sku", mSku); + //Log.d("XXX", "Haciendo request a " + url); + //Log.d("XXX", "ticket: " + pc.getConsumableValue("ticket", sku)); + //Log.d("XXX", "purchaseToken: " + pc.getConsumableValue("token", sku)); + //Log.d("XXX", "sku: " + sku); + param.put("package", context.getApplicationContext().getPackageName()); + HttpRequester requester = new HttpRequester(); + String jsonResponse = requester.post(param); + //Log.d("XXX", "Validation response:\n"+jsonResponse); + return jsonResponse; + } - @Override - protected void onPostExecute(String response) { - if (dialog != null) { - dialog.dismiss(); - } - JSONObject j; - try { - j = new JSONObject(response); - if (j.getString("status").equals("OK")) { - success(); - return; - } else if (j.getString("status") != null) { - error(j.getString("message")); - } else { - error("Connection error"); - } - } catch (JSONException e) { - error(e.getMessage()); - } catch (Exception e) { - error(e.getMessage()); - } + private void onPostExecute(String response) { + if (dialog != null) { + dialog.dismiss(); + dialog = null; + } + JSONObject j; + try { + j = new JSONObject(response); + if (j.getString("status").equals("OK")) { + success(); + return; + } else if (j.getString("status") != null) { + error(j.getString("message")); + } else { + error("Connection error"); } + } catch (JSONException e) { + error(e.getMessage()); + } catch (Exception e) { + error(e.getMessage()); } - .execute(); } + abstract protected void success(); abstract protected void error(String message); abstract protected void canceled(); diff --git a/platform/android/java/src/org/godotengine/godot/utils/Crypt.java b/platform/android/java/src/org/godotengine/godot/utils/Crypt.java index f34511137e..4c551d1d21 100644 --- a/platform/android/java/src/org/godotengine/godot/utils/Crypt.java +++ b/platform/android/java/src/org/godotengine/godot/utils/Crypt.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java b/platform/android/java/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java index 03a7a71bb1..b61007faa3 100644 --- a/platform/android/java/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java +++ b/platform/android/java/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -37,10 +37,8 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; - import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; - import org.apache.http.conn.ssl.SSLSocketFactory; /** diff --git a/platform/android/java/src/org/godotengine/godot/utils/HttpRequester.java b/platform/android/java/src/org/godotengine/godot/utils/HttpRequester.java index cfe9c4fef0..e98f533c23 100644 --- a/platform/android/java/src/org/godotengine/godot/utils/HttpRequester.java +++ b/platform/android/java/src/org/godotengine/godot/utils/HttpRequester.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,6 +30,9 @@ package org.godotengine.godot.utils; +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -39,7 +42,6 @@ import java.security.KeyStore; import java.util.ArrayList; import java.util.Date; import java.util.List; - import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.NameValuePair; @@ -64,10 +66,6 @@ import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; - /** * * @author Luis Linietsky <luis.linietsky@gmail.com> @@ -105,7 +103,7 @@ public class HttpRequester { long timeInit = new Date().getTime(); response = request(httpget); long delay = new Date().getTime() - timeInit; - Log.d("com.app11tt.android.utils.HttpRequest::get(url)", "Url: " + params.getUrl() + " downloaded in " + String.format("%.03f", delay / 1000.0f) + " seconds"); + Log.d("HttpRequest::get(url)", "Url: " + params.getUrl() + " downloaded in " + String.format("%.03f", delay / 1000.0f) + " seconds"); if (response == null || response.length() == 0) { response = ""; } else { @@ -200,7 +198,7 @@ public class HttpRequester { SharedPreferences.Editor editor = sharedPref.edit(); editor.putString("request_" + Crypt.md5(request), response); editor.putLong("request_" + Crypt.md5(request) + "_ttl", new Date().getTime() + getTtl()); - editor.commit(); + editor.apply(); } public String getResponseFromCache(String request) { diff --git a/platform/android/java/src/org/godotengine/godot/utils/RequestParams.java b/platform/android/java/src/org/godotengine/godot/utils/RequestParams.java index a1d5b26b3c..b9fe0dd0c9 100644 --- a/platform/android/java/src/org/godotengine/godot/utils/RequestParams.java +++ b/platform/android/java/src/org/godotengine/godot/utils/RequestParams.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -34,7 +34,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; - import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index 022ccb7d89..2bed1f0892 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +29,7 @@ /*************************************************************************/ #include "java_class_wrapper.h" +#include "string_android.h" #include "thread_jandroid.h" bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error, Variant &ret) { @@ -553,7 +554,7 @@ void JavaClassWrapper::_bind_methods() { bool JavaClassWrapper::_get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig) { jstring name2 = (jstring)env->CallObjectMethod(obj, Class_getName); - String str_type = env->GetStringUTFChars(name2, NULL); + String str_type = jstring_to_string(name2, env); env->DeleteLocalRef(name2); uint32_t t = 0; @@ -697,7 +698,7 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va } break; case ARG_TYPE_STRING: { - var = String::utf8(env->GetStringUTFChars((jstring)obj, NULL)); + var = jstring_to_string((jstring)obj, env); return true; } break; case ARG_TYPE_CLASS: { @@ -1030,7 +1031,7 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va if (!o) ret.push_back(Variant()); else { - String val = String::utf8(env->GetStringUTFChars((jstring)o, NULL)); + String val = jstring_to_string((jstring)o, env); ret.push_back(val); } env->DeleteLocalRef(o); @@ -1075,7 +1076,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { ERR_CONTINUE(!obj); jstring name = (jstring)env->CallObjectMethod(obj, getName); - String str_method = env->GetStringUTFChars(name, NULL); + String str_method = jstring_to_string(name, env); env->DeleteLocalRef(name); Vector<String> params; @@ -1204,7 +1205,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { ERR_CONTINUE(!obj); jstring name = (jstring)env->CallObjectMethod(obj, Field_getName); - String str_field = env->GetStringUTFChars(name, NULL); + String str_field = jstring_to_string(name, env); env->DeleteLocalRef(name); int mods = env->CallIntMethod(obj, Field_getModifiers); if ((mods & 0x8) && (mods & 0x10) && (mods & 0x1)) { //static final public! diff --git a/platform/android/java_class_wrapper.h b/platform/android/java_class_wrapper.h index ea3760452f..e9471a1897 100644 --- a/platform/android/java_class_wrapper.h +++ b/platform/android/java_class_wrapper.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp new file mode 100644 index 0000000000..ade7c03d58 --- /dev/null +++ b/platform/android/java_godot_io_wrapper.cpp @@ -0,0 +1,207 @@ +/*************************************************************************/ +/* java_godot_io_wrapper.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "java_godot_io_wrapper.h" +#include "core/error_list.h" + +// JNIEnv is only valid within the thread it belongs to, in a multi threading environment +// we can't cache it. +// For GodotIO we call all access methods from our thread and we thus get a valid JNIEnv +// from ThreadAndroid. + +GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instance) { + godot_io_instance = p_env->NewGlobalRef(p_godot_io_instance); + if (godot_io_instance) { + cls = p_env->GetObjectClass(godot_io_instance); + if (cls) { + cls = (jclass)p_env->NewGlobalRef(cls); + } else { + // this is a pretty serious fail.. bail... pointers will stay 0 + return; + } + + _open_URI = p_env->GetMethodID(cls, "openURI", "(Ljava/lang/String;)I"); + _get_data_dir = p_env->GetMethodID(cls, "getDataDir", "()Ljava/lang/String;"); + _get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;"); + _get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;"); + _get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I"); + _get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;"); + _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;)V"); + _hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V"); + _set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V"); + _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;"); + _play_video = p_env->GetMethodID(cls, "playVideo", "(Ljava/lang/String;)V"); + _is_video_playing = p_env->GetMethodID(cls, "isVideoPlaying", "()Z"); + _pause_video = p_env->GetMethodID(cls, "pauseVideo", "()V"); + _stop_video = p_env->GetMethodID(cls, "stopVideo", "()V"); + } +} + +GodotIOJavaWrapper::~GodotIOJavaWrapper() { + // nothing to do here for now +} + +jobject GodotIOJavaWrapper::get_instance() { + return godot_io_instance; +} + +Error GodotIOJavaWrapper::open_uri(const String &p_uri) { + if (_open_URI) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring jStr = env->NewStringUTF(p_uri.utf8().get_data()); + return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; + } else { + return ERR_UNAVAILABLE; + } +} + +String GodotIOJavaWrapper::get_user_data_dir() { + if (_get_data_dir) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir); + return jstring_to_string(s, env); + } else { + return String(); + } +} + +String GodotIOJavaWrapper::get_locale() { + if (_get_locale) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_locale); + return jstring_to_string(s, env); + } else { + return String(); + } +} + +String GodotIOJavaWrapper::get_model() { + if (_get_model) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_model); + return jstring_to_string(s, env); + } else { + return String(); + } +} + +int GodotIOJavaWrapper::get_screen_dpi() { + if (_get_screen_DPI) { + JNIEnv *env = ThreadAndroid::get_env(); + return env->CallIntMethod(godot_io_instance, _get_screen_DPI); + } else { + return 160; + } +} + +String GodotIOJavaWrapper::get_unique_id() { + if (_get_unique_id) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_unique_id); + return jstring_to_string(s, env); + } else { + return String(); + } +} + +bool GodotIOJavaWrapper::has_vk() { + return (_show_keyboard != 0) && (_hide_keyboard != 0); +} + +void GodotIOJavaWrapper::show_vk(const String &p_existing) { + if (_show_keyboard) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); + env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr); + } +} + +void GodotIOJavaWrapper::hide_vk() { + if (_hide_keyboard) { + JNIEnv *env = ThreadAndroid::get_env(); + env->CallVoidMethod(godot_io_instance, _hide_keyboard); + } +} + +void GodotIOJavaWrapper::set_screen_orientation(int p_orient) { + if (_set_screen_orientation) { + JNIEnv *env = ThreadAndroid::get_env(); + env->CallVoidMethod(godot_io_instance, _set_screen_orientation, p_orient); + } +} + +String GodotIOJavaWrapper::get_system_dir(int p_dir) { + if (_get_system_dir) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir); + return jstring_to_string(s, env); + } else { + return String("."); + } +} + +void GodotIOJavaWrapper::play_video(const String &p_path) { + // Why is this not here?!?! +} + +bool GodotIOJavaWrapper::is_video_playing() { + if (_is_video_playing) { + JNIEnv *env = ThreadAndroid::get_env(); + return env->CallBooleanMethod(godot_io_instance, _is_video_playing); + } else { + return false; + } +} + +void GodotIOJavaWrapper::pause_video() { + if (_pause_video) { + JNIEnv *env = ThreadAndroid::get_env(); + env->CallVoidMethod(godot_io_instance, _pause_video); + } +} + +void GodotIOJavaWrapper::stop_video() { + if (_stop_video) { + JNIEnv *env = ThreadAndroid::get_env(); + env->CallVoidMethod(godot_io_instance, _stop_video); + } +} + +// volatile because it can be changed from non-main thread and we need to +// ensure the change is immediately visible to other threads. +static volatile int virtual_keyboard_height; + +int GodotIOJavaWrapper::get_vk_height() { + return virtual_keyboard_height; +} + +void GodotIOJavaWrapper::set_vk_height(int p_height) { + virtual_keyboard_height = p_height; +} diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h new file mode 100644 index 0000000000..100e50fd66 --- /dev/null +++ b/platform/android/java_godot_io_wrapper.h @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* java_godot_io_wrapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +// note, swapped java and godot around in the file name so all the java +// wrappers are together + +#ifndef JAVA_GODOT_IO_WRAPPER_H +#define JAVA_GODOT_IO_WRAPPER_H + +#include <android/log.h> +#include <jni.h> + +#include "string_android.h" + +// Class that makes functions in java/src/org/godotengine/godot/GodotIO.java callable from C++ +class GodotIOJavaWrapper { +private: + jobject godot_io_instance; + jclass cls; + + jmethodID _open_URI = 0; + jmethodID _get_data_dir = 0; + jmethodID _get_locale = 0; + jmethodID _get_model = 0; + jmethodID _get_screen_DPI = 0; + jmethodID _get_unique_id = 0; + jmethodID _show_keyboard = 0; + jmethodID _hide_keyboard = 0; + jmethodID _set_screen_orientation = 0; + jmethodID _get_system_dir = 0; + jmethodID _play_video = 0; + jmethodID _is_video_playing = 0; + jmethodID _pause_video = 0; + jmethodID _stop_video = 0; + +public: + GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instance); + ~GodotIOJavaWrapper(); + + jobject get_instance(); + + Error open_uri(const String &p_uri); + String get_user_data_dir(); + String get_locale(); + String get_model(); + int get_screen_dpi(); + String get_unique_id(); + bool has_vk(); + void show_vk(const String &p_existing); + void hide_vk(); + int get_vk_height(); + void set_vk_height(int p_height); + void set_screen_orientation(int p_orient); + String get_system_dir(int p_dir); + void play_video(const String &p_path); + bool is_video_playing(); + void pause_video(); + void stop_video(); +}; + +#endif /* !JAVA_GODOT_IO_WRAPPER_H */ diff --git a/platform/android/java_glue.cpp b/platform/android/java_godot_lib_jni.cpp index fb9c0f08ad..466f79c215 100644 --- a/platform/android/java_glue.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* java_glue.cpp */ +/* java_godot_lib_jni.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,7 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "java_glue.h" +#include "java_godot_lib_jni.h" +#include "java_godot_io_wrapper.h" +#include "java_godot_wrapper.h" + #include "android/asset_manager_jni.h" #include "audio_driver_jandroid.h" #include "core/engine.h" @@ -41,11 +44,14 @@ #include "main/input_default.h" #include "main/main.h" #include "os_android.h" +#include "string_android.h" #include "thread_jandroid.h" #include <unistd.h> static JavaClassWrapper *java_class_wrapper = NULL; static OS_Android *os_android = NULL; +static GodotJavaWrapper *godot_java = NULL; +static GodotIOJavaWrapper *godot_io_java = NULL; struct jvalret { @@ -223,7 +229,7 @@ String _get_class_name(JNIEnv *env, jclass cls, bool *array) { jboolean isarr = env->CallBooleanMethod(cls, isArray); (*array) = isarr ? true : false; } - String name = env->GetStringUTFChars(clsName, NULL); + String name = jstring_to_string(clsName, env); env->DeleteLocalRef(clsName); return name; @@ -241,7 +247,7 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { if (name == "java.lang.String") { - return String::utf8(env->GetStringUTFChars((jstring)obj, NULL)); + return jstring_to_string((jstring)obj, env); }; if (name == "[Ljava.lang.String;") { @@ -252,7 +258,7 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { for (int i = 0; i < stringCount; i++) { jstring string = (jstring)env->GetObjectArrayElement(arr, i); - sarr.push_back(String::utf8(env->GetStringUTFChars(string, NULL))); + sarr.push_back(jstring_to_string(string, env)); env->DeleteLocalRef(string); } @@ -487,7 +493,7 @@ public: case Variant::STRING: { jobject o = env->CallObjectMethodA(instance, E->get().method, v); - ret = String::utf8(env->GetStringUTFChars((jstring)o, NULL)); + ret = jstring_to_string((jstring)o, env); env->DeleteLocalRef(o); } break; case Variant::POOL_STRING_ARRAY: { @@ -587,251 +593,73 @@ TST tst; static bool initialized = false; static int step = 0; + static Size2 new_size; static Vector3 accelerometer; static Vector3 gravity; static Vector3 magnetometer; static Vector3 gyroscope; static HashMap<String, JNISingleton *> jni_singletons; -static jobject godot_io; - -typedef void (*GFXInitFunc)(void *ud, bool gl2); - -static jmethodID _on_video_init = 0; -static jobject _godot_instance; - -static jmethodID _openURI = 0; -static jmethodID _getDataDir = 0; -static jmethodID _getLocale = 0; -static jmethodID _getClipboard = 0; -static jmethodID _setClipboard = 0; -static jmethodID _getModel = 0; -static jmethodID _getScreenDPI = 0; -static jmethodID _showKeyboard = 0; -static jmethodID _hideKeyboard = 0; -static jmethodID _setScreenOrientation = 0; -static jmethodID _getUniqueID = 0; -static jmethodID _getSystemDir = 0; -static jmethodID _getGLESVersionCode = 0; -static jmethodID _playVideo = 0; -static jmethodID _isVideoPlaying = 0; -static jmethodID _pauseVideo = 0; -static jmethodID _stopVideo = 0; -static jmethodID _setKeepScreenOn = 0; -static jmethodID _alertDialog = 0; - -static void _gfx_init_func(void *ud, bool gl2) { -} - -static int _open_uri(const String &p_uri) { - - JNIEnv *env = ThreadAndroid::get_env(); - jstring jStr = env->NewStringUTF(p_uri.utf8().get_data()); - return env->CallIntMethod(godot_io, _openURI, jStr); -} - -static String _get_user_data_dir() { - - JNIEnv *env = ThreadAndroid::get_env(); - jstring s = (jstring)env->CallObjectMethod(godot_io, _getDataDir); - return String(env->GetStringUTFChars(s, NULL)); -} - -static String _get_locale() { - - JNIEnv *env = ThreadAndroid::get_env(); - jstring s = (jstring)env->CallObjectMethod(godot_io, _getLocale); - return String(env->GetStringUTFChars(s, NULL)); -} - -static String _get_clipboard() { - JNIEnv *env = ThreadAndroid::get_env(); - jstring s = (jstring)env->CallObjectMethod(_godot_instance, _getClipboard); - return String(env->GetStringUTFChars(s, NULL)); -} - -static void _set_clipboard(const String &p_text) { - - JNIEnv *env = ThreadAndroid::get_env(); - jstring jStr = env->NewStringUTF(p_text.utf8().get_data()); - env->CallVoidMethod(_godot_instance, _setClipboard, jStr); -} - -static String _get_model() { - - JNIEnv *env = ThreadAndroid::get_env(); - jstring s = (jstring)env->CallObjectMethod(godot_io, _getModel); - return String(env->GetStringUTFChars(s, NULL)); -} - -static int _get_screen_dpi() { - - JNIEnv *env = ThreadAndroid::get_env(); - return env->CallIntMethod(godot_io, _getScreenDPI); -} - -static String _get_unique_id() { - - JNIEnv *env = ThreadAndroid::get_env(); - jstring s = (jstring)env->CallObjectMethod(godot_io, _getUniqueID); - return String(env->GetStringUTFChars(s, NULL)); -} - -static void _show_vk(const String &p_existing) { - - JNIEnv *env = ThreadAndroid::get_env(); - jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); - env->CallVoidMethod(godot_io, _showKeyboard, jStr); -} - -static void _set_screen_orient(int p_orient) { - - JNIEnv *env = ThreadAndroid::get_env(); - env->CallVoidMethod(godot_io, _setScreenOrientation, p_orient); -} - -static String _get_system_dir(int p_dir) { - - JNIEnv *env = ThreadAndroid::get_env(); - jstring s = (jstring)env->CallObjectMethod(godot_io, _getSystemDir, p_dir); - return String(env->GetStringUTFChars(s, NULL)); -} - -static int _get_gles_version_code() { - JNIEnv *env = ThreadAndroid::get_env(); - return env->CallIntMethod(_godot_instance, _getGLESVersionCode); -} - -static void _hide_vk() { - - JNIEnv *env = ThreadAndroid::get_env(); - 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); -} - -static void _set_keep_screen_on(bool p_enabled) { - JNIEnv *env = ThreadAndroid::get_env(); - env->CallVoidMethod(_godot_instance, _setKeepScreenOn, p_enabled); -} - -static void _alert(const String &p_message, const String &p_title) { - JNIEnv *env = ThreadAndroid::get_env(); - jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data()); - jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data()); - env->CallVoidMethod(_godot_instance, _alertDialog, jStrMessage, jStrTitle); -} - -// volatile because it can be changed from non-main thread and we need to -// ensure the change is immediately visible to other threads. -static volatile int virtual_keyboard_height; - -static int _get_vk_height() { - return virtual_keyboard_height; -} - JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jobject obj, jint p_height) { - virtual_keyboard_height = p_height; + if (godot_io_java) { + godot_io_java->set_vk_height(p_height); + } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jboolean p_need_reload_hook, jobject p_asset_manager, jboolean p_use_apk_expansion) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion) { initialized = true; JavaVM *jvm; env->GetJavaVM(&jvm); - _godot_instance = env->NewGlobalRef(activity); - //_godot_instance=activity; - - { - //setup IO Object + // create our wrapper classes + godot_java = new GodotJavaWrapper(env, activity); // our activity is our godot instance is our activity.. + godot_io_java = new GodotIOJavaWrapper(env, godot_java->get_member_object("io", "Lorg/godotengine/godot/GodotIO;", env)); - jclass cls = env->FindClass("org/godotengine/godot/Godot"); - if (cls) { - - cls = (jclass)env->NewGlobalRef(cls); - } - - jfieldID fid = env->GetStaticFieldID(cls, "io", "Lorg/godotengine/godot/GodotIO;"); - jobject ob = env->GetStaticObjectField(cls, fid); - jobject gob = env->NewGlobalRef(ob); - - godot_io = gob; - - _on_video_init = env->GetMethodID(cls, "onVideoInit", "()V"); - _setKeepScreenOn = env->GetMethodID(cls, "setKeepScreenOn", "(Z)V"); - _alertDialog = env->GetMethodID(cls, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); - _getGLESVersionCode = env->GetMethodID(cls, "getGLESVersionCode", "()I"); - _getClipboard = env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;"); - _setClipboard = env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V"); - - if (cls) { - jclass c = env->GetObjectClass(gob); - _openURI = env->GetMethodID(c, "openURI", "(Ljava/lang/String;)I"); - _getDataDir = env->GetMethodID(c, "getDataDir", "()Ljava/lang/String;"); - _getLocale = env->GetMethodID(c, "getLocale", "()Ljava/lang/String;"); - _getModel = env->GetMethodID(c, "getModel", "()Ljava/lang/String;"); - _getScreenDPI = env->GetMethodID(c, "getScreenDPI", "()I"); - _getUniqueID = env->GetMethodID(c, "getUniqueID", "()Ljava/lang/String;"); - _showKeyboard = env->GetMethodID(c, "showKeyboard", "(Ljava/lang/String;)V"); - _hideKeyboard = env->GetMethodID(c, "hideKeyboard", "()V"); - _setScreenOrientation = env->GetMethodID(c, "setScreenOrientation", "(I)V"); - _getSystemDir = env->GetMethodID(c, "getSystemDir", "(I)Ljava/lang/String;"); - _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); + ThreadAndroid::make_default(jvm); #ifdef USE_JAVA_FILE_ACCESS - FileAccessJAndroid::setup(gob); + FileAccessJAndroid::setup(godot_io_java->get_instance()); #else - jobject amgr = env->NewGlobalRef(p_asset_manager); + jobject amgr = env->NewGlobalRef(p_asset_manager); - FileAccessAndroid::asset_manager = AAssetManager_fromJava(env, amgr); + FileAccessAndroid::asset_manager = AAssetManager_fromJava(env, amgr); #endif - DirAccessJAndroid::setup(gob); - AudioDriverAndroid::setup(gob); - } - os_android = new OS_Android(_gfx_init_func, env, _open_uri, _get_user_data_dir, _get_locale, _get_model, _get_screen_dpi, _show_vk, _hide_vk, _get_vk_height, _set_screen_orient, _get_unique_id, _get_system_dir, _get_gles_version_code, _play_video, _is_video_playing, _pause_video, _stop_video, _set_keep_screen_on, _alert, _set_clipboard, _get_clipboard, p_use_apk_expansion); - os_android->set_need_reload_hooks(p_need_reload_hook); + DirAccessJAndroid::setup(godot_io_java->get_instance()); + AudioDriverAndroid::setup(godot_io_java->get_instance()); + + os_android = new OS_Android(godot_java, godot_io_java, p_use_apk_expansion); char wd[500]; getcwd(wd, 500); - env->CallVoidMethod(_godot_instance, _on_video_init); + godot_java->on_video_init(env); +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env) { + // lets cleanup + if (godot_io_java) { + delete godot_io_java; + } + if (godot_java) { + delete godot_java; + } + if (os_android) { + delete os_android; + } } static void _initialize_java_modules() { if (!ProjectSettings::get_singleton()->has_setting("android/modules")) { - print_line("Android modules: Nothing to load, aborting"); return; } @@ -843,27 +671,19 @@ static void _initialize_java_modules() { Vector<String> mods = modules.split(",", false); if (mods.size()) { + jobject cls = godot_java->get_class_loader(); - JNIEnv *env = ThreadAndroid::get_env(); - - jclass activityClass = env->FindClass("org/godotengine/godot/Godot"); - - jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); - - jobject cls = env->CallObjectMethod(_godot_instance, getClassLoader); - //cls=env->NewGlobalRef(cls); + // TODO create wrapper for class loader + JNIEnv *env = ThreadAndroid::get_env(); jclass classLoader = env->FindClass("java/lang/ClassLoader"); - //classLoader=(jclass)env->NewGlobalRef(classLoader); - jmethodID findClass = env->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); for (int i = 0; i < mods.size(); i++) { String m = mods[i]; - //jclass singletonClass = env->FindClass(m.utf8().get_data()); - print_line("Loading module: " + m); + print_line("Loading Android module: " + m); jstring strClassName = env->NewStringUTF(m.utf8().get_data()); jclass singletonClass = (jclass)env->CallObjectMethod(cls, findClass, strClassName); @@ -872,7 +692,6 @@ static void _initialize_java_modules() { ERR_EXPLAIN("Couldn't find singleton for class: " + m); ERR_CONTINUE(!singletonClass); } - //singletonClass=(jclass)env->NewGlobalRef(singletonClass); jmethodID initialize = env->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lorg/godotengine/godot/Godot$SingletonBase;"); @@ -881,7 +700,7 @@ static void _initialize_java_modules() { 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); + jobject obj = env->CallStaticObjectMethod(singletonClass, initialize, godot_java->get_activity()); env->NewGlobalRef(obj); } } @@ -891,12 +710,14 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jo ThreadAndroid::setup_thread(); const char **cmdline = NULL; + jstring *j_cmdline = NULL; int cmdlen = 0; if (p_cmdline) { cmdlen = env->GetArrayLength(p_cmdline); if (cmdlen) { - cmdline = (const char **)malloc((env->GetArrayLength(p_cmdline) + 1) * sizeof(const char *)); + cmdline = (const char **)malloc((cmdlen + 1) * sizeof(const char *)); cmdline[cmdlen] = NULL; + j_cmdline = (jstring *)malloc(cmdlen * sizeof(jstring)); for (int i = 0; i < cmdlen; i++) { @@ -904,12 +725,19 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jo const char *rawString = env->GetStringUTFChars(string, 0); cmdline[i] = rawString; + j_cmdline[i] = string; } } } Error err = Main::setup("apk", cmdlen, (char **)cmdline, false); if (cmdline) { + if (j_cmdline) { + for (int i = 0; i < cmdlen; ++i) { + env->ReleaseStringUTFChars(j_cmdline[i], cmdline[i]); + } + free(j_cmdline); + } free(cmdline); } @@ -917,12 +745,12 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jo return; //should exit instead and print the error } - java_class_wrapper = memnew(JavaClassWrapper(_godot_instance)); + java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity())); Engine::get_singleton()->add_singleton(Engine::Singleton("JavaClassWrapper", java_class_wrapper)); _initialize_java_modules(); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height, jboolean reload) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height) { if (os_android) os_android->set_display_size(Size2(width, height)); @@ -931,12 +759,15 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits) { if (os_android) { - os_android->set_context_is_16_bits(!p_32_bits); - } - - if (os_android && step > 0) { - - os_android->reload_gfx(); + if (step == 0) { + // During startup + os_android->set_context_is_16_bits(!p_32_bits); + } else { + // GL context recreated because it was lost; restart app to let it reload everything + os_android->main_loop_end(); + godot_java->restart(env); + step = -1; // Ensure no further steps are attempted + } } } @@ -948,6 +779,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, job } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj) { + if (step == -1) + return; + if (step == 0) { // Since Godot is initialized on the UI thread, _main_thread_id was set to that thread's id, @@ -967,18 +801,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, job } os_android->process_accelerometer(accelerometer); - os_android->process_gravity(gravity); - os_android->process_magnetometer(magnetometer); - os_android->process_gyroscope(gyroscope); if (os_android->main_loop_iterate()) { - jclass cls = env->FindClass("org/godotengine/godot/Godot"); - jmethodID _finish = env->GetMethodID(cls, "forceQuit", "()V"); - env->CallVoidMethod(_godot_instance, _finish); + godot_java->force_quit(env); } } @@ -1313,7 +1142,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, j JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jobject obj, jint p_device, jboolean p_connected, jstring p_name) { if (os_android) { - String name = env->GetStringUTFChars(p_name, NULL); + String name = jstring_to_string(p_name, env); os_android->joy_connection_changed(p_device, p_connected, name); } } @@ -1386,7 +1215,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jo JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_singleton(JNIEnv *env, jobject obj, jstring name, jobject p_object) { - String singname = env->GetStringUTFChars(name, NULL); + String singname = jstring_to_string(name, env); JNISingleton *s = memnew(JNISingleton); s->set_instance(env->NewGlobalRef(p_object)); jni_singletons[singname] = s; @@ -1463,21 +1292,21 @@ static const char *get_jni_sig(const String &p_type) { JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jobject obj, jstring path) { - String js = env->GetStringUTFChars(path, NULL); + String js = jstring_to_string(path, env); return env->NewStringUTF(ProjectSettings::get_singleton()->get(js).operator String().utf8().get_data()); } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_method(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) { - String singname = env->GetStringUTFChars(sname, NULL); + String singname = jstring_to_string(sname, env); ERR_FAIL_COND(!jni_singletons.has(singname)); JNISingleton *s = jni_singletons.get(singname); - String mname = env->GetStringUTFChars(name, NULL); - String retval = env->GetStringUTFChars(ret, NULL); + String mname = jstring_to_string(name, env); + String retval = jstring_to_string(ret, env); Vector<Variant::Type> types; String cs = "("; @@ -1486,9 +1315,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_method(JNIEnv *env, j for (int i = 0; i < stringCount; i++) { jstring string = (jstring)env->GetObjectArrayElement(args, i); - const char *rawString = env->GetStringUTFChars(string, 0); - types.push_back(get_jni_type(String(rawString))); - cs += get_jni_sig(String(rawString)); + const String rawString = jstring_to_string(string, env); + types.push_back(get_jni_type(rawString)); + cs += get_jni_sig(rawString); } cs += ")"; @@ -1511,7 +1340,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en int res = env->PushLocalFrame(16); ERR_FAIL_COND(res != 0); - String str_method = env->GetStringUTFChars(method, NULL); + String str_method = jstring_to_string(method, env); int count = env->GetArrayLength(params); Variant *vlist = (Variant *)alloca(sizeof(Variant) * count); @@ -1543,7 +1372,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv * int res = env->PushLocalFrame(16); ERR_FAIL_COND(res != 0); - String str_method = env->GetStringUTFChars(method, NULL); + String str_method = jstring_to_string(method, env); int count = env->GetArrayLength(params); Variant args[VARIANT_ARG_MAX]; @@ -1561,6 +1390,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv * env->PopLocalFrame(NULL); } -//Main::cleanup(); - -//return os.get_exit_code(); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jobject p_obj, jstring p_permission, jboolean p_result) { + String permission = jstring_to_string(p_permission, env); + if (permission == "android.permission.RECORD_AUDIO" && p_result) { + AudioDriver::get_singleton()->capture_start(); + } +} diff --git a/platform/android/java_glue.h b/platform/android/java_godot_lib_jni.h index dc5b9cca49..f99935bf7c 100644 --- a/platform/android/java_glue.h +++ b/platform/android/java_godot_lib_jni.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* java_glue.h */ +/* java_godot_lib_jni.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,16 +28,19 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef JAVA_GLUE_H -#define JAVA_GLUE_H +#ifndef JAVA_GODOT_LIB_JNI_H +#define JAVA_GODOT_LIB_JNI_H #include <android/log.h> #include <jni.h> +// These functions can be called from within JAVA and are the means by which our JAVA implementation calls back into our C++ code. +// See java/src/org/godotengine/godot/GodotLib.java for the JAVA side of this (yes that's why we have the long names) extern "C" { -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jboolean p_need_reload_hook, jobject p_asset_manager, jboolean p_use_apk_expansion); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jobject obj, jobjectArray p_cmdline); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height, jboolean reload); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jobject obj); @@ -60,6 +63,7 @@ JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv * JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jobject obj, jint p_height); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jobject p_obj, jstring p_permission, jboolean p_result); } -#endif // JAVA_GLUE_H +#endif /* !JAVA_GODOT_LIB_JNI_H */ diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp new file mode 100644 index 0000000000..e92d4437b1 --- /dev/null +++ b/platform/android/java_godot_wrapper.cpp @@ -0,0 +1,193 @@ +/*************************************************************************/ +/* java_godot_wrapper.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "java_godot_wrapper.h" + +// JNIEnv is only valid within the thread it belongs to, in a multi threading environment +// we can't cache it. +// For Godot we call most access methods from our thread and we thus get a valid JNIEnv +// from ThreadAndroid. For one or two we expect to pass the environment + +// TODO we could probably create a base class for this... + +GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { + godot_instance = p_env->NewGlobalRef(p_godot_instance); + + // get info about our Godot class so we can get pointers and stuff... + cls = p_env->FindClass("org/godotengine/godot/Godot"); + if (cls) { + cls = (jclass)p_env->NewGlobalRef(cls); + } else { + // this is a pretty serious fail.. bail... pointers will stay 0 + return; + } + + // get some method pointers... + _on_video_init = p_env->GetMethodID(cls, "onVideoInit", "()V"); + _restart = p_env->GetMethodID(cls, "restart", "()V"); + _finish = p_env->GetMethodID(cls, "forceQuit", "()V"); + _set_keep_screen_on = p_env->GetMethodID(cls, "setKeepScreenOn", "(Z)V"); + _alert = p_env->GetMethodID(cls, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); + _get_GLES_version_code = p_env->GetMethodID(cls, "getGLESVersionCode", "()I"); + _get_clipboard = p_env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;"); + _set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V"); + _request_permission = p_env->GetMethodID(cls, "requestPermission", "(Ljava/lang/String;)Z"); + _init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V"); +} + +GodotJavaWrapper::~GodotJavaWrapper() { + // nothing to do here for now +} + +jobject GodotJavaWrapper::get_activity() { + // our godot instance is our activity + return godot_instance; +} + +jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env) { + if (cls) { + if (p_env == NULL) + p_env = ThreadAndroid::get_env(); + + jfieldID fid = p_env->GetStaticFieldID(cls, p_name, p_class); + return p_env->GetStaticObjectField(cls, fid); + } else { + return NULL; + } +} + +jobject GodotJavaWrapper::get_class_loader() { + if (cls) { + JNIEnv *env = ThreadAndroid::get_env(); + jmethodID getClassLoader = env->GetMethodID(cls, "getClassLoader", "()Ljava/lang/ClassLoader;"); + return env->CallObjectMethod(godot_instance, getClassLoader); + } else { + return NULL; + } +} + +void GodotJavaWrapper::gfx_init(bool gl2) { + // beats me what this once did, there was no code, + // but we're getting false if our GLES3 driver is initialised + // and true for our GLES2 driver + // Maybe we're supposed to communicate this back or store it? +} + +void GodotJavaWrapper::on_video_init(JNIEnv *p_env) { + if (_on_video_init) + if (p_env == NULL) + p_env = ThreadAndroid::get_env(); + + p_env->CallVoidMethod(godot_instance, _on_video_init); +} + +void GodotJavaWrapper::restart(JNIEnv *p_env) { + if (_restart) + if (p_env == NULL) + p_env = ThreadAndroid::get_env(); + + p_env->CallVoidMethod(godot_instance, _restart); +} + +void GodotJavaWrapper::force_quit(JNIEnv *p_env) { + if (_finish) + if (p_env == NULL) + p_env = ThreadAndroid::get_env(); + + p_env->CallVoidMethod(godot_instance, _finish); +} + +void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) { + if (_set_keep_screen_on) { + JNIEnv *env = ThreadAndroid::get_env(); + env->CallVoidMethod(godot_instance, _set_keep_screen_on, p_enabled); + } +} + +void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { + if (_alert) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data()); + jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data()); + env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle); + } +} + +int GodotJavaWrapper::get_gles_version_code() { + JNIEnv *env = ThreadAndroid::get_env(); + if (_get_GLES_version_code) { + return env->CallIntMethod(godot_instance, _get_GLES_version_code); + } + + return 0; +} + +bool GodotJavaWrapper::has_get_clipboard() { + return _get_clipboard != 0; +} + +String GodotJavaWrapper::get_clipboard() { + if (_get_clipboard) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_clipboard); + return jstring_to_string(s, env); + } else { + return String(); + } +} + +bool GodotJavaWrapper::has_set_clipboard() { + return _set_clipboard != 0; +} + +void GodotJavaWrapper::set_clipboard(const String &p_text) { + if (_set_clipboard) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring jStr = env->NewStringUTF(p_text.utf8().get_data()); + env->CallVoidMethod(godot_instance, _set_clipboard, jStr); + } +} + +bool GodotJavaWrapper::request_permission(const String &p_name) { + if (_request_permission) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring jStrName = env->NewStringUTF(p_name.utf8().get_data()); + return env->CallBooleanMethod(godot_instance, _request_permission, jStrName); + } else { + return false; + } +} + +void GodotJavaWrapper::init_input_devices() { + if (_init_input_devices) { + JNIEnv *env = ThreadAndroid::get_env(); + env->CallVoidMethod(godot_instance, _init_input_devices); + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h new file mode 100644 index 0000000000..be4f109d8c --- /dev/null +++ b/platform/android/java_godot_wrapper.h @@ -0,0 +1,83 @@ +/*************************************************************************/ +/* java_godot_wrapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +// note, swapped java and godot around in the file name so all the java +// wrappers are together + +#ifndef JAVA_GODOT_WRAPPER_H +#define JAVA_GODOT_WRAPPER_H + +#include <android/log.h> +#include <jni.h> + +#include "string_android.h" + +// Class that makes functions in java/src/org/godotengine/godot/Godot.java callable from C++ +class GodotJavaWrapper { +private: + jobject godot_instance; + jclass cls; + + jmethodID _on_video_init = 0; + jmethodID _restart = 0; + jmethodID _finish = 0; + jmethodID _set_keep_screen_on = 0; + jmethodID _alert = 0; + jmethodID _get_GLES_version_code = 0; + jmethodID _get_clipboard = 0; + jmethodID _set_clipboard = 0; + jmethodID _request_permission = 0; + jmethodID _init_input_devices = 0; + +public: + GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance); + ~GodotJavaWrapper(); + + jobject get_activity(); + jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = NULL); + + jobject get_class_loader(); + + void gfx_init(bool gl2); + void on_video_init(JNIEnv *p_env = NULL); + void restart(JNIEnv *p_env = NULL); + void force_quit(JNIEnv *p_env = NULL); + void set_keep_screen_on(bool p_enabled); + void alert(const String &p_message, const String &p_title); + int get_gles_version_code(); + bool has_get_clipboard(); + String get_clipboard(); + bool has_set_clipboard(); + void set_clipboard(const String &p_text); + bool request_permission(const String &p_name); + void init_input_devices(); +}; + +#endif /* !JAVA_GODOT_WRAPPER_H */ diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index afdd108987..f8076dfd2d 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -46,6 +46,9 @@ #include <dlfcn.h> +#include "java_godot_io_wrapper.h" +#include "java_godot_wrapper.h" + class AndroidLogger : public Logger { public: virtual void logv(const char *p_format, va_list p_list, bool p_err) { @@ -118,20 +121,19 @@ int OS_Android::get_current_video_driver() const { Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - bool use_gl3 = get_gl_version_code_func() >= 0x00030000; + bool use_gl3 = godot_java->get_gles_version_code() >= 0x00030000; use_gl3 = use_gl3 && (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3"); bool gl_initialization_error = false; while (true) { if (use_gl3) { if (RasterizerGLES3::is_viable() == OK) { - if (gfx_init_func) - gfx_init_func(gfx_init_ud, false); + godot_java->gfx_init(false); RasterizerGLES3::register_config(); RasterizerGLES3::make_current(); break; } else { - if (GLOBAL_GET("rendering/quality/driver/driver_fallback") == "Best") { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) { p_video_driver = VIDEO_DRIVER_GLES2; use_gl3 = false; continue; @@ -142,8 +144,7 @@ Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int } } else { if (RasterizerGLES2::is_viable() == OK) { - if (gfx_init_func) - gfx_init_func(gfx_init_ud, true); + godot_java->gfx_init(true); RasterizerGLES2::register_config(); RasterizerGLES2::make_current(); break; @@ -175,7 +176,7 @@ Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int input = memnew(InputDefault); input->set_fallback_mapping("Default Android Gamepad"); - //power_manager = memnew(power_android); + //power_manager = memnew(PowerAndroid); return OK; } @@ -195,11 +196,23 @@ void OS_Android::finalize() { memdelete(input); } +GodotJavaWrapper *OS_Android::get_godot_java() { + return godot_java; +} + +GodotIOJavaWrapper *OS_Android::get_godot_io_java() { + return godot_io_java; +} + void OS_Android::alert(const String &p_alert, const String &p_title) { //print("ALERT: %s\n", p_alert.utf8().get_data()); - if (alert_func) - alert_func(p_alert, p_title); + godot_java->alert(p_alert, p_title); +} + +bool OS_Android::request_permission(const String &p_name) { + + return godot_java->request_permission(p_name); } Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { @@ -238,6 +251,10 @@ int OS_Android::get_mouse_button_state() const { } void OS_Android::set_window_title(const String &p_title) { + //This queries/updates the currently connected devices/joypads + //Set_window_title is called when initializing the main loop (main.cpp) + //therefore this place is found to be suitable (I found no better). + godot_java->init_input_devices(); } void OS_Android::set_video_mode(const VideoMode &p_video_mode, int p_screen) { @@ -256,9 +273,7 @@ void OS_Android::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) void OS_Android::set_keep_screen_on(bool p_enabled) { OS::set_keep_screen_on(p_enabled); - if (set_keep_screen_on_func) { - set_keep_screen_on_func(p_enabled); - } + godot_java->set_keep_screen_on(p_enabled); } Size2 OS_Android::get_window_size() const { @@ -266,7 +281,7 @@ Size2 OS_Android::get_window_size() const { return Vector2(default_videomode.width, default_videomode.height); } -String OS_Android::get_name() { +String OS_Android::get_name() const { return "Android"; } @@ -281,14 +296,6 @@ bool OS_Android::can_draw() const { return true; //always? } -void OS_Android::set_cursor_shape(CursorShape p_shape) { - - //android really really really has no mouse.. how amazing.. -} - -void OS_Android::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { -} - void OS_Android::main_loop_begin() { if (main_loop) @@ -499,18 +506,16 @@ bool OS_Android::has_virtual_keyboard() const { } int OS_Android::get_virtual_keyboard_height() const { - if (get_virtual_keyboard_height_func) { - return get_virtual_keyboard_height_func(); - } + return godot_io_java->get_vk_height(); - ERR_PRINT("Cannot obtain virtual keyboard height."); - return 0; + // ERR_PRINT("Cannot obtain virtual keyboard height."); + // return 0; } void OS_Android::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect) { - if (show_virtual_keyboard_func) { - show_virtual_keyboard_func(p_existing_text); + if (godot_io_java->has_vk()) { + godot_io_java->show_vk(p_existing_text); } else { ERR_PRINT("Virtual keyboard not available"); @@ -519,9 +524,9 @@ void OS_Android::show_virtual_keyboard(const String &p_existing_text, const Rect void OS_Android::hide_virtual_keyboard() { - if (hide_virtual_keyboard_func) { + if (godot_io_java->has_vk()) { - hide_virtual_keyboard_func(); + godot_io_java->hide_vk(); } else { ERR_PRINT("Virtual keyboard not available"); @@ -548,19 +553,9 @@ void OS_Android::set_display_size(Size2 p_size) { default_videomode.height = p_size.y; } -void OS_Android::reload_gfx() { - - if (gfx_init_func) - gfx_init_func(gfx_init_ud, use_gl2); - //if (rasterizer) - // rasterizer->reload_vram(); -} - Error OS_Android::shell_open(String p_uri) { - if (open_uri_func) - return open_uri_func(p_uri) ? ERR_CANT_OPEN : OK; - return ERR_UNAVAILABLE; + return godot_io_java->open_uri(p_uri); } String OS_Android::get_resource_dir() const { @@ -570,23 +565,29 @@ String OS_Android::get_resource_dir() const { String OS_Android::get_locale() const { - if (get_locale_func) - return get_locale_func(); + String locale = godot_io_java->get_locale(); + if (locale != "") { + return locale; + } + return OS_Unix::get_locale(); } void OS_Android::set_clipboard(const String &p_text) { - if (set_clipboard_func) { - set_clipboard_func(p_text); + // DO we really need the fallback to OS_Unix here?! + if (godot_java->has_set_clipboard()) { + godot_java->set_clipboard(p_text); } else { OS_Unix::set_clipboard(p_text); } } String OS_Android::get_clipboard() const { - if (get_clipboard_func) { - return get_clipboard_func(); + + // DO we really need the fallback to OS_Unix here?! + if (godot_java->has_get_clipboard()) { + return godot_java->get_clipboard(); } return OS_Unix::get_clipboard(); @@ -594,22 +595,16 @@ String OS_Android::get_clipboard() const { String OS_Android::get_model_name() const { - if (get_model_func) - return get_model_func(); + String model = godot_io_java->get_model(); + if (model != "") + return model; + return OS_Unix::get_model_name(); } int OS_Android::get_screen_dpi(int p_screen) const { - if (get_screen_dpi_func) { - return get_screen_dpi_func(); - } - return 160; -} - -void OS_Android::set_need_reload_hooks(bool p_needs_them) { - - use_reload_hooks = p_needs_them; + return godot_io_java->get_screen_dpi(); } String OS_Android::get_user_data_dir() const { @@ -617,8 +612,8 @@ String OS_Android::get_user_data_dir() const { if (data_dir_cache != String()) return data_dir_cache; - if (get_user_data_dir_func) { - String data_dir = get_user_data_dir_func(); + String data_dir = godot_io_java->get_user_data_dir(); + if (data_dir != "") { //store current dir char real_current_dir_name[2048]; @@ -645,45 +640,43 @@ String OS_Android::get_user_data_dir() const { void OS_Android::set_screen_orientation(ScreenOrientation p_orientation) { - if (set_screen_orientation_func) - set_screen_orientation_func(p_orientation); + godot_io_java->set_screen_orientation(p_orientation); } String OS_Android::get_unique_id() const { - if (get_unique_id_func) - return get_unique_id_func(); + String unique_id = godot_io_java->get_unique_id(); + if (unique_id != "") + return unique_id; + return OS::get_unique_id(); } Error OS_Android::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) { // FIXME: Add support for volume, audio and subtitle tracks - if (video_play_func) - video_play_func(p_path); + + godot_io_java->play_video(p_path); return OK; } bool OS_Android::native_video_is_playing() const { - if (video_is_playing_func) - return video_is_playing_func(); - return false; + + return godot_io_java->is_video_playing(); } void OS_Android::native_video_pause() { - if (video_pause_func) - video_pause_func(); + + godot_io_java->pause_video(); } String OS_Android::get_system_dir(SystemDir p_dir) const { - if (get_system_dir_func) - return get_system_dir_func(p_dir); - return String("."); + return godot_io_java->get_system_dir(p_dir); } void OS_Android::native_video_stop() { - if (video_stop_func) - video_stop_func(); + + godot_io_java->stop_video(); } void OS_Android::set_context_is_16_bits(bool p_is_16) { @@ -706,7 +699,7 @@ String OS_Android::get_joy_guid(int p_device) const { } bool OS_Android::_check_internal_feature_support(const String &p_feature) { - if (p_feature == "mobile" || p_feature == "etc" || p_feature == "etc2") { + if (p_feature == "mobile") { //TODO support etc2 only if GLES3 driver is selected return true; } @@ -726,7 +719,7 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) { return false; } -OS_Android::OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, GetGLVersionCodeFunc p_get_gl_version_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, SetClipboardFunc p_set_clipboard_func, GetClipboardFunc p_get_clipboard_func, bool p_use_apk_expansion) { +OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion) { use_apk_expansion = p_use_apk_expansion; default_videomode.width = 800; @@ -734,38 +727,13 @@ OS_Android::OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURI default_videomode.fullscreen = true; default_videomode.resizable = false; - gfx_init_func = p_gfx_init_func; - gfx_init_ud = p_gfx_init_ud; main_loop = NULL; gl_extensions = NULL; //rasterizer = NULL; use_gl2 = false; - open_uri_func = p_open_uri_func; - get_user_data_dir_func = p_get_user_data_dir_func; - get_locale_func = p_get_locale_func; - get_model_func = p_get_model_func; - get_screen_dpi_func = p_get_screen_dpi_func; - get_unique_id_func = p_get_unique_id; - get_system_dir_func = p_get_sdir_func; - get_gl_version_code_func = p_get_gl_version_func; - - video_play_func = p_video_play_func; - video_is_playing_func = p_video_is_playing_func; - video_pause_func = p_video_pause_func; - video_stop_func = p_video_stop_func; - - show_virtual_keyboard_func = p_show_vk; - hide_virtual_keyboard_func = p_hide_vk; - get_virtual_keyboard_height_func = p_vk_height_func; - - set_clipboard_func = p_set_clipboard_func; - get_clipboard_func = p_get_clipboard_func; - - set_screen_orientation_func = p_screen_orient; - set_keep_screen_on_func = p_set_keep_screen_on_func; - alert_func = p_alert_func; - use_reload_hooks = false; + godot_java = p_godot_java; + godot_io_java = p_godot_io_java; Vector<Logger *> loggers; loggers.push_back(memnew(AndroidLogger)); diff --git a/platform/android/os_android.h b/platform/android/os_android.h index ad6fe1976a..4dbc96f4da 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -41,28 +41,8 @@ #include "servers/audio_server.h" #include "servers/visual/rasterizer.h" -typedef void (*GFXInitFunc)(void *ud, bool gl2); -typedef int (*OpenURIFunc)(const String &); -typedef String (*GetUserDataDirFunc)(); -typedef String (*GetLocaleFunc)(); -typedef void (*SetClipboardFunc)(const String &); -typedef String (*GetClipboardFunc)(); -typedef String (*GetModelFunc)(); -typedef int (*GetScreenDPIFunc)(); -typedef String (*GetUniqueIDFunc)(); -typedef void (*ShowVirtualKeyboardFunc)(const String &); -typedef void (*HideVirtualKeyboardFunc)(); -typedef void (*SetScreenOrientationFunc)(int); -typedef String (*GetSystemDirFunc)(int); -typedef int (*GetGLVersionCodeFunc)(); - -typedef void (*VideoPlayFunc)(const String &); -typedef bool (*VideoIsPlayingFunc)(); -typedef void (*VideoPauseFunc)(); -typedef void (*VideoStopFunc)(); -typedef void (*SetKeepScreenOnFunc)(bool p_enabled); -typedef void (*AlertFunc)(const String &, const String &); -typedef int (*VirtualKeyboardHeightFunc)(); +class GodotJavaWrapper; +class GodotIOJavaWrapper; class OS_Android : public OS_Unix { public: @@ -90,11 +70,7 @@ public: private: Vector<TouchPos> touch; - GFXInitFunc gfx_init_func; - void *gfx_init_ud; - bool use_gl2; - bool use_reload_hooks; bool use_apk_expansion; bool use_16bits_fbo; @@ -112,29 +88,11 @@ private: VideoMode default_videomode; MainLoop *main_loop; - OpenURIFunc open_uri_func; - GetUserDataDirFunc get_user_data_dir_func; - GetLocaleFunc get_locale_func; - SetClipboardFunc set_clipboard_func; - GetClipboardFunc get_clipboard_func; - GetModelFunc get_model_func; - GetScreenDPIFunc get_screen_dpi_func; - ShowVirtualKeyboardFunc show_virtual_keyboard_func; - HideVirtualKeyboardFunc hide_virtual_keyboard_func; - VirtualKeyboardHeightFunc get_virtual_keyboard_height_func; - SetScreenOrientationFunc set_screen_orientation_func; - GetUniqueIDFunc get_unique_id_func; - GetSystemDirFunc get_system_dir_func; - GetGLVersionCodeFunc get_gl_version_code_func; - - VideoPlayFunc video_play_func; - VideoIsPlayingFunc video_is_playing_func; - VideoPauseFunc video_pause_func; - VideoStopFunc video_stop_func; - SetKeepScreenOnFunc set_keep_screen_on_func; - AlertFunc alert_func; - - //power_android *power_manager; + GodotJavaWrapper *godot_java; + GodotIOJavaWrapper *godot_io_java; + + //PowerAndroid *power_manager_func; + int video_driver_index; public: @@ -158,8 +116,11 @@ public: typedef int64_t ProcessID; static OS *get_singleton(); + GodotJavaWrapper *get_godot_java(); + GodotIOJavaWrapper *get_godot_io_java(); virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); + virtual bool request_permission(const String &p_name); virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); @@ -178,14 +139,11 @@ public: virtual Size2 get_window_size() const; - virtual String get_name(); + virtual String get_name() const; virtual MainLoop *get_main_loop() const; virtual bool can_draw() const; - virtual void set_cursor_shape(CursorShape p_shape); - virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); - void main_loop_begin(); bool main_loop_iterate(); void main_loop_request_go_back(); @@ -203,10 +161,8 @@ public: void set_opengl_extensions(const char *p_gl_extensions); void set_display_size(Size2 p_size); - void reload_gfx(); void set_context_is_16_bits(bool p_is_16); - void set_need_reload_hooks(bool p_needs_them); virtual void set_screen_orientation(ScreenOrientation p_orientation); virtual Error shell_open(String p_uri); @@ -241,7 +197,7 @@ public: void joy_connection_changed(int p_device, bool p_connected, String p_name); virtual bool _check_internal_feature_support(const String &p_feature); - OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, GetGLVersionCodeFunc p_get_gl_version_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, SetClipboardFunc p_set_clipboard, GetClipboardFunc p_get_clipboard, bool p_use_apk_expansion); + OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion); ~OS_Android(); }; diff --git a/platform/android/platform_config.h b/platform/android/platform_config.h index 299d8563dd..ac58be8444 100644 --- a/platform/android/platform_config.h +++ b/platform/android/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/power_android.cpp b/platform/android/power_android.cpp index 0a6bf9dfcb..4d2fbfbf1a 100644 --- a/platform/android/power_android.cpp +++ b/platform/android/power_android.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -190,7 +190,7 @@ int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seco return 0; } -bool power_android::GetPowerInfo_Android() { +bool PowerAndroid::GetPowerInfo_Android() { int battery; int plugged; int charged; @@ -218,7 +218,7 @@ bool power_android::GetPowerInfo_Android() { return true; } -OS::PowerState power_android::get_power_state() { +OS::PowerState PowerAndroid::get_power_state() { if (GetPowerInfo_Android()) { return power_state; } else { @@ -227,7 +227,7 @@ OS::PowerState power_android::get_power_state() { } } -int power_android::get_power_seconds_left() { +int PowerAndroid::get_power_seconds_left() { if (GetPowerInfo_Android()) { return nsecs_left; } else { @@ -236,7 +236,7 @@ int power_android::get_power_seconds_left() { } } -int power_android::get_power_percent_left() { +int PowerAndroid::get_power_percent_left() { if (GetPowerInfo_Android()) { return percent_left; } else { @@ -245,11 +245,11 @@ int power_android::get_power_percent_left() { } } -power_android::power_android() : +PowerAndroid::PowerAndroid() : nsecs_left(-1), percent_left(-1), power_state(OS::POWERSTATE_UNKNOWN) { } -power_android::~power_android() { +PowerAndroid::~PowerAndroid() { } diff --git a/platform/android/power_android.h b/platform/android/power_android.h index c39764222e..6cb745b6c0 100644 --- a/platform/android/power_android.h +++ b/platform/android/power_android.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,13 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef PLATFORM_ANDROID_POWER_ANDROID_H_ -#define PLATFORM_ANDROID_POWER_ANDROID_H_ +#ifndef POWER_ANDROID_H +#define POWER_ANDROID_H #include "core/os/os.h" + #include <android/native_window_jni.h> -class power_android { +class PowerAndroid { struct LocalReferenceHolder { JNIEnv *m_env; @@ -65,8 +66,8 @@ private: public: static int s_active; - power_android(); - virtual ~power_android(); + PowerAndroid(); + virtual ~PowerAndroid(); static bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env); static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func); static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder); @@ -76,4 +77,4 @@ public: int get_power_percent_left(); }; -#endif /* PLATFORM_ANDROID_POWER_ANDROID_H_ */ +#endif // POWER_ANDROID_H diff --git a/platform/android/globals/global_defaults.cpp b/platform/android/string_android.h index efeb8598e5..fe627a3e0c 100644 --- a/platform/android/globals/global_defaults.cpp +++ b/platform/android/string_android.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* global_defaults.cpp */ +/* string_android.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,8 +28,31 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "global_defaults.h" -#include "core/project_settings.h" +#ifndef STRING_ANDROID_H +#define STRING_ANDROID_H +#include "core/ustring.h" +#include "thread_jandroid.h" +#include <jni.h> -void register_android_global_defaults() { +/** + * Converts JNI jstring to Godot String. + * @param source Source JNI string. If null an empty string is returned. + * @param env JNI environment instance. If null obtained by ThreadAndroid::get_env(). + * @return Godot string instance. + */ +static inline String jstring_to_string(jstring source, JNIEnv *env = NULL) { + String result; + if (source) { + if (!env) { + env = ThreadAndroid::get_env(); + } + const char *const source_utf8 = env->GetStringUTFChars(source, NULL); + if (source_utf8) { + result.parse_utf8(source_utf8); + env->ReleaseStringUTFChars(source, source_utf8); + } + } + return result; } + +#endif // STRING_ANDROID_H diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp index 6795315e63..9df9e57b24 100644 --- a/platform/android/thread_jandroid.cpp +++ b/platform/android/thread_jandroid.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -34,9 +34,13 @@ #include "core/safe_refcount.h" #include "core/script_language.h" +static void _thread_id_key_destr_callback(void *p_value) { + memdelete(static_cast<Thread::ID *>(p_value)); +} + static pthread_key_t _create_thread_id_key() { pthread_key_t key; - pthread_key_create(&key, NULL); + pthread_key_create(&key, &_thread_id_key_destr_callback); return key; } @@ -59,7 +63,7 @@ void *ThreadAndroid::thread_callback(void *userdata) { setup_thread(); ScriptServer::thread_enter(); //scripts may need to attach a stack t->id = atomic_increment(&next_thread_id); - pthread_setspecific(thread_id_key, (void *)t->id); + pthread_setspecific(thread_id_key, (void *)memnew(ID(t->id))); t->callback(t->user); ScriptServer::thread_exit(); return NULL; @@ -80,7 +84,14 @@ Thread *ThreadAndroid::create_func_jandroid(ThreadCreateCallback p_callback, voi Thread::ID ThreadAndroid::get_thread_id_func_jandroid() { - return (ID)pthread_getspecific(thread_id_key); + void *value = pthread_getspecific(thread_id_key); + + if (value) + return *static_cast<ID *>(value); + + ID new_id = atomic_increment(&next_thread_id); + pthread_setspecific(thread_id_key, (void *)memnew(ID(new_id))); + return new_id; } void ThreadAndroid::wait_to_finish_func_jandroid(Thread *p_thread) { diff --git a/platform/android/thread_jandroid.h b/platform/android/thread_jandroid.h index a57bc47e6d..1e1c00ab39 100644 --- a/platform/android/thread_jandroid.h +++ b/platform/android/thread_jandroid.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/audio_driver_media_kit.cpp b/platform/haiku/audio_driver_media_kit.cpp index 7e68c01fad..3c4e31da36 100644 --- a/platform/haiku/audio_driver_media_kit.cpp +++ b/platform/haiku/audio_driver_media_kit.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -39,11 +39,11 @@ int32_t *AudioDriverMediaKit::samples_in = NULL; Error AudioDriverMediaKit::init() { active = false; - mix_rate = 44100; + mix_rate = GLOBAL_DEF_RST("audio/mix_rate", DEFAULT_MIX_RATE); speaker_mode = SPEAKER_MODE_STEREO; channels = 2; - int latency = GLOBAL_DEF_RST("audio/output_latency", 25); + int latency = GLOBAL_DEF_RST("audio/output_latency", DEFAULT_OUTPUT_LATENCY); buffer_size = next_power_of_2(latency * mix_rate / 1000); samples_in = memnew_arr(int32_t, buffer_size * channels); diff --git a/platform/haiku/audio_driver_media_kit.h b/platform/haiku/audio_driver_media_kit.h index 02fefcf52a..634438fd2a 100644 --- a/platform/haiku/audio_driver_media_kit.h +++ b/platform/haiku/audio_driver_media_kit.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/context_gl_haiku.cpp b/platform/haiku/context_gl_haiku.cpp index 41df17d2d1..d73d893603 100644 --- a/platform/haiku/context_gl_haiku.cpp +++ b/platform/haiku/context_gl_haiku.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/context_gl_haiku.h b/platform/haiku/context_gl_haiku.h index 2c20570a8d..8452f5fbfb 100644 --- a/platform/haiku/context_gl_haiku.h +++ b/platform/haiku/context_gl_haiku.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -33,12 +33,10 @@ #if defined(OPENGL_ENABLED) -#include "drivers/gl_context/context_gl.h" - #include "haiku_direct_window.h" #include "haiku_gl_view.h" -class ContextGL_Haiku : public ContextGL { +class ContextGL_Haiku { private: HaikuGLView *view; HaikuDirectWindow *window; @@ -46,18 +44,18 @@ private: bool use_vsync; public: - virtual Error initialize(); - virtual void release_current(); - virtual void make_current(); - virtual void swap_buffers(); - virtual int get_window_width(); - virtual int get_window_height(); + Error initialize(); + void release_current(); + void make_current(); + void swap_buffers(); + int get_window_width(); + int get_window_height(); - virtual void set_use_vsync(bool p_use); - virtual bool is_using_vsync() const; + void set_use_vsync(bool p_use); + bool is_using_vsync() const; ContextGL_Haiku(HaikuDirectWindow *p_window); - virtual ~ContextGL_Haiku(); + ~ContextGL_Haiku(); }; #endif diff --git a/platform/haiku/detect.py b/platform/haiku/detect.py index 8d704ac657..f33c77a407 100644 --- a/platform/haiku/detect.py +++ b/platform/haiku/detect.py @@ -37,14 +37,14 @@ def configure(env): ## Build type if (env["target"] == "release"): - env.Prepend(CCFLAGS=['-O3', '-ffast-math']) + env.Prepend(CCFLAGS=['-O3']) if (env["debug_symbols"] == "yes"): env.Prepend(CCFLAGS=['-g1']) if (env["debug_symbols"] == "full"): env.Prepend(CCFLAGS=['-g2']) elif (env["target"] == "release_debug"): - env.Prepend(CCFLAGS=['-O2', '-ffast-math', '-DDEBUG_ENABLED']) + env.Prepend(CCFLAGS=['-O2', '-DDEBUG_ENABLED']) if (env["debug_symbols"] == "yes"): env.Prepend(CCFLAGS=['-g1']) if (env["debug_symbols"] == "full"): @@ -137,7 +137,7 @@ def configure(env): if not env['builtin_miniupnpc']: # No pkgconfig file so far, hardcode default paths. - env.Append(CPPPATH=["/system/develop/headers/x86/miniupnpc"]) + env.Prepend(CPPPATH=["/system/develop/headers/x86/miniupnpc"]) env.Append(LIBS=["miniupnpc"]) # On Linux wchar_t should be 32-bits @@ -147,9 +147,9 @@ def configure(env): ## Flags - env.Append(CPPPATH=['#platform/haiku']) + env.Prepend(CPPPATH=['#platform/haiku']) env.Append(CPPFLAGS=['-DUNIX_ENABLED', '-DOPENGL_ENABLED', '-DGLES_ENABLED']) env.Append(CPPFLAGS=['-DMEDIA_KIT_ENABLED']) - # env.Append(CCFLAGS=['-DFREETYPE_ENABLED']) + # env.Append(CPPFLAGS=['-DFREETYPE_ENABLED']) env.Append(CPPFLAGS=['-DPTHREAD_NO_RENAME']) # TODO: enable when we have pthread_setname_np env.Append(LIBS=['be', 'game', 'media', 'network', 'bnetapi', 'z', 'GL']) diff --git a/platform/haiku/godot_haiku.cpp b/platform/haiku/godot_haiku.cpp index b042d81650..d772632d1c 100644 --- a/platform/haiku/godot_haiku.cpp +++ b/platform/haiku/godot_haiku.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/haiku_application.cpp b/platform/haiku/haiku_application.cpp index 4f5e3e42a2..f548904547 100644 --- a/platform/haiku/haiku_application.cpp +++ b/platform/haiku/haiku_application.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/haiku_application.h b/platform/haiku/haiku_application.h index a870037985..d808c03863 100644 --- a/platform/haiku/haiku_application.h +++ b/platform/haiku/haiku_application.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/haiku_direct_window.cpp b/platform/haiku/haiku_direct_window.cpp index 6b64082250..c0dc1d2eee 100644 --- a/platform/haiku/haiku_direct_window.cpp +++ b/platform/haiku/haiku_direct_window.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/haiku_direct_window.h b/platform/haiku/haiku_direct_window.h index eee09191fa..77aa71c9e0 100644 --- a/platform/haiku/haiku_direct_window.h +++ b/platform/haiku/haiku_direct_window.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/haiku_gl_view.cpp b/platform/haiku/haiku_gl_view.cpp index a40cbe5765..56db7b1ffb 100644 --- a/platform/haiku/haiku_gl_view.cpp +++ b/platform/haiku/haiku_gl_view.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/haiku_gl_view.h b/platform/haiku/haiku_gl_view.h index 6869cb7de7..cc31268c72 100644 --- a/platform/haiku/haiku_gl_view.h +++ b/platform/haiku/haiku_gl_view.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/key_mapping_haiku.cpp b/platform/haiku/key_mapping_haiku.cpp index ebe8117d5d..486e9a3d17 100644 --- a/platform/haiku/key_mapping_haiku.cpp +++ b/platform/haiku/key_mapping_haiku.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/key_mapping_haiku.h b/platform/haiku/key_mapping_haiku.h index 917151bc4a..a6a50659e0 100644 --- a/platform/haiku/key_mapping_haiku.h +++ b/platform/haiku/key_mapping_haiku.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/haiku/os_haiku.cpp b/platform/haiku/os_haiku.cpp index f9f12af817..438b50053f 100644 --- a/platform/haiku/os_haiku.cpp +++ b/platform/haiku/os_haiku.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -69,7 +69,7 @@ void OS_Haiku::run() { main_loop->finish(); } -String OS_Haiku::get_name() { +String OS_Haiku::get_name() const { return "Haiku"; } @@ -203,6 +203,10 @@ void OS_Haiku::set_cursor_shape(CursorShape p_shape) { //ERR_PRINT("set_cursor_shape() NOT IMPLEMENTED"); } +OS::CursorShape OS_Haiku::get_cursor_shape() const { + // TODO: implement get_cursor_shape +} + void OS_Haiku::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { // TODO } @@ -322,7 +326,7 @@ String OS_Haiku::get_executable_path() const { bool OS_Haiku::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc" || p_feature == "s3tc"; + return p_feature == "pc"; } String OS_Haiku::get_config_path() const { diff --git a/platform/haiku/os_haiku.h b/platform/haiku/os_haiku.h index e862eb44c3..e1d4cf8d87 100644 --- a/platform/haiku/os_haiku.h +++ b/platform/haiku/os_haiku.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -74,7 +74,7 @@ public: OS_Haiku(); void run(); - virtual String get_name(); + virtual String get_name() const; virtual MainLoop *get_main_loop() const; @@ -86,6 +86,7 @@ public: virtual Point2 get_mouse_position() const; virtual int get_mouse_button_state() const; virtual void set_cursor_shape(CursorShape p_shape); + virtual CursorShape get_cursor_shape() const; virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); virtual int get_screen_count() const; diff --git a/platform/haiku/platform_config.h b/platform/haiku/platform_config.h index b00510f5a1..3ce26136e8 100644 --- a/platform/haiku/platform_config.h +++ b/platform/haiku/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index debf051eda..fa1b124561 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -2,12 +2,10 @@ Import('env') -import os - iphone_lib = [ 'godot_iphone.cpp', 'os_iphone.cpp', - 'sem_iphone.cpp', + 'semaphore_iphone.cpp', 'gl_view.mm', 'main.m', 'app_delegate.mm', @@ -23,7 +21,7 @@ ios_lib = env_ios.add_library('iphone', iphone_lib) def combine_libs(target=None, source=None, env=None): lib_path = target[0].srcnode().abspath - if ("OSXCROSS_IOS" in os.environ): + if "osxcross" in env: libtool = '$IPHONEPATH/usr/bin/${ios_triple}libtool' else: libtool = "$IPHONEPATH/usr/bin/libtool" diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h index c34b5053d6..4c43f10e89 100644 --- a/platform/iphone/app_delegate.h +++ b/platform/iphone/app_delegate.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index cc4985eb0c..64405bfa5b 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -598,8 +598,10 @@ static int frame_count = 0; }; - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { - OS::get_singleton()->get_main_loop()->notification( - MainLoop::NOTIFICATION_OS_MEMORY_WARNING); + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification( + MainLoop::NOTIFICATION_OS_MEMORY_WARNING); + } }; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { @@ -613,18 +615,6 @@ static int frame_count = 0; // Create a full-screen window window = [[UIWindow alloc] initWithFrame:rect]; - // window.autoresizesSubviews = YES; - //[window setAutoresizingMask:UIViewAutoresizingFlexibleWidth | - // UIViewAutoresizingFlexibleWidth]; - - // Create the OpenGL ES view and add it to the window - GLView *glView = [[GLView alloc] initWithFrame:rect]; - printf("glview is %p\n", glView); - //[window addSubview:glView]; - glView.delegate = self; - // glView.autoresizesSubviews = YES; - //[glView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | - // UIViewAutoresizingFlexibleWidth]; OS::VideoMode vm = _get_video_mode(); @@ -639,6 +629,12 @@ static int frame_count = 0; return FALSE; }; + // WARNING: We must *always* create the GLView after we have constructed the + // OS with iphone_main. This allows the GLView to access project settings so + // it can properly initialize the OpenGL context + GLView *glView = [[GLView alloc] initWithFrame:rect]; + glView.delegate = self; + view_controller = [[ViewController alloc] init]; view_controller.view = glView; window.rootViewController = view_controller; diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index c9f37931b0..d9f710e456 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -1,5 +1,4 @@ import os -import string import sys from methods import detect_darwin_sdk_path @@ -29,7 +28,6 @@ def get_opts(): BoolVariable('icloud', 'Support for iCloud', True), BoolVariable('ios_exceptions', 'Enable exceptions', False), ('ios_triple', 'Triple for ios toolchain', ''), - BoolVariable('ios_sim', 'Build simulator binary', False), ] @@ -47,27 +45,25 @@ def configure(env): if (env["target"].startswith("release")): env.Append(CPPFLAGS=['-DNDEBUG', '-DNS_BLOCK_ASSERTIONS=1']) if (env["optimize"] == "speed"): #optimize for speed (default) - env.Append(CPPFLAGS=['-O2', '-ftree-vectorize', '-fomit-frame-pointer', '-ffast-math', '-funsafe-math-optimizations']) + env.Append(CCFLAGS=['-O2', '-ftree-vectorize', '-fomit-frame-pointer']) env.Append(LINKFLAGS=['-O2']) else: #optimize for size - env.Append(CPPFLAGS=['-Os', '-ftree-vectorize']) + env.Append(CCFLAGS=['-Os', '-ftree-vectorize']) env.Append(LINKFLAGS=['-Os']) if env["target"] == "release_debug": env.Append(CPPFLAGS=['-DDEBUG_ENABLED']) elif (env["target"] == "debug"): - env.Append(CPPFLAGS=['-D_DEBUG', '-DDEBUG=1', '-gdwarf-2', '-O0', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) + env.Append(CCFLAGS=['-gdwarf-2', '-O0']) + env.Append(CPPFLAGS=['-D_DEBUG', '-DDEBUG=1', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) if (env["use_lto"]): - env.Append(CPPFLAGS=['-flto']) + env.Append(CCFLAGS=['-flto']) env.Append(LINKFLAGS=['-flto']) ## Architecture - if env["ios_sim"] and not ("arch" in env): - env["arch"] = "x86" - - if env["arch"] == "x86": # i386, simulator + if env["arch"] == "x86": # i386 env["bits"] = "32" elif env["arch"] == "x86_64": env["bits"] = "64" @@ -80,6 +76,10 @@ def configure(env): ## Compiler configuration + # Save this in environment for use by other modules + if "OSXCROSS_IOS" in os.environ: + env["osxcross"] = True + env['ENV']['PATH'] = env['IPHONEPATH'] + "/Developer/usr/bin/:" + env['ENV']['PATH'] compiler_path = '$IPHONEPATH/usr/bin/${ios_triple}' @@ -105,26 +105,28 @@ def configure(env): detect_darwin_sdk_path('iphonesimulator', env) env['ENV']['MACOSX_DEPLOYMENT_TARGET'] = '10.9' arch_flag = "i386" if env["arch"] == "x86" else env["arch"] - env.Append(CCFLAGS=('-arch ' + arch_flag + ' -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=9.0 -DCUSTOM_MATRIX_TRANSFORM_H=\\\"build/iphone/matrix4_iphone.h\\\" -DCUSTOM_VECTOR3_TRANSFORM_H=\\\"build/iphone/vector3_iphone.h\\\"').split()) + env.Append(CCFLAGS=('-arch ' + arch_flag + ' -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0').split()) elif (env["arch"] == "arm"): detect_darwin_sdk_path('iphone', env) - env.Append(CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=9.0 -MMD -MT dependencies'.split()) + env.Append(CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split()) elif (env["arch"] == "arm64"): detect_darwin_sdk_path('iphone', env) - env.Append(CCFLAGS='-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=9.0 -isysroot $IPHONESDK'.split()) + env.Append(CCFLAGS='-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK'.split()) env.Append(CPPFLAGS=['-DNEED_LONG_INT']) env.Append(CPPFLAGS=['-DLIBYUV_DISABLE_NEON']) - if env['ios_exceptions']: - env.Append(CPPFLAGS=['-fexceptions']) - else: - env.Append(CPPFLAGS=['-fno-exceptions']) + # Disable exceptions on non-tools (template) builds + if not env['tools']: + if env['ios_exceptions']: + env.Append(CCFLAGS=['-fexceptions']) + else: + env.Append(CCFLAGS=['-fno-exceptions']) ## Link flags if (env["arch"] == "x86" or env["arch"] == "x86_64"): arch_flag = "i386" if env["arch"] == "x86" else env["arch"] - env.Append(LINKFLAGS=['-arch', arch_flag, '-mios-simulator-version-min=9.0', + env.Append(LINKFLAGS=['-arch', arch_flag, '-mios-simulator-version-min=10.0', '-isysroot', '$IPHONESDK', '-Xlinker', '-objc_abi_version', @@ -132,9 +134,9 @@ def configure(env): '-F$IPHONESDK', ]) elif (env["arch"] == "arm"): - env.Append(LINKFLAGS=['-arch', 'armv7', '-Wl,-dead_strip', '-miphoneos-version-min=9.0']) + env.Append(LINKFLAGS=['-arch', 'armv7', '-Wl,-dead_strip', '-miphoneos-version-min=10.0']) if (env["arch"] == "arm64"): - env.Append(LINKFLAGS=['-arch', 'arm64', '-Wl,-dead_strip', '-miphoneos-version-min=9.0']) + env.Append(LINKFLAGS=['-arch', 'arm64', '-Wl,-dead_strip', '-miphoneos-version-min=10.0']) env.Append(LINKFLAGS=['-isysroot', '$IPHONESDK', '-framework', 'AudioToolbox', @@ -165,20 +167,12 @@ def configure(env): if env['icloud']: env.Append(CPPFLAGS=['-DICLOUD_ENABLED']) - env.Append(CPPPATH=['$IPHONESDK/usr/include', - '$IPHONESDK/System/Library/Frameworks/OpenGLES.framework/Headers', - '$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers', - ]) + env.Prepend(CPPPATH=['$IPHONESDK/usr/include', + '$IPHONESDK/System/Library/Frameworks/OpenGLES.framework/Headers', + '$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers', + ]) env['ENV']['CODESIGN_ALLOCATE'] = '/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate' - env.Append(CPPPATH=['#platform/iphone']) - env.Append(CPPFLAGS=['-DIPHONE_ENABLED', '-DUNIX_ENABLED', '-DGLES_ENABLED', '-DMPC_FIXED_POINT', '-DCOREAUDIO_ENABLED']) - - # TODO: Move that to opus module's config - if 'module_opus_enabled' in env and env['module_opus_enabled']: - env.opus_fixed_point = "yes" - if (env["arch"] == "arm"): - env.Append(CFLAGS=["-DOPUS_ARM_OPT"]) - elif (env["arch"] == "arm64"): - env.Append(CFLAGS=["-DOPUS_ARM64_OPT"]) + env.Prepend(CPPPATH=['#platform/iphone']) + env.Append(CPPFLAGS=['-DIPHONE_ENABLED', '-DUNIX_ENABLED', '-DGLES_ENABLED', '-DCOREAUDIO_ENABLED']) diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 8a9b254cdc..ba405ab7ae 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -105,7 +105,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { if (pname.length() == 0) { if (r_error) { - *r_error = "Identifier is missing."; + *r_error = TTR("Identifier is missing."); } return false; } @@ -116,7 +116,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { CharType c = pname[i]; if (first && c == '.') { if (r_error) { - *r_error = "Identifier segments must be of non-zero length."; + *r_error = TTR("Identifier segments must be of non-zero length."); } return false; } @@ -127,19 +127,19 @@ class EditorExportPlatformIOS : public EditorExportPlatform { } if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) { if (r_error) { - *r_error = "The character '" + String::chr(c) + "' is not allowed in Identifier."; + *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); } return false; } if (first && (c >= '0' && c <= '9')) { if (r_error) { - *r_error = "A digit cannot be the first character in a Identifier segment."; + *r_error = TTR("A digit cannot be the first character in a Identifier segment."); } return false; } if (first && c == '_') { if (r_error) { - *r_error = "The character '" + String::chr(c) + "' cannot be the first character in a Identifier segment."; + *r_error = vformat(TTR("The character '%s' cannot be the first character in a Identifier segment."), String::chr(c)); } return false; } @@ -148,14 +148,14 @@ class EditorExportPlatformIOS : public EditorExportPlatform { if (segments == 0) { if (r_error) { - *r_error = "The Identifier must have at least one '.' separator."; + *r_error = TTR("The Identifier must have at least one '.' separator."); } return false; } if (first) { if (r_error) { - *r_error = "Identifier segments must be of non-zero length."; + *r_error = TTR("Identifier segments must be of non-zero length."); } return false; } @@ -196,15 +196,16 @@ public: void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { - if (p_preset->get("texture_format/s3tc")) { - r_features->push_back("s3tc"); - } - if (p_preset->get("texture_format/etc")) { + String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); + if (driver == "GLES2") { r_features->push_back("etc"); - } - if (p_preset->get("texture_format/etc2")) { + } else if (driver == "GLES3") { r_features->push_back("etc2"); + if (ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2")) { + r_features->push_back("etc"); + } } + Vector<String> architectures = _get_preset_architectures(p_preset); for (int i = 0; i < architectures.size(); ++i) { r_features->push_back(architectures[i]); @@ -246,7 +247,7 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "iPhone Developer")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release"), "")); @@ -255,12 +256,26 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "come.example.game"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/arkit"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/game_center"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/in_app_purchases"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/push_notifications"), false)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/camera_usage_description"), "Godot would like to use your camera")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/photolibrary_usage_description"), "Godot would like to use your photos")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/portrait"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/landscape_left"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/landscape_right"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/portrait_upside_down"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/iphone_120x120", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPhone/iPod Touch with retina display r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/ipad_76x76", PROPERTY_HINT_FILE, "*.png"), "")); // Home screen on iPad r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "required_icons/app_store_1024x1024", PROPERTY_HINT_FILE, "*.png"), "")); // App Store @@ -275,10 +290,6 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, loading_screen_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png"), "")); } - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), true)); - Vector<ExportArchitecture> architectures = _get_supported_architectures(); for (int i = 0; i < architectures.size(); ++i) { r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + architectures[i].name), architectures[i].is_default)); @@ -337,6 +348,59 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n"; } else if (lines[i].find("$cpp_code") != -1) { strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n"; + } else if (lines[i].find("$access_wifi") != -1) { + bool is_on = p_preset->get("capabilities/access_wifi"); + strnew += lines[i].replace("$access_wifi", is_on ? "1" : "0") + "\n"; + } else if (lines[i].find("$game_center") != -1) { + bool is_on = p_preset->get("capabilities/game_center"); + strnew += lines[i].replace("$game_center", is_on ? "1" : "0") + "\n"; + } else if (lines[i].find("$in_app_purchases") != -1) { + bool is_on = p_preset->get("capabilities/in_app_purchases"); + strnew += lines[i].replace("$in_app_purchases", is_on ? "1" : "0") + "\n"; + } else if (lines[i].find("$push_notifications") != -1) { + bool is_on = p_preset->get("capabilities/push_notifications"); + strnew += lines[i].replace("$push_notifications", is_on ? "1" : "0") + "\n"; + } else if (lines[i].find("$required_device_capabilities") != -1) { + String capabilities; + + // I've removed armv7 as we can run on 64bit only devices + // Note that capabilities listed here are requirements for the app to be installed. + // They don't enable anything. + + if ((bool)p_preset->get("capabilities/arkit")) { + capabilities += "<string>arkit</string>\n"; + } + if ((bool)p_preset->get("capabilities/game_center")) { + capabilities += "<string>gamekit</string>\n"; + } + if ((bool)p_preset->get("capabilities/access_wifi")) { + capabilities += "<string>wifi</string>\n"; + } + + strnew += lines[i].replace("$required_device_capabilities", capabilities); + } else if (lines[i].find("$interface_orientations") != -1) { + String orientations; + + if ((bool)p_preset->get("orientation/portrait")) { + orientations += "<string>UIInterfaceOrientationPortrait</string>\n"; + } + if ((bool)p_preset->get("orientation/landscape_left")) { + orientations += "<string>UIInterfaceOrientationLandscapeLeft</string>\n"; + } + if ((bool)p_preset->get("orientation/landscape_right")) { + orientations += "<string>UIInterfaceOrientationLandscapeRight</string>\n"; + } + if ((bool)p_preset->get("orientation/portrait_upside_down")) { + orientations += "<string>UIInterfaceOrientationPortraitUpsideDown</string>\n"; + } + + strnew += lines[i].replace("$interface_orientations", orientations); + } else if (lines[i].find("$camera_usage_description") != -1) { + String description = p_preset->get("privacy/camera_usage_description"); + strnew += lines[i].replace("$camera_usage_description", description) + "\n"; + } else if (lines[i].find("$photolibrary_usage_description") != -1) { + String description = p_preset->get("privacy/photolibrary_usage_description"); + strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n"; } else { strnew += lines[i] + "\n"; } @@ -648,6 +712,10 @@ void EditorExportPlatformIOS::_add_assets_to_project(Vector<uint8_t> &p_project_ pbx_files += file_info_format.format(format_dict, "$_"); } + // Note, frameworks like gamekit are always included in our project.pbxprof file + // even if turned off in capabilities. + // Frameworks that are used by modules (like arkit) we may need to optionally add here. + String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size()); str = str.replace("$additional_pbx_files", pbx_files); str = str.replace("$additional_pbx_frameworks_build", pbx_frameworks_build); @@ -751,7 +819,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p String dest_dir = p_path.get_base_dir() + "/"; String binary_name = p_path.get_file().get_basename(); - EditorProgress ep("export", "Exporting for iOS", 5); + EditorProgress ep("export", "Exporting for iOS", 5, true); String team_id = p_preset->get("application/app_store_team_id"); ERR_EXPLAIN("App Store Team ID not specified - cannot configure the project."); @@ -771,6 +839,10 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p } } + if (!DirAccess::exists(dest_dir)) { + return ERR_FILE_BAD_PATH; + } + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (da) { String current_dir = da->get_current_dir(); @@ -796,14 +868,18 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p memdelete(da); } - ep.step("Making .pck", 0); + if (ep.step("Making .pck", 0)) { + return ERR_SKIP; + } String pack_path = dest_dir + binary_name + ".pck"; Vector<SharedObject> libraries; Error err = save_pack(p_preset, pack_path, &libraries); if (err) return err; - ep.step("Extracting and configuring Xcode project", 1); + if (ep.step("Extracting and configuring Xcode project", 1)) { + return ERR_SKIP; + } String library_to_use = "libgodot.iphone." + String(p_debug ? "debug" : "release") + ".fat.a"; @@ -848,7 +924,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p EditorNode::add_io_error("Could not open export template (not a zip file?):\n" + src_pkg_name); return ERR_CANT_OPEN; } - ERR_FAIL_COND_V(!src_pkg_zip, ERR_CANT_OPEN); + int ret = unzGoToFirstFile(src_pkg_zip); Vector<uint8_t> project_file_data; while (ret == UNZ_OK) { @@ -981,7 +1057,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p memdelete(f); #ifdef OSX_ENABLED - ep.step("Code-signing dylibs", 2); + if (ep.step("Code-signing dylibs", 2)) { + return ERR_SKIP; + } DirAccess *dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs"); ERR_FAIL_COND_V(!dylibs_dir, ERR_CANT_OPEN); CodesignData codesign_data(p_preset, p_debug); @@ -989,7 +1067,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p memdelete(dylibs_dir); ERR_FAIL_COND_V(err, err); - ep.step("Making .xcarchive", 3); + if (ep.step("Making .xcarchive", 3)) { + return ERR_SKIP; + } String archive_path = p_path.get_basename() + ".xcarchive"; List<String> archive_args; archive_args.push_back("-project"); @@ -1008,7 +1088,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p err = OS::get_singleton()->execute("xcodebuild", archive_args, true); ERR_FAIL_COND_V(err, err); - ep.step("Making .ipa", 4); + if (ep.step("Making .ipa", 4)) { + return ERR_SKIP; + } List<String> export_args; export_args.push_back("-exportArchive"); export_args.push_back("-archivePath"); @@ -1029,32 +1111,38 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { - bool valid = true; String err; + r_missing_templates = find_export_template("iphone.zip") == String(); - if (!exists_export_template("iphone.zip", &err)) { - valid = false; + if (p_preset->get("custom_package/debug") != "") { + if (FileAccess::exists(p_preset->get("custom_package/debug"))) { + r_missing_templates = false; + } else { + err += TTR("Custom debug template not found.") + "\n"; + } } - if (p_preset->get("custom_package/debug") != "" && !FileAccess::exists(p_preset->get("custom_package/debug"))) { - valid = false; - err += "Custom debug package not found.\n"; + if (p_preset->get("custom_package/release") != "") { + if (FileAccess::exists(p_preset->get("custom_package/release"))) { + r_missing_templates = false; + } else { + err += TTR("Custom release template not found.") + "\n"; + } } - if (p_preset->get("custom_package/release") != "" && !FileAccess::exists(p_preset->get("custom_package/release"))) { - valid = false; - err += "Custom release package not found.\n"; - } + bool valid = !r_missing_templates; String team_id = p_preset->get("application/app_store_team_id"); if (team_id.length() == 0) { - err += "App Store Team ID not specified - cannot configure the project.\n"; + err += TTR("App Store Team ID not specified - cannot configure the project.") + "\n"; + valid = false; } String identifier = p_preset->get("application/identifier"); String pn_err; if (!is_package_name_valid(identifier, &pn_err)) { - err += "Invalid Identifier - " + pn_err + "\n"; + err += TTR("Invalid Identifier:") + " " + pn_err + "\n"; + valid = false; } for (unsigned int i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) { @@ -1062,17 +1150,23 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset String icon_path = p_preset->get(info.preset_key); if (icon_path.length() == 0) { if (info.is_required) { - err += "Required icon is not specified in the preset.\n"; + err += TTR("Required icon is not specified in the preset.") + "\n"; + valid = false; } break; } } + String etc_error = test_etc2(); + if (etc_error != String()) { + valid = false; + err += etc_error; + } + if (!err.empty()) r_error = err; - r_missing_templates = !valid; - return err.empty(); + return valid; } EditorExportPlatformIOS::EditorExportPlatformIOS() { diff --git a/platform/iphone/export/export.h b/platform/iphone/export/export.h index ea79973290..3da58def33 100644 --- a/platform/iphone/export/export.h +++ b/platform/iphone/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h index 9a62cccb1a..1a14968f02 100644 --- a/platform/iphone/game_center.h +++ b/platform/iphone/game_center.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm index e210bfb862..97d6f0c71b 100644 --- a/platform/iphone/game_center.mm +++ b/platform/iphone/game_center.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h index 0d101eb696..4fb721f159 100644 --- a/platform/iphone/gl_view.h +++ b/platform/iphone/gl_view.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm index 5b4d1f8226..1cb8d0e44e 100644 --- a/platform/iphone/gl_view.mm +++ b/platform/iphone/gl_view.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -47,6 +47,7 @@ @end */ +bool gles3_available = true; int gl_view_base_fb; static String keyboard_text; static GLView *_instance = NULL; @@ -283,12 +284,34 @@ static void clear_touches() { kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil]; + bool fallback_gl2 = false; + // Create a GL ES 3 context based on the gl driver from project settings + if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3") { + context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + NSLog(@"Setting up an OpenGL ES 3.0 context. Based on Project Settings \"rendering/quality/driver/driver_name\""); + if (!context && GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) { + gles3_available = false; + fallback_gl2 = true; + NSLog(@"Failed to create OpenGL ES 3.0 context. Falling back to OpenGL ES 2.0"); + } + } - // Create our EAGLContext, and if successful make it current and create our framebuffer. - context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + // Create GL ES 2 context + if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2" || fallback_gl2) { + context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + NSLog(@"Setting up an OpenGL ES 2.0 context."); + if (!context) { + NSLog(@"Failed to create OpenGL ES 2.0 context!"); + return nil; + } + } - if (!context || ![EAGLContext setCurrentContext:context] || ![self createFramebuffer]) { - [self release]; + if (![EAGLContext setCurrentContext:context]) { + NSLog(@"Failed to set EAGLContext!"); + return nil; + } + if (![self createFramebuffer]) { + NSLog(@"Failed to create frame buffer!"); return nil; } @@ -578,7 +601,7 @@ static void clear_touches() { character.parse_utf8([p_text UTF8String]); keyboard_text = keyboard_text + character; OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true); - printf("inserting text with character %i\n", character[0]); + printf("inserting text with character %lc\n", (CharType)character[0]); }; - (void)audioRouteChangeListenerCallback:(NSNotification *)notification { diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.cpp index f9b9654a8c..db93db5021 100644 --- a/platform/iphone/godot_iphone.cpp +++ b/platform/iphone/godot_iphone.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/icloud.h b/platform/iphone/icloud.h index 52bb1131a0..aa4e1d4582 100644 --- a/platform/iphone/icloud.h +++ b/platform/iphone/icloud.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm index a9748bf562..e32618e8f6 100644 --- a/platform/iphone/icloud.mm +++ b/platform/iphone/icloud.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/in_app_store.h b/platform/iphone/in_app_store.h index 353438676d..7ed699c4f1 100644 --- a/platform/iphone/in_app_store.h +++ b/platform/iphone/in_app_store.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm index 2cdd477ed1..490e84c571 100644 --- a/platform/iphone/in_app_store.mm +++ b/platform/iphone/in_app_store.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/ios.h b/platform/iphone/ios.h index 5e77683949..91c4725b35 100644 --- a/platform/iphone/ios.h +++ b/platform/iphone/ios.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 7eb4f495f7..686422ceb2 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/main.m b/platform/iphone/main.m index 0f0810f28f..e9f009eabe 100644 --- a/platform/iphone/main.m +++ b/platform/iphone/main.m @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp index e996a5905b..6a65cadf09 100644 --- a/platform/iphone/os_iphone.cpp +++ b/platform/iphone/os_iphone.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,6 +32,7 @@ #include "os_iphone.h" +#include "drivers/gles2/rasterizer_gles2.h" #include "drivers/gles3/rasterizer_gles3.h" #include "servers/visual/visual_server_raster.h" #include "servers/visual/visual_server_wrap_mt.h" @@ -44,19 +45,27 @@ #include "core/project_settings.h" #include "drivers/unix/syslog_logger.h" -#include "sem_iphone.h" +#include "semaphore_iphone.h" #include "ios.h" + #include <dlfcn.h> int OSIPhone::get_video_driver_count() const { - return 1; + return 2; }; const char *OSIPhone::get_video_driver_name(int p_driver) const { - return "GLES3"; + switch (p_driver) { + case VIDEO_DRIVER_GLES3: + return "GLES3"; + case VIDEO_DRIVER_GLES2: + return "GLES2"; + } + ERR_EXPLAIN("Invalid video driver index " + itos(p_driver)); + ERR_FAIL_V(NULL); }; OSIPhone *OSIPhone::get_singleton() { @@ -75,14 +84,14 @@ void OSIPhone::set_data_dir(String p_dir) { memdelete(da); }; -void OSIPhone::set_unique_id(String p_ID) { +void OSIPhone::set_unique_id(String p_id) { - unique_ID = p_ID; + unique_id = p_id; }; String OSIPhone::get_unique_id() const { - return unique_ID; + return unique_id; }; void OSIPhone::initialize_core() { @@ -97,16 +106,48 @@ int OSIPhone::get_current_video_driver() const { return video_driver_index; } +extern bool gles3_available; // from gl_view.mm + Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - video_driver_index = VIDEO_DRIVER_GLES3; + bool use_gl3 = GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3"; + bool gl_initialization_error = false; + + while (true) { + if (use_gl3) { + if (RasterizerGLES3::is_viable() == OK && gles3_available) { + RasterizerGLES3::register_config(); + RasterizerGLES3::make_current(); + break; + } else { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) { + p_video_driver = VIDEO_DRIVER_GLES2; + use_gl3 = false; + continue; + } else { + gl_initialization_error = true; + break; + } + } + } else { + if (RasterizerGLES2::is_viable() == OK) { + RasterizerGLES2::register_config(); + RasterizerGLES2::make_current(); + break; + } else { + gl_initialization_error = true; + break; + } + } + } - if (RasterizerGLES3::is_viable() != OK) { + if (gl_initialization_error) { + OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", + "Unable to initialize Video driver"); return ERR_UNAVAILABLE; } - RasterizerGLES3::register_config(); - RasterizerGLES3::make_current(); + video_driver_index = p_video_driver; visual_server = memnew(VisualServerRaster); // FIXME: Reimplement threaded rendering if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { @@ -117,7 +158,10 @@ Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p //visual_server->cursor_set_visible(false, 0); // reset this to what it should be, it will have been set to 0 after visual_server->init() is called - RasterizerStorageGLES3::system_fbo = gl_view_base_fb; + if (use_gl3) + RasterizerStorageGLES3::system_fbo = gl_view_base_fb; + else + RasterizerStorageGLES2::system_fbo = gl_view_base_fb; AudioDriverManager::initialize(p_audio_driver); @@ -446,18 +490,12 @@ void OSIPhone::set_keep_screen_on(bool p_enabled) { _set_keep_screen_on(p_enabled); }; -void OSIPhone::set_cursor_shape(CursorShape p_shape){ - -}; - String OSIPhone::get_user_data_dir() const { return data_dir; }; -void OSIPhone::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot){}; - -String OSIPhone::get_name() { +String OSIPhone::get_name() const { return "iOS"; }; @@ -543,7 +581,7 @@ void OSIPhone::native_video_stop() { bool OSIPhone::_check_internal_feature_support(const String &p_feature) { - return p_feature == "mobile" || p_feature == "etc" || p_feature == "pvrtc" || p_feature == "etc2"; + return p_feature == "mobile"; } // Initialization order between compilation units is not guaranteed, diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 64a3c6355a..017125209c 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -107,7 +107,7 @@ private: void queue_event(const Ref<InputEvent> &p_event); String data_dir; - String unique_ID; + String unique_id; String locale_code; InputDefault *input; @@ -167,9 +167,6 @@ public: virtual void hide_virtual_keyboard(); virtual int get_virtual_keyboard_height() const; - virtual void set_cursor_shape(CursorShape p_shape); - virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); - virtual Size2 get_window_size() const; virtual Rect2 get_window_safe_area() const; @@ -177,7 +174,7 @@ public: void set_data_dir(String p_dir); - virtual String get_name(); + virtual String get_name() const; Error shell_open(String p_uri); @@ -186,7 +183,7 @@ public: void set_locale(String p_locale); String get_locale() const; - void set_unique_id(String p_ID); + void set_unique_id(String p_id); String get_unique_id() const; virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h index d9fd61fb6e..1884e03403 100644 --- a/platform/iphone/platform_config.h +++ b/platform/iphone/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/platform_refcount.h b/platform/iphone/platform_refcount.h index 34338d92e7..56fd4e6e81 100644 --- a/platform/iphone/platform_refcount.h +++ b/platform/iphone/platform_refcount.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/power_iphone.cpp b/platform/iphone/power_iphone.cpp index 7f9dadc363..e2631b7822 100644 --- a/platform/iphone/power_iphone.cpp +++ b/platform/iphone/power_iphone.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/power_iphone.h b/platform/iphone/power_iphone.h index 9619064915..d7d4bf4a69 100644 --- a/platform/iphone/power_iphone.h +++ b/platform/iphone/power_iphone.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef PLATFORM_IPHONE_POWER_IPHONE_H_ -#define PLATFORM_IPHONE_POWER_IPHONE_H_ +#ifndef POWER_IPHONE_H +#define POWER_IPHONE_H #include <os/os.h> @@ -50,4 +50,4 @@ public: int get_power_percent_left(); }; -#endif /* PLATFORM_IPHONE_POWER_IPHONE_H_ */ +#endif // POWER_IPHONE_H diff --git a/platform/iphone/sem_iphone.cpp b/platform/iphone/semaphore_iphone.cpp index ebab9db8fa..cc7dde72f7 100644 --- a/platform/iphone/sem_iphone.cpp +++ b/platform/iphone/semaphore_iphone.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* sem_iphone.cpp */ +/* semaphore_iphone.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "sem_iphone.h" +#include "semaphore_iphone.h" #include <fcntl.h> #include <unistd.h> @@ -71,6 +71,7 @@ void cgsem_destroy(cgsem_t *cgsem) { } #include "core/os/memory.h" + #include <errno.h> Error SemaphoreIphone::wait() { diff --git a/platform/iphone/sem_iphone.h b/platform/iphone/semaphore_iphone.h index 3edc4492eb..16658384e6 100644 --- a/platform/iphone/sem_iphone.h +++ b/platform/iphone/semaphore_iphone.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* sem_iphone.h */ +/* semaphore_iphone.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SEM_IPHONE_H -#define SEM_IPHONE_H +#ifndef SEMAPHORE_IPHONE_H +#define SEMAPHORE_IPHONE_H struct cgsem { int pipefd[2]; @@ -56,4 +56,4 @@ public: ~SemaphoreIphone(); }; -#endif +#endif // SEMAPHORE_IPHONE_H diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h index 31c4f0daf3..fc18661f62 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/view_controller.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index f75f0fd812..0358abf9e2 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index a93c98a89f..85a633442e 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -20,6 +20,13 @@ for lib in js_libraries: env.Append(LINKFLAGS=['--js-library', env.File(lib).path]) env.Depends(build, js_libraries) +js_modules = [ + 'id_handler.js', +] +for module in js_modules: + env.Append(LINKFLAGS=['--pre-js', env.File(module).path]) +env.Depends(build, js_modules) + wrapper_start = env.File('pre.js') wrapper_end = env.File('engine.js') js_wrapped = env.Textfile('#bin/godot', [wrapper_start, js, wrapper_end], TEXTFILESUFFIX='${PROGSUFFIX}.wrapped.js') diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index c7a6d53561..d4dc43d57c 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/javascript/api/api.h b/platform/javascript/api/api.h index 5f22f082e9..52d87528f6 100644 --- a/platform/javascript/api/api.h +++ b/platform/javascript/api/api.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/javascript/api/javascript_eval.h b/platform/javascript/api/javascript_eval.h index 49d5309737..49e460fffd 100644 --- a/platform/javascript/api/javascript_eval.h +++ b/platform/javascript/api/javascript_eval.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index a5b627b8dc..11104007e2 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -146,6 +146,15 @@ void AudioDriverJavaScript::start() { /* clang-format on */ } +void AudioDriverJavaScript::resume() { + /* clang-format off */ + EM_ASM({ + if (_audioDriver_audioContext.resume) + _audioDriver_audioContext.resume(); + }); + /* clang-format on */ +} + int AudioDriverJavaScript::get_mix_rate() const { /* clang-format off */ @@ -200,7 +209,7 @@ Error AudioDriverJavaScript::capture_start() { } function gotMediaInputError(e) { - console.log(e); + out(e); } if (navigator.mediaDevices.getUserMedia) { diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index c8aeb0b446..9dcba02c96 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -49,6 +49,7 @@ public: virtual Error init(); virtual void start(); + void resume(); virtual int get_mix_rate() const; virtual SpeakerMode get_speaker_mode() const; virtual void lock(); diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 22b5f1f87a..145ac42863 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -1,5 +1,4 @@ import os -import sys def is_active(): @@ -104,17 +103,19 @@ def configure(env): ## Compile flags - env.Append(CPPPATH=['#platform/javascript']) + env.Prepend(CPPPATH=['#platform/javascript']) env.Append(CPPDEFINES=['JAVASCRIPT_ENABLED', 'UNIX_ENABLED']) # No multi-threading (SharedArrayBuffer) available yet, # once feasible also consider memory buffer size issues. env.Append(CPPDEFINES=['NO_THREADS']) - # These flags help keep the file size down. - env.Append(CCFLAGS=['-fno-exceptions', '-fno-rtti']) - # Don't use dynamic_cast, necessary with no-rtti. - env.Append(CPPDEFINES=['NO_SAFE_CAST']) + # Disable exceptions and rtti on non-tools (template) builds + if not env['tools']: + # These flags help keep the file size down. + env.Append(CCFLAGS=['-fno-exceptions', '-fno-rtti']) + # Don't use dynamic_cast, necessary with no-rtti. + env.Append(CPPDEFINES=['NO_SAFE_CAST']) if env['javascript_eval']: env.Append(CPPDEFINES=['JAVASCRIPT_EVAL_ENABLED']) @@ -129,10 +130,6 @@ def configure(env): # us since we don't know requirements at compile-time. env.Append(LINKFLAGS=['-s', 'ALLOW_MEMORY_GROWTH=1']) - # Since we use both memory growth and MEMFS preloading, - # this avoids unecessary copying on start-up. - env.Append(LINKFLAGS=['--no-heap-copy']) - # This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1. env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1']) @@ -140,7 +137,3 @@ def configure(env): # TODO: Reevaluate usage of this setting now that engine.js manages engine runtime. env.Append(LINKFLAGS=['-s', 'NO_EXIT_RUNTIME=1']) - - # TODO: Move that to opus module's config. - if 'module_opus_enabled' in env and env['module_opus_enabled']: - env.opus_fixed_point = 'yes' diff --git a/platform/javascript/dom_keys.inc b/platform/javascript/dom_keys.inc index a30818decc..bf99ea5d42 100644 --- a/platform/javascript/dom_keys.inc +++ b/platform/javascript/dom_keys.inc @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js index 91458eb4c3..860d6707ff 100644 --- a/platform/javascript/engine.js +++ b/platform/javascript/engine.js @@ -199,7 +199,8 @@ } LIBS.FS.mkdirTree(dir); } - LIBS.FS.createDataFile('/', file.path, new Uint8Array(file.buffer), true, true, true); + // With memory growth, canOwn should be false. + LIBS.FS.createDataFile(file.path, null, new Uint8Array(file.buffer), true, true, false); }, this); preloadedFiles = null; diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 7a325e81dd..487da77b10 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -104,22 +104,27 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { - if (p_preset->get("texture_format/s3tc")) { + if (p_preset->get("vram_texture_compression/for_desktop")) { r_features->push_back("s3tc"); } - if (p_preset->get("texture_format/etc")) { - r_features->push_back("etc"); - } - if (p_preset->get("texture_format/etc2")) { - r_features->push_back("etc2"); + + if (p_preset->get("vram_texture_compression/for_mobile")) { + String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); + if (driver == "GLES2") { + r_features->push_back("etc"); + } else if (driver == "GLES3") { + r_features->push_back("etc2"); + if (ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2")) { + r_features->push_back("etc"); + } + } } } void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_options) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); @@ -155,7 +160,7 @@ bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p if (FileAccess::exists(p_preset->get("custom_template/debug"))) { valid = true; } else { - err += "Custom debug template not found.\n"; + err += TTR("Custom debug template not found.") + "\n"; } } @@ -163,14 +168,23 @@ bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p if (FileAccess::exists(p_preset->get("custom_template/release"))) { valid = true; } else { - err += "Custom release template not found.\n"; + err += TTR("Custom release template not found.") + "\n"; + } + } + + r_missing_templates = !valid; + + if (p_preset->get("vram_texture_compression/for_mobile")) { + String etc_error = test_etc2(); + if (etc_error != String()) { + valid = false; + err += etc_error; } } if (!err.empty()) r_error = err; - r_missing_templates = !valid; return valid; } @@ -200,6 +214,10 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE); } + if (!DirAccess::exists(p_path.get_base_dir())) { + return ERR_FILE_BAD_PATH; + } + if (template_path != String() && !FileAccess::exists(template_path)) { EditorNode::get_singleton()->show_warning(TTR("Template file not found:") + "\n" + template_path); return ERR_FILE_NOT_FOUND; @@ -349,7 +367,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese if (err) { return err; } - OS::get_singleton()->shell_open(path); + OS::get_singleton()->shell_open(String("file://") + path); return OK; } diff --git a/platform/javascript/export/export.h b/platform/javascript/export/export.h index 2835d0723f..7ebbcd6f00 100644 --- a/platform/javascript/export/export.h +++ b/platform/javascript/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc index d75d33a33a..d707d623ab 100644 --- a/platform/javascript/http_client.h.inc +++ b/platform/javascript/http_client.h.inc @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp index ccf4f8a11b..b4bab9a999 100644 --- a/platform/javascript/http_client_javascript.cpp +++ b/platform/javascript/http_client_javascript.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/javascript/http_request.h b/platform/javascript/http_request.h index b5ff46d7fe..4a596057da 100644 --- a/platform/javascript/http_request.h +++ b/platform/javascript/http_request.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/javascript/http_request.js b/platform/javascript/http_request.js index ee1c06c623..66dacfc3d4 100644 --- a/platform/javascript/http_request.js +++ b/platform/javascript/http_request.js @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2019 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 */ @@ -82,7 +82,7 @@ var GodotHTTPRequest = { godot_xhr_send_string: function(xhrId, strPtr) { if (!strPtr) { - console.warn("Failed to send string per XHR: null pointer"); + err("Failed to send string per XHR: null pointer"); return; } GodotHTTPRequest.requests[xhrId].send(UTF8ToString(strPtr)); @@ -90,11 +90,11 @@ var GodotHTTPRequest = { godot_xhr_send_data: function(xhrId, ptr, len) { if (!ptr) { - console.warn("Failed to send data per XHR: null pointer"); + err("Failed to send data per XHR: null pointer"); return; } if (len < 0) { - console.warn("Failed to send data per XHR: buffer length less than 0"); + err("Failed to send data per XHR: buffer length less than 0"); return; } GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len)); diff --git a/platform/windows/ctxgl_procaddr.h b/platform/javascript/id_handler.js index cd229fb8db..36ef5aa8ef 100644 --- a/platform/windows/ctxgl_procaddr.h +++ b/platform/javascript/id_handler.js @@ -1,12 +1,11 @@ /*************************************************************************/ -/* ctxgl_procaddr.h */ +/* id_handler.js */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ -/* https://godotengine.org */ +/* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,12 +27,36 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef CTXGL_PROCADDR_H -#define CTXGL_PROCADDR_H +var IDHandler = function() { -#ifdef OPENGL_ENABLED -#include <windows.h> + var ids = {}; + var size = 0; -PROC get_gl_proc_address(const char *p_address); -#endif -#endif // CTXGL_PROCADDR_H + this.has = function(id) { + return ids.hasOwnProperty(id); + } + + this.add = function(obj) { + size += 1; + var id = crypto.getRandomValues(new Int32Array(32))[0]; + ids[id] = obj; + return id; + } + + this.get = function(id) { + return ids[id]; + } + + this.remove = function(id) { + size -= 1; + delete ids[id]; + } + + this.size = function() { + return size; + } + + this.ids = ids; +}; + +Module.IDHandler = new IDHandler; diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp index 9b8174cc71..dd3eba74e4 100644 --- a/platform/javascript/javascript_eval.cpp +++ b/platform/javascript/javascript_eval.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -69,7 +69,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { eval_ret = eval(UTF8ToString(CODE)); } } catch (e) { - console.warn(e); + err(e); eval_ret = null; } @@ -97,7 +97,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { if (array_ptr!==0) { _free(array_ptr) } - console.warn(e); + err(e); // fall through } break; diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index ec60571402..bdf9dfe18f 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 9250ca4903..c69e6f0cb8 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -245,6 +245,8 @@ EM_BOOL OS_JavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboa return false; } os->input->parse_input_event(ev); + // Resume audio context after input in case autoplay was denied. + os->audio_driver_javascript.resume(); return true; } @@ -295,6 +297,30 @@ EM_BOOL OS_JavaScript::mouse_button_callback(int p_event_type, const EmscriptenM default: return false; } + if (ev->is_pressed()) { + + uint64_t diff = p_event->timestamp - os->last_click_ms; + + if (ev->get_button_index() == os->last_click_button_index) { + + if (diff < 400 && Point2(os->last_click_pos).distance_to(ev->get_position()) < 5) { + + os->last_click_ms = 0; + os->last_click_pos = Point2(-100, -100); + os->last_click_button_index = -1; + ev->set_doubleclick(true); + } + + } else { + os->last_click_button_index = ev->get_button_index(); + } + + if (!ev->is_doubleclick()) { + os->last_click_ms += diff; + os->last_click_pos = ev->get_position(); + } + } + int mask = os->input->get_mouse_button_mask(); int button_flag = 1 << (ev->get_button_index() - 1); if (ev->is_pressed()) { @@ -311,6 +337,8 @@ EM_BOOL OS_JavaScript::mouse_button_callback(int p_event_type, const EmscriptenM ev->set_button_mask(mask); os->input->parse_input_event(ev); + // Resume audio context after input in case autoplay was denied. + os->audio_driver_javascript.resume(); // Prevent multi-click text selection and wheel-click scrolling anchor. // Context menu is prevented through contextmenu event. return true; @@ -391,12 +419,134 @@ void OS_JavaScript::set_cursor_shape(CursorShape p_shape) { ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + if (get_mouse_mode() == MOUSE_MODE_VISIBLE) { + if (cursors[p_shape] != "") { + Vector<String> url = cursors[p_shape].split("?"); + set_css_cursor(("url(\"" + url[0] + "\") " + url[1] + ", auto").utf8()); + } else { + set_css_cursor(godot2dom_cursor(p_shape)); + } + } + cursor_shape = p_shape; - if (get_mouse_mode() != MOUSE_MODE_HIDDEN) - set_css_cursor(godot2dom_cursor(cursor_shape)); } void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { + + if (p_cursor.is_valid()) { + Ref<Texture> texture = p_cursor; + Ref<AtlasTexture> atlas_texture = p_cursor; + Ref<Image> image; + Size2 texture_size; + Rect2 atlas_rect; + + if (texture.is_valid()) { + image = texture->get_data(); + if (image.is_valid()) { + image->duplicate(); + } + } + + if (!image.is_valid() && atlas_texture.is_valid()) { + texture = atlas_texture->get_atlas(); + + atlas_rect.size.width = texture->get_width(); + atlas_rect.size.height = texture->get_height(); + atlas_rect.position.x = atlas_texture->get_region().position.x; + atlas_rect.position.y = atlas_texture->get_region().position.y; + + texture_size.width = atlas_texture->get_region().size.x; + texture_size.height = atlas_texture->get_region().size.y; + } else if (image.is_valid()) { + texture_size.width = texture->get_width(); + texture_size.height = texture->get_height(); + } + + ERR_FAIL_COND(!texture.is_valid()); + ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); + ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); + ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); + + image = texture->get_data(); + + ERR_FAIL_COND(!image.is_valid()); + + image = image->duplicate(); + + if (atlas_texture.is_valid()) + image->crop_from_point( + atlas_rect.position.x, + atlas_rect.position.y, + texture_size.width, + texture_size.height); + + if (image->get_format() != Image::FORMAT_RGBA8) { + image->convert(Image::FORMAT_RGBA8); + } + + png_image png_meta; + memset(&png_meta, 0, sizeof png_meta); + png_meta.version = PNG_IMAGE_VERSION; + png_meta.width = texture_size.width; + png_meta.height = texture_size.height; + png_meta.format = PNG_FORMAT_RGBA; + + PoolByteArray png; + size_t len; + PoolByteArray::Read r = image->get_data().read(); + ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, r.ptr(), 0, NULL)); + + png.resize(len); + PoolByteArray::Write w = png.write(); + ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, w.ptr(), &len, 0, r.ptr(), 0, NULL)); + w = PoolByteArray::Write(); + + r = png.read(); + + char *object_url; + /* clang-format off */ + EM_ASM({ + var PNG_PTR = $0; + var PNG_LEN = $1; + var PTR = $2; + + var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: 'image/png' }); + var url = URL.createObjectURL(png); + var length_bytes = lengthBytesUTF8(url) + 1; + var string_on_wasm_heap = _malloc(length_bytes); + setValue(PTR, string_on_wasm_heap, '*'); + stringToUTF8(url, string_on_wasm_heap, length_bytes); + }, r.ptr(), len, &object_url); + /* clang-format on */ + r = PoolByteArray::Read(); + + String url = String::utf8(object_url) + "?" + itos(p_hotspot.x) + " " + itos(p_hotspot.y); + + /* clang-format off */ + EM_ASM({ _free($0); }, object_url); + /* clang-format on */ + + if (cursors[p_shape] != "") { + /* clang-format off */ + EM_ASM({ + URL.revokeObjectURL(UTF8ToString($0).split('?')[0]); + }, cursors[p_shape].utf8().get_data()); + /* clang-format on */ + cursors[p_shape] = ""; + } + + cursors[p_shape] = url; + + } else if (cursors[p_shape] != "") { + /* clang-format off */ + EM_ASM({ + URL.revokeObjectURL(UTF8ToString($0).split('?')[0]); + }, cursors[p_shape].utf8().get_data()); + /* clang-format on */ + cursors[p_shape] = ""; + } + + set_cursor_shape(cursor_shape); } void OS_JavaScript::set_mouse_mode(OS::MouseMode p_mode) { @@ -408,7 +558,9 @@ void OS_JavaScript::set_mouse_mode(OS::MouseMode p_mode) { if (p_mode == MOUSE_MODE_VISIBLE) { + // set_css_cursor must be called before set_cursor_shape to make the cursor visible set_css_cursor(godot2dom_cursor(cursor_shape)); + set_cursor_shape(cursor_shape); emscripten_exit_pointerlock(); } else if (p_mode == MOUSE_MODE_HIDDEN) { @@ -422,7 +574,9 @@ void OS_JavaScript::set_mouse_mode(OS::MouseMode p_mode) { ERR_EXPLAIN("MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback"); ERR_FAIL_COND(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED); ERR_FAIL_COND(result != EMSCRIPTEN_RESULT_SUCCESS); + // set_css_cursor must be called before set_cursor_shape to make the cursor visible set_css_cursor(godot2dom_cursor(cursor_shape)); + set_cursor_shape(cursor_shape); } } @@ -452,7 +606,6 @@ EM_BOOL OS_JavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEve InputDefault *input = get_singleton()->input; Ref<InputEventMouseButton> ev; ev.instance(); - ev->set_button_mask(input->get_mouse_button_mask()); ev->set_position(input->get_mouse_position()); ev->set_global_position(ev->get_position()); @@ -475,10 +628,14 @@ EM_BOOL OS_JavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEve // Different browsers give wildly different delta values, and we can't // interpret deltaMode, so use default value for wheel events' factor. + int button_flag = 1 << (ev->get_button_index() - 1); + ev->set_pressed(true); + ev->set_button_mask(input->get_mouse_button_mask() | button_flag); input->parse_input_event(ev); ev->set_pressed(false); + ev->set_button_mask(input->get_mouse_button_mask() & ~button_flag); input->parse_input_event(ev); return true; @@ -515,6 +672,8 @@ EM_BOOL OS_JavaScript::touch_press_callback(int p_event_type, const EmscriptenTo os->input->parse_input_event(ev); } + // Resume audio context after input in case autoplay was denied. + os->audio_driver_javascript.resume(); return true; } @@ -670,7 +829,7 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, RasterizerGLES3::make_current(); break; } else { - if (GLOBAL_GET("rendering/quality/driver/driver_fallback") == "Best") { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) { p_video_driver = VIDEO_DRIVER_GLES2; gles3 = false; continue; @@ -707,8 +866,21 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, video_driver_index = p_video_driver; video_mode = p_desired; - // Can't fulfill fullscreen request during start-up due to browser security. + // fullscreen_change_callback will correct this if the request is successful. video_mode.fullscreen = false; + // Emscripten only attempts fullscreen requests if the user input callback + // was registered through one its own functions, so request manually for + // start-up fullscreen. + if (p_desired.fullscreen) { + /* clang-format off */ + EM_ASM({ + (canvas.requestFullscreen || canvas.msRequestFullscreen || + canvas.mozRequestFullScreen || canvas.mozRequestFullscreen || + canvas.webkitRequestFullscreen + ).call(canvas); + }); + /* clang-format on */ + } /* clang-format off */ if (EM_ASM_INT_V({ return Module.resizeCanvasOnStart })) { /* clang-format on */ @@ -814,15 +986,16 @@ bool OS_JavaScript::main_loop_iterate() { if (sync_wait_time < 0) { /* clang-format off */ EM_ASM( - FS.syncfs(function(err) { - if (err) { console.warn('Failed to save IDB file system: ' + err.message); } + FS.syncfs(function(error) { + if (error) { err('Failed to save IDB file system: ' + error.message); } }); ); /* clang-format on */ } } - process_joypads(); + if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) + process_joypads(); if (just_exited_fullscreen) { if (window_maximized) { @@ -862,7 +1035,7 @@ void OS_JavaScript::finalize() { // Miscellaneous -Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr) { +Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { ERR_EXPLAIN("OS::execute() is not available on the HTML5 platform"); ERR_FAIL_V(ERR_UNAVAILABLE); @@ -898,16 +1071,6 @@ bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { return true; #endif - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context(); - // All extensions are already automatically enabled, this function allows - // checking WebGL extension support without inline JavaScript - if (p_feature == "s3tc") - return emscripten_webgl_enable_extension(ctx, "WEBGL_compressed_texture_s3tc_srgb"); - if (p_feature == "etc") - return emscripten_webgl_enable_extension(ctx, "WEBGL_compressed_texture_etc1"); - if (p_feature == "etc2") - return emscripten_webgl_enable_extension(ctx, "WEBGL_compressed_texture_etc"); - return false; } @@ -996,7 +1159,7 @@ Error OS_JavaScript::shell_open(String p_uri) { return OK; } -String OS_JavaScript::get_name() { +String OS_JavaScript::get_name() const { return "HTML5"; } @@ -1067,6 +1230,10 @@ OS_JavaScript::OS_JavaScript(int p_argc, char *p_argv[]) { } set_cmdline(p_argv[0], arguments); + last_click_button_index = -1; + last_click_ms = 0; + last_click_pos = Point2(-100, -100); + window_maximized = false; entering_fullscreen = false; just_exited_fullscreen = false; diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 79dac5940f..a0c7c31f2d 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -50,9 +50,15 @@ class OS_JavaScript : public OS_Unix { InputDefault *input; Ref<InputEventKey> deferred_key_event; CursorShape cursor_shape; + String cursors[CURSOR_MAX]; Point2 touches[32]; + Point2i last_click_pos; + uint64_t last_click_ms; + int last_click_button_index; + MainLoop *main_loop; + int video_driver_index; AudioDriverJavaScript audio_driver_javascript; bool idb_available; @@ -80,8 +86,6 @@ class OS_JavaScript : public OS_Unix { static void file_access_close_callback(const String &p_file, int p_flags); - int video_driver_index; - protected: virtual int get_current_video_driver() const; @@ -133,7 +137,7 @@ public: void run_async(); bool main_loop_iterate(); - virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false); + virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL); virtual Error kill(const ProcessID &p_pid); virtual int get_process_id() const; @@ -142,7 +146,7 @@ public: virtual void set_icon(const Ref<Image> &p_icon); String get_executable_path() const; virtual Error shell_open(String p_uri); - virtual String get_name(); + virtual String get_name() const; virtual bool can_draw() const; virtual String get_resource_dir() const; diff --git a/platform/javascript/platform_config.h b/platform/javascript/platform_config.h index af4cf07393..baba6325b3 100644 --- a/platform/javascript/platform_config.h +++ b/platform/javascript/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/osx/SCsub b/platform/osx/SCsub index dc407eee9e..e15b4339a7 100644 --- a/platform/osx/SCsub +++ b/platform/osx/SCsub @@ -2,7 +2,6 @@ Import('env') -import os from platform_methods import run_in_subprocess import platform_osx_builders @@ -10,7 +9,7 @@ files = [ 'crash_handler_osx.mm', 'os_osx.mm', 'godot_main_osx.mm', - 'sem_osx.cpp', + 'semaphore_osx.cpp', 'dir_access_osx.mm', 'joypad_osx.cpp', 'power_osx.cpp', diff --git a/platform/osx/crash_handler_osx.h b/platform/osx/crash_handler_osx.h index 5d93afb22d..6a72ce8ae9 100644 --- a/platform/osx/crash_handler_osx.h +++ b/platform/osx/crash_handler_osx.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -45,4 +45,4 @@ public: ~CrashHandler(); }; -#endif +#endif // CRASH_HANDLER_OSX_H diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 490155bb24..e19fdf1b9f 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,9 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#include "crash_handler_osx.h" + +#include "core/os/os.h" #include "core/project_settings.h" #include "main/main.h" -#include "os_osx.h" #include <string.h> #include <unistd.h> @@ -75,7 +77,12 @@ static void handle_crash(int sig) { void *bt_buffer[256]; size_t size = backtrace(bt_buffer, 256); String _execpath = OS::get_singleton()->get_executable_path(); - String msg = GLOBAL_GET("debug/settings/crash_handler/message"); + + String msg; + const ProjectSettings *proj_settings = ProjectSettings::get_singleton(); + if (proj_settings) { + msg = proj_settings->get("debug/settings/crash_handler/message"); + } // Dump the backtrace to stderr with a message to the user fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 051836b66d..4c88f91d13 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -42,7 +42,7 @@ def configure(env): if (env["target"] == "release"): if (env["optimize"] == "speed"): #optimize for speed (default) - env.Prepend(CCFLAGS=['-O3', '-ffast-math', '-fomit-frame-pointer', '-ftree-vectorize', '-msse2']) + env.Prepend(CCFLAGS=['-O3', '-fomit-frame-pointer', '-ftree-vectorize', '-msse2']) else: #optimize for size env.Prepend(CCFLAGS=['-Os','-ftree-vectorize', '-msse2']) @@ -53,16 +53,18 @@ def configure(env): elif (env["target"] == "release_debug"): if (env["optimize"] == "speed"): #optimize for speed (default) - env.Prepend(CCFLAGS=['-O2', '-DDEBUG_ENABLED']) + env.Prepend(CCFLAGS=['-O2']) else: #optimize for size - env.Prepend(CCFLAGS=['-Os', '-DDEBUG_ENABLED']) + env.Prepend(CCFLAGS=['-Os']) + env.Prepend(CPPFLAGS=['-DDEBUG_ENABLED']) if (env["debug_symbols"] == "yes"): env.Prepend(CCFLAGS=['-g1']) if (env["debug_symbols"] == "full"): env.Prepend(CCFLAGS=['-g2']) elif (env["target"] == "debug"): - env.Prepend(CCFLAGS=['-g3', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) + env.Prepend(CCFLAGS=['-g3']) + env.Prepend(CPPFLAGS=['-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) ## Architecture @@ -72,7 +74,11 @@ def configure(env): ## Compiler configuration - if "OSXCROSS_ROOT" not in os.environ: # regular native build + # Save this in environment for use by other modules + if "OSXCROSS_ROOT" in os.environ: + env["osxcross"] = True + + if not "osxcross" in env: # regular native build env.Append(CCFLAGS=['-arch', 'x86_64']) env.Append(LINKFLAGS=['-arch', 'x86_64']) if (env["macports_clang"] != 'no'): @@ -84,10 +90,10 @@ def configure(env): env['AR'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ar" env['RANLIB'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ranlib" env['AS'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-as" - env.Append(CCFLAGS=['-D__MACPORTS__']) #hack to fix libvpx MM256_BROADCASTSI128_SI256 define + env.Append(CPPFLAGS=['-D__MACPORTS__']) #hack to fix libvpx MM256_BROADCASTSI128_SI256 define detect_darwin_sdk_path('osx', env) - env.Append(CPPFLAGS=['-isysroot', '$MACOS_SDK_PATH']) + env.Append(CCFLAGS=['-isysroot', '$MACOS_SDK_PATH']) env.Append(LINKFLAGS=['-isysroot', '$MACOS_SDK_PATH']) else: # osxcross build @@ -106,7 +112,7 @@ def configure(env): env['AR'] = basecmd + "ar" env['RANLIB'] = basecmd + "ranlib" env['AS'] = basecmd + "as" - env.Append(CCFLAGS=['-D__MACPORTS__']) #hack to fix libvpx MM256_BROADCASTSI128_SI256 define + env.Append(CPPFLAGS=['-D__MACPORTS__']) #hack to fix libvpx MM256_BROADCASTSI128_SI256 define if (env["CXX"] == "clang++"): env.Append(CPPFLAGS=['-DTYPED_METHOD_BIND']) @@ -120,10 +126,10 @@ def configure(env): ## Flags - env.Append(CPPPATH=['#platform/osx']) + env.Prepend(CPPPATH=['#platform/osx']) env.Append(CPPFLAGS=['-DOSX_ENABLED', '-DUNIX_ENABLED', '-DGLES_ENABLED', '-DAPPLE_STYLE_KEYS', '-DCOREAUDIO_ENABLED', '-DCOREMIDI_ENABLED']) - env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreMIDI', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback']) + env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreMIDI', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback', '-framework', 'CoreVideo']) env.Append(LIBS=['pthread']) - env.Append(CPPFLAGS=['-mmacosx-version-min=10.9']) + env.Append(CCFLAGS=['-mmacosx-version-min=10.9']) env.Append(LINKFLAGS=['-mmacosx-version-min=10.9']) diff --git a/platform/osx/dir_access_osx.h b/platform/osx/dir_access_osx.h index a9d6d63a8e..e1aa038c61 100644 --- a/platform/osx/dir_access_osx.h +++ b/platform/osx/dir_access_osx.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/osx/dir_access_osx.mm b/platform/osx/dir_access_osx.mm index cf66cab060..ada142005b 100644 --- a/platform/osx/dir_access_osx.mm +++ b/platform/osx/dir_access_osx.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index 12a0193521..9dabbb12fc 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -121,7 +121,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); @@ -409,7 +409,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p String src_pkg_name; - EditorProgress ep("export", "Exporting for OSX", 3); + EditorProgress ep("export", "Exporting for OSX", 3, true); if (p_debug) src_pkg_name = p_preset->get("custom_package/debug"); @@ -425,10 +425,16 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } } + if (!DirAccess::exists(p_path.get_base_dir())) { + return ERR_FILE_BAD_PATH; + } + FileAccess *src_f = NULL; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - ep.step("Creating app", 0); + if (ep.step("Creating app", 0)) { + return ERR_SKIP; + } unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); if (!src_pkg_zip) { @@ -437,7 +443,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p return ERR_FILE_NOT_FOUND; } - ERR_FAIL_COND_V(!src_pkg_zip, ERR_CANT_OPEN); int ret = unzGoToFirstFile(src_pkg_zip); String binary_to_use = "godot_osx_" + String(p_debug ? "debug" : "release") + ".64"; @@ -539,11 +544,21 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p iconpath = ProjectSettings::get_singleton()->get("application/config/icon"); if (iconpath != "") { - Ref<Image> icon; - icon.instance(); - icon->load(iconpath); - if (!icon->empty()) { - _make_icon(icon, data); + if (iconpath.get_extension() == "icns") { + FileAccess *icon = FileAccess::open(iconpath, FileAccess::READ); + if (icon) { + data.resize(icon->get_len()); + icon->get_buffer(&data.write[0], icon->get_len()); + icon->close(); + memdelete(icon); + } + } else { + Ref<Image> icon; + icon.instance(); + icon->load(iconpath); + if (!icon->empty()) { + _make_icon(icon, data); + } } } //bleh? @@ -564,7 +579,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p f->close(); if (is_execute) { // Chmod with 0755 if the file is executable - f->_chmod(file, 0755); + FileAccess::set_unix_permissions(file, 0755); } memdelete(f); } else { @@ -613,7 +628,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } if (err == OK) { - ep.step("Making PKG", 1); + if (ep.step("Making PKG", 1)) { + return ERR_SKIP; + } if (export_format == "dmg") { String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck"; @@ -635,7 +652,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } if (err == OK && identity != "") { - ep.step("Code signing bundle", 2); + if (ep.step("Code signing bundle", 2)) { + return ERR_SKIP; + } // the order in which we code sign is important, this is a bit of a shame or we could do this in our loop that extracts the files from our ZIP @@ -660,7 +679,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p // and finally create a DMG if (err == OK) { - ep.step("Making DMG", 3); + if (ep.step("Making DMG", 3)) { + return ERR_SKIP; + } err = _create_dmg(p_path, pkg_name, tmp_app_path_name); } @@ -749,7 +770,7 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset if (FileAccess::exists(p_preset->get("custom_package/debug"))) { valid = true; } else { - err += "Custom debug package not found.\n"; + err += TTR("Custom debug template not found.") + "\n"; } } @@ -757,7 +778,7 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset if (FileAccess::exists(p_preset->get("custom_package/release"))) { valid = true; } else { - err += "Custom release package not found.\n"; + err += TTR("Custom release template not found.") + "\n"; } } diff --git a/platform/osx/export/export.h b/platform/osx/export/export.h index 08294fc33c..7e7e697488 100644 --- a/platform/osx/export/export.h +++ b/platform/osx/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm index 64116fa1e0..79364314aa 100644 --- a/platform/osx/godot_main_osx.mm +++ b/platform/osx/godot_main_osx.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index 365b39c573..fa124dac11 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h index addbefc5ad..255a674f09 100644 --- a/platform/osx/joypad_osx.h +++ b/platform/osx/joypad_osx.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 546c88e74a..212966af11 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -34,7 +34,7 @@ #include "core/os/input.h" #include "crash_handler_osx.h" #include "drivers/coreaudio/audio_driver_coreaudio.h" -#include "drivers/coremidi/core_midi.h" +#include "drivers/coremidi/midi_driver_coremidi.h" #include "drivers/unix/os_unix.h" #include "joypad_osx.h" #include "main/input_default.h" @@ -43,8 +43,11 @@ #include "servers/visual/rasterizer.h" #include "servers/visual/visual_server_wrap_mt.h" #include "servers/visual_server.h" + +#include <AppKit/AppKit.h> #include <AppKit/NSCursor.h> #include <ApplicationServices/ApplicationServices.h> +#include <CoreVideo/CoreVideo.h> #undef CursorShape /** @@ -102,10 +105,13 @@ public: id window_view; id autoreleasePool; id cursor; - id pixelFormat; - id context; + NSOpenGLPixelFormat *pixelFormat; + NSOpenGLContext *context; bool layered_window; + bool waiting_for_vsync; + NSCondition *vsync_condition; + CVDisplayLinkRef displayLink; CursorShape cursor_shape; NSCursor *cursors[CURSOR_MAX]; @@ -124,10 +130,10 @@ public: Point2 im_position; bool im_active; - ImeCallback im_callback; - void *im_target; + String im_text; + Point2 im_selection; - power_osx *power_manager; + PowerOSX *power_manager; CrashHandler crash_handler; @@ -159,13 +165,14 @@ public: void wm_minimized(bool p_minimized); - virtual String get_name(); + virtual String get_name() const; virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); virtual void set_cursor_shape(CursorShape p_shape); + virtual CursorShape get_cursor_shape() const; virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); virtual void set_mouse_show(bool p_show); @@ -179,6 +186,7 @@ public: virtual Size2 get_window_size() const; virtual Size2 get_real_window_size() const; + virtual void set_native_icon(const String &p_filename); virtual void set_icon(const Ref<Image> &p_icon); virtual MainLoop *get_main_loop() const; @@ -245,7 +253,8 @@ public: virtual void set_ime_active(const bool p_active); virtual void set_ime_position(const Point2 &p_pos); - virtual void set_ime_intermediate_text_callback(ImeCallback p_callback, void *p_inp); + virtual Point2 get_ime_selection() const; + virtual String get_ime_text() const; virtual String get_unique_id() const; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index e7b3e35381..113c6636f0 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -37,7 +37,7 @@ #include "drivers/gles2/rasterizer_gles2.h" #include "drivers/gles3/rasterizer_gles3.h" #include "main/main.h" -#include "sem_osx.h" +#include "semaphore_osx.h" #include "servers/visual/visual_server_raster.h" #include <mach-o/dyld.h> @@ -102,8 +102,6 @@ static void push_to_key_event_buffer(const OS_OSX::KeyEvent &p_event) { static int mouse_x = 0; static int mouse_y = 0; -static int prev_mouse_x = 0; -static int prev_mouse_y = 0; static int button_mask = 0; static bool mouse_down_control = false; @@ -117,6 +115,21 @@ static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFacto return Vector2(mouse_x, mouse_y); } +// DisplayLinkCallback is called from our DisplayLink OS thread informing us right before +// a screen update is required. We can use it to work around the broken vsync. +static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { + OS_OSX *os = (OS_OSX *)displayLinkContext; + + // Set flag so we know we can output our next frame and signal our conditional lock + // if we're not doing vsync this will be ignored + [os->vsync_condition lock]; + os->waiting_for_vsync = false; + [os->vsync_condition signal]; + [os->vsync_condition unlock]; + + return kCVReturnSuccess; +} + @interface GodotApplication : NSApplication @end @@ -258,6 +271,8 @@ static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFacto - (void)windowDidExitFullScreen:(NSNotification *)notification { OS_OSX::singleton->zoomed = false; + if (!OS_OSX::singleton->resizable) + [OS_OSX::singleton->window_object setStyleMask:[OS_OSX::singleton->window_object styleMask] & ~NSWindowStyleMaskResizable]; } - (void)windowDidChangeBackingProperties:(NSNotification *)notification { @@ -267,7 +282,9 @@ static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFacto NSWindow *window = (NSWindow *)[notification object]; CGFloat newBackingScaleFactor = [window backingScaleFactor]; CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; - [OS_OSX::singleton->window_view setWantsBestResolutionOpenGLSurface:YES]; + if (OS_OSX::singleton->is_hidpi_allowed()) { + [OS_OSX::singleton->window_view setWantsBestResolutionOpenGLSurface:YES]; + } if (newBackingScaleFactor != oldBackingScaleFactor) { //Set new display scale and window size @@ -304,7 +321,9 @@ static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFacto if (OS_OSX::singleton->main_loop) { Main::force_redraw(); //Event retrieval blocks until resize is over. Call Main::iteration() directly. - Main::iteration(); + if (!Main::is_iterating()) { //avoid cyclic loop + Main::iteration(); + } } /* @@ -318,6 +337,11 @@ static Vector2 get_mouse_pos(NSPoint locationInWindow, CGFloat backingScaleFacto } - (void)windowDidMove:(NSNotification *)notification { + + if (OS_OSX::singleton->get_main_loop()) { + OS_OSX::singleton->input->release_pressed_events(); + } + /* [window->nsgl.context update]; @@ -427,11 +451,13 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; } else { [markedText initWithString:aString]; } - if (OS_OSX::singleton->im_callback) { + if (OS_OSX::singleton->im_active) { imeMode = true; - String ret; - ret.parse_utf8([[markedText mutableString] UTF8String]); - OS_OSX::singleton->im_callback(OS_OSX::singleton->im_target, ret, Point2(selectedRange.location, selectedRange.length)); + OS_OSX::singleton->im_text.parse_utf8([[markedText mutableString] UTF8String]); + OS_OSX::singleton->im_selection = Point2(selectedRange.location, selectedRange.length); + + if (OS_OSX::singleton->get_main_loop()) + OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); } } @@ -443,8 +469,13 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)unmarkText { imeMode = false; [[markedText mutableString] setString:@""]; - if (OS_OSX::singleton->im_callback) - OS_OSX::singleton->im_callback(OS_OSX::singleton->im_target, "", Point2()); + if (OS_OSX::singleton->im_active) { + OS_OSX::singleton->im_text = String(); + OS_OSX::singleton->im_selection = Point2(); + + if (OS_OSX::singleton->get_main_loop()) + OS_OSX::singleton->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); + } } - (NSArray *)validAttributesForMarkedText { @@ -575,12 +606,13 @@ static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { Ref<InputEventMouseButton> mb; mb.instance(); - + const CGFloat backingScaleFactor = [[event window] backingScaleFactor]; + const Vector2 pos = get_mouse_pos([event locationInWindow], backingScaleFactor); get_key_modifier_state([event modifierFlags], mb); mb->set_button_index(index); mb->set_pressed(pressed); - mb->set_position(Vector2(mouse_x, mouse_y)); - mb->set_global_position(Vector2(mouse_x, mouse_y)); + mb->set_position(pos); + mb->set_global_position(pos); mb->set_button_mask(button_mask); if (index == BUTTON_LEFT && pressed) { mb->set_doubleclick([event clickCount] == 2); @@ -616,12 +648,11 @@ static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { mm.instance(); mm->set_button_mask(button_mask); - prev_mouse_x = mouse_x; - prev_mouse_y = mouse_y; const CGFloat backingScaleFactor = [[event window] backingScaleFactor]; const Vector2 pos = get_mouse_pos([event locationInWindow], backingScaleFactor); mm->set_position(pos); mm->set_global_position(pos); + mm->set_speed(OS_OSX::singleton->input->get_last_mouse_speed()); Vector2 relativeMotion = Vector2(); relativeMotion.x = [event deltaX] * OS_OSX::singleton -> _mouse_scale(backingScaleFactor); relativeMotion.y = [event deltaY] * OS_OSX::singleton -> _mouse_scale(backingScaleFactor); @@ -686,8 +717,6 @@ static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED) OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT); - if (OS_OSX::singleton->input) - OS_OSX::singleton->input->set_mouse_in_window(false); } - (void)mouseEntered:(NSEvent *)event { @@ -695,8 +724,6 @@ static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { return; if (OS_OSX::singleton->main_loop && OS_OSX::singleton->mouse_mode != OS::MOUSE_MODE_CAPTURED) OS_OSX::singleton->main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); - if (OS_OSX::singleton->input) - OS_OSX::singleton->input->set_mouse_in_window(true); OS::CursorShape p_shape = OS_OSX::singleton->cursor_shape; OS_OSX::singleton->cursor_shape = OS::CURSOR_MAX; @@ -738,9 +765,41 @@ static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { [super updateTrackingAreas]; } +static bool isNumpadKey(unsigned int key) { + + static const unsigned int table[] = { + 0x41, /* kVK_ANSI_KeypadDecimal */ + 0x43, /* kVK_ANSI_KeypadMultiply */ + 0x45, /* kVK_ANSI_KeypadPlus */ + 0x47, /* kVK_ANSI_KeypadClear */ + 0x4b, /* kVK_ANSI_KeypadDivide */ + 0x4c, /* kVK_ANSI_KeypadEnter */ + 0x4e, /* kVK_ANSI_KeypadMinus */ + 0x51, /* kVK_ANSI_KeypadEquals */ + 0x52, /* kVK_ANSI_Keypad0 */ + 0x53, /* kVK_ANSI_Keypad1 */ + 0x54, /* kVK_ANSI_Keypad2 */ + 0x55, /* kVK_ANSI_Keypad3 */ + 0x56, /* kVK_ANSI_Keypad4 */ + 0x57, /* kVK_ANSI_Keypad5 */ + 0x58, /* kVK_ANSI_Keypad6 */ + 0x59, /* kVK_ANSI_Keypad7 */ + 0x5b, /* kVK_ANSI_Keypad8 */ + 0x5c, /* kVK_ANSI_Keypad9 */ + 0x5f, /* kVK_JIS_KeypadComma */ + 0x00 + }; + for (int i = 0; table[i] != 0; i++) { + if (key == table[i]) + return true; + } + return false; +} + // Translates a OS X keycode to a Godot keycode // static int translateKey(unsigned int key) { + // Keyboard symbol translation table static const unsigned int table[128] = { /* 00 */ KEY_A, @@ -753,7 +812,7 @@ static int translateKey(unsigned int key) { /* 07 */ KEY_X, /* 08 */ KEY_C, /* 09 */ KEY_V, - /* 0a */ KEY_UNKNOWN, + /* 0a */ KEY_SECTION, /* ISO Section */ /* 0b */ KEY_B, /* 0c */ KEY_Q, /* 0d */ KEY_W, @@ -807,7 +866,7 @@ static int translateKey(unsigned int key) { /* 3d */ KEY_ALT, /* 3e */ KEY_CONTROL, /* 3f */ KEY_UNKNOWN, /* Function */ - /* 40 */ KEY_UNKNOWN, + /* 40 */ KEY_UNKNOWN, /* F17 */ /* 41 */ KEY_KP_PERIOD, /* 42 */ KEY_UNKNOWN, /* 43 */ KEY_KP_MULTIPLY, @@ -815,16 +874,16 @@ static int translateKey(unsigned int key) { /* 45 */ KEY_KP_ADD, /* 46 */ KEY_UNKNOWN, /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */ - /* 48 */ KEY_UNKNOWN, /* VolumeUp */ - /* 49 */ KEY_UNKNOWN, /* VolumeDown */ - /* 4a */ KEY_UNKNOWN, /* Mute */ + /* 48 */ KEY_VOLUMEUP, /* VolumeUp */ + /* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */ + /* 4a */ KEY_VOLUMEMUTE, /* Mute */ /* 4b */ KEY_KP_DIVIDE, /* 4c */ KEY_KP_ENTER, /* 4d */ KEY_UNKNOWN, /* 4e */ KEY_KP_SUBTRACT, - /* 4f */ KEY_UNKNOWN, - /* 50 */ KEY_UNKNOWN, - /* 51 */ KEY_EQUAL, //wtf equal? + /* 4f */ KEY_UNKNOWN, /* F18 */ + /* 50 */ KEY_UNKNOWN, /* F19 */ + /* 51 */ KEY_EQUAL, /* KeypadEqual */ /* 52 */ KEY_KP_0, /* 53 */ KEY_KP_1, /* 54 */ KEY_KP_2, @@ -833,27 +892,27 @@ static int translateKey(unsigned int key) { /* 57 */ KEY_KP_5, /* 58 */ KEY_KP_6, /* 59 */ KEY_KP_7, - /* 5a */ KEY_UNKNOWN, + /* 5a */ KEY_UNKNOWN, /* F20 */ /* 5b */ KEY_KP_8, /* 5c */ KEY_KP_9, - /* 5d */ KEY_UNKNOWN, - /* 5e */ KEY_UNKNOWN, - /* 5f */ KEY_UNKNOWN, + /* 5d */ KEY_YEN, /* JIS Yen */ + /* 5e */ KEY_UNDERSCORE, /* JIS Underscore */ + /* 5f */ KEY_COMMA, /* JIS KeypadComma */ /* 60 */ KEY_F5, /* 61 */ KEY_F6, /* 62 */ KEY_F7, /* 63 */ KEY_F3, /* 64 */ KEY_F8, /* 65 */ KEY_F9, - /* 66 */ KEY_UNKNOWN, + /* 66 */ KEY_UNKNOWN, /* JIS Eisu */ /* 67 */ KEY_F11, - /* 68 */ KEY_UNKNOWN, + /* 68 */ KEY_UNKNOWN, /* JIS Kana */ /* 69 */ KEY_F13, /* 6a */ KEY_F16, /* 6b */ KEY_F14, /* 6c */ KEY_UNKNOWN, /* 6d */ KEY_F10, - /* 6e */ KEY_UNKNOWN, + /* 6e */ KEY_MENU, /* 6f */ KEY_F12, /* 70 */ KEY_UNKNOWN, /* 71 */ KEY_F15, @@ -911,10 +970,10 @@ static const _KeyCodeMap _keycodes[55] = { { 'i', KEY_I }, { 'o', KEY_O }, { 'p', KEY_P }, - { '[', KEY_BRACERIGHT }, - { ']', KEY_BRACELEFT }, - { '{', KEY_BRACERIGHT }, - { '}', KEY_BRACELEFT }, + { '[', KEY_BRACELEFT }, + { ']', KEY_BRACERIGHT }, + { '{', KEY_BRACELEFT }, + { '}', KEY_BRACERIGHT }, { 'a', KEY_A }, { 's', KEY_S }, { 'd', KEY_D }, @@ -944,6 +1003,9 @@ static const _KeyCodeMap _keycodes[55] = { static int remapKey(unsigned int key) { + if (isNumpadKey(key)) + return translateKey(key); + TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); if (!currentKeyboard) return translateKey(key); @@ -1068,6 +1130,9 @@ static int remapKey(unsigned int key) { inline void sendScrollEvent(int button, double factor, int modifierFlags) { + unsigned int mask = 1 << (button - 1); + Vector2 mouse_pos = Vector2(mouse_x, mouse_y); + Ref<InputEventMouseButton> sc; sc.instance(); @@ -1075,12 +1140,20 @@ inline void sendScrollEvent(int button, double factor, int modifierFlags) { sc->set_button_index(button); sc->set_factor(factor); sc->set_pressed(true); - Vector2 mouse_pos = Vector2(mouse_x, mouse_y); sc->set_position(mouse_pos); sc->set_global_position(mouse_pos); + button_mask |= mask; sc->set_button_mask(button_mask); OS_OSX::singleton->push_input(sc); + + sc.instance(); + sc->set_button_index(button); + sc->set_factor(factor); sc->set_pressed(false); + sc->set_position(mouse_pos); + sc->set_global_position(mouse_pos); + button_mask &= ~mask; + sc->set_button_mask(button_mask); OS_OSX::singleton->push_input(sc); } @@ -1136,12 +1209,14 @@ inline void sendPanEvent(double dx, double dy, int modifierFlags) { @end -void OS_OSX::set_ime_intermediate_text_callback(ImeCallback p_callback, void *p_inp) { - im_callback = p_callback; - im_target = p_inp; - if (!im_callback) { - [window_view cancelComposition]; - } +Point2 OS_OSX::get_ime_selection() const { + + return im_selection; +} + +String OS_OSX::get_ime_text() const { + + return im_text; } String OS_OSX::get_unique_id() const { @@ -1169,10 +1244,14 @@ String OS_OSX::get_unique_id() const { } void OS_OSX::set_ime_active(const bool p_active) { + im_active = p_active; + if (!im_active) + [window_view cancelComposition]; } void OS_OSX::set_ime_position(const Point2 &p_pos) { + im_position = p_pos; } @@ -1344,6 +1423,15 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a [context makeCurrentContext]; + // setup our display link, this will inform us when a refresh is needed + CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); + CVDisplayLinkSetOutputCallback(displayLink, &DisplayLinkCallback, this); + CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, context.CGLContextObj, pixelFormat.CGLPixelFormatObj); + CVDisplayLinkStart(displayLink); + + // initialise a conditional lock object + vsync_condition = [[NSCondition alloc] init]; + set_use_vsync(p_desired.use_vsync); [NSApp activateIgnoringOtherApps:YES]; @@ -1372,7 +1460,7 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a RasterizerGLES3::make_current(); break; } else { - if (GLOBAL_GET("rendering/quality/driver/driver_fallback") == "Best" || editor) { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) { p_video_driver = VIDEO_DRIVER_GLES2; gles3 = false; continue; @@ -1413,7 +1501,7 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a input = memnew(InputDefault); joypad_osx = memnew(JoypadOSX); - power_manager = memnew(power_osx); + power_manager = memnew(PowerOSX); _ensure_user_data_dir(); @@ -1431,6 +1519,11 @@ void OS_OSX::finalize() { midi_driver.close(); #endif + if (displayLink) { + CVDisplayLinkRelease(displayLink); + } + [vsync_condition release]; + CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, kTISNotifySelectedKeyboardInputSourceChanged, NULL); CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL); @@ -1458,7 +1551,7 @@ void OS_OSX::delete_main_loop() { main_loop = NULL; } -String OS_OSX::get_name() { +String OS_OSX::get_name() const { return "OSX"; } @@ -1596,13 +1689,19 @@ void OS_OSX::set_cursor_shape(CursorShape p_shape) { case CURSOR_VSPLIT: [[NSCursor resizeUpDownCursor] set]; break; case CURSOR_HSPLIT: [[NSCursor resizeLeftRightCursor] set]; break; case CURSOR_HELP: [[NSCursor arrowCursor] set]; break; - default: {}; + default: { + }; } } cursor_shape = p_shape; } +OS::CursorShape OS_OSX::get_cursor_shape() const { + + return cursor_shape; +} + void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { if (p_cursor.is_valid()) { Ref<Texture> texture = p_cursor; @@ -1759,6 +1858,31 @@ void OS_OSX::set_window_title(const String &p_title) { [window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; } +void OS_OSX::set_native_icon(const String &p_filename) { + + FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); + ERR_FAIL_COND(!f); + + Vector<uint8_t> data; + uint32_t len = f->get_len(); + data.resize(len); + f->get_buffer((uint8_t *)&data.write[0], len); + memdelete(f); + + NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease]; + if (!icon_data) { + ERR_EXPLAIN("Error reading icon data"); + ERR_FAIL(); + } + NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease]; + if (!icon) { + ERR_EXPLAIN("Error loading icon"); + ERR_FAIL(); + } + + [NSApp setApplicationIconImage:icon]; +} + void OS_OSX::set_icon(const Ref<Image> &p_icon) { Ref<Image> img = p_icon; @@ -1944,6 +2068,17 @@ String OS_OSX::get_locale() const { } void OS_OSX::swap_buffers() { + if (is_vsync_enabled()) { + // Wait until our DisplayLink callback unsets our flag... + [vsync_condition lock]; + while (waiting_for_vsync) + [vsync_condition wait]; + + // Make sure we wait again next frame around + waiting_for_vsync = true; + + [vsync_condition unlock]; + } [context flushBuffer]; } @@ -2168,12 +2303,12 @@ Size2 OS_OSX::get_window_size() const { Size2 OS_OSX::get_real_window_size() const { NSRect frame = [window_object frame]; - return Size2(frame.size.width, frame.size.height); + return Size2(frame.size.width, frame.size.height) * _display_scale(); } void OS_OSX::set_window_size(const Size2 p_size) { - Size2 size = p_size; + Size2 size = p_size / _display_scale(); if (get_borderless_window() == false) { // NSRect used by setFrame includes the title bar, so add it to our size.y @@ -2198,6 +2333,8 @@ void OS_OSX::set_window_fullscreen(bool p_enabled) { if (zoomed != p_enabled) { if (layered_window) set_window_per_pixel_transparency_enabled(false); + if (!resizable) + [window_object setStyleMask:[window_object styleMask] | NSWindowStyleMaskResizable]; [window_object toggleFullScreen:nil]; } zoomed = p_enabled; @@ -2212,7 +2349,7 @@ void OS_OSX::set_window_resizable(bool p_enabled) { if (p_enabled) [window_object setStyleMask:[window_object styleMask] | NSWindowStyleMaskResizable]; - else + else if (!zoomed) [window_object setStyleMask:[window_object styleMask] & ~NSWindowStyleMaskResizable]; resizable = p_enabled; @@ -2259,7 +2396,8 @@ bool OS_OSX::is_window_maximized() const { void OS_OSX::move_window_to_foreground() { - [window_object orderFrontRegardless]; + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; + [window_object makeKeyAndOrderFront:nil]; } void OS_OSX::set_window_always_on_top(bool p_enabled) { @@ -2463,6 +2601,8 @@ void OS_OSX::process_events() { [autoreleasePool drain]; autoreleasePool = [[NSAutoreleasePool alloc] init]; + + input->flush_accumulated_events(); } void OS_OSX::process_key_events() { @@ -2505,7 +2645,7 @@ void OS_OSX::process_key_events() { void OS_OSX::push_input(const Ref<InputEvent> &p_event) { Ref<InputEvent> ev = p_event; - input->parse_input_event(ev); + input->accumulate_input_event(ev); } void OS_OSX::force_process_input() { @@ -2610,22 +2750,22 @@ Error OS_OSX::move_to_trash(const String &p_path) { } void OS_OSX::_set_use_vsync(bool p_enable) { - CGLContextObj ctx = CGLGetCurrentContext(); + // CGLCPSwapInterval broke in OSX 10.14 and it seems Apple is not interested in fixing + // it as OpenGL is now deprecated and Metal solves this differently. + // Following SDLs example we're working around this using DisplayLink + // When vsync is enabled we set a flag "waiting_for_vsync" to true. + // This flag is set to false when DisplayLink informs us our display is about to refresh. + + /* CGLContextObj ctx = CGLGetCurrentContext(); if (ctx) { GLint swapInterval = p_enable ? 1 : 0; CGLSetParameter(ctx, kCGLCPSwapInterval, &swapInterval); - } -} -/* -bool OS_OSX::is_vsync_enabled() const { - GLint swapInterval = 0; - CGLContextObj ctx = CGLGetCurrentContext(); - if (ctx) { - CGLGetParameter(ctx, kCGLCPSwapInterval, &swapInterval); - } - return swapInterval ? true : false; + }*/ + + ///TODO Maybe pause/unpause display link? + waiting_for_vsync = p_enable; } -*/ + OS_OSX *OS_OSX::singleton = NULL; OS_OSX::OS_OSX() { @@ -2637,8 +2777,6 @@ OS_OSX::OS_OSX() { singleton = this; im_active = false; im_position = Point2(); - im_callback = NULL; - im_target = NULL; layered_window = false; autoreleasePool = [[NSAutoreleasePool alloc] init]; @@ -2748,7 +2886,7 @@ OS_OSX::OS_OSX() { } bool OS_OSX::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc" || p_feature == "s3tc"; + return p_feature == "pc"; } void OS_OSX::disable_crash_handler() { diff --git a/platform/osx/platform_config.h b/platform/osx/platform_config.h index 1d32d5b5b9..ddb533cb79 100644 --- a/platform/osx/platform_config.h +++ b/platform/osx/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/osx/power_osx.cpp b/platform/osx/power_osx.cpp index 22e279dfa7..04d423d8c5 100644 --- a/platform/osx/power_osx.cpp +++ b/platform/osx/power_osx.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -67,7 +67,7 @@ Adapted from corresponding SDL 2.0 code. CFDictionaryGetValueIfPresent(dict, CFSTR(k), (const void **)v) /* Note that AC power sources also include a laptop battery it is charging. */ -void power_osx::checkps(CFDictionaryRef dict, bool *have_ac, bool *have_battery, bool *charging) { +void PowerOSX::checkps(CFDictionaryRef dict, bool *have_ac, bool *have_battery, bool *charging) { CFStringRef strval; /* don't CFRelease() this. */ CFBooleanRef bval; CFNumberRef numval; @@ -169,7 +169,7 @@ void power_osx::checkps(CFDictionaryRef dict, bool *have_ac, bool *have_battery, #undef STRMATCH // CODE CHUNK IMPORTED FROM SDL 2.0 -bool power_osx::GetPowerInfo_MacOSX() { +bool PowerOSX::GetPowerInfo_MacOSX() { CFTypeRef blob = IOPSCopyPowerSourcesInfo(); nsecs_left = -1; @@ -211,14 +211,14 @@ bool power_osx::GetPowerInfo_MacOSX() { return true; /* always the definitive answer on Mac OS X. */ } -bool power_osx::UpdatePowerInfo() { +bool PowerOSX::UpdatePowerInfo() { if (GetPowerInfo_MacOSX()) { return true; } return false; } -OS::PowerState power_osx::get_power_state() { +OS::PowerState PowerOSX::get_power_state() { if (UpdatePowerInfo()) { return power_state; } else { @@ -226,7 +226,7 @@ OS::PowerState power_osx::get_power_state() { } } -int power_osx::get_power_seconds_left() { +int PowerOSX::get_power_seconds_left() { if (UpdatePowerInfo()) { return nsecs_left; } else { @@ -234,7 +234,7 @@ int power_osx::get_power_seconds_left() { } } -int power_osx::get_power_percent_left() { +int PowerOSX::get_power_percent_left() { if (UpdatePowerInfo()) { return percent_left; } else { @@ -242,11 +242,11 @@ int power_osx::get_power_percent_left() { } } -power_osx::power_osx() : +PowerOSX::PowerOSX() : nsecs_left(-1), percent_left(-1), power_state(OS::POWERSTATE_UNKNOWN) { } -power_osx::~power_osx() { +PowerOSX::~PowerOSX() { } diff --git a/platform/osx/power_osx.h b/platform/osx/power_osx.h index 9ad51e505b..40d0d40fd4 100644 --- a/platform/osx/power_osx.h +++ b/platform/osx/power_osx.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,15 +28,16 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef PLATFORM_OSX_POWER_OSX_H_ -#define PLATFORM_OSX_POWER_OSX_H_ +#ifndef POWER_OSX_H +#define POWER_OSX_H #include "core/os/file_access.h" #include "core/os/os.h" #include "dir_access_osx.h" + #include <CoreFoundation/CoreFoundation.h> -class power_osx { +class PowerOSX { private: int nsecs_left; @@ -47,12 +48,12 @@ private: bool UpdatePowerInfo(); public: - power_osx(); - virtual ~power_osx(); + PowerOSX(); + virtual ~PowerOSX(); OS::PowerState get_power_state(); int get_power_seconds_left(); int get_power_percent_left(); }; -#endif /* PLATFORM_OSX_POWER_OSX_H_ */ +#endif // POWER_OSX_H diff --git a/platform/osx/sem_osx.cpp b/platform/osx/semaphore_osx.cpp index 9b42abdb8d..fe7d19bd9e 100644 --- a/platform/osx/sem_osx.cpp +++ b/platform/osx/semaphore_osx.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* sem_osx.cpp */ +/* semaphore_osx.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "sem_osx.h" +#include "semaphore_osx.h" #include <fcntl.h> #include <unistd.h> @@ -66,6 +66,7 @@ void cgsem_destroy(cgsem_t *cgsem) { } #include "core/os/memory.h" + #include <errno.h> Error SemaphoreOSX::wait() { diff --git a/platform/osx/sem_osx.h b/platform/osx/semaphore_osx.h index 0ab82873c6..c8e7c45227 100644 --- a/platform/osx/sem_osx.h +++ b/platform/osx/semaphore_osx.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* sem_osx.h */ +/* semaphore_osx.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SEM_OSX_H -#define SEM_OSX_H +#ifndef SEMAPHORE_OSX_H +#define SEMAPHORE_OSX_H struct cgsem { int pipefd[2]; @@ -56,4 +56,4 @@ public: ~SemaphoreOSX(); }; -#endif +#endif // SEMAPHORE_OSX_H diff --git a/platform/register_platform_apis.h b/platform/register_platform_apis.h index 1c50eb95e3..10876bacc7 100644 --- a/platform/register_platform_apis.h +++ b/platform/register_platform_apis.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/server/SCsub b/platform/server/SCsub index 51fd05a87e..f977275595 100644 --- a/platform/server/SCsub +++ b/platform/server/SCsub @@ -1,7 +1,5 @@ #!/usr/bin/env python -import os -import platform import sys Import('env') @@ -13,7 +11,7 @@ common_server = [\ if sys.platform == "darwin": common_server.append("#platform/osx/crash_handler_osx.mm") common_server.append("#platform/osx/power_osx.cpp") - common_server.append("#platform/osx/sem_osx.cpp") + common_server.append("#platform/osx/semaphore_osx.cpp") else: common_server.append("#platform/x11/crash_handler_x11.cpp") common_server.append("#platform/x11/power_x11.cpp") diff --git a/platform/server/detect.py b/platform/server/detect.py index 0b23e9c649..08c2eb6aaf 100644 --- a/platform/server/detect.py +++ b/platform/server/detect.py @@ -2,6 +2,8 @@ import os import platform import sys +# This file is mostly based on platform/x11/detect.py. +# If editing this file, make sure to apply relevant changes here too. def is_active(): return True @@ -26,10 +28,16 @@ def can_build(): def get_opts(): - from SCons.Variables import BoolVariable + from SCons.Variables import BoolVariable, EnumVariable return [ BoolVariable('use_llvm', 'Use the LLVM compiler', False), BoolVariable('use_static_cpp', 'Link libgcc and libstdc++ statically for better portability', False), + BoolVariable('use_ubsan', 'Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)', False), + BoolVariable('use_asan', 'Use LLVM/GCC compiler address sanitizer (ASAN))', False), + BoolVariable('use_lsan', 'Use LLVM/GCC compiler leak sanitizer (LSAN))', False), + EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')), + BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False), + BoolVariable('execinfo', 'Use libexecinfo on systems where glibc is not available', False), ] @@ -43,13 +51,32 @@ def configure(env): ## Build type if (env["target"] == "release"): - env.Append(CCFLAGS=['-O2', '-ffast-math', '-fomit-frame-pointer']) + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Prepend(CCFLAGS=['-O3']) + else: #optimize for size + env.Prepend(CCFLAGS=['-Os']) + + if (env["debug_symbols"] == "yes"): + env.Prepend(CCFLAGS=['-g1']) + if (env["debug_symbols"] == "full"): + env.Prepend(CCFLAGS=['-g2']) elif (env["target"] == "release_debug"): - env.Append(CCFLAGS=['-O2', '-ffast-math', '-DDEBUG_ENABLED']) + if (env["optimize"] == "speed"): #optimize for speed (default) + env.Prepend(CCFLAGS=['-O2']) + else: #optimize for size + env.Prepend(CCFLAGS=['-Os']) + env.Prepend(CPPFLAGS=['-DDEBUG_ENABLED']) + + if (env["debug_symbols"] == "yes"): + env.Prepend(CCFLAGS=['-g1']) + if (env["debug_symbols"] == "full"): + env.Prepend(CCFLAGS=['-g2']) elif (env["target"] == "debug"): - env.Append(CCFLAGS=['-g2', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) + env.Prepend(CCFLAGS=['-g3']) + env.Prepend(CPPFLAGS=['-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) + env.Append(LINKFLAGS=['-rdynamic']) ## Architecture @@ -59,6 +86,10 @@ def configure(env): ## Compiler configuration + if 'CXX' in env and 'clang' in os.path.basename(env['CXX']): + # Convenience check to enforce the use_llvm overrides when CXX is clang(++) + env['use_llvm'] = True + if env['use_llvm']: if ('clang++' not in os.path.basename(env['CXX'])): env["CC"] = "clang" @@ -67,6 +98,35 @@ def configure(env): env.Append(CPPFLAGS=['-DTYPED_METHOD_BIND']) env.extra_suffix = ".llvm" + env.extra_suffix + + if env['use_ubsan'] or env['use_asan'] or env['use_lsan']: + env.extra_suffix += "s" + + if env['use_ubsan']: + env.Append(CCFLAGS=['-fsanitize=undefined']) + env.Append(LINKFLAGS=['-fsanitize=undefined']) + + if env['use_asan']: + env.Append(CCFLAGS=['-fsanitize=address']) + env.Append(LINKFLAGS=['-fsanitize=address']) + + if env['use_lsan']: + env.Append(CCFLAGS=['-fsanitize=leak']) + env.Append(LINKFLAGS=['-fsanitize=leak']) + + if env['use_lto']: + env.Append(CCFLAGS=['-flto']) + if not env['use_llvm'] and env.GetOption("num_jobs") > 1: + env.Append(LINKFLAGS=['-flto=' + str(env.GetOption("num_jobs"))]) + else: + env.Append(LINKFLAGS=['-flto']) + if not env['use_llvm']: + env['RANLIB'] = 'gcc-ranlib' + env['AR'] = 'gcc-ar' + + env.Append(CCFLAGS=['-pipe']) + env.Append(LINKFLAGS=['-pipe']) + ## Dependencies # FIXME: Check for existence of the libs before parsing their flags with pkg-config @@ -88,7 +148,7 @@ def configure(env): # We need at least version 2.88 import subprocess bullet_version = subprocess.check_output(['pkg-config', 'bullet', '--modversion']).strip() - if bullet_version < "2.88": + if str(bullet_version) < "2.88": # Abort as system bullet was requested but too old print("Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(bullet_version, "2.88")) sys.exit(255) @@ -110,6 +170,10 @@ def configure(env): env['builtin_libogg'] = False # Needed to link against system libtheora env['builtin_libvorbis'] = False # Needed to link against system libtheora env.ParseConfig('pkg-config theora theoradec --cflags --libs') + else: + list_of_x86 = ['x86_64', 'x86', 'i386', 'i586'] + if any(platform.machine() in s for s in list_of_x86): + env["x86_libtheora_opt_gcc"] = True if not env['builtin_libvpx']: env.ParseConfig('pkg-config vpx --cflags --libs') @@ -137,7 +201,7 @@ def configure(env): if not env['builtin_miniupnpc']: # No pkgconfig file so far, hardcode default paths. - env.Append(CPPPATH=["/usr/include/miniupnpc"]) + env.Prepend(CPPPATH=["/usr/include/miniupnpc"]) env.Append(LIBS=["miniupnpc"]) # On Linux wchar_t should be 32-bits @@ -151,7 +215,7 @@ def configure(env): if not env['builtin_zlib']: env.ParseConfig('pkg-config zlib --cflags --libs') - env.Append(CPPPATH=['#platform/server']) + env.Prepend(CPPPATH=['#platform/server']) env.Append(CPPFLAGS=['-DSERVER_ENABLED', '-DUNIX_ENABLED']) if (platform.system() == "Darwin"): @@ -163,6 +227,9 @@ def configure(env): env.Append(LIBS=['dl']) if (platform.system().find("BSD") >= 0): + env["execinfo"] = True + + if env["execinfo"]: env.Append(LIBS=['execinfo']) # Link those statically for portability diff --git a/platform/server/godot_server.cpp b/platform/server/godot_server.cpp index 3e48f0bf7f..91bd96ac31 100644 --- a/platform/server/godot_server.cpp +++ b/platform/server/godot_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp index 60f20d6009..12e53054bc 100644 --- a/platform/server/os_server.cpp +++ b/platform/server/os_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -27,6 +27,7 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ + #include "os_server.h" #include "core/print_string.h" @@ -92,14 +93,14 @@ Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int input = memnew(InputDefault); #ifdef __APPLE__ - power_manager = memnew(power_osx); + power_manager = memnew(PowerOSX); #else power_manager = memnew(PowerX11); #endif _ensure_user_data_dir(); - resource_loader_dummy = memnew(ResourceFormatDummyTexture); + resource_loader_dummy.instance(); ResourceLoader::add_resource_format_loader(resource_loader_dummy); return OK; @@ -118,7 +119,8 @@ void OS_Server::finalize() { memdelete(power_manager); - memdelete(resource_loader_dummy); + ResourceLoader::remove_resource_format_loader(resource_loader_dummy); + resource_loader_dummy.unref(); args.clear(); } @@ -188,7 +190,7 @@ bool OS_Server::can_draw() const { return false; //can never draw }; -String OS_Server::get_name() { +String OS_Server::get_name() const { return "Server"; } @@ -196,12 +198,6 @@ String OS_Server::get_name() { void OS_Server::move_window_to_foreground() { } -void OS_Server::set_cursor_shape(CursorShape p_shape) { -} - -void OS_Server::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { -} - OS::PowerState OS_Server::get_power_state() { return power_manager->get_power_state(); } diff --git a/platform/server/os_server.h b/platform/server/os_server.h index 0367ec3db9..e3488a693d 100644 --- a/platform/server/os_server.h +++ b/platform/server/os_server.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -27,17 +27,17 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ + #ifndef OS_SERVER_H #define OS_SERVER_H #include "drivers/dummy/texture_loader_dummy.h" -#include "drivers/rtaudio/audio_driver_rtaudio.h" #include "drivers/unix/os_unix.h" #include "main/input_default.h" #ifdef __APPLE__ #include "platform/osx/crash_handler_osx.h" #include "platform/osx/power_osx.h" -#include "platform/osx/sem_osx.h" +#include "platform/osx/semaphore_osx.h" #else #include "platform/x11/crash_handler_x11.h" #include "platform/x11/power_x11.h" @@ -68,7 +68,7 @@ class OS_Server : public OS_Unix { InputDefault *input; #ifdef __APPLE__ - power_osx *power_manager; + PowerOSX *power_manager; #else PowerX11 *power_manager; #endif @@ -77,7 +77,7 @@ class OS_Server : public OS_Unix { int video_driver_index; - ResourceFormatDummyTexture *resource_loader_dummy; + Ref<ResourceFormatDummyTexture> resource_loader_dummy; protected: virtual int get_video_driver_count() const; @@ -93,10 +93,7 @@ protected: virtual void set_main_loop(MainLoop *p_main_loop); public: - virtual String get_name(); - - virtual void set_cursor_shape(CursorShape p_shape); - virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); + virtual String get_name() const; virtual void set_mouse_show(bool p_show); virtual void set_mouse_grab(bool p_grab); diff --git a/platform/server/platform_config.h b/platform/server/platform_config.h index 26ba8f26c6..bedbff0b80 100644 --- a/platform/server/platform_config.h +++ b/platform/server/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/uwp/SCsub b/platform/uwp/SCsub index fb0c4a92ae..c14290f0c4 100644 --- a/platform/uwp/SCsub +++ b/platform/uwp/SCsub @@ -4,11 +4,11 @@ Import('env') files = [ 'thread_uwp.cpp', - '#platform/windows/key_mapping_win.cpp', + '#platform/windows/key_mapping_windows.cpp', '#platform/windows/windows_terminal_logger.cpp', 'joypad_uwp.cpp', 'power_uwp.cpp', - 'gl_context_egl.cpp', + 'context_egl_uwp.cpp', 'app.cpp', 'os_uwp.cpp', ] diff --git a/platform/uwp/app.cpp b/platform/uwp/app.cpp index b769925849..4f2ee0237a 100644 --- a/platform/uwp/app.cpp +++ b/platform/uwp/app.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -39,7 +39,7 @@ #include "core/os/keyboard.h" #include "main/main.h" -#include "platform/windows/key_mapping_win.h" +#include "platform/windows/key_mapping_windows.h" #include <collection.h> @@ -99,7 +99,7 @@ void App::Initialize(CoreApplicationView ^ applicationView) { // Information about the Suspending and Resuming event handlers can be found here: // http://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh994930.aspx - os = new OSUWP; + os = new OS_UWP; } // Called when the CoreWindow object is created (or re-created). @@ -398,7 +398,7 @@ void App::OnMouseMoved(MouseDevice ^ mouse_device, MouseEventArgs ^ args) { void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Windows::UI::Core::KeyEventArgs ^ key_args, Windows::UI::Core::CharacterReceivedEventArgs ^ char_args) { - OSUWP::KeyEvent ke; + OS_UWP::KeyEvent ke; ke.control = sender->GetAsyncKeyState(VirtualKey::Control) == CoreVirtualKeyStates::Down; ke.alt = sender->GetAsyncKeyState(VirtualKey::Menu) == CoreVirtualKeyStates::Down; @@ -408,14 +408,14 @@ void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Wind if (key_args != nullptr) { - ke.type = OSUWP::KeyEvent::MessageType::KEY_EVENT_MESSAGE; + ke.type = OS_UWP::KeyEvent::MessageType::KEY_EVENT_MESSAGE; ke.unicode = 0; ke.scancode = KeyMappingWindows::get_keysym((unsigned int)key_args->VirtualKey); ke.echo = (!p_pressed && !key_args->KeyStatus.IsKeyReleased) || (p_pressed && key_args->KeyStatus.WasKeyDown); } else { - ke.type = OSUWP::KeyEvent::MessageType::CHAR_EVENT_MESSAGE; + ke.type = OS_UWP::KeyEvent::MessageType::CHAR_EVENT_MESSAGE; ke.unicode = char_args->KeyCode; ke.scancode = 0; ke.echo = (!p_pressed && !char_args->KeyStatus.IsKeyReleased) || (p_pressed && char_args->KeyStatus.WasKeyDown); diff --git a/platform/uwp/app.h b/platform/uwp/app.h index 5f69f2cb0e..0bd996d483 100644 --- a/platform/uwp/app.h +++ b/platform/uwp/app.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -103,7 +103,7 @@ namespace GodotUWP EGLSurface mEglSurface; CoreWindow^ window; - OSUWP* os; + OS_UWP* os; int last_touch_x[32]; // 20 fingers, index 31 reserved for the mouse int last_touch_y[32]; diff --git a/platform/uwp/gl_context_egl.cpp b/platform/uwp/context_egl_uwp.cpp index 6c60b27f5a..061c54687c 100644 --- a/platform/uwp/gl_context_egl.cpp +++ b/platform/uwp/context_egl_uwp.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* gl_context_egl.cpp */ +/* context_egl_uwp.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,33 +28,33 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "gl_context_egl.h" +#include "context_egl_uwp.h" #include "EGL/eglext.h" using Platform::Exception; -void ContextEGL::release_current() { +void ContextEGL_UWP::release_current() { eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglContext); }; -void ContextEGL::make_current() { +void ContextEGL_UWP::make_current() { eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext); }; -int ContextEGL::get_window_width() { +int ContextEGL_UWP::get_window_width() { return width; }; -int ContextEGL::get_window_height() { +int ContextEGL_UWP::get_window_height() { return height; }; -void ContextEGL::reset() { +void ContextEGL_UWP::reset() { cleanup(); @@ -62,7 +62,7 @@ void ContextEGL::reset() { initialize(); }; -void ContextEGL::swap_buffers() { +void ContextEGL_UWP::swap_buffers() { if (eglSwapBuffers(mEglDisplay, mEglSurface) != EGL_TRUE) { cleanup(); @@ -74,7 +74,7 @@ void ContextEGL::swap_buffers() { } }; -Error ContextEGL::initialize() { +Error ContextEGL_UWP::initialize() { EGLint configAttribList[] = { EGL_RED_SIZE, 8, @@ -190,7 +190,7 @@ Error ContextEGL::initialize() { return OK; }; -void ContextEGL::cleanup() { +void ContextEGL_UWP::cleanup() { if (mEglDisplay != EGL_NO_DISPLAY && mEglSurface != EGL_NO_SURFACE) { eglDestroySurface(mEglDisplay, mEglSurface); @@ -208,14 +208,14 @@ void ContextEGL::cleanup() { } }; -ContextEGL::ContextEGL(CoreWindow ^ p_window, Driver p_driver) : +ContextEGL_UWP::ContextEGL_UWP(CoreWindow ^ p_window, Driver p_driver) : mEglDisplay(EGL_NO_DISPLAY), mEglContext(EGL_NO_CONTEXT), mEglSurface(EGL_NO_SURFACE), driver(p_driver), window(p_window) {} -ContextEGL::~ContextEGL() { +ContextEGL_UWP::~ContextEGL_UWP() { cleanup(); }; diff --git a/platform/uwp/gl_context_egl.h b/platform/uwp/context_egl_uwp.h index 3c7115cc34..0c62fe7456 100644 --- a/platform/uwp/gl_context_egl.h +++ b/platform/uwp/context_egl_uwp.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* gl_context_egl.h */ +/* context_egl_uwp.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,19 +28,19 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef CONTEXT_EGL_H -#define CONTEXT_EGL_H +#ifndef CONTEXT_EGL_UWP_H +#define CONTEXT_EGL_UWP_H #include <wrl.h> -#include "EGL/egl.h" +#include <EGL/egl.h> + #include "core/error_list.h" #include "core/os/os.h" -#include "drivers/gl_context/context_gl.h" using namespace Windows::UI::Core; -class ContextEGL : public ContextGL { +class ContextEGL_UWP { public: enum Driver { @@ -63,24 +63,24 @@ private: Driver driver; public: - virtual void release_current(); + void release_current(); - virtual void make_current(); + void make_current(); - virtual int get_window_width(); - virtual int get_window_height(); - virtual void swap_buffers(); + int get_window_width(); + int get_window_height(); + void swap_buffers(); - virtual void set_use_vsync(bool use) { vsync = use; } - virtual bool is_using_vsync() const { return vsync; } + void set_use_vsync(bool use) { vsync = use; } + bool is_using_vsync() const { return vsync; } - virtual Error initialize(); + Error initialize(); void reset(); void cleanup(); - ContextEGL(CoreWindow ^ p_window, Driver p_driver); - virtual ~ContextEGL(); + ContextEGL_UWP(CoreWindow ^ p_window, Driver p_driver); + ~ContextEGL_UWP(); }; -#endif +#endif // CONTEXT_EGL_UWP_H diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py index f25b9ba9cd..00f419f4f0 100644 --- a/platform/uwp/detect.py +++ b/platform/uwp/detect.py @@ -1,6 +1,5 @@ import methods import os -import string import sys @@ -25,11 +24,8 @@ def can_build(): def get_opts(): - from SCons.Variables import BoolVariable - return [ ('msvc_version', 'MSVC version to use (ignored if the VCINSTALLDIR environment variable is set)', None), - BoolVariable('use_mingw', 'Use the MinGW compiler even if MSVC is installed (only used on Windows)', False), ] @@ -57,18 +53,20 @@ def configure(env): ## Build type if (env["target"] == "release"): - env.Append(CPPFLAGS=['/O2', '/GL']) - env.Append(CPPFLAGS=['/MD']) + env.Append(CCFLAGS=['/O2', '/GL']) + env.Append(CCFLAGS=['/MD']) env.Append(LINKFLAGS=['/SUBSYSTEM:WINDOWS', '/LTCG']) elif (env["target"] == "release_debug"): - env.Append(CCFLAGS=['/O2', '/Zi', '/DDEBUG_ENABLED']) - env.Append(CPPFLAGS=['/MD']) + env.Append(CCFLAGS=['/O2', '/Zi']) + env.Append(CCFLAGS=['/MD']) + env.Append(CPPFLAGS=['/DDEBUG_ENABLED']) env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE']) elif (env["target"] == "debug"): - env.Append(CCFLAGS=['/Zi', '/DDEBUG_ENABLED', '/DDEBUG_MEMORY_ENABLED']) - env.Append(CPPFLAGS=['/MDd']) + env.Append(CCFLAGS=['/Zi']) + env.Append(CCFLAGS=['/MDd']) + env.Append(CPPFLAGS=['/DDEBUG_ENABLED', '/DDEBUG_MEMORY_ENABLED']) env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE']) env.Append(LINKFLAGS=['/DEBUG']) @@ -79,7 +77,7 @@ def configure(env): # ANGLE angle_root = os.getenv("ANGLE_SRC_PATH") - env.Append(CPPPATH=[angle_root + '/include']) + env.Prepend(CPPPATH=[angle_root + '/include']) jobs = str(env.GetOption("num_jobs")) angle_build_cmd = "msbuild.exe " + angle_root + "/winrt/10/src/angle.sln /nologo /v:m /m:" + jobs + " /p:Configuration=Release /p:Platform=" @@ -139,19 +137,20 @@ def configure(env): ## Compile flags - env.Append(CPPPATH=['#platform/uwp', '#drivers/windows']) - env.Append(CCFLAGS=['/DUWP_ENABLED', '/DWINDOWS_ENABLED', '/DTYPED_METHOD_BIND']) - env.Append(CCFLAGS=['/DGLES_ENABLED', '/DGL_GLEXT_PROTOTYPES', '/DEGL_EGLEXT_PROTOTYPES', '/DANGLE_ENABLED']) + env.Prepend(CPPPATH=['#platform/uwp', '#drivers/windows']) + env.Append(CPPFLAGS=['/DUWP_ENABLED', '/DWINDOWS_ENABLED', '/DTYPED_METHOD_BIND']) + env.Append(CPPFLAGS=['/DGLES_ENABLED', '/DGL_GLEXT_PROTOTYPES', '/DEGL_EGLEXT_PROTOTYPES', '/DANGLE_ENABLED']) winver = "0x0602" # Windows 8 is the minimum target for UWP build - env.Append(CCFLAGS=['/DWINVER=%s' % winver, '/D_WIN32_WINNT=%s' % winver]) + env.Append(CPPFLAGS=['/DWINVER=%s' % winver, '/D_WIN32_WINNT=%s' % winver]) - env.Append(CPPFLAGS=['/D', '__WRL_NO_DEFAULT_LIB__', '/D', 'WIN32', '/DPNG_ABORT=abort']) + env.Append(CPPFLAGS=['/D__WRL_NO_DEFAULT_LIB__', '/DWIN32', '/DPNG_ABORT=abort']) env.Append(CPPFLAGS=['/AI', vc_base_path + 'lib/store/references']) env.Append(CPPFLAGS=['/AI', vc_base_path + 'lib/x86/store/references']) - env.Append(CCFLAGS='/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /D "_UNICODE" /D "UNICODE" /D "WINAPI_FAMILY=WINAPI_FAMILY_APP" /errorReport:prompt /WX- /Zc:forScope /Gd /EHsc /nologo'.split()) - env.Append(CXXFLAGS='/ZW /FS'.split()) + env.Append(CCFLAGS='/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /errorReport:prompt /WX- /Zc:forScope /Gd /EHsc /nologo'.split()) + env.Append(CPPFLAGS=['/D_UNICODE', '/DUNICODE', '/D "WINAPI_FAMILY=WINAPI_FAMILY_APP"']) + env.Append(CXXFLAGS=['/ZW']) env.Append(CCFLAGS=['/AI', vc_base_path + '\\vcpackages', '/AI', os.environ['WINDOWSSDKDIR'] + '\\References\\CommonConfiguration\\Neutral']) ## Link flags diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index c0ea13e7fb..cdcad33f6d 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -73,7 +73,7 @@ static const char *uwp_uap_capabilities[] = { "voipCall", NULL }; -static const char *uwp_device_capabilites[] = { +static const char *uwp_device_capabilities[] = { "bluetooth", "location", "microphone", @@ -187,7 +187,7 @@ class AppxPackager { public: void set_progress_task(String p_task) { progress_task = p_task; } void init(FileAccess *p_fa); - void add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress = false); + Error add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress = false); void finish(); AppxPackager(); @@ -468,10 +468,12 @@ void AppxPackager::init(FileAccess *p_fa) { tmp_content_types_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml"); } -void AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress) { +Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress) { if (p_file_no >= 1 && p_total_files >= 1) { - EditorNode::progress_task_step(progress_task, "File: " + p_file_name, (p_file_no * 100) / p_total_files); + if (EditorNode::progress_task_step(progress_task, "File: " + p_file_name, (p_file_no * 100) / p_total_files)) { + return ERR_SKIP; + } } FileMeta meta; @@ -505,7 +507,7 @@ void AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t while (p_len - step > 0) { - size_t block_size = (p_len - step) > BLOCK_SIZE ? BLOCK_SIZE : (p_len - step); + size_t block_size = (p_len - step) > BLOCK_SIZE ? (size_t)BLOCK_SIZE : (p_len - step); for (uint32_t i = 0; i < block_size; i++) { strm_in.write[i] = p_buffer[step + i]; @@ -584,6 +586,8 @@ void AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t package->store_buffer(file_buffer.ptr(), file_buffer.size()); file_metadata.push_back(meta); + + return OK; } void AppxPackager::finish() { @@ -646,9 +650,9 @@ AppxPackager::~AppxPackager() {} //////////////////////////////////////////////////////////////////// -class EditorExportUWP : public EditorExportPlatform { +class EditorExportPlatformUWP : public EditorExportPlatform { - GDCLASS(EditorExportUWP, EditorExportPlatform); + GDCLASS(EditorExportPlatformUWP, EditorExportPlatform); Ref<ImageTexture> logo; @@ -841,7 +845,7 @@ class EditorExportUWP : public EditorExportPlatform { } uap++; } - const char **device = uwp_device_capabilites; + const char **device = uwp_device_capabilities; while (*device) { if ((bool)p_preset->get("capabilities/" + String(*device))) { capabilities_elements += " <DeviceCapability Name=\"" + String(*device) + "\" />\n"; @@ -1008,9 +1012,7 @@ class EditorExportUWP : public EditorExportPlatform { AppxPackager *packager = (AppxPackager *)p_userdata; String dst_path = p_path.replace_first("res://", "game/"); - packager->add_file(dst_path, p_data.ptr(), p_data.size(), p_file, p_total, _should_compress_asset(p_path, p_data)); - - return OK; + return packager->add_file(dst_path, p_data.ptr(), p_data.size(), p_file, p_total, _should_compress_asset(p_path, p_data)); } public: @@ -1035,13 +1037,13 @@ public: r_features->push_back("s3tc"); r_features->push_back("etc"); switch ((int)p_preset->get("architecture/target")) { - case EditorExportUWP::ARM: { + case EditorExportPlatformUWP::ARM: { r_features->push_back("arm"); } break; - case EditorExportUWP::X86: { + case EditorExportPlatformUWP::X86: { r_features->push_back("32"); } break; - case EditorExportUWP::X64: { + case EditorExportPlatformUWP::X64: { r_features->push_back("64"); } break; } @@ -1105,7 +1107,7 @@ public: uap++; } - const char **device = uwp_device_capabilites; + const char **device = uwp_device_capabilities; while (*device) { r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/" + String(*device).camelcase_to_underscore(false)), false)); device++; @@ -1123,13 +1125,13 @@ public: String platform_infix; switch (arch) { - case EditorExportUWP::ARM: { + case EditorExportPlatformUWP::ARM: { platform_infix = "arm"; } break; - case EditorExportUWP::X86: { + case EditorExportPlatformUWP::X86: { platform_infix = "x86"; } break; - case EditorExportUWP::X64: { + case EditorExportPlatformUWP::X64: { platform_infix = "x64"; } break; } @@ -1151,12 +1153,12 @@ public: if (!FileAccess::exists(custom_debug_binary)) { dvalid = false; - err = "\nCustom debug binary not found."; + err += TTR("Custom debug template not found.") + "\n"; } if (!FileAccess::exists(custom_release_binary)) { rvalid = false; - err += "\nCustom release binary not found."; + err += TTR("Custom release template not found.") + "\n"; } if (dvalid || rvalid) @@ -1169,57 +1171,57 @@ public: if (!_valid_resource_name(p_preset->get("package/unique_name"))) { valid = false; - err += "\nInvalid unique name."; + err += TTR("Invalid package unique name.") + "\n"; } if (!_valid_guid(p_preset->get("identity/product_guid"))) { valid = false; - err += "\nInvalid product GUID."; + err += TTR("Invalid product GUID.") + "\n"; } if (!_valid_guid(p_preset->get("identity/publisher_guid"))) { valid = false; - err += "\nInvalid publisher GUID."; + err += TTR("Invalid publisher GUID.") + "\n"; } if (!_valid_bgcolor(p_preset->get("images/background_color"))) { valid = false; - err += "\nInvalid background color."; + err += TTR("Invalid background color.") + "\n"; } if (!p_preset->get("images/store_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/store_logo"))), 50, 50)) { valid = false; - err += "\nInvalid Store Logo image dimensions (should be 50x50)."; + err += TTR("Invalid Store Logo image dimensions (should be 50x50).") + "\n"; } if (!p_preset->get("images/square44x44_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square44x44_logo"))), 44, 44)) { valid = false; - err += "\nInvalid square 44x44 logo image dimensions (should be 44x44)."; + err += TTR("Invalid square 44x44 logo image dimensions (should be 44x44).") + "\n"; } if (!p_preset->get("images/square71x71_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square71x71_logo"))), 71, 71)) { valid = false; - err += "\nInvalid square 71x71 logo image dimensions (should be 71x71)."; + err += TTR("Invalid square 71x71 logo image dimensions (should be 71x71).") + "\n"; } if (!p_preset->get("images/square150x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square150x150_logo"))), 150, 0)) { valid = false; - err += "\nInvalid square 150x150 logo image dimensions (should be 150x150)."; + err += TTR("Invalid square 150x150 logo image dimensions (should be 150x150).") + "\n"; } if (!p_preset->get("images/square310x310_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/square310x310_logo"))), 310, 310)) { valid = false; - err += "\nInvalid square 310x310 logo image dimensions (should be 310x310)."; + err += TTR("Invalid square 310x310 logo image dimensions (should be 310x310).") + "\n"; } if (!p_preset->get("images/wide310x150_logo").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/wide310x150_logo"))), 310, 150)) { valid = false; - err += "\nInvalid wide 310x150 logo image dimensions (should be 310x150)."; + err += TTR("Invalid wide 310x150 logo image dimensions (should be 310x150).") + "\n"; } if (!p_preset->get("images/splash_screen").is_zero() && !_valid_image((Object::cast_to<StreamTexture>((Object *)p_preset->get("images/splash_screen"))), 620, 300)) { valid = false; - err += "\nInvalid splash screen image dimensions (should be 620x300)."; + err += TTR("Invalid splash screen image dimensions (should be 620x300).") + "\n"; } r_error = err; @@ -1230,7 +1232,7 @@ public: String src_appx; - EditorProgress ep("export", "Exporting for Windows Universal", 7); + EditorProgress ep("export", "Exporting for Windows Universal", 7, true); if (p_debug) src_appx = p_preset->get("custom_template/debug"); @@ -1265,6 +1267,10 @@ public: } } + if (!DirAccess::exists(p_path.get_base_dir())) { + return ERR_FILE_BAD_PATH; + } + Error err = OK; FileAccess *fa_pack = FileAccess::open(p_path, FileAccess::WRITE, &err); @@ -1276,7 +1282,9 @@ public: FileAccess *src_f = NULL; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - ep.step("Creating package...", 0); + if (ep.step("Creating package...", 0)) { + return ERR_SKIP; + } unzFile pkg = unzOpen2(src_appx.utf8().get_data(), &io); @@ -1288,7 +1296,9 @@ public: int ret = unzGoToFirstFile(pkg); - ep.step("Copying template files...", 1); + if (ep.step("Copying template files...", 1)) { + return ERR_SKIP; + } EditorNode::progress_add_task("template_files", "Template files", 100); packager.set_progress_task("template_files"); @@ -1337,14 +1347,19 @@ public: print_line("ADDING: " + path); - packager.add_file(path, data.ptr(), data.size(), template_file_no++, template_files_amount, _should_compress_asset(path, data)); + err = packager.add_file(path, data.ptr(), data.size(), template_file_no++, template_files_amount, _should_compress_asset(path, data)); + if (err != OK) { + return err; + } ret = unzGoToNextFile(pkg); } EditorNode::progress_end_task("template_files"); - ep.step("Creating command line...", 2); + if (ep.step("Creating command line...", 2)) { + return ERR_SKIP; + } Vector<String> cl = ((String)p_preset->get("command_line/extra_args")).strip_edges().split(" "); for (int i = 0; i < cl.size(); i++) { @@ -1378,9 +1393,14 @@ public: print_line(itos(i) + " param: " + cl[i]); } - packager.add_file("__cl__.cl", clf.ptr(), clf.size(), -1, -1, false); + err = packager.add_file("__cl__.cl", clf.ptr(), clf.size(), -1, -1, false); + if (err != OK) { + return err; + } - ep.step("Adding project files...", 3); + if (ep.step("Adding project files...", 3)) { + return ERR_SKIP; + } EditorNode::progress_add_task("project_files", "Project Files", 100); packager.set_progress_task("project_files"); @@ -1389,7 +1409,9 @@ public: EditorNode::progress_end_task("project_files"); - ep.step("Closing package...", 7); + if (ep.step("Closing package...", 7)) { + return ERR_SKIP; + } unzClose(pkg); @@ -1459,7 +1481,7 @@ public: virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { } - EditorExportUWP() { + EditorExportPlatformUWP() { Ref<Image> img = memnew(Image(_uwp_logo)); logo.instance(); logo->create_from_image(img); @@ -1478,7 +1500,7 @@ void register_uwp_exporter() { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "export/uwp/debug_algorithm", PROPERTY_HINT_ENUM, "MD5,SHA1,SHA256")); #endif // WINDOWS_ENABLED - Ref<EditorExportUWP> exporter; + Ref<EditorExportPlatformUWP> exporter; exporter.instance(); EditorExport::get_singleton()->add_export_platform(exporter); } diff --git a/platform/uwp/export/export.h b/platform/uwp/export/export.h index bd759c8647..e4839eeda2 100644 --- a/platform/uwp/export/export.h +++ b/platform/uwp/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/uwp/joypad_uwp.cpp b/platform/uwp/joypad_uwp.cpp index dcc9f7d3af..0899c7592c 100644 --- a/platform/uwp/joypad_uwp.cpp +++ b/platform/uwp/joypad_uwp.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/uwp/joypad_uwp.h b/platform/uwp/joypad_uwp.h index 98202cce9e..31fc9bcd11 100644 --- a/platform/uwp/joypad_uwp.h +++ b/platform/uwp/joypad_uwp.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp index 1f81d476ea..f9d22481dd 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -67,22 +67,22 @@ using namespace Windows::Devices::Sensors; using namespace Windows::ApplicationModel::DataTransfer; using namespace concurrency; -int OSUWP::get_video_driver_count() const { +int OS_UWP::get_video_driver_count() const { return 2; } -Size2 OSUWP::get_window_size() const { +Size2 OS_UWP::get_window_size() const { Size2 size; size.width = video_mode.width; size.height = video_mode.height; return size; } -int OSUWP::get_current_video_driver() const { +int OS_UWP::get_current_video_driver() const { return video_driver_index; } -void OSUWP::set_window_size(const Size2 p_size) { +void OS_UWP::set_window_size(const Size2 p_size) { Windows::Foundation::Size new_size; new_size.Width = p_size.width; @@ -97,7 +97,7 @@ void OSUWP::set_window_size(const Size2 p_size) { } } -void OSUWP::set_window_fullscreen(bool p_enabled) { +void OS_UWP::set_window_fullscreen(bool p_enabled) { ApplicationView ^ view = ApplicationView::GetForCurrentView(); @@ -117,12 +117,12 @@ void OSUWP::set_window_fullscreen(bool p_enabled) { } } -bool OSUWP::is_window_fullscreen() const { +bool OS_UWP::is_window_fullscreen() const { return ApplicationView::GetForCurrentView()->IsFullScreenMode; } -void OSUWP::set_keep_screen_on(bool p_enabled) { +void OS_UWP::set_keep_screen_on(bool p_enabled) { if (is_keep_screen_on() == p_enabled) return; @@ -134,7 +134,7 @@ void OSUWP::set_keep_screen_on(bool p_enabled) { OS::set_keep_screen_on(p_enabled); } -void OSUWP::initialize_core() { +void OS_UWP::initialize_core() { last_button_state = 0; @@ -167,49 +167,49 @@ void OSUWP::initialize_core() { cursor_shape = CURSOR_ARROW; } -bool OSUWP::can_draw() const { +bool OS_UWP::can_draw() const { return !minimized; }; -void OSUWP::set_window(Windows::UI::Core::CoreWindow ^ p_window) { +void OS_UWP::set_window(Windows::UI::Core::CoreWindow ^ p_window) { window = p_window; } -void OSUWP::screen_size_changed() { +void OS_UWP::screen_size_changed() { gl_context->reset(); }; -Error OSUWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { +Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { main_loop = NULL; outside = true; - ContextEGL::Driver opengl_api_type = ContextEGL::GLES_2_0; + ContextEGL_UWP::Driver opengl_api_type = ContextEGL_UWP::GLES_2_0; if (p_video_driver == VIDEO_DRIVER_GLES2) { - opengl_api_type = ContextEGL::GLES_2_0; + opengl_api_type = ContextEGL_UWP::GLES_2_0; } bool gl_initialization_error = false; gl_context = NULL; while (!gl_context) { - gl_context = memnew(ContextEGL(window, opengl_api_type)); + gl_context = memnew(ContextEGL_UWP(window, opengl_api_type)); if (gl_context->initialize() != OK) { memdelete(gl_context); gl_context = NULL; - if (GLOBAL_GET("rendering/quality/driver/driver_fallback") == "Best") { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) { if (p_video_driver == VIDEO_DRIVER_GLES2) { gl_initialization_error = true; break; } p_video_driver = VIDEO_DRIVER_GLES2; - opengl_api_type = ContextEGL::GLES_2_0; + opengl_api_type = ContextEGL_UWP::GLES_2_0; } else { gl_initialization_error = true; break; @@ -218,15 +218,15 @@ Error OSUWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_au } while (true) { - if (opengl_api_type == ContextEGL::GLES_3_0) { + if (opengl_api_type == ContextEGL_UWP::GLES_3_0) { if (RasterizerGLES3::is_viable() == OK) { RasterizerGLES3::register_config(); RasterizerGLES3::make_current(); break; } else { - if (GLOBAL_GET("rendering/quality/driver/driver_fallback") == "Best") { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) { p_video_driver = VIDEO_DRIVER_GLES2; - opengl_api_type = ContextEGL::GLES_2_0; + opengl_api_type = ContextEGL_UWP::GLES_2_0; continue; } else { gl_initialization_error = true; @@ -235,7 +235,7 @@ Error OSUWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_au } } - if (opengl_api_type == ContextEGL::GLES_2_0) { + if (opengl_api_type == ContextEGL_UWP::GLES_2_0) { if (RasterizerGLES2::is_viable() == OK) { RasterizerGLES2::register_config(); RasterizerGLES2::make_current(); @@ -349,7 +349,7 @@ Error OSUWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_au return OK; } -void OSUWP::set_clipboard(const String &p_text) { +void OS_UWP::set_clipboard(const String &p_text) { DataPackage ^ clip = ref new DataPackage(); clip->RequestedOperation = DataPackageOperation::Copy; @@ -358,7 +358,7 @@ void OSUWP::set_clipboard(const String &p_text) { Clipboard::SetContent(clip); }; -String OSUWP::get_clipboard() const { +String OS_UWP::get_clipboard() const { if (managed_object->clipboard != nullptr) return managed_object->clipboard->Data(); @@ -366,25 +366,25 @@ String OSUWP::get_clipboard() const { return ""; }; -void OSUWP::input_event(const Ref<InputEvent> &p_event) { +void OS_UWP::input_event(const Ref<InputEvent> &p_event) { input->parse_input_event(p_event); }; -void OSUWP::delete_main_loop() { +void OS_UWP::delete_main_loop() { if (main_loop) memdelete(main_loop); main_loop = NULL; } -void OSUWP::set_main_loop(MainLoop *p_main_loop) { +void OS_UWP::set_main_loop(MainLoop *p_main_loop) { input->set_main_loop(p_main_loop); main_loop = p_main_loop; } -void OSUWP::finalize() { +void OS_UWP::finalize() { if (main_loop) memdelete(main_loop); @@ -403,19 +403,19 @@ void OSUWP::finalize() { joypad = nullptr; } -void OSUWP::finalize_core() { +void OS_UWP::finalize_core() { NetSocketPosix::cleanup(); } -void OSUWP::alert(const String &p_alert, const String &p_title) { +void OS_UWP::alert(const String &p_alert, const String &p_title) { Platform::String ^ alert = ref new Platform::String(p_alert.c_str()); Platform::String ^ title = ref new Platform::String(p_title.c_str()); MessageDialog ^ msg = ref new MessageDialog(alert, title); - UICommand ^ close = ref new UICommand("Close", ref new UICommandInvokedHandler(managed_object, &OSUWP::ManagedType::alert_close)); + UICommand ^ close = ref new UICommand("Close", ref new UICommandInvokedHandler(managed_object, &OS_UWP::ManagedType::alert_close)); msg->Commands->Append(close); msg->DefaultCommandIndex = 0; @@ -424,17 +424,17 @@ void OSUWP::alert(const String &p_alert, const String &p_title) { msg->ShowAsync(); } -void OSUWP::ManagedType::alert_close(IUICommand ^ command) { +void OS_UWP::ManagedType::alert_close(IUICommand ^ command) { alert_close_handle = false; } -void OSUWP::ManagedType::on_clipboard_changed(Platform::Object ^ sender, Platform::Object ^ ev) { +void OS_UWP::ManagedType::on_clipboard_changed(Platform::Object ^ sender, Platform::Object ^ ev) { update_clipboard(); } -void OSUWP::ManagedType::update_clipboard() { +void OS_UWP::ManagedType::update_clipboard() { DataPackageView ^ data = Clipboard::GetContent(); @@ -446,7 +446,7 @@ void OSUWP::ManagedType::update_clipboard() { } } -void OSUWP::ManagedType::on_accelerometer_reading_changed(Accelerometer ^ sender, AccelerometerReadingChangedEventArgs ^ args) { +void OS_UWP::ManagedType::on_accelerometer_reading_changed(Accelerometer ^ sender, AccelerometerReadingChangedEventArgs ^ args) { AccelerometerReading ^ reading = args->Reading; @@ -456,7 +456,7 @@ void OSUWP::ManagedType::on_accelerometer_reading_changed(Accelerometer ^ sender reading->AccelerationZ)); } -void OSUWP::ManagedType::on_magnetometer_reading_changed(Magnetometer ^ sender, MagnetometerReadingChangedEventArgs ^ args) { +void OS_UWP::ManagedType::on_magnetometer_reading_changed(Magnetometer ^ sender, MagnetometerReadingChangedEventArgs ^ args) { MagnetometerReading ^ reading = args->Reading; @@ -466,7 +466,7 @@ void OSUWP::ManagedType::on_magnetometer_reading_changed(Magnetometer ^ sender, reading->MagneticFieldZ)); } -void OSUWP::ManagedType::on_gyroscope_reading_changed(Gyrometer ^ sender, GyrometerReadingChangedEventArgs ^ args) { +void OS_UWP::ManagedType::on_gyroscope_reading_changed(Gyrometer ^ sender, GyrometerReadingChangedEventArgs ^ args) { GyrometerReading ^ reading = args->Reading; @@ -476,7 +476,7 @@ void OSUWP::ManagedType::on_gyroscope_reading_changed(Gyrometer ^ sender, Gyrome reading->AngularVelocityZ)); } -void OSUWP::set_mouse_mode(MouseMode p_mode) { +void OS_UWP::set_mouse_mode(MouseMode p_mode) { if (p_mode == MouseMode::MOUSE_MODE_CAPTURED) { @@ -501,41 +501,41 @@ void OSUWP::set_mouse_mode(MouseMode p_mode) { SetEvent(mouse_mode_changed); } -OSUWP::MouseMode OSUWP::get_mouse_mode() const { +OS_UWP::MouseMode OS_UWP::get_mouse_mode() const { return mouse_mode; } -Point2 OSUWP::get_mouse_position() const { +Point2 OS_UWP::get_mouse_position() const { return Point2(old_x, old_y); } -int OSUWP::get_mouse_button_state() const { +int OS_UWP::get_mouse_button_state() const { return last_button_state; } -void OSUWP::set_window_title(const String &p_title) { +void OS_UWP::set_window_title(const String &p_title) { } -void OSUWP::set_video_mode(const VideoMode &p_video_mode, int p_screen) { +void OS_UWP::set_video_mode(const VideoMode &p_video_mode, int p_screen) { video_mode = p_video_mode; } -OS::VideoMode OSUWP::get_video_mode(int p_screen) const { +OS::VideoMode OS_UWP::get_video_mode(int p_screen) const { return video_mode; } -void OSUWP::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { +void OS_UWP::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { } -String OSUWP::get_name() { +String OS_UWP::get_name() const { return "UWP"; } -OS::Date OSUWP::get_date(bool utc) const { +OS::Date OS_UWP::get_date(bool utc) const { SYSTEMTIME systemtime; if (utc) @@ -551,7 +551,7 @@ OS::Date OSUWP::get_date(bool utc) const { date.dst = false; return date; } -OS::Time OSUWP::get_time(bool utc) const { +OS::Time OS_UWP::get_time(bool utc) const { SYSTEMTIME systemtime; if (utc) @@ -566,7 +566,7 @@ OS::Time OSUWP::get_time(bool utc) const { return time; } -OS::TimeZoneInfo OSUWP::get_time_zone_info() const { +OS::TimeZoneInfo OS_UWP::get_time_zone_info() const { TIME_ZONE_INFORMATION info; bool daylight = false; if (GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT) @@ -579,11 +579,13 @@ OS::TimeZoneInfo OSUWP::get_time_zone_info() const { ret.name = info.StandardName; } - ret.bias = info.Bias; + // Bias value returned by GetTimeZoneInformation is inverted of what we expect + // For example on GMT-3 GetTimeZoneInformation return a Bias of 180, so invert the value to get -180 + ret.bias = -info.Bias; return ret; } -uint64_t OSUWP::get_unix_time() const { +uint64_t OS_UWP::get_unix_time() const { FILETIME ft; SYSTEMTIME st; @@ -605,14 +607,14 @@ uint64_t OSUWP::get_unix_time() const { return (*(uint64_t *)&ft - *(uint64_t *)&fep) / 10000000; }; -void OSUWP::delay_usec(uint32_t p_usec) const { +void OS_UWP::delay_usec(uint32_t p_usec) const { int msec = p_usec < 1000 ? 1 : p_usec / 1000; // no Sleep() WaitForSingleObjectEx(GetCurrentThread(), msec, false); } -uint64_t OSUWP::get_ticks_usec() const { +uint64_t OS_UWP::get_ticks_usec() const { uint64_t ticks; uint64_t time; @@ -626,13 +628,13 @@ uint64_t OSUWP::get_ticks_usec() const { return time; } -void OSUWP::process_events() { +void OS_UWP::process_events() { joypad->process_controllers(); process_key_events(); } -void OSUWP::process_key_events() { +void OS_UWP::process_key_events() { for (int i = 0; i < key_event_pos; i++) { @@ -653,7 +655,7 @@ void OSUWP::process_key_events() { key_event_pos = 0; } -void OSUWP::queue_key_event(KeyEvent &p_event) { +void OS_UWP::queue_key_event(KeyEvent &p_event) { // This merges Char events with the previous Key event, so // the unicode can be retrieved without sending duplicate events. if (p_event.type == KeyEvent::MessageType::CHAR_EVENT_MESSAGE && key_event_pos > 0) { @@ -670,7 +672,7 @@ void OSUWP::queue_key_event(KeyEvent &p_event) { key_event_buffer[key_event_pos++] = p_event; } -void OSUWP::set_cursor_shape(CursorShape p_shape) { +void OS_UWP::set_cursor_shape(CursorShape p_shape) { ERR_FAIL_INDEX(p_shape, CURSOR_MAX); @@ -702,57 +704,67 @@ void OSUWP::set_cursor_shape(CursorShape p_shape) { cursor_shape = p_shape; } -void OSUWP::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { +OS::CursorShape OS_UWP::get_cursor_shape() const { + + return cursor_shape; +} + +void OS_UWP::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { // TODO } -Error OSUWP::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr) { +Error OS_UWP::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { return FAILED; }; -Error OSUWP::kill(const ProcessID &p_pid) { +Error OS_UWP::kill(const ProcessID &p_pid) { return FAILED; }; -Error OSUWP::set_cwd(const String &p_cwd) { +Error OS_UWP::set_cwd(const String &p_cwd) { return FAILED; } -String OSUWP::get_executable_path() const { +String OS_UWP::get_executable_path() const { return ""; } -void OSUWP::set_icon(const Ref<Image> &p_icon) { +void OS_UWP::set_icon(const Ref<Image> &p_icon) { } -bool OSUWP::has_environment(const String &p_var) const { +bool OS_UWP::has_environment(const String &p_var) const { return false; }; -String OSUWP::get_environment(const String &p_var) const { +String OS_UWP::get_environment(const String &p_var) const { return ""; }; -String OSUWP::get_stdin_string(bool p_block) { +bool OS_UWP::set_environment(const String &p_var, const String &p_value) const { + + return false; +} + +String OS_UWP::get_stdin_string(bool p_block) { return String(); } -void OSUWP::move_window_to_foreground() { +void OS_UWP::move_window_to_foreground() { } -Error OSUWP::shell_open(String p_uri) { +Error OS_UWP::shell_open(String p_uri) { return FAILED; } -String OSUWP::get_locale() const { +String OS_UWP::get_locale() const { #if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP // this should work on phone 8.1, but it doesn't return "en"; @@ -762,39 +774,39 @@ String OSUWP::get_locale() const { #endif } -void OSUWP::release_rendering_thread() { +void OS_UWP::release_rendering_thread() { gl_context->release_current(); } -void OSUWP::make_rendering_thread() { +void OS_UWP::make_rendering_thread() { gl_context->make_current(); } -void OSUWP::swap_buffers() { +void OS_UWP::swap_buffers() { gl_context->swap_buffers(); } -bool OSUWP::has_touchscreen_ui_hint() const { +bool OS_UWP::has_touchscreen_ui_hint() const { TouchCapabilities ^ tc = ref new TouchCapabilities(); return tc->TouchPresent != 0 || UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch; } -bool OSUWP::has_virtual_keyboard() const { +bool OS_UWP::has_virtual_keyboard() const { return UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch; } -void OSUWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect) { +void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect) { InputPane ^ pane = InputPane::GetForCurrentView(); pane->TryShow(); } -void OSUWP::hide_virtual_keyboard() { +void OS_UWP::hide_virtual_keyboard() { InputPane ^ pane = InputPane::GetForCurrentView(); pane->TryHide(); @@ -813,7 +825,7 @@ static String format_error_message(DWORD id) { return msg; } -Error OSUWP::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { +Error OS_UWP::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { String full_path = "game/" + p_path; p_library_handle = (void *)LoadPackagedLibrary(full_path.c_str(), 0); @@ -825,14 +837,14 @@ Error OSUWP::open_dynamic_library(const String p_path, void *&p_library_handle, return OK; } -Error OSUWP::close_dynamic_library(void *p_library_handle) { +Error OS_UWP::close_dynamic_library(void *p_library_handle) { if (!FreeLibrary((HMODULE)p_library_handle)) { return FAILED; } return OK; } -Error OSUWP::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) { +Error OS_UWP::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) { p_symbol_handle = (void *)GetProcAddress((HMODULE)p_library_handle, p_name.utf8().get_data()); if (!p_symbol_handle) { if (!p_optional) { @@ -845,7 +857,7 @@ Error OSUWP::get_dynamic_library_symbol_handle(void *p_library_handle, const Str return OK; } -void OSUWP::run() { +void OS_UWP::run() { if (!main_loop) return; @@ -869,35 +881,35 @@ void OSUWP::run() { main_loop->finish(); } -MainLoop *OSUWP::get_main_loop() const { +MainLoop *OS_UWP::get_main_loop() const { return main_loop; } -String OSUWP::get_user_data_dir() const { +String OS_UWP::get_user_data_dir() const { Windows::Storage::StorageFolder ^ data_folder = Windows::Storage::ApplicationData::Current->LocalFolder; return String(data_folder->Path->Data()).replace("\\", "/"); } -bool OSUWP::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc" || p_feature == "s3tc"; +bool OS_UWP::_check_internal_feature_support(const String &p_feature) { + return p_feature == "pc"; } -OS::PowerState OSUWP::get_power_state() { +OS::PowerState OS_UWP::get_power_state() { return power_manager->get_power_state(); } -int OSUWP::get_power_seconds_left() { +int OS_UWP::get_power_seconds_left() { return power_manager->get_power_seconds_left(); } -int OSUWP::get_power_percent_left() { +int OS_UWP::get_power_percent_left() { return power_manager->get_power_percent_left(); } -OSUWP::OSUWP() { +OS_UWP::OS_UWP() { key_event_pos = 0; force_quit = false; @@ -931,7 +943,7 @@ OSUWP::OSUWP() { _set_logger(memnew(CompositeLogger(loggers))); } -OSUWP::~OSUWP() { +OS_UWP::~OS_UWP() { #ifdef STDOUT_FILE fclose(stdo); #endif diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h index 9641b9cde9..1bb68bc75d 100644 --- a/platform/uwp/os_uwp.h +++ b/platform/uwp/os_uwp.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,15 +28,15 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef OSUWP_H -#define OSUWP_H +#ifndef OS_UWP_H +#define OS_UWP_H +#include "context_egl_uwp.h" #include "core/math/transform_2d.h" #include "core/os/input.h" #include "core/os/os.h" #include "core/ustring.h" #include "drivers/xaudio2/audio_driver_xaudio2.h" -#include "gl_context_egl.h" #include "joypad_uwp.h" #include "main/input_default.h" #include "power_uwp.h" @@ -52,7 +52,7 @@ /** @author Juan Linietsky <reduzio@gmail.com> */ -class OSUWP : public OS { +class OS_UWP : public OS { public: struct KeyEvent { @@ -95,7 +95,7 @@ private: VisualServer *visual_server; int pressrc; - ContextEGL *gl_context; + ContextEGL_UWP *gl_context; Windows::UI::Core::CoreWindow ^ window; VideoMode video_mode; @@ -144,7 +144,7 @@ private: /* clang-format off */ internal: ManagedType() { alert_close_handle = false; } - property OSUWP* os; + property OS_UWP* os; /* clang-format on */ }; ManagedType ^ managed_object; @@ -195,7 +195,7 @@ public: virtual MainLoop *get_main_loop() const; - virtual String get_name(); + virtual String get_name() const; virtual Date get_date(bool utc) const; virtual Time get_time(bool utc) const; @@ -208,16 +208,18 @@ public: virtual void delay_usec(uint32_t p_usec) const; virtual uint64_t get_ticks_usec() const; - virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false); + virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL); virtual Error kill(const ProcessID &p_pid); virtual bool has_environment(const String &p_var) const; virtual String get_environment(const String &p_var) const; + virtual bool set_environment(const String &p_var, const String &p_value) const; virtual void set_clipboard(const String &p_text); virtual String get_clipboard() const; void set_cursor_shape(CursorShape p_shape); + CursorShape get_cursor_shape() const; virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); void set_icon(const Ref<Image> &p_icon); @@ -261,8 +263,8 @@ public: void queue_key_event(KeyEvent &p_event); - OSUWP(); - ~OSUWP(); + OS_UWP(); + ~OS_UWP(); }; #endif diff --git a/platform/uwp/platform_config.h b/platform/uwp/platform_config.h index 606795479b..311a41454d 100644 --- a/platform/uwp/platform_config.h +++ b/platform/uwp/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/uwp/power_uwp.cpp b/platform/uwp/power_uwp.cpp index 4eb48d2dd9..ba1311a1b1 100644 --- a/platform/uwp/power_uwp.cpp +++ b/platform/uwp/power_uwp.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/uwp/power_uwp.h b/platform/uwp/power_uwp.h index da1cffe8f0..cc19904a62 100644 --- a/platform/uwp/power_uwp.h +++ b/platform/uwp/power_uwp.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef PLATFORM_UWP_POWER_UWP_H_ -#define PLATFORM_UWP_POWER_UWP_H_ +#ifndef POWER_UWP_H +#define POWER_UWP_H #include "core/os/dir_access.h" #include "core/os/file_access.h" @@ -53,4 +53,4 @@ public: int get_power_percent_left(); }; -#endif /* PLATFORM_UWP_POWER_UWP_H_ */ +#endif // POWER_UWP_H diff --git a/platform/uwp/thread_uwp.cpp b/platform/uwp/thread_uwp.cpp index c755204ec4..0e4e138aa6 100644 --- a/platform/uwp/thread_uwp.cpp +++ b/platform/uwp/thread_uwp.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/uwp/thread_uwp.h b/platform/uwp/thread_uwp.h index 16e7efb60b..e29b45a903 100644 --- a/platform/uwp/thread_uwp.h +++ b/platform/uwp/thread_uwp.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/SCsub b/platform/windows/SCsub index e07d373c4b..892d734734 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -7,13 +7,12 @@ from platform_methods import run_in_subprocess import platform_windows_builders common_win = [ - "godot_win.cpp", - "context_gl_win.cpp", - "crash_handler_win.cpp", + "godot_windows.cpp", + "context_gl_windows.cpp", + "crash_handler_windows.cpp", "os_windows.cpp", - "ctxgl_procaddr.cpp", - "key_mapping_win.cpp", - "joypad.cpp", + "key_mapping_windows.cpp", + "joypad_windows.cpp", "power_windows.cpp", "windows_terminal_logger.cpp" ] diff --git a/platform/windows/context_gl_win.cpp b/platform/windows/context_gl_windows.cpp index 2d70b00dda..e715999378 100644 --- a/platform/windows/context_gl_win.cpp +++ b/platform/windows/context_gl_windows.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* context_gl_win.cpp */ +/* context_gl_windows.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,7 +32,7 @@ // Author: Juan Linietsky <reduzio@gmail.com>, (C) 2008 -#include "context_gl_win.h" +#include "context_gl_windows.h" #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 @@ -43,32 +43,32 @@ typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *); -void ContextGL_Win::release_current() { +void ContextGL_Windows::release_current() { wglMakeCurrent(hDC, NULL); } -void ContextGL_Win::make_current() { +void ContextGL_Windows::make_current() { wglMakeCurrent(hDC, hRC); } -int ContextGL_Win::get_window_width() { +int ContextGL_Windows::get_window_width() { return OS::get_singleton()->get_video_mode().width; } -int ContextGL_Win::get_window_height() { +int ContextGL_Windows::get_window_height() { return OS::get_singleton()->get_video_mode().height; } -void ContextGL_Win::swap_buffers() { +void ContextGL_Windows::swap_buffers() { SwapBuffers(hDC); } -void ContextGL_Win::set_use_vsync(bool p_use) { +void ContextGL_Windows::set_use_vsync(bool p_use) { if (wglSwapIntervalEXT) { wglSwapIntervalEXT(p_use ? 1 : 0); @@ -76,14 +76,14 @@ void ContextGL_Win::set_use_vsync(bool p_use) { use_vsync = p_use; } -bool ContextGL_Win::is_using_vsync() const { +bool ContextGL_Windows::is_using_vsync() const { return use_vsync; } #define _WGL_CONTEXT_DEBUG_BIT_ARB 0x0001 -Error ContextGL_Win::initialize() { +Error ContextGL_Windows::initialize() { static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor @@ -172,14 +172,14 @@ Error ContextGL_Win::initialize() { return OK; } -ContextGL_Win::ContextGL_Win(HWND hwnd, bool p_opengl_3_context) { +ContextGL_Windows::ContextGL_Windows(HWND hwnd, bool p_opengl_3_context) { opengl_3_context = p_opengl_3_context; hWnd = hwnd; use_vsync = false; } -ContextGL_Win::~ContextGL_Win() { +ContextGL_Windows::~ContextGL_Windows() { } #endif diff --git a/platform/windows/context_gl_win.h b/platform/windows/context_gl_windows.h index 5bcdb433b3..d23fba50e1 100644 --- a/platform/windows/context_gl_win.h +++ b/platform/windows/context_gl_windows.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* context_gl_win.h */ +/* context_gl_windows.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -37,13 +37,12 @@ #include "core/error_list.h" #include "core/os/os.h" -#include "drivers/gl_context/context_gl.h" #include <windows.h> typedef bool(APIENTRY *PFNWGLSWAPINTERVALEXTPROC)(int interval); -class ContextGL_Win : public ContextGL { +class ContextGL_Windows { HDC hDC; HGLRC hRC; @@ -55,21 +54,21 @@ class ContextGL_Win : public ContextGL { PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; public: - virtual void release_current(); + void release_current(); - virtual void make_current(); + void make_current(); - virtual int get_window_width(); - virtual int get_window_height(); - virtual void swap_buffers(); + int get_window_width(); + int get_window_height(); + void swap_buffers(); - virtual Error initialize(); + Error initialize(); - virtual void set_use_vsync(bool p_use); - virtual bool is_using_vsync() const; + void set_use_vsync(bool p_use); + bool is_using_vsync() const; - ContextGL_Win(HWND hwnd, bool p_opengl_3_context); - virtual ~ContextGL_Win(); + ContextGL_Windows(HWND hwnd, bool p_opengl_3_context); + ~ContextGL_Windows(); }; #endif diff --git a/platform/windows/crash_handler_win.cpp b/platform/windows/crash_handler_windows.cpp index 2760e87b8b..0716ee67f4 100644 --- a/platform/windows/crash_handler_win.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* crash_handler_win.cpp */ +/* crash_handler_windows.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,9 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#include "crash_handler_windows.h" + +#include "core/os/os.h" #include "core/project_settings.h" #include "main/main.h" -#include "os_windows.h" #ifdef CRASH_HANDLER_EXCEPTION @@ -39,6 +41,7 @@ #include <psapi.h> #include <algorithm> #include <iterator> +#include <vector> #pragma comment(lib, "psapi.lib") #pragma comment(lib, "dbghelp.lib") @@ -163,11 +166,16 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { line.SizeOfStruct = sizeof(line); IMAGE_NT_HEADERS *h = ImageNtHeader(base); DWORD image_type = h->FileHeader.Machine; - int n = 0; - String msg = GLOBAL_GET("debug/settings/crash_handler/message"); + + String msg; + const ProjectSettings *proj_settings = ProjectSettings::get_singleton(); + if (proj_settings) { + msg = proj_settings->get("debug/settings/crash_handler/message"); + } fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); + int n = 0; do { if (skip_first) { skip_first = false; diff --git a/platform/windows/crash_handler_win.h b/platform/windows/crash_handler_windows.h index 95b1468197..eba72beb7e 100644 --- a/platform/windows/crash_handler_win.h +++ b/platform/windows/crash_handler_windows.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* crash_handler_win.h */ +/* crash_handler_windows.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef CRASH_HANDLER_WIN_H -#define CRASH_HANDLER_WIN_H +#ifndef CRASH_HANDLER_WINDOWS_H +#define CRASH_HANDLER_WINDOWS_H #include <windows.h> @@ -54,4 +54,4 @@ public: ~CrashHandler(); }; -#endif +#endif // CRASH_HANDLER_WINDOWS_H diff --git a/platform/windows/ctxgl_procaddr.cpp b/platform/windows/ctxgl_procaddr.cpp deleted file mode 100644 index 434eeea16e..0000000000 --- a/platform/windows/ctxgl_procaddr.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/*************************************************************************/ -/* ctxgl_procaddr.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifdef OPENGL_ENABLED -#include "ctxgl_procaddr.h" -#include <GL/gl.h> -#include <stdio.h> - -static PROC _gl_procs[] = { - (PROC)glCullFace, - (PROC)glFrontFace, - (PROC)glHint, - (PROC)glLineWidth, - (PROC)glPointSize, - (PROC)glPolygonMode, - (PROC)glScissor, - (PROC)glTexParameterf, - (PROC)glTexParameterfv, - (PROC)glTexParameteri, - (PROC)glTexParameteriv, - (PROC)glTexImage1D, - (PROC)glTexImage2D, - (PROC)glDrawBuffer, - (PROC)glClear, - (PROC)glClearColor, - (PROC)glClearStencil, - (PROC)glClearDepth, - (PROC)glStencilMask, - (PROC)glColorMask, - (PROC)glDepthMask, - (PROC)glDisable, - (PROC)glEnable, - (PROC)glFinish, - (PROC)glFlush, - (PROC)glBlendFunc, - (PROC)glLogicOp, - (PROC)glStencilFunc, - (PROC)glStencilOp, - (PROC)glDepthFunc, - (PROC)glPixelStoref, - (PROC)glPixelStorei, - (PROC)glReadBuffer, - (PROC)glReadPixels, - (PROC)glGetBooleanv, - (PROC)glGetDoublev, - (PROC)glGetError, - (PROC)glGetFloatv, - (PROC)glGetIntegerv, - (PROC)glGetString, - (PROC)glGetTexImage, - (PROC)glGetTexParameterfv, - (PROC)glGetTexParameteriv, - (PROC)glGetTexLevelParameterfv, - (PROC)glGetTexLevelParameteriv, - (PROC)glIsEnabled, - (PROC)glDepthRange, - (PROC)glViewport, - /* not detected in ATI */ - (PROC)glDrawArrays, - (PROC)glDrawElements, - (PROC)glGetPointerv, - (PROC)glPolygonOffset, - (PROC)glCopyTexImage1D, - (PROC)glCopyTexImage2D, - (PROC)glCopyTexSubImage1D, - (PROC)glCopyTexSubImage2D, - (PROC)glTexSubImage1D, - (PROC)glTexSubImage2D, - (PROC)glBindTexture, - (PROC)glDeleteTextures, - (PROC)glGenTextures, - (PROC)glIsTexture, - - 0 -}; - -static const char *_gl_proc_names[] = { - "glCullFace", - "glFrontFace", - "glHint", - "glLineWidth", - "glPointSize", - "glPolygonMode", - "glScissor", - "glTexParameterf", - "glTexParameterfv", - "glTexParameteri", - "glTexParameteriv", - "glTexImage1D", - "glTexImage2D", - "glDrawBuffer", - "glClear", - "glClearColor", - "glClearStencil", - "glClearDepth", - "glStencilMask", - "glColorMask", - "glDepthMask", - "glDisable", - "glEnable", - "glFinish", - "glFlush", - "glBlendFunc", - "glLogicOp", - "glStencilFunc", - "glStencilOp", - "glDepthFunc", - "glPixelStoref", - "glPixelStorei", - "glReadBuffer", - "glReadPixels", - "glGetBooleanv", - "glGetDoublev", - "glGetError", - "glGetFloatv", - "glGetIntegerv", - "glGetString", - "glGetTexImage", - "glGetTexParameterfv", - "glGetTexParameteriv", - "glGetTexLevelParameterfv", - "glGetTexLevelParameteriv", - "glIsEnabled", - "glDepthRange", - "glViewport", - /* not detected in ati */ - "glDrawArrays", - "glDrawElements", - "glGetPointerv", - "glPolygonOffset", - "glCopyTexImage1D", - "glCopyTexImage2D", - "glCopyTexSubImage1D", - "glCopyTexSubImage2D", - "glTexSubImage1D", - "glTexSubImage2D", - "glBindTexture", - "glDeleteTextures", - "glGenTextures", - "glIsTexture", - - 0 -}; - -PROC get_gl_proc_address(const char *p_address) { - - PROC proc = wglGetProcAddress((const CHAR *)p_address); - if (!proc) { - - int i = 0; - while (_gl_procs[i]) { - - if (strcmp(p_address, _gl_proc_names[i]) == 0) { - return _gl_procs[i]; - } - i++; - } - } - return proc; -} -#endif diff --git a/platform/windows/detect.py b/platform/windows/detect.py index e14db9a201..4b4b507499 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -1,6 +1,5 @@ import methods import os -import sys def is_active(): @@ -197,19 +196,21 @@ def configure_msvc(env, manual_msvc_config): ## Compile/link flags env.AppendUnique(CCFLAGS=['/MT', '/Gd', '/GR', '/nologo']) + if int(env['MSVC_VERSION'].split('.')[0]) >= 14: #vs2015 and later + env.AppendUnique(CCFLAGS=['/utf-8']) env.AppendUnique(CXXFLAGS=['/TP']) # assume all sources are C++ if manual_msvc_config: # should be automatic if SCons found it if os.getenv("WindowsSdkDir") is not None: - env.Append(CPPPATH=[os.getenv("WindowsSdkDir") + "/Include"]) + env.Prepend(CPPPATH=[os.getenv("WindowsSdkDir") + "/Include"]) else: print("Missing environment variable: WindowsSdkDir") env.AppendUnique(CPPDEFINES = ['WINDOWS_ENABLED', 'OPENGL_ENABLED', - 'RTAUDIO_ENABLED', 'WASAPI_ENABLED', - 'WINMIDI_ENABLED', 'TYPED_METHOD_BIND', + 'WASAPI_ENABLED', 'WINMIDI_ENABLED', + 'TYPED_METHOD_BIND', 'WIN32', 'MSVC', - 'WINVER=$target_win_version', - '_WIN32_WINNT=$target_win_version']) + 'WINVER=%s' % env["target_win_version"], + '_WIN32_WINNT=%s' % env["target_win_version"]]) env.AppendUnique(CPPDEFINES=['NOMINMAX']) # disable bogus min/max WinDef.h macros if env["bits"] == "64": env.AppendUnique(CPPDEFINES=['_WIN64']) @@ -218,7 +219,7 @@ def configure_msvc(env, manual_msvc_config): LIBS = ['winmm', 'opengl32', 'dsound', 'kernel32', 'ole32', 'oleaut32', 'user32', 'gdi32', 'IPHLPAPI', 'Shlwapi', 'wsock32', 'Ws2_32', - 'shell32', 'advapi32', 'dinput8', 'dxguid', 'imm32', 'bcrypt'] + 'shell32', 'advapi32', 'dinput8', 'dxguid', 'imm32', 'bcrypt','Avrt'] env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS]) if manual_msvc_config: @@ -238,7 +239,7 @@ def configure_msvc(env, manual_msvc_config): env.AppendUnique(LINKFLAGS=['/LTCG']) if manual_msvc_config: - env.Append(CPPPATH=[p for p in os.getenv("INCLUDE").split(";")]) + env.Prepend(CPPPATH=[p for p in os.getenv("INCLUDE").split(";")]) env.Append(LIBPATH=[p for p in os.getenv("LIB").split(";")]) # Incremental linking fix @@ -272,7 +273,8 @@ def configure_mingw(env): env.Prepend(CCFLAGS=['-g2']) elif (env["target"] == "release_debug"): - env.Append(CCFLAGS=['-O2', '-DDEBUG_ENABLED']) + env.Append(CCFLAGS=['-O2']) + env.Append(CPPFLAGS=['-DDEBUG_ENABLED']) if (env["debug_symbols"] == "yes"): env.Prepend(CCFLAGS=['-g1']) if (env["debug_symbols"] == "full"): @@ -283,7 +285,8 @@ def configure_mingw(env): env.Prepend(CCFLAGS=['-Os']) elif (env["target"] == "debug"): - env.Append(CCFLAGS=['-g3', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) + env.Append(CCFLAGS=['-g3']) + env.Append(CPPFLAGS=['-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) ## Compiler configuration @@ -324,12 +327,13 @@ def configure_mingw(env): ## Compile flags - env.Append(CCFLAGS=['-DWINDOWS_ENABLED', '-mwindows']) - env.Append(CCFLAGS=['-DOPENGL_ENABLED']) - env.Append(CCFLAGS=['-DRTAUDIO_ENABLED']) - env.Append(CCFLAGS=['-DWASAPI_ENABLED']) - env.Append(CCFLAGS=['-DWINVER=%s' % env['target_win_version'], '-D_WIN32_WINNT=%s' % env['target_win_version']]) - env.Append(LIBS=['mingw32', 'opengl32', 'dsound', 'ole32', 'd3d9', 'winmm', 'gdi32', 'iphlpapi', 'shlwapi', 'wsock32', 'ws2_32', 'kernel32', 'oleaut32', 'dinput8', 'dxguid', 'ksuser', 'imm32', 'bcrypt']) + env.Append(CCFLAGS=['-mwindows']) + env.Append(CPPFLAGS=['-DWINDOWS_ENABLED']) + env.Append(CPPFLAGS=['-DOPENGL_ENABLED']) + env.Append(CPPFLAGS=['-DWASAPI_ENABLED']) + env.Append(CPPFLAGS=['-DWINMIDI_ENABLED']) + env.Append(CPPFLAGS=['-DWINVER=%s' % env['target_win_version'], '-D_WIN32_WINNT=%s' % env['target_win_version']]) + env.Append(LIBS=['mingw32', 'opengl32', 'dsound', 'ole32', 'd3d9', 'winmm', 'gdi32', 'iphlpapi', 'shlwapi', 'wsock32', 'ws2_32', 'kernel32', 'oleaut32', 'dinput8', 'dxguid', 'ksuser', 'imm32', 'bcrypt', 'avrt', 'uuid']) env.Append(CPPFLAGS=['-DMINGW_ENABLED']) @@ -338,7 +342,7 @@ def configure_mingw(env): def configure(env): # At this point the env has been set up with basic tools/compilers. - env.Append(CPPPATH=['#platform/windows']) + env.Prepend(CPPPATH=['#platform/windows']) print("Configuring for Windows: target=%s, bits=%s" % (env['target'], env['bits'])) @@ -347,12 +351,12 @@ def configure(env): env['ENV']['TMP'] = os.environ['TMP'] # First figure out which compiler, version, and target arch we're using - if os.getenv("VCINSTALLDIR"): + if os.getenv("VCINSTALLDIR") and not env["use_mingw"]: # Manual setup of MSVC setup_msvc_manual(env) env.msvc = True manual_msvc_config = True - elif env.get('MSVC_VERSION', ''): + elif env.get('MSVC_VERSION', '') and not env["use_mingw"]: setup_msvc_auto(env) env.msvc = True manual_msvc_config = False diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index dcaae40b10..141ab96370 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -73,7 +73,7 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> } #endif - String icon_path = p_preset->get("application/icon"); + String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon")); String file_verion = p_preset->get("application/file_version"); String product_version = p_preset->get("application/product_version"); String company_name = p_preset->get("application/company_name"); @@ -137,7 +137,7 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) { EditorExportPlatformPC::get_export_options(r_options); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_GLOBAL_FILE, "*.ico"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), "")); diff --git a/platform/windows/export/export.h b/platform/windows/export/export.h index 7f62e8955c..e3431797b4 100644 --- a/platform/windows/export/export.h +++ b/platform/windows/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/godot.natvis b/platform/windows/godot.natvis index 01963035a1..55c83c3f3c 100644 --- a/platform/windows/godot.natvis +++ b/platform/windows/godot.natvis @@ -2,92 +2,109 @@ <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <Type Name="Vector<*>"> <Expand> - <Item Name="size">(_cowdata && _cowdata->_ptr) ? (((const unsigned int *)(_cowdata->_ptr))[-1]) : 0</Item> + <Item Name="[size]">_cowdata._ptr ? (((const unsigned int *)(_cowdata._ptr))[-1]) : 0</Item> <ArrayItems> - <Size>(_cowdata && _cowdata->_ptr) ? (((const unsigned int *)(_cowdata->_ptr))[-1]) : 0</Size> - <ValuePointer>(_cowdata) ? (_cowdata->_ptr) : 0</ValuePointer> + <Size>_cowdata._ptr ? (((const unsigned int *)(_cowdata._ptr))[-1]) : 0</Size> + <ValuePointer>_cowdata._ptr</ValuePointer> </ArrayItems> </Expand> </Type> <Type Name="PoolVector<*>"> <Expand> - <Item Name="size">alloc ? (alloc->size / sizeof($T1)) : 0</Item> + <Item Name="[size]">alloc ? (alloc->size / sizeof($T1)) : 0</Item> <ArrayItems> <Size>alloc ? (alloc->size / sizeof($T1)) : 0</Size> <ValuePointer>alloc ? (($T1 *)alloc->mem) : 0</ValuePointer> </ArrayItems> </Expand> </Type> + + <Type Name="List<*>"> + <Expand> + <Item Name="[size]">_data ? (_data->size_cache) : 0</Item> + <LinkedListItems> + <Size>_data ? (_data->size_cache) : 0</Size> + <HeadPointer>_data->first</HeadPointer> + <NextPointer>next_ptr</NextPointer> + <ValueNode>value</ValueNode> + </LinkedListItems> + </Expand> + </Type> <Type Name="Variant"> - <DisplayString Condition="this->type == Variant::NIL">nil</DisplayString> - <DisplayString Condition="this->type == Variant::BOOL">{_data._bool}</DisplayString> - <DisplayString Condition="this->type == Variant::INT">{_data._int}</DisplayString> - <DisplayString Condition="this->type == Variant::REAL">{_data._real}</DisplayString> - <DisplayString Condition="this->type == Variant::TRANSFORM2D">{_data._transform2d}</DisplayString> - <DisplayString Condition="this->type == Variant::AABB">{_data._aabb}</DisplayString> - <DisplayString Condition="this->type == Variant::BASIS">{_data._basis}</DisplayString> - <DisplayString Condition="this->type == Variant::TRANSFORM">{_data._transform}</DisplayString> - <DisplayString Condition="this->type == Variant::ARRAY">{*(Array *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::STRING && ((String *)(&_data._mem[0]))->_cowdata._ptr == 0">""</DisplayString> - <DisplayString Condition="this->type == Variant::STRING && ((String *)(&_data._mem[0]))->_cowdata._ptr != 0">{((String *)(&_data._mem[0]))->_cowdata._ptr,su}</DisplayString> - <DisplayString Condition="this->type == Variant::VECTOR2">{*(Vector2 *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::RECT2">{*(Rect2 *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::VECTOR3">{*(Vector3 *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::PLANE">{*(Plane *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::QUAT">{*(Quat *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::COLOR">{*(Color *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::NODE_PATH">{*(NodePath *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::_RID">{*(RID *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::OBJECT">{*(Object *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::DICTIONARY">{*(Dictionary *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::ARRAY">{*(Array *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::POOL_BYTE_ARRAY">{*(PoolByteArray *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::POOL_INT_ARRAY">{*(PoolIntArray *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::POOL_REAL_ARRAY">{*(PoolRealArray *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::POOL_STRING_ARRAY">{*(PoolStringArray *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::POOL_VECTOR2_ARRAY">{*(PoolVector2Array *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::POOL_VECTOR3_ARRAY">{*(PoolVector3Array *)_data._mem}</DisplayString> - <DisplayString Condition="this->type == Variant::POOL_COLOR_ARRAY">{*(PoolColorArray *)_data._mem}</DisplayString> - - <StringView Condition="this->type == Variant::STRING && ((String *)(&_data._mem[0]))->_cowdata._ptr != 0">((String *)(&_data._mem[0]))->_cowdata._ptr,su</StringView> + <DisplayString Condition="type == Variant::NIL">nil</DisplayString> + <DisplayString Condition="type == Variant::BOOL">{_data._bool}</DisplayString> + <DisplayString Condition="type == Variant::INT">{_data._int}</DisplayString> + <DisplayString Condition="type == Variant::REAL">{_data._real}</DisplayString> + <DisplayString Condition="type == Variant::TRANSFORM2D">{_data._transform2d}</DisplayString> + <DisplayString Condition="type == Variant::AABB">{_data._aabb}</DisplayString> + <DisplayString Condition="type == Variant::BASIS">{_data._basis}</DisplayString> + <DisplayString Condition="type == Variant::TRANSFORM">{_data._transform}</DisplayString> + <DisplayString Condition="type == Variant::STRING">{*(String *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::VECTOR2">{*(Vector2 *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::RECT2">{*(Rect2 *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::VECTOR3">{*(Vector3 *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::PLANE">{*(Plane *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::QUAT">{*(Quat *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::COLOR">{*(Color *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::NODE_PATH">{*(NodePath *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::_RID">{*(RID *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::OBJECT">{*(Object *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::DICTIONARY">{*(Dictionary *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::ARRAY">{*(Array *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::POOL_BYTE_ARRAY">{*(PoolByteArray *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::POOL_INT_ARRAY">{*(PoolIntArray *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::POOL_REAL_ARRAY">{*(PoolRealArray *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::POOL_STRING_ARRAY">{*(PoolStringArray *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::POOL_VECTOR2_ARRAY">{*(PoolVector2Array *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::POOL_VECTOR3_ARRAY">{*(PoolVector3Array *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::POOL_COLOR_ARRAY">{*(PoolColorArray *)_data._mem}</DisplayString> + <StringView Condition="type == Variant::STRING && ((String *)(_data._mem))->_cowdata._ptr">((String *)(_data._mem))->_cowdata._ptr,su</StringView> + <Expand> - <Item Name="value" Condition="this->type == Variant::BOOL">_data._bool</Item> - <Item Name="value" Condition="this->type == Variant::INT">_data._int</Item> - <Item Name="value" Condition="this->type == Variant::REAL">_data._real</Item> - <Item Name="value" Condition="this->type == Variant::TRANSFORM2D">_data._transform2d</Item> - <Item Name="value" Condition="this->type == Variant::AABB">_data._aabb</Item> - <Item Name="value" Condition="this->type == Variant::BASIS">_data._basis</Item> - <Item Name="value" Condition="this->type == Variant::TRANSFORM">_data._transform</Item> - <Item Name="value" Condition="this->type == Variant::ARRAY">*(Array *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::STRING">*(String *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::VECTOR2">*(Vector2 *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::RECT2">*(Rect2 *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::VECTOR3">*(Vector3 *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::PLANE">*(Plane *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::QUAT">*(Quat *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::COLOR">*(Color *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::NODE_PATH">*(NodePath *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::_RID">*(RID *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::OBJECT">*(Object *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::DICTIONARY">*(Dictionary *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::ARRAY">*(Array *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::POOL_BYTE_ARRAY">*(PoolByteArray *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::POOL_INT_ARRAY">*(PoolIntArray *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::POOL_REAL_ARRAY">*(PoolRealArray *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::POOL_STRING_ARRAY">*(PoolStringArray *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::POOL_VECTOR2_ARRAY">*(PoolVector2Array *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::POOL_VECTOR3_ARRAY">*(PoolVector3Array *)_data._mem</Item> - <Item Name="value" Condition="this->type == Variant::POOL_COLOR_ARRAY">*(PoolColorArray *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::BOOL">_data._bool</Item> + <Item Name="[value]" Condition="type == Variant::INT">_data._int</Item> + <Item Name="[value]" Condition="type == Variant::REAL">_data._real</Item> + <Item Name="[value]" Condition="type == Variant::TRANSFORM2D">_data._transform2d</Item> + <Item Name="[value]" Condition="type == Variant::AABB">_data._aabb</Item> + <Item Name="[value]" Condition="type == Variant::BASIS">_data._basis</Item> + <Item Name="[value]" Condition="type == Variant::TRANSFORM">_data._transform</Item> + <Item Name="[value]" Condition="type == Variant::STRING">*(String *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::VECTOR2">*(Vector2 *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::RECT2">*(Rect2 *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::VECTOR3">*(Vector3 *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::PLANE">*(Plane *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::QUAT">*(Quat *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::COLOR">*(Color *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::NODE_PATH">*(NodePath *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::_RID">*(RID *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::OBJECT">*(Object *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::DICTIONARY">*(Dictionary *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::ARRAY">*(Array *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::POOL_BYTE_ARRAY">*(PoolByteArray *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::POOL_INT_ARRAY">*(PoolIntArray *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::POOL_REAL_ARRAY">*(PoolRealArray *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::POOL_STRING_ARRAY">*(PoolStringArray *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::POOL_VECTOR2_ARRAY">*(PoolVector2Array *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::POOL_VECTOR3_ARRAY">*(PoolVector3Array *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::POOL_COLOR_ARRAY">*(PoolColorArray *)_data._mem</Item> </Expand> </Type> <Type Name="String"> - <DisplayString Condition="this->_cowdata._ptr == 0">empty</DisplayString> - <DisplayString Condition="this->_cowdata._ptr != 0">{this->_cowdata._ptr,su}</DisplayString> - <StringView Condition="this->_cowdata._ptr != 0">this->_cowdata._ptr,su</StringView> + <DisplayString Condition="_cowdata._ptr == 0">[empty]</DisplayString> + <DisplayString Condition="_cowdata._ptr != 0">{_cowdata._ptr,su}</DisplayString> + <StringView Condition="_cowdata._ptr != 0">_cowdata._ptr,su</StringView> + </Type> + + <Type Name="StringName"> + <DisplayString Condition="_data && _data->cname">{_data->cname}</DisplayString> + <DisplayString Condition="_data && !_data->cname">{_data->name,su}</DisplayString> + <DisplayString Condition="!_data">[empty]</DisplayString> + <StringView Condition="_data && _data->cname">_data->cname</StringView> + <StringView Condition="_data && !_data->cname">_data->name,su</StringView> </Type> <Type Name="Vector2"> diff --git a/platform/windows/godot_win.cpp b/platform/windows/godot_windows.cpp index 504a9a0380..0b52682c7c 100644 --- a/platform/windows/godot_win.cpp +++ b/platform/windows/godot_windows.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* godot_win.cpp */ +/* godot_windows.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,6 +30,7 @@ #include "main/main.h" #include "os_windows.h" + #include <locale.h> #include <stdio.h> diff --git a/platform/windows/joypad.cpp b/platform/windows/joypad_windows.cpp index 7201714fb8..5a399cdf90 100644 --- a/platform/windows/joypad.cpp +++ b/platform/windows/joypad_windows.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* joypad.cpp */ +/* joypad_windows.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,10 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "joypad.h" +#include "joypad_windows.h" + #include <oleauto.h> #include <wbemidl.h> -#include <iostream> #ifndef __GNUC__ #define __builtin_bswap32 _byteswap_ulong @@ -149,15 +149,12 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) { const DWORD devtype = (instance->dwDevType & 0xFF); if ((devtype != DI8DEVTYPE_JOYSTICK) && (devtype != DI8DEVTYPE_GAMEPAD) && (devtype != DI8DEVTYPE_1STPERSON)) { - //printf("ignore device %s, type %x\n", instance->tszProductName, devtype); return false; } hr = dinput->CreateDevice(instance->guidInstance, &joy->di_joy, NULL); if (FAILED(hr)) { - - //std::wcout << "failed to create device: " << instance->tszProductName << std::endl; return false; } @@ -383,8 +380,6 @@ void JoypadWindows::process_joypads() { hr = joy->di_joy->GetDeviceState(sizeof(DIJOYSTATE2), &js); if (FAILED(hr)) { - - //printf("failed to read joy #%d\n", i); continue; } diff --git a/platform/windows/joypad.h b/platform/windows/joypad_windows.h index 0d14480733..4af5d9bd6a 100644 --- a/platform/windows/joypad.h +++ b/platform/windows/joypad_windows.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* joypad.h */ +/* joypad_windows.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,10 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef JOYPAD_H -#define JOYPAD_H +#ifndef JOYPAD_WINDOWS_H +#define JOYPAD_WINDOWS_H #include "os_windows.h" + #define DIRECTINPUT_VERSION 0x0800 #include <dinput.h> #include <xinput.h> // on unix the file is called "xinput.h", on windows I'm sure it won't mind @@ -145,4 +146,4 @@ private: XInputSetState_t xinput_set_state; }; -#endif +#endif // JOYPAD_WINDOWS_H diff --git a/platform/windows/key_mapping_win.cpp b/platform/windows/key_mapping_windows.cpp index 80580a63b3..01bbb072bb 100644 --- a/platform/windows/key_mapping_win.cpp +++ b/platform/windows/key_mapping_windows.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* key_mapping_win.cpp */ +/* key_mapping_windows.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "key_mapping_win.h" +#include "key_mapping_windows.h" #include <stdio.h> diff --git a/platform/windows/key_mapping_win.h b/platform/windows/key_mapping_windows.h index 340f916e1c..dbb8c20f1e 100644 --- a/platform/windows/key_mapping_win.h +++ b/platform/windows/key_mapping_windows.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* key_mapping_win.h */ +/* key_mapping_windows.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -45,4 +45,4 @@ public: static unsigned int get_keysym(unsigned int p_code); }; -#endif +#endif // KEY_MAPPING_WINDOWS_H diff --git a/platform/windows/lang_table.h b/platform/windows/lang_table.h index 78bfadfeae..f12893a8dc 100644 --- a/platform/windows/lang_table.h +++ b/platform/windows/lang_table.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 55d2bb2153..6df2ad4821 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -43,7 +43,7 @@ #include "drivers/windows/rw_lock_windows.h" #include "drivers/windows/semaphore_windows.h" #include "drivers/windows/thread_windows.h" -#include "joypad.h" +#include "joypad_windows.h" #include "lang_table.h" #include "main/main.h" #include "servers/audio_server.h" @@ -51,6 +51,9 @@ #include "servers/visual/visual_server_wrap_mt.h" #include "windows_terminal_logger.h" +#include <avrt.h> +#include <direct.h> +#include <knownfolders.h> #include <process.h> #include <regstr.h> #include <shlobj.h> @@ -269,7 +272,7 @@ void OS_Windows::_touch_event(bool p_pressed, float p_x, float p_y, int idx) { event->set_position(Vector2(p_x, p_y)); if (main_loop) { - input->parse_input_event(event); + input->accumulate_input_event(event); } }; @@ -291,11 +294,21 @@ void OS_Windows::_drag_event(float p_x, float p_y, int idx) { event->set_position(Vector2(p_x, p_y)); if (main_loop) - input->parse_input_event(event); + input->accumulate_input_event(event); }; LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (drop_events) { + + if (user_proc) { + + return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); + } else { + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + }; + switch (uMsg) // Check For Windows Messages { case WM_SETFOCUS: { @@ -333,6 +346,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) control_mem = false; shift_mem = false; } else { // WM_INACTIVE + input->release_pressed_events(); main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); alt_mem = false; }; @@ -372,8 +386,6 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) outside = true; if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED) main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT); - if (input) - input->set_mouse_in_window(false); } break; case WM_INPUT: { @@ -446,7 +458,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } if (window_has_focus && main_loop && mm->get_relative() != Vector2()) - input->parse_input_event(mm); + input->accumulate_input_event(mm); } delete[] lpb; } break; @@ -468,8 +480,6 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED) main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); - if (input) - input->set_mouse_in_window(true); CursorShape c = cursor_shape; cursor_shape = CURSOR_MAX; @@ -496,15 +506,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) mm->set_shift((wParam & MK_SHIFT) != 0); mm->set_alt(alt_mem); - int bmask = 0; - bmask |= (wParam & MK_LBUTTON) ? (1 << 0) : 0; - bmask |= (wParam & MK_RBUTTON) ? (1 << 1) : 0; - bmask |= (wParam & MK_MBUTTON) ? (1 << 2) : 0; - bmask |= (wParam & MK_XBUTTON1) ? (1 << 7) : 0; - bmask |= (wParam & MK_XBUTTON2) ? (1 << 8) : 0; - mm->set_button_mask(bmask); - - last_button_state = mm->get_button_mask(); + mm->set_button_mask(last_button_state); mm->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); mm->set_global_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); @@ -541,7 +543,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) old_x = mm->get_position().x; old_y = mm->get_position().y; if (window_has_focus && main_loop) - input->parse_input_event(mm); + input->accumulate_input_event(mm); } break; case WM_LBUTTONDOWN: @@ -666,22 +668,21 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) mb->set_button_index(BUTTON_XBUTTON2); mb->set_doubleclick(true); } break; - default: { return 0; } + default: { + return 0; + } } mb->set_control((wParam & MK_CONTROL) != 0); mb->set_shift((wParam & MK_SHIFT) != 0); mb->set_alt(alt_mem); //mb->get_alt()=(wParam&MK_MENU)!=0; - int bmask = 0; - bmask |= (wParam & MK_LBUTTON) ? (1 << 0) : 0; - bmask |= (wParam & MK_RBUTTON) ? (1 << 1) : 0; - bmask |= (wParam & MK_MBUTTON) ? (1 << 2) : 0; - bmask |= (wParam & MK_XBUTTON1) ? (1 << 7) : 0; - bmask |= (wParam & MK_XBUTTON2) ? (1 << 8) : 0; - mb->set_button_mask(bmask); - - last_button_state = mb->get_button_mask(); + if (mb->is_pressed()) + last_button_state |= (1 << (mb->get_button_index() - 1)); + else + last_button_state &= ~(1 << (mb->get_button_index() - 1)); + mb->set_button_mask(last_button_state); + mb->set_position(Vector2(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); if (mouse_mode == MOUSE_MODE_CAPTURED && !use_raw_input) { @@ -704,7 +705,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } } } else if (mouse_mode != MOUSE_MODE_CAPTURED) { - // for reasons unknown to mankind, wheel comes in screen cordinates + // for reasons unknown to mankind, wheel comes in screen coordinates POINT coords; coords.x = mb->get_position().x; coords.y = mb->get_position().y; @@ -717,12 +718,14 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) mb->set_global_position(mb->get_position()); if (main_loop) { - input->parse_input_event(mb); + input->accumulate_input_event(mb); if (mb->is_pressed() && mb->get_button_index() > 3 && mb->get_button_index() < 8) { //send release for mouse wheel Ref<InputEventMouseButton> mbd = mb->duplicate(); + last_button_state &= ~(1 << (mbd->get_button_index() - 1)); + mbd->set_button_mask(last_button_state); mbd->set_pressed(false); - input->parse_input_event(mbd); + input->accumulate_input_event(mbd); } } } break; @@ -784,6 +787,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } break; case WM_ENTERSIZEMOVE: { + input->release_pressed_events(); move_timer_id = SetTimer(hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC)NULL); } break; case WM_EXITSIZEMOVE: { @@ -792,7 +796,9 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_TIMER: { if (wParam == move_timer_id) { process_key_events(); - Main::iteration(); + if (!Main::is_iterating()) { + Main::iteration(); + } } } break; @@ -811,6 +817,13 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) gr_mem = alt_mem; } + if (mouse_mode == MOUSE_MODE_CAPTURED) { + // When SetCapture is used, ALT+F4 hotkey is ignored by Windows, so handle it ourselves + if (wParam == VK_F4 && alt_mem && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN)) { + if (main_loop) + main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST); + } + } /* if (wParam==VK_WIN) TODO wtf is this? meta_mem=uMsg==WM_KEYDOWN; @@ -976,7 +989,7 @@ void OS_Windows::process_key_events() { if (k->get_unicode() < 32) k->set_unicode(0); - input->parse_input_event(k); + input->accumulate_input_event(k); } //do nothing @@ -1014,7 +1027,7 @@ void OS_Windows::process_key_events() { k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30)))); - input->parse_input_event(k); + input->accumulate_input_event(k); } break; } @@ -1272,13 +1285,13 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int gl_context = NULL; while (!gl_context) { - gl_context = memnew(ContextGL_Win(hWnd, gles3_context)); + gl_context = memnew(ContextGL_Windows(hWnd, gles3_context)); if (gl_context->initialize() != OK) { memdelete(gl_context); gl_context = NULL; - if (GLOBAL_GET("rendering/quality/driver/driver_fallback") == "Best" || editor) { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) { if (p_video_driver == VIDEO_DRIVER_GLES2) { gl_initialization_error = true; break; @@ -1300,7 +1313,7 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int RasterizerGLES3::make_current(); break; } else { - if (GLOBAL_GET("rendering/quality/driver/driver_fallback") == "Best" || editor) { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) { p_video_driver = VIDEO_DRIVER_GLES2; gles3_context = false; continue; @@ -1380,31 +1393,49 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int set_ime_active(false); + if (!OS::get_singleton()->is_in_low_processor_usage_mode()) { + //SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); + SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); + DWORD index = 0; + HANDLE handle = AvSetMmThreadCharacteristics("Games", &index); + if (handle) + AvSetMmThreadPriority(handle, AVRT_PRIORITY_CRITICAL); + + // This is needed to make sure that background work does not starve the main thread. + // This is only setting priority of this thread, not the whole process. + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + } + + update_real_mouse_position(); + return OK; } void OS_Windows::set_clipboard(const String &p_text) { + // Convert LF line endings to CRLF in clipboard content + // Otherwise, line endings won't be visible when pasted in other software + String text = p_text.replace("\n", "\r\n"); + if (!OpenClipboard(hWnd)) { ERR_EXPLAIN("Unable to open clipboard."); ERR_FAIL(); }; EmptyClipboard(); - HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (p_text.length() + 1) * sizeof(CharType)); + HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (text.length() + 1) * sizeof(CharType)); if (mem == NULL) { ERR_EXPLAIN("Unable to allocate memory for clipboard contents."); ERR_FAIL(); }; LPWSTR lptstrCopy = (LPWSTR)GlobalLock(mem); - memcpy(lptstrCopy, p_text.c_str(), (p_text.length() + 1) * sizeof(CharType)); - //memset((lptstrCopy + p_text.length()), 0, sizeof(CharType)); + memcpy(lptstrCopy, text.c_str(), (text.length() + 1) * sizeof(CharType)); GlobalUnlock(mem); SetClipboardData(CF_UNICODETEXT, mem); // set the CF_TEXT version (not needed?) - CharString utf8 = p_text.utf8(); + CharString utf8 = text.utf8(); mem = GlobalAlloc(GMEM_MOVEABLE, utf8.length() + 1); if (mem == NULL) { ERR_EXPLAIN("Unable to allocate memory for clipboard contents."); @@ -1581,6 +1612,19 @@ Point2 OS_Windows::get_mouse_position() const { return Point2(old_x, old_y); } +void OS_Windows::update_real_mouse_position() { + + POINT mouse_pos; + if (GetCursorPos(&mouse_pos) && ScreenToClient(hWnd, &mouse_pos)) { + if (mouse_pos.x > 0 && mouse_pos.y > 0 && mouse_pos.x <= video_mode.width && mouse_pos.y <= video_mode.height) { + old_x = mouse_pos.x; + old_y = mouse_pos.y; + old_invalid = false; + input->set_mouse_position(Point2i(mouse_pos.x, mouse_pos.y)); + } + } +} + int OS_Windows::get_mouse_button_state() const { return last_button_state; @@ -1723,6 +1767,7 @@ void OS_Windows::set_window_position(const Point2 &p_position) { } last_pos = p_position; + update_real_mouse_position(); } Size2 OS_Windows::get_window_size() const { @@ -2074,7 +2119,7 @@ void OS_Windows::request_attention() { FlashWindowEx(&info); } -String OS_Windows::get_name() { +String OS_Windows::get_name() const { return "Windows"; } @@ -2123,7 +2168,9 @@ OS::TimeZoneInfo OS_Windows::get_time_zone_info() const { ret.name = info.StandardName; } - ret.bias = info.Bias; + // Bias value returned by GetTimeZoneInformation is inverted of what we expect + // For example on GMT-3 GetTimeZoneInformation return a Bias of 180, so invert the value to get -180 + ret.bias = -info.Bias; return ret; } @@ -2151,8 +2198,13 @@ uint64_t OS_Windows::get_unix_time() const { uint64_t OS_Windows::get_system_time_secs() const { - const uint64_t WINDOWS_TICK = 10000000; - const uint64_t SEC_TO_UNIX_EPOCH = 11644473600LL; + return get_system_time_msecs() / 1000; +} + +uint64_t OS_Windows::get_system_time_msecs() const { + + const uint64_t WINDOWS_TICK = 10000; + const uint64_t MSEC_TO_UNIX_EPOCH = 11644473600000LL; SYSTEMTIME st; GetSystemTime(&st); @@ -2163,7 +2215,7 @@ uint64_t OS_Windows::get_system_time_secs() const { ret <<= 32; ret |= ft.dwLowDateTime; - return (uint64_t)(ret / WINDOWS_TICK - SEC_TO_UNIX_EPOCH); + return (uint64_t)(ret / WINDOWS_TICK - MSEC_TO_UNIX_EPOCH); } void OS_Windows::delay_usec(uint32_t p_usec) const { @@ -2192,7 +2244,9 @@ void OS_Windows::process_events() { MSG msg; - joypad->process_joypads(); + if (!drop_events) { + joypad->process_joypads(); + } while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { @@ -2200,7 +2254,10 @@ void OS_Windows::process_events() { DispatchMessageW(&msg); } - process_key_events(); + if (!drop_events) { + process_key_events(); + input->flush_accumulated_events(); + } } void OS_Windows::set_cursor_shape(CursorShape p_shape) { @@ -2244,6 +2301,11 @@ void OS_Windows::set_cursor_shape(CursorShape p_shape) { cursor_shape = p_shape; } +OS::CursorShape OS_Windows::get_cursor_shape() const { + + return cursor_shape; +} + void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { if (p_cursor.is_valid()) { Ref<Texture> texture = p_cursor; @@ -2323,6 +2385,9 @@ void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shap iconinfo.hbmMask = hAndMask; iconinfo.hbmColor = hXorMask; + if (cursors[p_shape]) + DestroyIcon(cursors[p_shape]); + cursors[p_shape] = CreateIconIndirect(&iconinfo); if (p_shape == cursor_shape) { @@ -2401,7 +2466,7 @@ void OS_Windows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, DeleteDC(hMainDC); } -Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr) { +Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) { if (p_blocking && r_pipe) { @@ -2410,7 +2475,13 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) { - argss += String(" \"") + E->get() + "\""; + argss += " \"" + E->get() + "\""; + } + + argss += "\""; + + if (read_stderr) { + argss += " 2>&1"; // Read stderr too } FILE *f = _wpopen(argss.c_str(), L"r"); @@ -2420,7 +2491,13 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, char buf[65535]; while (fgets(buf, 65535, f)) { + if (p_pipe_mutex) { + p_pipe_mutex->lock(); + } (*r_pipe) += buf; + if (p_pipe_mutex) { + p_pipe_mutex->unlock(); + } } int rv = _pclose(f); @@ -2506,6 +2583,117 @@ String OS_Windows::get_executable_path() const { return s; } +void OS_Windows::set_native_icon(const String &p_filename) { + + FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); + ERR_FAIL_COND(!f); + + ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR)); + int pos = 0; + + icon_dir->idReserved = f->get_32(); + pos += sizeof(WORD); + f->seek(pos); + + icon_dir->idType = f->get_32(); + pos += sizeof(WORD); + f->seek(pos); + + if (icon_dir->idType != 1) { + ERR_EXPLAIN("Invalid icon file format!"); + ERR_FAIL(); + } + + icon_dir->idCount = f->get_32(); + pos += sizeof(WORD); + f->seek(pos); + + icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY)); + f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY)); + + int small_icon_index = -1; // Select 16x16 with largest color count + int small_icon_cc = 0; + int big_icon_index = -1; // Select largest + int big_icon_width = 16; + int big_icon_cc = 0; + + for (int i = 0; i < icon_dir->idCount; i++) { + int colors = (icon_dir->idEntries[i].bColorCount == 0) ? 32768 : icon_dir->idEntries[i].bColorCount; + int width = (icon_dir->idEntries[i].bWidth == 0) ? 256 : icon_dir->idEntries[i].bWidth; + if (width == 16) { + if (colors >= small_icon_cc) { + small_icon_index = i; + small_icon_cc = colors; + } + } + if (width >= big_icon_width) { + if (colors >= big_icon_cc) { + big_icon_index = i; + big_icon_width = width; + big_icon_cc = colors; + } + } + } + + if (big_icon_index == -1) { + ERR_EXPLAIN("No valid icons found!"); + ERR_FAIL(); + } + + if (small_icon_index == -1) { + WARN_PRINTS("No small icon found, reusing " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon!"); + small_icon_index = big_icon_index; + small_icon_cc = big_icon_cc; + } + + // Read the big icon + DWORD bytecount_big = icon_dir->idEntries[big_icon_index].dwBytesInRes; + Vector<uint8_t> data_big; + data_big.resize(bytecount_big); + pos = icon_dir->idEntries[big_icon_index].dwImageOffset; + f->seek(pos); + f->get_buffer((uint8_t *)&data_big.write[0], bytecount_big); + HICON icon_big = CreateIconFromResource((PBYTE)&data_big.write[0], bytecount_big, TRUE, 0x00030000); + if (!icon_big) { + ERR_EXPLAIN("Could not create " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon, error: " + format_error_message(GetLastError())); + ERR_FAIL(); + } + + // Read the small icon + DWORD bytecount_small = icon_dir->idEntries[small_icon_index].dwBytesInRes; + Vector<uint8_t> data_small; + data_small.resize(bytecount_small); + pos = icon_dir->idEntries[small_icon_index].dwImageOffset; + f->seek(pos); + f->get_buffer((uint8_t *)&data_small.write[0], bytecount_small); + HICON icon_small = CreateIconFromResource((PBYTE)&data_small.write[0], bytecount_small, TRUE, 0x00030000); + if (!icon_small) { + ERR_EXPLAIN("Could not create 16x16 @" + itos(small_icon_cc) + " icon, error: " + format_error_message(GetLastError())); + ERR_FAIL(); + } + + // Online tradition says to be sure last error is cleared and set the small icon first + int err = 0; + SetLastError(err); + + SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)icon_small); + err = GetLastError(); + if (err) { + ERR_EXPLAIN("Error setting ICON_SMALL: " + format_error_message(err)); + ERR_FAIL(); + } + + SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)icon_big); + err = GetLastError(); + if (err) { + ERR_EXPLAIN("Error setting ICON_BIG: " + format_error_message(err)); + ERR_FAIL(); + } + + memdelete(f); + memdelete(icon_dir); +} + void OS_Windows::set_icon(const Ref<Image> &p_icon) { ERR_FAIL_COND(!p_icon.is_valid()); @@ -2582,6 +2770,11 @@ String OS_Windows::get_environment(const String &p_var) const { return ""; } +bool OS_Windows::set_environment(const String &p_var, const String &p_value) const { + + return (bool)SetEnvironmentVariableW(p_var.c_str(), p_value.c_str()); +} + String OS_Windows::get_stdin_string(bool p_block) { if (p_block) { @@ -2806,39 +2999,41 @@ String OS_Windows::get_godot_dir_name() const { String OS_Windows::get_system_dir(SystemDir p_dir) const { - int id; + KNOWNFOLDERID id; switch (p_dir) { case SYSTEM_DIR_DESKTOP: { - id = CSIDL_DESKTOPDIRECTORY; + id = FOLDERID_Desktop; } break; case SYSTEM_DIR_DCIM: { - id = CSIDL_MYPICTURES; + id = FOLDERID_Pictures; } break; case SYSTEM_DIR_DOCUMENTS: { - id = CSIDL_PERSONAL; + id = FOLDERID_Documents; } break; case SYSTEM_DIR_DOWNLOADS: { - id = 0x000C; + id = FOLDERID_Downloads; } break; case SYSTEM_DIR_MOVIES: { - id = CSIDL_MYVIDEO; + id = FOLDERID_Videos; } break; case SYSTEM_DIR_MUSIC: { - id = CSIDL_MYMUSIC; + id = FOLDERID_Music; } break; case SYSTEM_DIR_PICTURES: { - id = CSIDL_MYPICTURES; + id = FOLDERID_Pictures; } break; case SYSTEM_DIR_RINGTONES: { - id = CSIDL_MYMUSIC; + id = FOLDERID_Music; } break; } - WCHAR szPath[MAX_PATH]; - HRESULT res = SHGetFolderPathW(NULL, id, NULL, 0, szPath); + PWSTR szPath; + HRESULT res = SHGetKnownFolderPath(id, 0, NULL, &szPath); ERR_FAIL_COND_V(res != S_OK, String()); - return String(szPath); + String path = String(szPath); + CoTaskMemFree(szPath); + return path; } String OS_Windows::get_user_data_dir() const { @@ -2930,7 +3125,7 @@ int OS_Windows::get_power_percent_left() { bool OS_Windows::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc" || p_feature == "s3tc" || p_feature == "bptc"; + return p_feature == "pc"; } void OS_Windows::disable_crash_handler() { @@ -2941,6 +3136,13 @@ bool OS_Windows::is_disable_crash_handler() const { return crash_handler.is_disabled(); } +void OS_Windows::process_and_drop_events() { + + drop_events = true; + process_events(); + drop_events = false; +} + Error OS_Windows::move_to_trash(const String &p_path) { SHFILEOPSTRUCTW sf; WCHAR *from = new WCHAR[p_path.length() + 2]; @@ -2969,6 +3171,7 @@ Error OS_Windows::move_to_trash(const String &p_path) { OS_Windows::OS_Windows(HINSTANCE _hInstance) { + drop_events = false; key_event_pos = 0; layered_window = false; hBitmap = NULL; @@ -2992,9 +3195,6 @@ OS_Windows::OS_Windows(HINSTANCE _hInstance) { #ifdef WASAPI_ENABLED AudioDriverManager::add_driver(&driver_wasapi); #endif -#ifdef RTAUDIO_ENABLED - AudioDriverManager::add_driver(&driver_rtaudio); -#endif #ifdef XAUDIO2_ENABLED AudioDriverManager::add_driver(&driver_xaudio2); #endif diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index d09ade4daa..0aacbcb9ff 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,14 +30,17 @@ #ifndef OS_WINDOWS_H #define OS_WINDOWS_H -#include "context_gl_win.h" + +#include "context_gl_windows.h" #include "core/os/input.h" #include "core/os/os.h" #include "core/project_settings.h" -#include "crash_handler_win.h" -#include "drivers/rtaudio/audio_driver_rtaudio.h" +#include "crash_handler_windows.h" +#include "drivers/unix/ip_unix.h" #include "drivers/wasapi/audio_driver_wasapi.h" -#include "drivers/winmidi/win_midi.h" +#include "drivers/winmidi/midi_driver_winmidi.h" +#include "key_mapping_windows.h" +#include "main/input_default.h" #include "power_windows.h" #include "servers/audio_server.h" #include "servers/visual/rasterizer.h" @@ -45,9 +48,6 @@ #ifdef XAUDIO2_ENABLED #include "drivers/xaudio2/audio_driver_xaudio2.h" #endif -#include "drivers/unix/ip_unix.h" -#include "key_mapping_win.h" -#include "main/input_default.h" #include <fcntl.h> #include <io.h> @@ -58,6 +58,25 @@ /** @author Juan Linietsky <reduzio@gmail.com> */ + +typedef struct { + BYTE bWidth; // Width, in pixels, of the image + BYTE bHeight; // Height, in pixels, of the image + BYTE bColorCount; // Number of colors in image (0 if >=8bpp) + BYTE bReserved; // Reserved ( must be 0) + WORD wPlanes; // Color Planes + WORD wBitCount; // Bits per pixel + DWORD dwBytesInRes; // How many bytes in this resource? + DWORD dwImageOffset; // Where in the file is this image? +} ICONDIRENTRY, *LPICONDIRENTRY; + +typedef struct { + WORD idReserved; // Reserved (must be 0) + WORD idType; // Resource Type (1 for icons) + WORD idCount; // How many images? + ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em) +} ICONDIR, *LPICONDIR; + class JoypadWindows; class OS_Windows : public OS { @@ -86,7 +105,7 @@ class OS_Windows : public OS { int old_x, old_y; Point2i center; #if defined(OPENGL_ENABLED) - ContextGL_Win *gl_context; + ContextGL_Windows *gl_context; #endif VisualServer *visual_server; int pressrc; @@ -127,6 +146,7 @@ class OS_Windows : public OS { bool window_has_focus; uint32_t last_button_state; bool use_raw_input; + bool drop_events; HCURSOR cursors[CURSOR_MAX] = { NULL }; CursorShape cursor_shape; @@ -141,9 +161,6 @@ class OS_Windows : public OS { #ifdef WASAPI_ENABLED AudioDriverWASAPI driver_wasapi; #endif -#ifdef RTAUDIO_ENABLED - AudioDriverRtAudio driver_rtaudio; -#endif #ifdef XAUDIO2_ENABLED AudioDriverXAudio2 driver_xaudio2; #endif @@ -200,6 +217,7 @@ public: virtual void warp_mouse_position(const Point2 &p_to); virtual Point2 get_mouse_position() const; + void update_real_mouse_position(); virtual int get_mouse_button_state() const; virtual void set_window_title(const String &p_title); @@ -247,13 +265,14 @@ public: virtual MainLoop *get_main_loop() const; - virtual String get_name(); + virtual String get_name() const; virtual Date get_date(bool utc) const; virtual Time get_time(bool utc) const; virtual TimeZoneInfo get_time_zone_info() const; virtual uint64_t get_unix_time() const; virtual uint64_t get_system_time_secs() const; + virtual uint64_t get_system_time_msecs() const; virtual bool can_draw() const; virtual Error set_cwd(const String &p_cwd); @@ -261,19 +280,23 @@ public: virtual void delay_usec(uint32_t p_usec) const; virtual uint64_t get_ticks_usec() const; - virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false); + virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL); virtual Error kill(const ProcessID &p_pid); virtual int get_process_id() const; virtual bool has_environment(const String &p_var) const; virtual String get_environment(const String &p_var) const; + virtual bool set_environment(const String &p_var, const String &p_value) const; virtual void set_clipboard(const String &p_text); virtual String get_clipboard() const; void set_cursor_shape(CursorShape p_shape); + CursorShape get_cursor_shape() const; virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap); + + void set_native_icon(const String &p_filename); void set_icon(const Ref<Image> &p_icon); virtual String get_executable_path() const; @@ -330,6 +353,8 @@ public: virtual Error move_to_trash(const String &p_path); + virtual void process_and_drop_events(); + OS_Windows(HINSTANCE _hInstance); ~OS_Windows(); }; diff --git a/platform/windows/platform_config.h b/platform/windows/platform_config.h index aa020ed470..8a4aab5c36 100644 --- a/platform/windows/platform_config.h +++ b/platform/windows/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/power_windows.cpp b/platform/windows/power_windows.cpp index 5612906251..b96ae51132 100644 --- a/platform/windows/power_windows.cpp +++ b/platform/windows/power_windows.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/power_windows.h b/platform/windows/power_windows.h index 4984b473ca..ef75ce6271 100644 --- a/platform/windows/power_windows.h +++ b/platform/windows/power_windows.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef PLATFORM_WINDOWS_POWER_WINDOWS_H_ -#define PLATFORM_WINDOWS_POWER_WINDOWS_H_ +#ifndef POWER_WINDOWS_H +#define POWER_WINDOWS_H #include "core/os/dir_access.h" #include "core/os/file_access.h" @@ -55,4 +55,4 @@ public: int get_power_percent_left(); }; -#endif /* PLATFORM_WINDOWS_POWER_WINDOWS_H_ */ +#endif // POWER_WINDOWS_H diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp index 12a4a1ae39..7def419103 100644 --- a/platform/windows/windows_terminal_logger.cpp +++ b/platform/windows/windows_terminal_logger.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/windows/windows_terminal_logger.h b/platform/windows/windows_terminal_logger.h index 1cd1941b8a..475e1f25ab 100644 --- a/platform/windows/windows_terminal_logger.h +++ b/platform/windows/windows_terminal_logger.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/x11/SCsub b/platform/x11/SCsub index 97d3d1b514..3d5aa15208 100644 --- a/platform/x11/SCsub +++ b/platform/x11/SCsub @@ -2,7 +2,6 @@ Import('env') -import os from platform_methods import run_in_subprocess import platform_x11_builders @@ -13,6 +12,7 @@ common_x11 = [ "key_mapping_x11.cpp", "joypad_linux.cpp", "power_x11.cpp", + "detect_prime.cpp" ] prog = env.add_program('#bin/godot', ['godot_x11.cpp'] + common_x11) diff --git a/platform/x11/context_gl_x11.cpp b/platform/x11/context_gl_x11.cpp index 8c1869a1f1..9718b03164 100644 --- a/platform/x11/context_gl_x11.cpp +++ b/platform/x11/context_gl_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -191,6 +191,7 @@ Error ContextGL_X11::initialize() { swa.colormap = XCreateColormap(x11_display, RootWindow(x11_display, vi->screen), vi->visual, AllocNone); x11_window = XCreateWindow(x11_display, RootWindow(x11_display, vi->screen), 0, 0, OS::get_singleton()->get_video_mode().width, OS::get_singleton()->get_video_mode().height, 0, vi->depth, InputOutput, vi->visual, valuemask, &swa); + XStoreName(x11_display, x11_window, "Godot Engine"); ERR_FAIL_COND_V(!x11_window, ERR_UNCONFIGURED); set_class_hint(x11_display, x11_window); diff --git a/platform/x11/context_gl_x11.h b/platform/x11/context_gl_x11.h index be3083d957..46420df48b 100644 --- a/platform/x11/context_gl_x11.h +++ b/platform/x11/context_gl_x11.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -39,13 +39,12 @@ #if defined(OPENGL_ENABLED) #include "core/os/os.h" -#include "drivers/gl_context/context_gl.h" #include <X11/Xlib.h> #include <X11/extensions/Xrender.h> struct ContextGL_X11_Private; -class ContextGL_X11 : public ContextGL { +class ContextGL_X11 { public: enum ContextType { @@ -67,19 +66,19 @@ private: ContextType context_type; public: - virtual void release_current(); - virtual void make_current(); - virtual void swap_buffers(); - virtual int get_window_width(); - virtual int get_window_height(); + void release_current(); + void make_current(); + void swap_buffers(); + int get_window_width(); + int get_window_height(); - virtual Error initialize(); + Error initialize(); - virtual void set_use_vsync(bool p_use); - virtual bool is_using_vsync() const; + void set_use_vsync(bool p_use); + bool is_using_vsync() const; ContextGL_X11(::Display *p_x11_display, ::Window &p_x11_window, const OS::VideoMode &p_default_video_mode, ContextType p_context_type); - virtual ~ContextGL_X11(); + ~ContextGL_X11(); }; #endif diff --git a/platform/x11/crash_handler_x11.cpp b/platform/x11/crash_handler_x11.cpp index 79c3d9aece..ca7251078f 100644 --- a/platform/x11/crash_handler_x11.cpp +++ b/platform/x11/crash_handler_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,15 +28,16 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef DEBUG_ENABLED -#define CRASH_HANDLER_ENABLED 1 -#endif - #include "crash_handler_x11.h" + #include "core/os/os.h" #include "core/project_settings.h" #include "main/main.h" +#ifdef DEBUG_ENABLED +#define CRASH_HANDLER_ENABLED 1 +#endif + #ifdef CRASH_HANDLER_ENABLED #include <cxxabi.h> #include <dlfcn.h> @@ -52,7 +53,12 @@ static void handle_crash(int sig) { void *bt_buffer[256]; size_t size = backtrace(bt_buffer, 256); String _execpath = OS::get_singleton()->get_executable_path(); - String msg = GLOBAL_GET("debug/settings/crash_handler/message"); + + String msg; + const ProjectSettings *proj_settings = ProjectSettings::get_singleton(); + if (proj_settings) { + msg = proj_settings->get("debug/settings/crash_handler/message"); + } // Dump the backtrace to stderr with a message to the user fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); @@ -95,12 +101,10 @@ static void handle_crash(int sig) { String output = ""; // Try to get the file/line number using addr2line - if (OS::get_singleton()) { - int ret; - Error err = OS::get_singleton()->execute(String("addr2line"), args, true, NULL, &output, &ret); - if (err == OK) { - output.erase(output.length() - 1, 1); - } + int ret; + Error err = OS::get_singleton()->execute(String("addr2line"), args, true, NULL, &output, &ret); + if (err == OK) { + output.erase(output.length() - 1, 1); } fprintf(stderr, "[%ld] %s (%ls)\n", i, fname, output.c_str()); diff --git a/platform/x11/crash_handler_x11.h b/platform/x11/crash_handler_x11.h index 49ca0df9a8..d0664aef85 100644 --- a/platform/x11/crash_handler_x11.h +++ b/platform/x11/crash_handler_x11.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -45,4 +45,4 @@ public: ~CrashHandler(); }; -#endif +#endif // CRASH_HANDLER_X11_H diff --git a/platform/x11/detect.py b/platform/x11/detect.py index 524c8448bc..933ee6b72e 100644 --- a/platform/x11/detect.py +++ b/platform/x11/detect.py @@ -1,7 +1,7 @@ import os import platform import sys -from compat import decode_utf8 +from methods import get_compiler_version, using_gcc, using_clang def is_active(): @@ -20,12 +20,10 @@ def can_build(): # Check the minimal dependencies x11_error = os.system("pkg-config --version > /dev/null") if (x11_error): - print("pkg-config not found.. x11 disabled.") return False x11_error = os.system("pkg-config x11 --modversion > /dev/null ") if (x11_error): - print("X11 not found.. x11 disabled.") return False x11_error = os.system("pkg-config xcursor --modversion > /dev/null ") @@ -48,6 +46,11 @@ def can_build(): print("xrender not found.. x11 disabled.") return False + x11_error = os.system("pkg-config xi --modversion > /dev/null ") + if (x11_error): + print("xi not found.. Aborting.") + return False + return True def get_opts(): @@ -55,10 +58,13 @@ def get_opts(): return [ BoolVariable('use_llvm', 'Use the LLVM compiler', False), + BoolVariable('use_lld', 'Use the LLD linker', False), + BoolVariable('use_thinlto', 'Use ThinLTO', False), BoolVariable('use_static_cpp', 'Link libgcc and libstdc++ statically for better portability', False), - BoolVariable('use_sanitizer', 'Use LLVM compiler address sanitizer', False), - BoolVariable('use_leak_sanitizer', 'Use LLVM compiler memory leaks sanitizer (implies use_sanitizer)', False), - BoolVariable('pulseaudio', 'Detect & use pulseaudio', True), + BoolVariable('use_ubsan', 'Use LLVM/GCC compiler undefined behavior sanitizer (UBSAN)', False), + BoolVariable('use_asan', 'Use LLVM/GCC compiler address sanitizer (ASAN))', False), + BoolVariable('use_lsan', 'Use LLVM/GCC compiler leak sanitizer (LSAN))', False), + BoolVariable('pulseaudio', 'Detect and use PulseAudio', True), BoolVariable('udev', 'Use udev for gamepad connection callbacks', False), EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')), BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False), @@ -81,10 +87,8 @@ def configure(env): ## Build type if (env["target"] == "release"): - # -O3 -ffast-math is identical to -Ofast. We need to split it out so we can selectively disable - # -ffast-math in code for which it generates wrong results. if (env["optimize"] == "speed"): #optimize for speed (default) - env.Prepend(CCFLAGS=['-O3', '-ffast-math']) + env.Prepend(CCFLAGS=['-O3']) else: #optimize for size env.Prepend(CCFLAGS=['-Os']) @@ -95,9 +99,10 @@ def configure(env): elif (env["target"] == "release_debug"): if (env["optimize"] == "speed"): #optimize for speed (default) - env.Prepend(CCFLAGS=['-O2', '-ffast-math', '-DDEBUG_ENABLED']) + env.Prepend(CCFLAGS=['-O2']) else: #optimize for size - env.Prepend(CCFLAGS=['-Os', '-DDEBUG_ENABLED']) + env.Prepend(CCFLAGS=['-Os']) + env.Prepend(CPPFLAGS=['-DDEBUG_ENABLED']) if (env["debug_symbols"] == "yes"): env.Prepend(CCFLAGS=['-g1']) @@ -105,7 +110,8 @@ def configure(env): env.Prepend(CCFLAGS=['-g2']) elif (env["target"] == "debug"): - env.Prepend(CCFLAGS=['-g3', '-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) + env.Prepend(CCFLAGS=['-g3']) + env.Prepend(CPPFLAGS=['-DDEBUG_ENABLED', '-DDEBUG_MEMORY_ENABLED']) env.Append(LINKFLAGS=['-rdynamic']) ## Architecture @@ -128,21 +134,43 @@ def configure(env): env.Append(CPPFLAGS=['-DTYPED_METHOD_BIND']) env.extra_suffix = ".llvm" + env.extra_suffix - # leak sanitizer requires (address) sanitizer - if env['use_sanitizer'] or env['use_leak_sanitizer']: - env.Append(CCFLAGS=['-fsanitize=address', '-fno-omit-frame-pointer']) - env.Append(LINKFLAGS=['-fsanitize=address']) + if env['use_lld']: + if env['use_llvm']: + env.Append(LINKFLAGS=['-fuse-ld=lld']) + if env['use_thinlto']: + # A convenience so you don't need to write use_lto too when using SCons + env['use_lto'] = True + else: + print("Using LLD with GCC is not supported yet, try compiling with 'use_llvm=yes'.") + sys.exit(255) + + if env['use_ubsan'] or env['use_asan'] or env['use_lsan']: env.extra_suffix += "s" - if env['use_leak_sanitizer']: + + if env['use_ubsan']: + env.Append(CCFLAGS=['-fsanitize=undefined']) + env.Append(LINKFLAGS=['-fsanitize=undefined']) + + if env['use_asan']: + env.Append(CCFLAGS=['-fsanitize=address']) + env.Append(LINKFLAGS=['-fsanitize=address']) + + if env['use_lsan']: env.Append(CCFLAGS=['-fsanitize=leak']) env.Append(LINKFLAGS=['-fsanitize=leak']) if env['use_lto']: - env.Append(CCFLAGS=['-flto']) if not env['use_llvm'] and env.GetOption("num_jobs") > 1: + env.Append(CCFLAGS=['-flto']) env.Append(LINKFLAGS=['-flto=' + str(env.GetOption("num_jobs"))]) else: - env.Append(LINKFLAGS=['-flto']) + if env['use_lld'] and env['use_thinlto']: + env.Append(CCFLAGS=['-flto=thin']) + env.Append(LINKFLAGS=['-flto=thin']) + else: + env.Append(CCFLAGS=['-flto']) + env.Append(LINKFLAGS=['-flto']) + if not env['use_llvm']: env['RANLIB'] = 'gcc-ranlib' env['AR'] = 'gcc-ar' @@ -150,16 +178,16 @@ def configure(env): env.Append(CCFLAGS=['-pipe']) env.Append(LINKFLAGS=['-pipe']) - # Check for gcc version > 5 before adding -no-pie - import re - import subprocess - proc = subprocess.Popen([env['CXX'], '--version'], stdout=subprocess.PIPE) - (stdout, _) = proc.communicate() - stdout = decode_utf8(stdout) - match = re.search('[0-9][0-9.]*', stdout) - if match is not None: - version = match.group().split('.') - if (version[0] > '5'): + # Check for gcc version >= 6 before adding -no-pie + if using_gcc(env): + version = get_compiler_version(env) + if version != None and version[0] >= '6': + env.Append(CCFLAGS=['-fpie']) + env.Append(LINKFLAGS=['-no-pie']) + # Do the same for clang should be fine with Clang 4 and higher + if using_clang(env): + version = get_compiler_version(env) + if version != None and version[0] >= '4': env.Append(CCFLAGS=['-fpie']) env.Append(LINKFLAGS=['-no-pie']) @@ -170,13 +198,9 @@ def configure(env): env.ParseConfig('pkg-config xinerama --cflags --libs') env.ParseConfig('pkg-config xrandr --cflags --libs') env.ParseConfig('pkg-config xrender --cflags --libs') + env.ParseConfig('pkg-config xi --cflags --libs') if (env['touch']): - x11_error = os.system("pkg-config xi --modversion > /dev/null ") - if (x11_error): - print("xi not found.. cannot build with touch. Aborting.") - sys.exit(255) - env.ParseConfig('pkg-config xi --cflags --libs') env.Append(CPPFLAGS=['-DTOUCH_ENABLED']) # FIXME: Check for existence of the libs before parsing their flags with pkg-config @@ -198,7 +222,7 @@ def configure(env): # We need at least version 2.88 import subprocess bullet_version = subprocess.check_output(['pkg-config', 'bullet', '--modversion']).strip() - if bullet_version < "2.88": + if str(bullet_version) < "2.88": # Abort as system bullet was requested but too old print("Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(bullet_version, "2.88")) sys.exit(255) @@ -251,7 +275,7 @@ def configure(env): if not env['builtin_miniupnpc']: # No pkgconfig file so far, hardcode default paths. - env.Append(CPPPATH=["/usr/include/miniupnpc"]) + env.Prepend(CPPPATH=["/usr/include/miniupnpc"]) env.Append(LIBS=["miniupnpc"]) # On Linux wchar_t should be 32-bits @@ -292,7 +316,7 @@ def configure(env): if not env['builtin_zlib']: env.ParseConfig('pkg-config zlib --cflags --libs') - env.Append(CPPPATH=['#platform/x11']) + env.Prepend(CPPPATH=['#platform/x11']) env.Append(CPPFLAGS=['-DX11_ENABLED', '-DUNIX_ENABLED', '-DOPENGL_ENABLED', '-DGLES_ENABLED']) env.Append(LIBS=['GL', 'pthread']) @@ -308,10 +332,10 @@ def configure(env): ## Cross-compilation if (is64 and env["bits"] == "32"): - env.Append(CPPFLAGS=['-m32']) + env.Append(CCFLAGS=['-m32']) env.Append(LINKFLAGS=['-m32', '-L/usr/lib/i386-linux-gnu']) elif (not is64 and env["bits"] == "64"): - env.Append(CPPFLAGS=['-m64']) + env.Append(CCFLAGS=['-m64']) env.Append(LINKFLAGS=['-m64', '-L/usr/lib/i686-linux-gnu']) # Link those statically for portability diff --git a/platform/x11/detect_prime.cpp b/platform/x11/detect_prime.cpp new file mode 100644 index 0000000000..0fde2a0c04 --- /dev/null +++ b/platform/x11/detect_prime.cpp @@ -0,0 +1,233 @@ +/*************************************************************************/ +/* detect_prime.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef X11_ENABLED +#if defined(OPENGL_ENABLED) + +#include "core/print_string.h" +#include "core/ustring.h" + +#include <stdlib.h> + +#include <GL/gl.h> +#include <GL/glx.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include <cstring> + +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 + +typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *); + +struct vendor { + const char *glxvendor; + int priority; +}; + +vendor vendormap[] = { + { "Advanced Micro Devices, Inc.", 30 }, + { "NVIDIA Corporation", 30 }, + { "X.Org", 30 }, + { "Intel Open Source Technology Center", 20 }, + { "nouveau", 10 }, + { "Mesa Project", 0 }, + { NULL, 0 } +}; + +// Runs inside a child. Exiting will not quit the engine. +void create_context() { + Display *x11_display = XOpenDisplay(NULL); + Window x11_window; + GLXContext glx_context; + + GLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (GLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress((const GLubyte *)"glXCreateContextAttribsARB"); + + static int visual_attribs[] = { + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_DOUBLEBUFFER, true, + GLX_RED_SIZE, 1, + GLX_GREEN_SIZE, 1, + GLX_BLUE_SIZE, 1, + GLX_DEPTH_SIZE, 24, + None + }; + + int fbcount; + GLXFBConfig fbconfig = 0; + XVisualInfo *vi = NULL; + + XSetWindowAttributes swa; + swa.event_mask = StructureNotifyMask; + swa.border_pixel = 0; + unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; + + GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs, &fbcount); + if (!fbc) + exit(1); + + vi = glXGetVisualFromFBConfig(x11_display, fbc[0]); + + fbconfig = fbc[0]; + + static int context_attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 3, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + None + }; + + glx_context = glXCreateContextAttribsARB(x11_display, fbconfig, NULL, true, context_attribs); + + swa.colormap = XCreateColormap(x11_display, RootWindow(x11_display, vi->screen), vi->visual, AllocNone); + x11_window = XCreateWindow(x11_display, RootWindow(x11_display, vi->screen), 0, 0, 10, 10, 0, vi->depth, InputOutput, vi->visual, valuemask, &swa); + + if (!x11_window) + exit(1); + + glXMakeCurrent(x11_display, x11_window, glx_context); + XFree(vi); +} + +int detect_prime() { + pid_t p; + int priorities[2]; + String vendors[2]; + String renderers[2]; + + vendors[0] = "Unknown"; + vendors[1] = "Unknown"; + renderers[0] = "Unknown"; + renderers[1] = "Unknown"; + + for (int i = 0; i < 2; ++i) { + int fdset[2]; + + if (pipe(fdset) == -1) { + print_verbose("Failed to pipe(), using default GPU"); + return 0; + } + + // Fork so the driver initialization can crash without taking down the engine. + p = fork(); + + if (p > 0) { + // Main thread + + int stat_loc = 0; + char string[201]; + string[200] = '\0'; + + close(fdset[1]); + + waitpid(p, &stat_loc, 0); + + if (!stat_loc) { + // No need to do anything complicated here. Anything less than + // PIPE_BUF will be delivered in one read() call. + read(fdset[0], string, sizeof(string) - 1); + + vendors[i] = string; + renderers[i] = string + strlen(string) + 1; + } + + close(fdset[0]); + + } else { + // In child, exit() here will not quit the engine. + + char string[201]; + + close(fdset[0]); + + if (i) setenv("DRI_PRIME", "1", 1); + create_context(); + + const char *vendor = (const char *)glGetString(GL_VENDOR); + const char *renderer = (const char *)glGetString(GL_RENDERER); + + unsigned int vendor_len = strlen(vendor) + 1; + unsigned int renderer_len = strlen(renderer) + 1; + + if (vendor_len + renderer_len >= sizeof(string)) { + renderer_len = 200 - vendor_len; + } + + memcpy(&string, vendor, vendor_len); + memcpy(&string[vendor_len], renderer, renderer_len); + + write(fdset[1], string, vendor_len + renderer_len); + + close(fdset[1]); + exit(0); + } + } + + int preferred = 0; + int priority = 0; + + if (vendors[0] == vendors[1]) { + print_verbose("Only one GPU found, using default."); + return 0; + } + + for (int i = 1; i >= 0; --i) { + vendor *v = vendormap; + while (v->glxvendor) { + if (v->glxvendor == vendors[i]) { + priorities[i] = v->priority; + + if (v->priority >= priority) { + priority = v->priority; + preferred = i; + } + } + ++v; + } + } + + print_verbose("Found renderers:"); + for (int i = 0; i < 2; ++i) { + print_verbose("Renderer " + itos(i) + ": " + renderers[i] + " with priority: " + itos(priorities[i])); + } + + print_verbose("Using renderer: " + renderers[preferred]); + return preferred; +} + +#endif +#endif diff --git a/platform/iphone/globals/global_defaults.h b/platform/x11/detect_prime.h index 3e3c220f4a..13bcf6fc38 100644 --- a/platform/iphone/globals/global_defaults.h +++ b/platform/x11/detect_prime.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* global_defaults.h */ +/* detect_prime.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,4 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -void register_iphone_global_defaults(); +#ifdef X11_ENABLED +#if defined(OPENGL_ENABLED) + +int detect_prime(); + +#endif +#endif diff --git a/platform/x11/export/export.cpp b/platform/x11/export/export.cpp index 904aceaca0..f7d98c1d68 100644 --- a/platform/x11/export/export.cpp +++ b/platform/x11/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/x11/export/export.h b/platform/x11/export/export.h index ac44cef8f7..61b9a77524 100644 --- a/platform/x11/export/export.h +++ b/platform/x11/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/x11/godot_x11.cpp b/platform/x11/godot_x11.cpp index 21148f8e86..79407cd9dc 100644 --- a/platform/x11/godot_x11.cpp +++ b/platform/x11/godot_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/x11/joypad_linux.cpp b/platform/x11/joypad_linux.cpp index aff9a50bd6..c4dd8fe0e0 100644 --- a/platform/x11/joypad_linux.cpp +++ b/platform/x11/joypad_linux.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -367,12 +367,12 @@ void JoypadLinux::open_joypad(const char *p_path) { joy.fd = fd; joy.devpath = String(p_path); setup_joypad_properties(joy_num); - sprintf(uid, "%04x%04x", __bswap_16(inpid.bustype), 0); + sprintf(uid, "%04x%04x", BSWAP16(inpid.bustype), 0); if (inpid.vendor && inpid.product && inpid.version) { - uint16_t vendor = __bswap_16(inpid.vendor); - uint16_t product = __bswap_16(inpid.product); - uint16_t version = __bswap_16(inpid.version); + uint16_t vendor = BSWAP16(inpid.vendor); + uint16_t product = BSWAP16(inpid.product); + uint16_t version = BSWAP16(inpid.version); sprintf(uid + String(uid).length(), "%04x%04x%04x%04x%04x%04x", vendor, 0, product, 0, version, 0); input->joy_connection_changed(joy_num, true, name, uid); @@ -476,7 +476,7 @@ void JoypadLinux::process_joypads() { // ev may be tainted and out of MAX_KEY range, which will cause // joy->key_map[ev.code] to crash - if (ev.code < 0 || ev.code >= MAX_KEY) + if (ev.code >= MAX_KEY) return; switch (ev.type) { diff --git a/platform/x11/joypad_linux.h b/platform/x11/joypad_linux.h index 34b240abf1..b5904ce90b 100644 --- a/platform/x11/joypad_linux.h +++ b/platform/x11/joypad_linux.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/x11/key_mapping_x11.cpp b/platform/x11/key_mapping_x11.cpp index 423e2737ee..9b5dfa4793 100644 --- a/platform/x11/key_mapping_x11.cpp +++ b/platform/x11/key_mapping_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/x11/key_mapping_x11.h b/platform/x11/key_mapping_x11.h index 6f05941c19..853fe7954a 100644 --- a/platform/x11/key_mapping_x11.h +++ b/platform/x11/key_mapping_x11.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 04854e93b6..f034b2389b 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +29,8 @@ /*************************************************************************/ #include "os_x11.h" +#include "detect_prime.h" + #include "core/os/dir_access.h" #include "core/print_string.h" #include "drivers/gles2/rasterizer_gles2.h" @@ -77,6 +79,13 @@ #include <X11/XKBlib.h> +// 2.2 is the first release with multitouch +#define XINPUT_CLIENT_VERSION_MAJOR 2 +#define XINPUT_CLIENT_VERSION_MINOR 2 + +static const double abs_resolution_mult = 10000.0; +static const double abs_resolution_range_mult = 10.0; + void OS_X11::initialize_core() { crash_handler.initialize(); @@ -96,6 +105,8 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a xmbstring = NULL; x11_window = 0; last_click_ms = 0; + last_click_button_index = -1; + last_click_pos = Point2(-100, -100); args = OS::get_singleton()->get_cmdline_args(); current_videomode = p_desired; main_loop = NULL; @@ -168,48 +179,12 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a } } -#ifdef TOUCH_ENABLED - if (!XQueryExtension(x11_display, "XInputExtension", &touch.opcode, &event_base, &error_base)) { - print_verbose("XInput extension not available, touch support disabled."); - } else { - // 2.2 is the first release with multitouch - int xi_major = 2; - int xi_minor = 2; - if (XIQueryVersion(x11_display, &xi_major, &xi_minor) != Success) { - print_verbose(vformat("XInput 2.2 not available (server supports %d.%d), touch support disabled.", xi_major, xi_minor)); - touch.opcode = 0; - } else { - int dev_count; - XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count); - - for (int i = 0; i < dev_count; i++) { - XIDeviceInfo *dev = &info[i]; - if (!dev->enabled) - continue; - if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave)) - continue; - - bool direct_touch = false; - for (int j = 0; j < dev->num_classes; j++) { - if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) { - direct_touch = true; - break; - } - } - if (direct_touch) { - touch.devices.push_back(dev->deviceid); - print_verbose("XInput: Using touch device: " + String(dev->name)); - } - } - - XIFreeDeviceInfo(info); - - if (!touch.devices.size()) { - print_verbose("XInput: No touch devices found."); - } - } + if (!refresh_device_info()) { + OS::get_singleton()->alert("Your system does not support XInput 2.\n" + "Please upgrade your distribution.", + "Unable to initialize XInput"); + return ERR_UNAVAILABLE; } -#endif xim = XOpenIM(x11_display, NULL, NULL, NULL); @@ -267,10 +242,45 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a // maybe contextgl wants to be in charge of creating the window #if defined(OPENGL_ENABLED) - // Set DRI_PRIME if not set. This means that Godot should default to a higher-power GPU if it exists. - // Note: Due to the final '0' parameter to setenv any existing DRI_PRIME environment variables will not - // be overwritten. - setenv("DRI_PRIME", "1", 0); + if (getenv("DRI_PRIME") == NULL) { + int use_prime = -1; + + if (getenv("PRIMUS_DISPLAY") || + getenv("PRIMUS_libGLd") || + getenv("PRIMUS_libGLa") || + getenv("PRIMUS_libGL") || + getenv("PRIMUS_LOAD_GLOBAL") || + getenv("BUMBLEBEE_SOCKET")) { + + print_verbose("Optirun/primusrun detected. Skipping GPU detection"); + use_prime = 0; + } + + if (getenv("LD_LIBRARY_PATH")) { + String ld_library_path(getenv("LD_LIBRARY_PATH")); + Vector<String> libraries = ld_library_path.split(":"); + + for (int i = 0; i < libraries.size(); ++i) { + if (FileAccess::exists(libraries[i] + "/libGL.so.1") || + FileAccess::exists(libraries[i] + "/libGL.so")) { + + print_verbose("Custom libGL override detected. Skipping GPU detection"); + use_prime = 0; + } + } + } + + if (use_prime == -1) { + print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic."); + use_prime = detect_prime(); + } + + if (use_prime) { + print_line("Found discrete GPU, setting DRI_PRIME=1 to use it."); + print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU."); + setenv("DRI_PRIME", "1", 1); + } + } ContextGL_X11::ContextType opengl_api_type = ContextGL_X11::GLES_3_0_COMPATIBLE; @@ -289,7 +299,7 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a memdelete(context_gl); context_gl = NULL; - if (GLOBAL_GET("rendering/quality/driver/driver_fallback") == "Best" || editor) { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) { if (p_video_driver == VIDEO_DRIVER_GLES2) { gl_initialization_error = true; break; @@ -311,7 +321,7 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a RasterizerGLES3::make_current(); break; } else { - if (GLOBAL_GET("rendering/quality/driver/driver_fallback") == "Best" || editor) { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2") || editor) { p_video_driver = VIDEO_DRIVER_GLES2; opengl_api_type = ContextGL_X11::GLES_2_0_COMPATIBLE; continue; @@ -413,34 +423,42 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a XChangeWindowAttributes(x11_display, x11_window, CWEventMask, &new_attr); -#ifdef TOUCH_ENABLED - if (touch.devices.size()) { + static unsigned char all_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; + static unsigned char all_master_mask_data[XIMaskLen(XI_LASTEVENT)] = {}; - // Must be alive after this block - static unsigned char mask_data[XIMaskLen(XI_LASTEVENT)] = {}; + xi.all_event_mask.deviceid = XIAllDevices; + xi.all_event_mask.mask_len = sizeof(all_mask_data); + xi.all_event_mask.mask = all_mask_data; - touch.event_mask.deviceid = XIAllDevices; - touch.event_mask.mask_len = sizeof(mask_data); - touch.event_mask.mask = mask_data; + xi.all_master_event_mask.deviceid = XIAllMasterDevices; + xi.all_master_event_mask.mask_len = sizeof(all_master_mask_data); + xi.all_master_event_mask.mask = all_master_mask_data; - XISetMask(touch.event_mask.mask, XI_TouchBegin); - XISetMask(touch.event_mask.mask, XI_TouchUpdate); - XISetMask(touch.event_mask.mask, XI_TouchEnd); - XISetMask(touch.event_mask.mask, XI_TouchOwnership); + XISetMask(xi.all_event_mask.mask, XI_HierarchyChanged); + XISetMask(xi.all_master_event_mask.mask, XI_DeviceChanged); + XISetMask(xi.all_master_event_mask.mask, XI_RawMotion); - XISelectEvents(x11_display, x11_window, &touch.event_mask, 1); - - // Disabled by now since grabbing also blocks mouse events - // (they are received as extended events instead of standard events) - /*XIClearMask(touch.event_mask.mask, XI_TouchOwnership); - - // Grab touch devices to avoid OS gesture interference - for (int i = 0; i < touch.devices.size(); ++i) { - XIGrabDevice(x11_display, touch.devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &touch.event_mask); - }*/ +#ifdef TOUCH_ENABLED + if (xi.touch_devices.size()) { + XISetMask(xi.all_event_mask.mask, XI_TouchBegin); + XISetMask(xi.all_event_mask.mask, XI_TouchUpdate); + XISetMask(xi.all_event_mask.mask, XI_TouchEnd); + XISetMask(xi.all_event_mask.mask, XI_TouchOwnership); } #endif + XISelectEvents(x11_display, x11_window, &xi.all_event_mask, 1); + XISelectEvents(x11_display, DefaultRootWindow(x11_display), &xi.all_master_event_mask, 1); + + // Disabled by now since grabbing also blocks mouse events + // (they are received as extended events instead of standard events) + /*XIClearMask(xi.touch_event_mask.mask, XI_TouchOwnership); + + // Grab touch devices to avoid OS gesture interference + for (int i = 0; i < xi.touch_devices.size(); ++i) { + XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask); + }*/ + /* set the titlebar name */ XStoreName(x11_display, x11_window, "Godot"); @@ -485,55 +503,57 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a current_cursor = CURSOR_ARROW; - if (cursor_theme) { - for (int i = 0; i < CURSOR_MAX; i++) { - - static const char *cursor_file[] = { - "left_ptr", - "xterm", - "hand2", - "cross", - "watch", - "left_ptr_watch", - "fleur", - "hand1", - "X_cursor", - "sb_v_double_arrow", - "sb_h_double_arrow", - "size_bdiag", - "size_fdiag", - "hand1", - "sb_v_double_arrow", - "sb_h_double_arrow", - "question_arrow" - }; + for (int i = 0; i < CURSOR_MAX; i++) { - img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size); - if (img[i]) { - cursors[i] = XcursorImageLoadCursor(x11_display, img[i]); - } else { - print_verbose("Failed loading custom cursor: " + String(cursor_file[i])); - } + static const char *cursor_file[] = { + "left_ptr", + "xterm", + "hand2", + "cross", + "watch", + "left_ptr_watch", + "fleur", + "hand1", + "X_cursor", + "sb_v_double_arrow", + "sb_h_double_arrow", + "size_bdiag", + "size_fdiag", + "hand1", + "sb_v_double_arrow", + "sb_h_double_arrow", + "question_arrow" + }; + + img[i] = XcursorLibraryLoadImage(cursor_file[i], cursor_theme, cursor_size); + if (img[i]) { + cursors[i] = XcursorImageLoadCursor(x11_display, img[i]); + } else { + print_verbose("Failed loading custom cursor: " + String(cursor_file[i])); } } { - Pixmap cursormask; - XGCValues xgc; - GC gc; - XColor col; - Cursor cursor; + // Creating an empty/transparent cursor + + // Create 1x1 bitmap + Pixmap cursormask = XCreatePixmap(x11_display, + RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1); - cursormask = XCreatePixmap(x11_display, RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1); + // Fill with zero + XGCValues xgc; xgc.function = GXclear; - gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc); + GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc); XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1); - col.pixel = 0; - col.red = 0; - col.flags = 4; - cursor = XCreatePixmapCursor(x11_display, - cursormask, cursormask, + + // Color value doesn't matter. Mask zero means no foreground or background will be drawn + XColor col = {}; + + Cursor cursor = XCreatePixmapCursor(x11_display, + cursormask, // source (using cursor mask as placeholder, since it'll all be ignored) + cursormask, // mask &col, &col, 0, 0); + XFreePixmap(x11_display, cursormask); XFreeGC(x11_display, gc); @@ -590,6 +610,101 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a return OK; } +bool OS_X11::refresh_device_info() { + int event_base, error_base; + + print_verbose("XInput: Refreshing devices."); + + if (!XQueryExtension(x11_display, "XInputExtension", &xi.opcode, &event_base, &error_base)) { + print_verbose("XInput extension not available. Please upgrade your distribution."); + return false; + } + + int xi_major_query = XINPUT_CLIENT_VERSION_MAJOR; + int xi_minor_query = XINPUT_CLIENT_VERSION_MINOR; + + if (XIQueryVersion(x11_display, &xi_major_query, &xi_minor_query) != Success) { + print_verbose(vformat("XInput 2 not available (server supports %d.%d).", xi_major_query, xi_minor_query)); + xi.opcode = 0; + return false; + } + + if (xi_major_query < XINPUT_CLIENT_VERSION_MAJOR || (xi_major_query == XINPUT_CLIENT_VERSION_MAJOR && xi_minor_query < XINPUT_CLIENT_VERSION_MINOR)) { + print_verbose(vformat("XInput %d.%d not available (server supports %d.%d). Touch input unavailable.", + XINPUT_CLIENT_VERSION_MAJOR, XINPUT_CLIENT_VERSION_MINOR, xi_major_query, xi_minor_query)); + } + + xi.absolute_devices.clear(); + xi.touch_devices.clear(); + + int dev_count; + XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count); + + for (int i = 0; i < dev_count; i++) { + XIDeviceInfo *dev = &info[i]; + if (!dev->enabled) + continue; + if (!(dev->use == XIMasterPointer || dev->use == XIFloatingSlave)) + continue; + + bool direct_touch = false; + bool absolute_mode = false; + int resolution_x = 0; + int resolution_y = 0; + int range_min_x = 0; + int range_min_y = 0; + int range_max_x = 0; + int range_max_y = 0; + for (int j = 0; j < dev->num_classes; j++) { +#ifdef TOUCH_ENABLED + if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) { + direct_touch = true; + } +#endif + if (dev->classes[j]->type == XIValuatorClass) { + XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j]; + + if (class_info->number == 0 && class_info->mode == XIModeAbsolute) { + resolution_x = class_info->resolution; + range_min_x = class_info->min; + range_max_x = class_info->max; + absolute_mode = true; + } else if (class_info->number == 1 && class_info->mode == XIModeAbsolute) { + resolution_y = class_info->resolution; + range_min_y = class_info->min; + range_max_y = class_info->max; + absolute_mode = true; + } + } + } + if (direct_touch) { + xi.touch_devices.push_back(dev->deviceid); + print_verbose("XInput: Using touch device: " + String(dev->name)); + } + if (absolute_mode) { + // If no resolution was reported, use the min/max ranges. + if (resolution_x <= 0) { + resolution_x = (range_max_x - range_min_x) * abs_resolution_range_mult; + } + if (resolution_y <= 0) { + resolution_y = (range_max_y - range_min_y) * abs_resolution_range_mult; + } + + xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y); + print_verbose("XInput: Absolute pointing device: " + String(dev->name)); + } + } + + XIFreeDeviceInfo(info); +#ifdef TOUCH_ENABLED + if (!xi.touch_devices.size()) { + print_verbose("XInput: No touch devices found."); + } +#endif + + return true; +} + void OS_X11::xim_destroy_callback(::XIM im, ::XPointer client_data, ::XPointer call_data) { @@ -662,10 +777,10 @@ void OS_X11::finalize() { #ifdef JOYDEV_ENABLED memdelete(joypad); #endif -#ifdef TOUCH_ENABLED - touch.devices.clear(); - touch.state.clear(); -#endif + + xi.touch_devices.clear(); + xi.state.clear(); + memdelete(input); visual_server->finish(); @@ -725,21 +840,8 @@ void OS_X11::set_mouse_mode(MouseMode p_mode) { if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED) { - while (true) { - //flush pending motion events - - if (XPending(x11_display) > 0) { - XEvent event; - XPeekEvent(x11_display, &event); - if (event.type == MotionNotify) { - XNextEvent(x11_display, &event); - } else { - break; - } - } else { - break; - } - } + //flush pending motion events + flush_mouse_motion(); if (XGrabPointer( x11_display, x11_window, True, @@ -780,6 +882,32 @@ void OS_X11::warp_mouse_position(const Point2 &p_to) { } } +void OS_X11::flush_mouse_motion() { + while (true) { + if (XPending(x11_display) > 0) { + XEvent event; + XPeekEvent(x11_display, &event); + + if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { + XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; + + if (event_data->evtype == XI_RawMotion) { + XNextEvent(x11_display, &event); + } else { + break; + } + } else { + break; + } + } else { + break; + } + } + + xi.relative_motion.x = 0; + xi.relative_motion.y = 0; +} + OS::MouseMode OS_X11::get_mouse_mode() const { return mouse_mode; } @@ -1039,7 +1167,7 @@ int OS_X11::get_screen_dpi(int p_screen) const { int height_mm = DisplayHeightMM(x11_display, p_screen); double xdpi = (width_mm ? sc.width / (double)width_mm * 25.4 : 0); double ydpi = (height_mm ? sc.height / (double)height_mm * 25.4 : 0); - if (xdpi || xdpi) + if (xdpi || ydpi) return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1); //could not get dpi @@ -1050,15 +1178,33 @@ Point2 OS_X11::get_window_position() const { int x, y; Window child; XTranslateCoordinates(x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child); - - int screen = get_current_screen(); - Point2i screen_position = get_screen_position(screen); - - return Point2i(x - screen_position.x, y - screen_position.y); + return Point2i(x, y); } void OS_X11::set_window_position(const Point2 &p_position) { - XMoveWindow(x11_display, x11_window, p_position.x, p_position.y); + int x = 0; + int y = 0; + if (get_borderless_window() == false) { + //exclude window decorations + XSync(x11_display, False); + Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); + if (prop != None) { + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = NULL; + if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { + if (format == 32 && len == 4) { + long *extents = (long *)data; + x = extents[0]; + y = extents[2]; + } + XFree(data); + } + } + } + XMoveWindow(x11_display, x11_window, p_position.x - x, p_position.y - y); update_real_mouse_position(); } @@ -1075,15 +1221,20 @@ Size2 OS_X11::get_real_window_size() const { int w = xwa.width; int h = xwa.height; Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = NULL; - if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { - long *extents = (long *)data; - w += extents[0] + extents[1]; // left, right - h += extents[2] + extents[3]; // top, bottom + if (prop != None) { + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = NULL; + if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { + if (format == 32 && len == 4) { + long *extents = (long *)data; + w += extents[0] + extents[1]; // left, right + h += extents[2] + extents[3]; // top, bottom + } + XFree(data); + } } return Size2(w, h); } @@ -1433,37 +1584,17 @@ void OS_X11::get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWith state->set_metakey((p_x11_state & Mod4Mask)); } -unsigned int OS_X11::get_mouse_button_state(unsigned int p_x11_state) { - - unsigned int state = 0; - - if (p_x11_state & Button1Mask) { - - state |= 1 << 0; - } - - if (p_x11_state & Button3Mask) { - - state |= 1 << 1; - } - - if (p_x11_state & Button2Mask) { - - state |= 1 << 2; - } - - if (p_x11_state & Button4Mask) { - - state |= 1 << 3; - } +unsigned int OS_X11::get_mouse_button_state(unsigned int p_x11_button, int p_x11_type) { - if (p_x11_state & Button5Mask) { + unsigned int mask = 1 << (p_x11_button - 1); - state |= 1 << 4; + if (p_x11_type == ButtonPress) { + last_button_state |= mask; + } else { + last_button_state &= ~mask; } - last_button_state = state; - return state; + return last_button_state; } void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { @@ -1554,7 +1685,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { k->set_shift(true); } - input->parse_input_event(k); + input->accumulate_input_event(k); } return; } @@ -1637,7 +1768,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { // is correct, but the xorg developers are // not very helpful today. - ::Time tresh = ABS(peek_event.xkey.time - xkeyevent->time); + ::Time tresh = ABSDIFF(peek_event.xkey.time, xkeyevent->time); if (peek_event.type == KeyPress && tresh < 5) { KeySym rk; XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, NULL); @@ -1698,7 +1829,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { } //printf("key: %x\n",k->get_scancode()); - input->parse_input_event(k); + input->accumulate_input_event(k); } struct Property { @@ -1796,17 +1927,72 @@ void OS_X11::process_xevents() { continue; } -#ifdef TOUCH_ENABLED if (XGetEventData(x11_display, &event.xcookie)) { - if (event.xcookie.type == GenericEvent && event.xcookie.extension == touch.opcode) { + if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; int index = event_data->detail; Vector2 pos = Vector2(event_data->event_x, event_data->event_y); switch (event_data->evtype) { + case XI_HierarchyChanged: + case XI_DeviceChanged: { + refresh_device_info(); + } break; + case XI_RawMotion: { + XIRawEvent *raw_event = (XIRawEvent *)event_data; + int device_id = raw_event->deviceid; + + // Determine the axis used (called valuators in XInput for some forsaken reason) + // Mask is a bitmask indicating which axes are involved. + // We are interested in the values of axes 0 and 1. + if (raw_event->valuators.mask_len <= 0) { + break; + } + const double *values = raw_event->raw_values; + + double rel_x = 0.0; + double rel_y = 0.0; + + if (XIMaskIsSet(raw_event->valuators.mask, 0)) { + rel_x = *values; + values++; + } + + if (XIMaskIsSet(raw_event->valuators.mask, 1)) { + rel_y = *values; + } + + // https://bugs.freedesktop.org/show_bug.cgi?id=71609 + // http://lists.libsdl.org/pipermail/commits-libsdl.org/2015-June/000282.html + if (raw_event->time == xi.last_relative_time && rel_x == xi.relative_motion.x && rel_y == xi.relative_motion.y) { + break; // Flush duplicate to avoid overly fast motion + } + + xi.old_raw_pos.x = xi.raw_pos.x; + xi.old_raw_pos.y = xi.raw_pos.y; + xi.raw_pos.x = rel_x; + xi.raw_pos.y = rel_y; + + Map<int, Vector2>::Element *abs_info = xi.absolute_devices.find(device_id); + + if (abs_info) { + // Absolute mode device + Vector2 mult = abs_info->value(); + + xi.relative_motion.x += (xi.raw_pos.x - xi.old_raw_pos.x) * mult.x; + xi.relative_motion.y += (xi.raw_pos.y - xi.old_raw_pos.y) * mult.y; + } else { + // Relative mode device + xi.relative_motion.x = xi.raw_pos.x; + xi.relative_motion.y = xi.raw_pos.y; + } + + xi.last_relative_time = raw_event->time; + } break; +#ifdef TOUCH_ENABLED case XI_TouchBegin: // Fall-through // Disabled hand-in-hand with the grabbing //XIAllowTouchEvents(x11_display, event_data->deviceid, event_data->detail, x11_window, XIAcceptTouch); @@ -1822,26 +2008,26 @@ void OS_X11::process_xevents() { st->set_pressed(is_begin); if (is_begin) { - if (touch.state.has(index)) // Defensive + if (xi.state.has(index)) // Defensive break; - touch.state[index] = pos; - if (touch.state.size() == 1) { + xi.state[index] = pos; + if (xi.state.size() == 1) { // X11 may send a motion event when a touch gesture begins, that would result // in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out - touch.mouse_pos_to_filter = pos; + xi.mouse_pos_to_filter = pos; } - input->parse_input_event(st); + input->accumulate_input_event(st); } else { - if (!touch.state.has(index)) // Defensive + if (!xi.state.has(index)) // Defensive break; - touch.state.erase(index); - input->parse_input_event(st); + xi.state.erase(index); + input->accumulate_input_event(st); } } break; case XI_TouchUpdate: { - Map<int, Vector2>::Element *curr_pos_elem = touch.state.find(index); + Map<int, Vector2>::Element *curr_pos_elem = xi.state.find(index); if (!curr_pos_elem) { // Defensive break; } @@ -1853,16 +2039,16 @@ void OS_X11::process_xevents() { sd->set_index(index); sd->set_position(pos); sd->set_relative(pos - curr_pos_elem->value()); - input->parse_input_event(sd); + input->accumulate_input_event(sd); curr_pos_elem->value() = pos; } } break; +#endif } } } XFreeEventData(x11_display, &event.xcookie); -#endif switch (event.type) { case Expose: @@ -1880,15 +2066,11 @@ void OS_X11::process_xevents() { case LeaveNotify: { if (main_loop && !mouse_mode_grab) main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT); - if (input) - input->set_mouse_in_window(false); } break; case EnterNotify: { if (main_loop && !mouse_mode_grab) main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); - if (input) - input->set_mouse_in_window(true); } break; case FocusIn: minimized = false; @@ -1908,8 +2090,8 @@ void OS_X11::process_xevents() { } #ifdef TOUCH_ENABLED // Grab touch devices to avoid OS gesture interference - /*for (int i = 0; i < touch.devices.size(); ++i) { - XIGrabDevice(x11_display, touch.devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &touch.event_mask); + /*for (int i = 0; i < xi.touch_devices.size(); ++i) { + XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask); }*/ #endif if (xic) { @@ -1919,7 +2101,9 @@ void OS_X11::process_xevents() { case FocusOut: window_has_focus = false; + input->release_pressed_events(); main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); + if (mouse_mode_grab) { //dear X11, I try, I really try, but you never work, you do whathever you want. if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -1930,20 +2114,20 @@ void OS_X11::process_xevents() { } #ifdef TOUCH_ENABLED // Ungrab touch devices so input works as usual while we are unfocused - /*for (int i = 0; i < touch.devices.size(); ++i) { - XIUngrabDevice(x11_display, touch.devices[i], CurrentTime); + /*for (int i = 0; i < xi.touch_devices.size(); ++i) { + XIUngrabDevice(x11_display, xi.touch_devices[i], CurrentTime); }*/ // Release every pointer to avoid sticky points - for (Map<int, Vector2>::Element *E = touch.state.front(); E; E = E->next()) { + for (Map<int, Vector2>::Element *E = xi.state.front(); E; E = E->next()) { Ref<InputEventScreenTouch> st; st.instance(); st->set_index(E->key()); st->set_position(E->get()); - input->parse_input_event(st); + input->accumulate_input_event(st); } - touch.state.clear(); + xi.state.clear(); #endif if (xic) { XUnsetICFocus(xic); @@ -1967,34 +2151,42 @@ void OS_X11::process_xevents() { mb.instance(); get_key_modifier_state(event.xbutton.state, mb); - mb->set_button_mask(get_mouse_button_state(event.xbutton.state)); - mb->set_position(Vector2(event.xbutton.x, event.xbutton.y)); - mb->set_global_position(mb->get_position()); mb->set_button_index(event.xbutton.button); if (mb->get_button_index() == 2) mb->set_button_index(3); else if (mb->get_button_index() == 3) mb->set_button_index(2); + mb->set_button_mask(get_mouse_button_state(mb->get_button_index(), event.xbutton.type)); + mb->set_position(Vector2(event.xbutton.x, event.xbutton.y)); + mb->set_global_position(mb->get_position()); mb->set_pressed((event.type == ButtonPress)); - if (event.type == ButtonPress && event.xbutton.button == 1) { + if (event.type == ButtonPress) { uint64_t diff = get_ticks_usec() / 1000 - last_click_ms; - if (diff < 400 && Point2(last_click_pos).distance_to(Point2(event.xbutton.x, event.xbutton.y)) < 5) { + if (mb->get_button_index() == last_click_button_index) { - last_click_ms = 0; - last_click_pos = Point2(-100, -100); - mb->set_doubleclick(true); + if (diff < 400 && Point2(last_click_pos).distance_to(Point2(event.xbutton.x, event.xbutton.y)) < 5) { - } else { + last_click_ms = 0; + last_click_pos = Point2(-100, -100); + last_click_button_index = -1; + mb->set_doubleclick(true); + } + + } else if (mb->get_button_index() < 4 || mb->get_button_index() > 7) { + last_click_button_index = mb->get_button_index(); + } + + if (!mb->is_doubleclick()) { last_click_ms += diff; last_click_pos = Point2(event.xbutton.x, event.xbutton.y); } } - input->parse_input_event(mb); + input->accumulate_input_event(mb); } break; case MotionNotify: { @@ -2028,34 +2220,27 @@ void OS_X11::process_xevents() { // Motion is also simple. // A little hack is in order // to be able to send relative motion events. - Point2i pos(event.xmotion.x, event.xmotion.y); + Point2 pos(event.xmotion.x, event.xmotion.y); -#ifdef TOUCH_ENABLED // Avoidance of spurious mouse motion (see handling of touch) bool filter = false; // Adding some tolerance to match better Point2i to Vector2 - if (touch.state.size() && Vector2(pos).distance_squared_to(touch.mouse_pos_to_filter) < 2) { + if (xi.state.size() && Vector2(pos).distance_squared_to(xi.mouse_pos_to_filter) < 2) { filter = true; } // Invalidate to avoid filtering a possible legitimate similar event coming later - touch.mouse_pos_to_filter = Vector2(1e10, 1e10); + xi.mouse_pos_to_filter = Vector2(1e10, 1e10); if (filter) { break; } -#endif if (mouse_mode == MOUSE_MODE_CAPTURED) { - - if (pos == Point2i(current_videomode.width / 2, current_videomode.height / 2)) { - //this sucks, it's a hack, etc and is a little inaccurate, etc. - //but nothing I can do, X11 sucks. - - center = pos; + if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) { break; } Point2i new_center = pos; - pos = last_mouse_pos + (pos - center); + pos = last_mouse_pos + xi.relative_motion; center = new_center; do_mouse_warp = window_has_focus; // warp the cursor if we're focused in } @@ -2066,7 +2251,24 @@ void OS_X11::process_xevents() { last_mouse_pos_valid = true; } - Point2i rel = pos - last_mouse_pos; + // Hackish but relative mouse motion is already handled in the RawMotion event. + // RawMotion does not provide the absolute mouse position (whereas MotionNotify does). + // Therefore, RawMotion cannot be the authority on absolute mouse position. + // RawMotion provides more precision than MotionNotify, which doesn't sense subpixel motion. + // Therefore, MotionNotify cannot be the authority on relative mouse motion. + // This means we need to take a combined approach... + Point2 rel; + + // Only use raw input if in capture mode. Otherwise use the classic behavior. + if (mouse_mode == MOUSE_MODE_CAPTURED) { + rel = xi.relative_motion; + } else { + rel = pos - last_mouse_pos; + } + + // Reset to prevent lingering motion + xi.relative_motion.x = 0; + xi.relative_motion.y = 0; if (mouse_mode == MOUSE_MODE_CAPTURED) { pos = Point2i(current_videomode.width / 2, current_videomode.height / 2); @@ -2075,12 +2277,16 @@ void OS_X11::process_xevents() { Ref<InputEventMouseMotion> mm; mm.instance(); + // Make the absolute position integral so it doesn't look _too_ weird :) + Point2i posi(pos); + get_key_modifier_state(event.xmotion.state, mm); - mm->set_button_mask(get_mouse_button_state(event.xmotion.state)); - mm->set_position(pos); - mm->set_global_position(pos); - input->set_mouse_position(pos); + mm->set_button_mask(get_mouse_button_state()); + mm->set_position(posi); + mm->set_global_position(posi); + input->set_mouse_position(posi); mm->set_speed(input->get_last_mouse_speed()); + mm->set_relative(rel); last_mouse_pos = pos; @@ -2090,7 +2296,7 @@ void OS_X11::process_xevents() { // this is so that the relative motion doesn't get messed up // after we regain focus. if (window_has_focus || !mouse_mode_grab) - input->parse_input_event(mm); + input->accumulate_input_event(mm); } break; case KeyPress: @@ -2172,7 +2378,7 @@ void OS_X11::process_xevents() { Vector<String> files = String((char *)p.data).split("\n", false); for (int i = 0; i < files.size(); i++) { - files.write[i] = files[i].replace("file://", "").replace("%20", " ").strip_escapes(); + files.write[i] = files[i].replace("file://", "").http_unescape().strip_escapes(); } main_loop->drop_files(files); @@ -2274,6 +2480,8 @@ void OS_X11::process_xevents() { printf("Win: %d,%d\n", win_x, win_y); */ } + + input->flush_accumulated_events(); } MainLoop *OS_X11::get_main_loop() const { @@ -2388,7 +2596,7 @@ String OS_X11::get_clipboard() const { return ret; } -String OS_X11::get_name() { +String OS_X11::get_name() const { return "X11"; } @@ -2410,7 +2618,7 @@ Error OS_X11::shell_open(String p_uri) { bool OS_X11::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc" || p_feature == "s3tc" || p_feature == "bptc"; + return p_feature == "pc"; } String OS_X11::get_config_path() const { @@ -2537,6 +2745,11 @@ void OS_X11::set_cursor_shape(CursorShape p_shape) { current_cursor = p_shape; } +OS::CursorShape OS_X11::get_cursor_shape() const { + + return current_cursor; +} + void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { if (p_cursor.is_valid()) { Ref<Texture> texture = p_cursor; @@ -2851,13 +3064,37 @@ bool OS_X11::is_vsync_enabled() const { void OS_X11::set_context(int p_context) { XClassHint *classHint = XAllocClassHint(); + if (classHint) { - if (p_context == CONTEXT_EDITOR) - classHint->res_name = (char *)"Godot_Editor"; - if (p_context == CONTEXT_PROJECTMAN) - classHint->res_name = (char *)"Godot_ProjectList"; - classHint->res_class = (char *)"Godot"; + CharString name_str; + switch (p_context) { + case CONTEXT_EDITOR: + name_str = "Godot_Editor"; + break; + case CONTEXT_PROJECTMAN: + name_str = "Godot_ProjectList"; + break; + case CONTEXT_ENGINE: + name_str = "Godot_Engine"; + break; + } + + CharString class_str; + if (p_context == CONTEXT_ENGINE) { + String config_name = GLOBAL_GET("application/config/name"); + if (config_name.length() == 0) { + class_str = "Godot_Engine"; + } else { + class_str = config_name.utf8(); + } + } else { + class_str = "Godot"; + } + + classHint->res_class = class_str.ptrw(); + classHint->res_name = name_str.ptrw(); + XSetClassHint(x11_display, x11_window, classHint); XFree(classHint); } @@ -3035,6 +3272,8 @@ OS_X11::OS_X11() { AudioDriverManager::add_driver(&driver_alsa); #endif + xi.opcode = 0; + xi.last_relative_time = 0; layered_window = false; minimized = false; xim_style = 0L; diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index bb8411e213..ad35cdb4f9 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -35,7 +35,7 @@ #include "core/os/input.h" #include "crash_handler_x11.h" #include "drivers/alsa/audio_driver_alsa.h" -#include "drivers/alsamidi/alsa_midi.h" +#include "drivers/alsamidi/midi_driver_alsamidi.h" #include "drivers/pulseaudio/audio_driver_pulseaudio.h" #include "drivers/unix/os_unix.h" #include "joypad_linux.h" @@ -48,11 +48,9 @@ #include <X11/Xcursor/Xcursor.h> #include <X11/Xlib.h> +#include <X11/extensions/XInput2.h> #include <X11/extensions/Xrandr.h> #include <X11/keysym.h> -#ifdef TOUCH_ENABLED -#include <X11/extensions/XInput2.h> -#endif // Hints for X11 fullscreen typedef struct { @@ -121,23 +119,32 @@ class OS_X11 : public OS_Unix { bool im_active; Vector2 im_position; - Point2i last_mouse_pos; + Point2 last_mouse_pos; bool last_mouse_pos_valid; Point2i last_click_pos; uint64_t last_click_ms; + int last_click_button_index; uint32_t last_button_state; -#ifdef TOUCH_ENABLED + struct { int opcode; - Vector<int> devices; - XIEventMask event_mask; + Vector<int> touch_devices; + Map<int, Vector2> absolute_devices; + XIEventMask all_event_mask; + XIEventMask all_master_event_mask; Map<int, Vector2> state; Vector2 mouse_pos_to_filter; - } touch; -#endif + Vector2 relative_motion; + Vector2 raw_pos; + Vector2 old_raw_pos; + ::Time last_relative_time; + } xi; + + bool refresh_device_info(); - unsigned int get_mouse_button_state(unsigned int p_x11_state); + unsigned int get_mouse_button_state(unsigned int p_x11_button, int p_x11_type); void get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state); + void flush_mouse_motion(); MouseMode mouse_mode; Point2i center; @@ -209,9 +216,10 @@ protected: bool is_window_maximize_allowed(); public: - virtual String get_name(); + virtual String get_name() const; virtual void set_cursor_shape(CursorShape p_shape); + virtual CursorShape get_cursor_shape() const; virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); void set_mouse_mode(MouseMode p_mode); diff --git a/platform/x11/platform_config.h b/platform/x11/platform_config.h index f6d7f5a8cd..9e6011ddf7 100644 --- a/platform/x11/platform_config.h +++ b/platform/x11/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/x11/power_x11.cpp b/platform/x11/power_x11.cpp index 4ac331d212..50da6a4967 100644 --- a/platform/x11/power_x11.cpp +++ b/platform/x11/power_x11.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -115,7 +115,7 @@ bool PowerX11::make_proc_acpi_key_val(char **_ptr, char **_key, char **_val) { *(ptr++) = '\0'; /* terminate the key. */ - while ((*ptr == ' ') && (*ptr != '\0')) { + while (*ptr == ' ') { ptr++; /* skip whitespace. */ } diff --git a/platform/x11/power_x11.h b/platform/x11/power_x11.h index d0805b6f8a..469e3910f4 100644 --- a/platform/x11/power_x11.h +++ b/platform/x11/power_x11.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef X11_POWER_H_ -#define X11_POWER_H_ +#ifndef POWER_X11_H +#define POWER_X11_H #include "core/os/dir_access.h" #include "core/os/file_access.h" @@ -63,4 +63,4 @@ public: int get_power_percent_left(); }; -#endif /* X11_POWER_H_ */ +#endif // POWER_X11_H |