diff options
Diffstat (limited to 'platform/android')
190 files changed, 6106 insertions, 7806 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 31fee5722c..65172a12c0 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -1,165 +1,46 @@ #!/usr/bin/env python -import shutil -from compat import open_utf8 -from distutils.version import LooseVersion from detect import get_ndk_version +from distutils.version import LooseVersion Import('env') android_files = [ - 'os_android.cpp', - 'godot_android.cpp', 'file_access_android.cpp', - 'dir_access_android.cpp', 'audio_driver_opensl.cpp', 'file_access_jandroid.cpp', 'dir_access_jandroid.cpp', 'thread_jandroid.cpp', 'audio_driver_jandroid.cpp', - 'ifaddrs_android.cpp', - 'android_native_app_glue.c', - 'java_glue.cpp', - 'cpu-features.c', + 'java_godot_lib_jni.cpp', 'java_class_wrapper.cpp', -# 'power_android.cpp' + 'java_godot_wrapper.cpp', + 'java_godot_io_wrapper.cpp', + #'power_android.cpp' ] -# env.Depends('#core/math/vector3.h', 'vector3_psp.h') - -#obj = env.SharedObject('godot_android.cpp') - env_android = env.Clone() -if env['target'] == "profile": - env_android.Append(CPPFLAGS=['-DPROFILER_ENABLED']) android_objects = [] for x in android_files: android_objects.append(env_android.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) - +env_thirdparty = env_android.Clone() +env_thirdparty.disable_warnings() +android_objects.append(env_thirdparty.SharedObject('#thirdparty/misc/ifaddrs-android.cc')) lib = env_android.add_shared_library("#bin/libgodot", [android_objects], SHLIBSUFFIX=env["SHLIBSUFFIX"]) lib_arch_dir = '' -if env['android_arch'] == 'armv6': - lib_arch_dir = 'armeabi' -elif env['android_arch'] == 'armv7': +if env['android_arch'] == 'armv7': lib_arch_dir = 'armeabi-v7a' 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') @@ -169,9 +50,8 @@ if lib_arch_dir != '': else: # release_debug, debug lib_type_dir = 'debug' - out_dir = '#platform/android/java/libs/' + lib_type_dir + '/' + lib_arch_dir + out_dir = '#platform/android/java/lib/libs/' + lib_type_dir + '/' + lib_arch_dir env_android.Command(out_dir + '/libgodot_android.so', '#bin/libgodot' + env['SHLIBSUFFIX'], Move("$TARGET", "$SOURCE")) - ndk_version = get_ndk_version(env["ANDROID_NDK_ROOT"]) - if ndk_version != None and LooseVersion(ndk_version) >= LooseVersion("15.0.4075724"): - stl_lib_path = str(env['ANDROID_NDK_ROOT']) + '/sources/cxx-stl/llvm-libc++/libs/' + lib_arch_dir + '/libc++_shared.so' - env_android.Command(out_dir + '/libc++_shared.so', stl_lib_path, Copy("$TARGET", "$SOURCE"))
\ No newline at end of file + + stl_lib_path = str(env['ANDROID_NDK_ROOT']) + '/sources/cxx-stl/llvm-libc++/libs/' + lib_arch_dir + '/libc++_shared.so' + env_android.Command(out_dir + '/libc++_shared.so', stl_lib_path, Copy("$TARGET", "$SOURCE")) diff --git a/platform/android/android_native_app_glue.c b/platform/android/android_native_app_glue.c deleted file mode 100644 index 965f6284cd..0000000000 --- a/platform/android/android_native_app_glue.c +++ /dev/null @@ -1,437 +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. - * - */ - -#ifdef ANDROID_NATIVE_ACTIVITY - -#include <jni.h> - - -#include <errno.h> -#include <string.h> -#include <unistd.h> -#include <sys/resource.h> - -#include "android_native_app_glue.h" -#include <android/log.h> - -#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__)) - -static void free_saved_state(struct android_app* android_app) { - pthread_mutex_lock(&android_app->mutex); - if (android_app->savedState != NULL) { - free(android_app->savedState); - android_app->savedState = NULL; - android_app->savedStateSize = 0; - } - pthread_mutex_unlock(&android_app->mutex); -} - -int8_t android_app_read_cmd(struct android_app* android_app) { - int8_t cmd; - if (read(android_app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) { - switch (cmd) { - case APP_CMD_SAVE_STATE: - free_saved_state(android_app); - break; - } - return cmd; - } else { - LOGI("No data on command pipe!"); - } - return -1; -} - -static void print_cur_config(struct android_app* android_app) { - char lang[2], country[2]; - AConfiguration_getLanguage(android_app->config, lang); - AConfiguration_getCountry(android_app->config, country); - - LOGI("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d " - "keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d " - "modetype=%d modenight=%d", - AConfiguration_getMcc(android_app->config), - AConfiguration_getMnc(android_app->config), - lang[0], lang[1], country[0], country[1], - AConfiguration_getOrientation(android_app->config), - AConfiguration_getTouchscreen(android_app->config), - AConfiguration_getDensity(android_app->config), - AConfiguration_getKeyboard(android_app->config), - AConfiguration_getNavigation(android_app->config), - AConfiguration_getKeysHidden(android_app->config), - AConfiguration_getNavHidden(android_app->config), - AConfiguration_getSdkVersion(android_app->config), - AConfiguration_getScreenSize(android_app->config), - AConfiguration_getScreenLong(android_app->config), - AConfiguration_getUiModeType(android_app->config), - AConfiguration_getUiModeNight(android_app->config)); -} - -void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) { - switch (cmd) { - case APP_CMD_INPUT_CHANGED: - LOGI("APP_CMD_INPUT_CHANGED\n"); - pthread_mutex_lock(&android_app->mutex); - if (android_app->inputQueue != NULL) { - AInputQueue_detachLooper(android_app->inputQueue); - } - android_app->inputQueue = android_app->pendingInputQueue; - if (android_app->inputQueue != NULL) { - LOGI("Attaching input queue to looper"); - AInputQueue_attachLooper(android_app->inputQueue, - android_app->looper, LOOPER_ID_INPUT, NULL, - &android_app->inputPollSource); - } - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - break; - - case APP_CMD_INIT_WINDOW: - LOGI("APP_CMD_INIT_WINDOW\n"); - pthread_mutex_lock(&android_app->mutex); - android_app->window = android_app->pendingWindow; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - break; - - case APP_CMD_TERM_WINDOW: - LOGI("APP_CMD_TERM_WINDOW\n"); - pthread_cond_broadcast(&android_app->cond); - break; - - case APP_CMD_RESUME: - case APP_CMD_START: - case APP_CMD_PAUSE: - case APP_CMD_STOP: - LOGI("activityState=%d\n", cmd); - pthread_mutex_lock(&android_app->mutex); - android_app->activityState = cmd; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - break; - - case APP_CMD_CONFIG_CHANGED: - LOGI("APP_CMD_CONFIG_CHANGED\n"); - AConfiguration_fromAssetManager(android_app->config, - android_app->activity->assetManager); - print_cur_config(android_app); - break; - - case APP_CMD_DESTROY: - LOGI("APP_CMD_DESTROY\n"); - android_app->destroyRequested = 1; - break; - } -} - -void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) { - switch (cmd) { - case APP_CMD_TERM_WINDOW: - LOGI("APP_CMD_TERM_WINDOW\n"); - pthread_mutex_lock(&android_app->mutex); - android_app->window = NULL; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - break; - - case APP_CMD_SAVE_STATE: - LOGI("APP_CMD_SAVE_STATE\n"); - pthread_mutex_lock(&android_app->mutex); - android_app->stateSaved = 1; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - break; - - case APP_CMD_RESUME: - free_saved_state(android_app); - break; - } -} - -void app_dummy() { - -} - -static void android_app_destroy(struct android_app* android_app) { - LOGI("android_app_destroy!"); - free_saved_state(android_app); - pthread_mutex_lock(&android_app->mutex); - if (android_app->inputQueue != NULL) { - AInputQueue_detachLooper(android_app->inputQueue); - } - AConfiguration_delete(android_app->config); - android_app->destroyed = 1; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - // Can't touch android_app object after this. -} - -static void process_input(struct android_app* app, struct android_poll_source* source) { - AInputEvent* event = NULL; - if (AInputQueue_getEvent(app->inputQueue, &event) >= 0) { - LOGI("New input event: type=%d\n", AInputEvent_getType(event)); - if (AInputQueue_preDispatchEvent(app->inputQueue, event)) { - return; - } - int32_t handled = 0; - if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event); - AInputQueue_finishEvent(app->inputQueue, event, handled); - } else { - LOGI("Failure reading next input event: %s\n", strerror(errno)); - } -} - -static void process_cmd(struct android_app* app, struct android_poll_source* source) { - int8_t cmd = android_app_read_cmd(app); - android_app_pre_exec_cmd(app, cmd); - if (app->onAppCmd != NULL) app->onAppCmd(app, cmd); - android_app_post_exec_cmd(app, cmd); -} - -static void* android_app_entry(void* param) { - struct android_app* android_app = (struct android_app*)param; - - android_app->config = AConfiguration_new(); - AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager); - - print_cur_config(android_app); - - android_app->cmdPollSource.id = LOOPER_ID_MAIN; - android_app->cmdPollSource.app = android_app; - android_app->cmdPollSource.process = process_cmd; - android_app->inputPollSource.id = LOOPER_ID_INPUT; - android_app->inputPollSource.app = android_app; - android_app->inputPollSource.process = process_input; - - ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); - ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, - &android_app->cmdPollSource); - android_app->looper = looper; - - pthread_mutex_lock(&android_app->mutex); - android_app->running = 1; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - - android_main(android_app); - - android_app_destroy(android_app); - return NULL; -} - -// -------------------------------------------------------------------- -// Native activity interaction (called from main thread) -// -------------------------------------------------------------------- - -static struct android_app* android_app_create(ANativeActivity* activity, - void* savedState, size_t savedStateSize) { - struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app)); - memset(android_app, 0, sizeof(struct android_app)); - android_app->activity = activity; - - pthread_mutex_init(&android_app->mutex, NULL); - pthread_cond_init(&android_app->cond, NULL); - - if (savedState != NULL) { - android_app->savedState = malloc(savedStateSize); - android_app->savedStateSize = savedStateSize; - memcpy(android_app->savedState, savedState, savedStateSize); - } - - int msgpipe[2]; - if (pipe(msgpipe)) { - LOGI("could not create pipe: %s", strerror(errno)); - } - android_app->msgread = msgpipe[0]; - android_app->msgwrite = msgpipe[1]; - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&android_app->thread, &attr, android_app_entry, android_app); - - // Wait for thread to start. - pthread_mutex_lock(&android_app->mutex); - while (!android_app->running) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - pthread_mutex_unlock(&android_app->mutex); - - return android_app; -} - -static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) { - if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) { - LOGI("Failure writing android_app cmd: %s\n", strerror(errno)); - } -} - -static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) { - pthread_mutex_lock(&android_app->mutex); - android_app->pendingInputQueue = inputQueue; - android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED); - while (android_app->inputQueue != android_app->pendingInputQueue) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - pthread_mutex_unlock(&android_app->mutex); -} - -static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) { - pthread_mutex_lock(&android_app->mutex); - if (android_app->pendingWindow != NULL) { - android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW); - } - android_app->pendingWindow = window; - if (window != NULL) { - android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW); - } - while (android_app->window != android_app->pendingWindow) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - pthread_mutex_unlock(&android_app->mutex); -} - -static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) { - pthread_mutex_lock(&android_app->mutex); - android_app_write_cmd(android_app, cmd); - while (android_app->activityState != cmd) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - pthread_mutex_unlock(&android_app->mutex); -} - -static void android_app_free(struct android_app* android_app) { - pthread_mutex_lock(&android_app->mutex); - android_app_write_cmd(android_app, APP_CMD_DESTROY); - while (!android_app->destroyed) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - pthread_mutex_unlock(&android_app->mutex); - - close(android_app->msgread); - close(android_app->msgwrite); - pthread_cond_destroy(&android_app->cond); - pthread_mutex_destroy(&android_app->mutex); - free(android_app); -} - -static void onDestroy(ANativeActivity* activity) { - LOGI("Destroy: %p\n", activity); - android_app_free((struct android_app*)activity->instance); -} - -static void onStart(ANativeActivity* activity) { - LOGI("Start: %p\n", activity); - android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START); -} - -static void onResume(ANativeActivity* activity) { - LOGI("Resume: %p\n", activity); - android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_RESUME); -} - -static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { - struct android_app* android_app = (struct android_app*)activity->instance; - void* savedState = NULL; - - LOGI("SaveInstanceState: %p\n", activity); - pthread_mutex_lock(&android_app->mutex); - android_app->stateSaved = 0; - android_app_write_cmd(android_app, APP_CMD_SAVE_STATE); - while (!android_app->stateSaved) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - - if (android_app->savedState != NULL) { - savedState = android_app->savedState; - *outLen = android_app->savedStateSize; - android_app->savedState = NULL; - android_app->savedStateSize = 0; - } - - pthread_mutex_unlock(&android_app->mutex); - - return savedState; -} - -static void onPause(ANativeActivity* activity) { - LOGI("Pause: %p\n", activity); - android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE); -} - -static void onStop(ANativeActivity* activity) { - LOGI("Stop: %p\n", activity); - android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_STOP); -} - -static void onConfigurationChanged(ANativeActivity* activity) { - struct android_app* android_app = (struct android_app*)activity->instance; - LOGI("ConfigurationChanged: %p\n", activity); - android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED); -} - -static void onLowMemory(ANativeActivity* activity) { - struct android_app* android_app = (struct android_app*)activity->instance; - LOGI("LowMemory: %p\n", activity); - android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY); -} - -static void onWindowFocusChanged(ANativeActivity* activity, int focused) { - LOGI("WindowFocusChanged: %p -- %d\n", activity, focused); - android_app_write_cmd((struct android_app*)activity->instance, - focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); -} - -static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { - LOGI("NativeWindowCreated: %p -- %p\n", activity, window); - android_app_set_window((struct android_app*)activity->instance, window); -} - -static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { - LOGI("NativeWindowDestroyed: %p -- %p\n", activity, window); - android_app_set_window((struct android_app*)activity->instance, NULL); -} - -static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { - LOGI("InputQueueCreated: %p -- %p\n", activity, queue); - android_app_set_input((struct android_app*)activity->instance, queue); -} - -static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { - LOGI("InputQueueDestroyed: %p -- %p\n", activity, queue); - android_app_set_input((struct android_app*)activity->instance, NULL); -} - -void ANativeActivity_onCreate(ANativeActivity* activity, - void* savedState, size_t savedStateSize) { - LOGI("Creating: %p\n", activity); - activity->callbacks->onDestroy = onDestroy; - activity->callbacks->onStart = onStart; - activity->callbacks->onResume = onResume; - activity->callbacks->onSaveInstanceState = onSaveInstanceState; - activity->callbacks->onPause = onPause; - activity->callbacks->onStop = onStop; - activity->callbacks->onConfigurationChanged = onConfigurationChanged; - activity->callbacks->onLowMemory = onLowMemory; - activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; - activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; - activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; - activity->callbacks->onInputQueueCreated = onInputQueueCreated; - activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; - - activity->instance = android_app_create(activity, savedState, savedStateSize); -} -#endif diff --git a/platform/android/android_native_app_glue.h b/platform/android/android_native_app_glue.h deleted file mode 100644 index 36278d4c66..0000000000 --- a/platform/android/android_native_app_glue.h +++ /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. - * - */ - -#ifndef _ANDROID_NATIVE_APP_GLUE_H -#define _ANDROID_NATIVE_APP_GLUE_H -#ifdef ANDROID_NATIVE_ACTIVITY - -#include <poll.h> -#include <pthread.h> -#include <sched.h> - -#include <android/configuration.h> -#include <android/looper.h> -#include <android/native_activity.h> - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * The native activity interface provided by <android/native_activity.h> - * is based on a set of application-provided callbacks that will be called - * by the Activity's main thread when certain events occur. - * - * This means that each one of this callbacks _should_ _not_ block, or they - * risk having the system force-close the application. This programming - * model is direct, lightweight, but constraining. - * - * The 'threaded_native_app' static library is used to provide a different - * execution model where the application can implement its own main event - * loop in a different thread instead. Here's how it works: - * - * 1/ The application must provide a function named "android_main()" that - * will be called when the activity is created, in a new thread that is - * distinct from the activity's main thread. - * - * 2/ android_main() receives a pointer to a valid "android_app" structure - * that contains references to other important objects, e.g. the - * ANativeActivity obejct instance the application is running in. - * - * 3/ the "android_app" object holds an ALooper instance that already - * listens to two important things: - * - * - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX - * declarations below. - * - * - input events coming from the AInputQueue attached to the activity. - * - * Each of these correspond to an ALooper identifier returned by - * ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT, - * respectively. - * - * Your application can use the same ALooper to listen to additional - * file-descriptors. They can either be callback based, or with return - * identifiers starting with LOOPER_ID_USER. - * - * 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event, - * the returned data will point to an android_poll_source structure. You - * can call the process() function on it, and fill in android_app->onAppCmd - * and android_app->onInputEvent to be called for your own processing - * of the event. - * - * Alternatively, you can call the low-level functions to read and process - * the data directly... look at the process_cmd() and process_input() - * implementations in the glue to see how to do this. - * - * See the sample named "native-activity" that comes with the NDK with a - * full usage example. Also look at the JavaDoc of NativeActivity. - */ - -struct android_app; - -/** - * Data associated with an ALooper fd that will be returned as the "outData" - * when that source has data ready. - */ -struct android_poll_source { - // The identifier of this source. May be LOOPER_ID_MAIN or - // LOOPER_ID_INPUT. - int32_t id; - - // The android_app this ident is associated with. - struct android_app* app; - - // Function to call to perform the standard processing of data from - // this source. - void (*process)(struct android_app* app, struct android_poll_source* source); -}; - -/** - * This is the interface for the standard glue code of a threaded - * application. In this model, the application's code is running - * in its own thread separate from the main thread of the process. - * It is not required that this thread be associated with the Java - * VM, although it will need to be in order to make JNI calls any - * Java objects. - */ -struct android_app { - // The application can place a pointer to its own state object - // here if it likes. - void* userData; - - // Fill this in with the function to process main app commands (APP_CMD_*) - void (*onAppCmd)(struct android_app* app, int32_t cmd); - - // Fill this in with the function to process input events. At this point - // the event has already been pre-dispatched, and it will be finished upon - // return. Return 1 if you have handled the event, 0 for any default - // dispatching. - int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event); - - // The ANativeActivity object instance that this app is running in. - ANativeActivity* activity; - - // The current configuration the app is running in. - AConfiguration* config; - - // This is the last instance's saved state, as provided at creation time. - // It is NULL if there was no state. You can use this as you need; the - // memory will remain around until you call android_app_exec_cmd() for - // APP_CMD_RESUME, at which point it will be freed and savedState set to NULL. - // These variables should only be changed when processing a APP_CMD_SAVE_STATE, - // at which point they will be initialized to NULL and you can malloc your - // state and place the information here. In that case the memory will be - // freed for you later. - void* savedState; - size_t savedStateSize; - - // The ALooper associated with the app's thread. - ALooper* looper; - - // When non-NULL, this is the input queue from which the app will - // receive user input events. - AInputQueue* inputQueue; - - // When non-NULL, this is the window surface that the app can draw in. - ANativeWindow* window; - - // Current content rectangle of the window; this is the area where the - // window's content should be placed to be seen by the user. - ARect contentRect; - - // Current state of the app's activity. May be either APP_CMD_START, - // APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below. - int activityState; - - // This is non-zero when the application's NativeActivity is being - // destroyed and waiting for the app thread to complete. - int destroyRequested; - - // ------------------------------------------------- - // Below are "private" implementation of the glue code. - - pthread_mutex_t mutex; - pthread_cond_t cond; - - int msgread; - int msgwrite; - - pthread_t thread; - - struct android_poll_source cmdPollSource; - struct android_poll_source inputPollSource; - - int running; - int stateSaved; - int destroyed; - int redrawNeeded; - AInputQueue* pendingInputQueue; - ANativeWindow* pendingWindow; - ARect pendingContentRect; -}; - -enum { - /** - * Looper data ID of commands coming from the app's main thread, which - * is returned as an identifier from ALooper_pollOnce(). The data for this - * identifier is a pointer to an android_poll_source structure. - * These can be retrieved and processed with android_app_read_cmd() - * and android_app_exec_cmd(). - */ - LOOPER_ID_MAIN = 1, - - /** - * Looper data ID of events coming from the AInputQueue of the - * application's window, which is returned as an identifier from - * ALooper_pollOnce(). The data for this identifier is a pointer to an - * android_poll_source structure. These can be read via the inputQueue - * object of android_app. - */ - LOOPER_ID_INPUT = 2, - - /** - * Start of user-defined ALooper identifiers. - */ - LOOPER_ID_USER = 3, -}; - -enum { - /** - * Command from main thread: the AInputQueue has changed. Upon processing - * this command, android_app->inputQueue will be updated to the new queue - * (or NULL). - */ - APP_CMD_INPUT_CHANGED, - - /** - * Command from main thread: a new ANativeWindow is ready for use. Upon - * receiving this command, android_app->window will contain the new window - * surface. - */ - APP_CMD_INIT_WINDOW, - - /** - * Command from main thread: the existing ANativeWindow needs to be - * terminated. Upon receiving this command, android_app->window still - * contains the existing window; after calling android_app_exec_cmd - * it will be set to NULL. - */ - APP_CMD_TERM_WINDOW, - - /** - * Command from main thread: the current ANativeWindow has been resized. - * Please redraw with its new size. - */ - APP_CMD_WINDOW_RESIZED, - - /** - * Command from main thread: the system needs that the current ANativeWindow - * be redrawn. You should redraw the window before handing this to - * android_app_exec_cmd() in order to avoid transient drawing glitches. - */ - APP_CMD_WINDOW_REDRAW_NEEDED, - - /** - * Command from main thread: the content area of the window has changed, - * such as from the soft input window being shown or hidden. You can - * find the new content rect in android_app::contentRect. - */ - APP_CMD_CONTENT_RECT_CHANGED, - - /** - * Command from main thread: the app's activity window has gained - * input focus. - */ - APP_CMD_GAINED_FOCUS, - - /** - * Command from main thread: the app's activity window has lost - * input focus. - */ - APP_CMD_LOST_FOCUS, - - /** - * Command from main thread: the current device configuration has changed. - */ - APP_CMD_CONFIG_CHANGED, - - /** - * Command from main thread: the system is running low on memory. - * Try to reduce your memory use. - */ - APP_CMD_LOW_MEMORY, - - /** - * Command from main thread: the app's activity has been started. - */ - APP_CMD_START, - - /** - * Command from main thread: the app's activity has been resumed. - */ - APP_CMD_RESUME, - - /** - * Command from main thread: the app should generate a new saved state - * for itself, to restore from later if needed. If you have saved state, - * allocate it with malloc and place it in android_app.savedState with - * the size in android_app.savedStateSize. The will be freed for you - * later. - */ - APP_CMD_SAVE_STATE, - - /** - * Command from main thread: the app's activity has been paused. - */ - APP_CMD_PAUSE, - - /** - * Command from main thread: the app's activity has been stopped. - */ - APP_CMD_STOP, - - /** - * Command from main thread: the app's activity is being destroyed, - * and waiting for the app thread to clean up and exit before proceeding. - */ - APP_CMD_DESTROY, -}; - -/** - * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next - * app command message. - */ -int8_t android_app_read_cmd(struct android_app* android_app); - -/** - * Call with the command returned by android_app_read_cmd() to do the - * initial pre-processing of the given command. You can perform your own - * actions for the command after calling this function. - */ -void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd); - -/** - * Call with the command returned by android_app_read_cmd() to do the - * final post-processing of the given command. You must have done your own - * actions for the command before calling this function. - */ -void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd); - -/** - * Dummy function you can call to ensure glue code isn't stripped. - */ -void app_dummy(); - -/** - * This is the function that application code must implement, representing - * the main entry to the app. - */ -extern void android_main(struct android_app* app); - -#ifdef __cplusplus -} -#endif - -#endif /* _ANDROID_NATIVE_APP_GLUE_H */ -#endif diff --git a/platform/android/audio_driver_jandroid.cpp b/platform/android/audio_driver_jandroid.cpp index 4fab40d534..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 */ @@ -34,8 +34,6 @@ #include "core/project_settings.h" #include "thread_jandroid.h" -#ifndef ANDROID_NATIVE_ACTIVITY - AudioDriverAndroid *AudioDriverAndroid::s_ad = NULL; jobject AudioDriverAndroid::io; @@ -204,5 +202,3 @@ AudioDriverAndroid::AudioDriverAndroid() { s_ad = this; active = false; } - -#endif diff --git a/platform/android/audio_driver_jandroid.h b/platform/android/audio_driver_jandroid.h index 763f0e9b5a..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,9 +33,7 @@ #include "servers/audio_server.h" -#ifndef ANDROID_NATIVE_ACTIVITY - -#include "java_glue.h" +#include "java_godot_lib_jni.h" class AudioDriverAndroid : public AudioDriver { @@ -78,5 +76,4 @@ public: AudioDriverAndroid(); }; -#endif #endif // AUDIO_DRIVER_ANDROID_H diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp index 28e3ea962f..711088c158 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 */ @@ -38,12 +38,7 @@ /* Structure for passing information to callback function */ void AudioDriverOpenSL::_buffer_callback( - SLAndroidSimpleBufferQueueItf queueItf - /* SLuint32 eventFlags, - const void * pBuffer, - SLuint32 bufferSize, - SLuint32 dataUsed*/ -) { + SLAndroidSimpleBufferQueueItf queueItf) { bool mix = true; @@ -58,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; } } @@ -71,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; } @@ -85,7 +80,6 @@ void AudioDriverOpenSL::_buffer_callbacks( AudioDriverOpenSL *ad = (AudioDriverOpenSL *)pContext; - //ad->_buffer_callback(queueItf,eventFlags,pBuffer,bufferSize,dataUsed); ad->_buffer_callback(queueItf); } @@ -98,25 +92,15 @@ const char *AudioDriverOpenSL::get_name() const { Error AudioDriverOpenSL::init() { - SLresult - res; + SLresult res; SLEngineOption EngineOption[] = { - (SLuint32)SL_ENGINEOPTION_THREADSAFE, - (SLuint32)SL_BOOLEAN_TRUE - + { (SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE } }; res = slCreateEngine(&sl, 1, EngineOption, 0, NULL, NULL); - if (res != SL_RESULT_SUCCESS) { + ERR_FAIL_COND_V_MSG(res != SL_RESULT_SUCCESS, ERR_INVALID_PARAMETER, "Could not initialize OpenSL."); - ERR_EXPLAIN("Could not Initialize OpenSL"); - ERR_FAIL_V(ERR_INVALID_PARAMETER); - } res = (*sl)->Realize(sl, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) { - - ERR_EXPLAIN("Could not Realize OpenSL"); - ERR_FAIL_V(ERR_INVALID_PARAMETER); - } + ERR_FAIL_COND_V_MSG(res != SL_RESULT_SUCCESS, ERR_INVALID_PARAMETER, "Could not realize OpenSL."); return OK; } @@ -126,8 +110,6 @@ void AudioDriverOpenSL::start() { mutex = Mutex::create(); active = false; - SLint32 numOutputs = 0; - SLuint32 deviceID = 0; SLresult res; buffer_size = 1024; @@ -222,9 +204,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; + capture_buffer_write(sample); + capture_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); + capture_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 8e879b27c5..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; @@ -70,21 +75,21 @@ class AudioDriverOpenSL : public AudioDriver { static AudioDriverOpenSL *s_ad; void _buffer_callback( - SLAndroidSimpleBufferQueueItf queueItf - /* SLuint32 eventFlags, - const void * pBuffer, - SLuint32 bufferSize, - SLuint32 dataUsed*/ - ); + SLAndroidSimpleBufferQueueItf queueItf); static void _buffer_callbacks( SLAndroidSimpleBufferQueueItf queueItf, - /*SLuint32 eventFlags, - const void * pBuffer, - SLuint32 bufferSize, - SLuint32 dataUsed,*/ 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(); @@ -100,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 cc45fee95f..0000000000 --- a/platform/android/build.gradle.template +++ /dev/null @@ -1,84 +0,0 @@ -buildscript { - repositories { - jcenter() - $$GRADLE_REPOSITORY_URLS$$ - } - dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' - $$GRADLE_CLASSPATH$$ - } -} - -apply plugin: 'com.android.application' - -allprojects { - repositories { - jcenter() - mavenCentral() - google() - $$GRADLE_REPOSITORY_URLS$$ - } -} - -dependencies { - $$GRADLE_DEPENDENCIES$$ -} - -android { - - lintOptions { - abortOnError false - disable 'MissingTranslation' - } - - compileSdkVersion 27 - buildToolsVersion "27.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 -> - // ApplicationVariant is undocumented, but this method is widely used; may break with another version of the Android Gradle plugin - variant.outputs.get(0).setOutputFile(new File("${projectDir}/../../../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 953a2fa6d2..8b62360888 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,21 +26,18 @@ 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', 'arm64v8', 'x86', 'x86_64')), BoolVariable('android_neon', 'Enable NEON support (armv7 only)', True), - BoolVariable('android_stl', 'Enable Android STL support (for modules)', True) ] def get_flags(): - return [ ('tools', False), ] def create(env): - tools = env['TOOLS'] if "mingw" in tools: tools.remove('mingw') @@ -52,7 +48,6 @@ def create(env): def configure(env): - # Workaround for MinGW. See: # http://www.scons.org/wiki/LongCmdLinesOnWin32 if (os.name == "nt"): @@ -92,9 +87,9 @@ def configure(env): env['SPAWN'] = mySpawn - ## Architecture + # Architecture - if env['android_arch'] not in ['armv7', 'armv6', 'arm64v8', 'x86']: + if env['android_arch'] not in ['armv7', 'arm64v8', 'x86', 'x86_64']: env['android_arch'] = 'armv7' neon_text = "" @@ -110,13 +105,16 @@ def configure(env): abi_subpath = "i686-linux-android" arch_subpath = "x86" env["x86_libtheora_opt_gcc"] = True - elif env['android_arch'] == 'armv6': - env['ARCH'] = 'arch-arm' - env.extra_suffix = ".armv6" + env.extra_suffix - target_subpath = "arm-linux-androideabi-4.9" - abi_subpath = "arm-linux-androideabi" - arch_subpath = "armeabi" - can_vectorize = False + 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"] == "armv7": env['ARCH'] = 'arch-arm' target_subpath = "arm-linux-androideabi-4.9" @@ -136,26 +134,29 @@ def configure(env): arch_subpath = "arm64-v8a" env.extra_suffix = ".armv8" + env.extra_suffix - ## Build type + # Build type if (env["target"].startswith("release")): - if (env["optimize"] == "speed"): #optimize for speed (default) + 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']) - else: #optimize for size - env.Append(CPPFLAGS=['-Os', '-DNDEBUG']) + env.Append(CCFLAGS=['-O2', '-fomit-frame-pointer']) + env.Append(CPPDEFINES=['NDEBUG']) + else: # optimize for size + env.Append(CCFLAGS=['-Os']) + env.Append(CPPDEFINES=['NDEBUG']) 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']) + env.Append(CPPDEFINES=['DEBUG_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(CPPDEFINES=['_DEBUG', 'DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED']) + env.Append(CPPFLAGS=['-UNDEBUG']) - ## Compiler configuration + # Compiler configuration env['SHLIBSUFFIX'] = '.so' @@ -186,7 +187,7 @@ def configure(env): env.PrependENVPath('PATH', tools_path) ccache_path = os.environ.get("CCACHE") - if ccache_path == None: + if ccache_path is None: env['CC'] = compiler_path + '/clang' env['CXX'] = compiler_path + '/clang++' else: @@ -200,79 +201,77 @@ def configure(env): common_opts = ['-fno-integrated-as', '-gcc-toolchain', gcc_toolchain_path] - lib_sysroot = env["ANDROID_NDK_ROOT"] + "/platforms/" + env['ndk_platform'] + "/" + env['ARCH'] + # Compile flags - ## Compile flags + 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=["-std=gnu++14"]) - if 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"]) + # Disable exceptions and rtti on non-tools (template) builds + if env['tools']: + env.Append(CXXFLAGS=['-frtti']) 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(CPPDEFINES=['NO_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=["-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 - env.Append(CPPFLAGS=["-D__ANDROID_API__=" + str(get_platform(env['ndk_platform']))]) - else: - 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(CPPFLAGS='-DNO_STATVFS -DGLES_ENABLED'.split()) + lib_sysroot = env["ANDROID_NDK_ROOT"] + "/platforms/" + env['ndk_platform'] + "/" + env['ARCH'] + + # Using NDK unified headers (NDK r15+) + sysroot = env["ANDROID_NDK_ROOT"] + "/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 + env.Append(CPPDEFINES=[('__ANDROID_API__', str(get_platform(env['ndk_platform'])))]) + + env.Append(CCFLAGS='-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing'.split()) + env.Append(CPPDEFINES=['NO_STATVFS', 'GLES_ENABLED']) 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"] == "armv6": - target_opts = ['-target', 'armv6-none-linux-androideabi'] - env.Append(CPPFLAGS='-D__ARM_ARCH_6__ -march=armv6 -mfpu=vfp -mfloat-abi=softfp'.split()) + elif env['android_arch'] == 'x86_64': + target_opts = ['-target', 'x86_64-none-linux-android'] 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(CPPDEFINES=['__ARM_ARCH_7__', '__ARM_ARCH_7A__']) if env['android_neon']: env['neon_enabled'] = True - env.Append(CPPFLAGS=['-mfpu=neon', '-D__ARM_NEON__']) + env.Append(CCFLAGS=['-mfpu=neon']) + env.Append(CPPDEFINES=['__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(CPPFLAGS=['-D__ARM_ARCH_8A__']) - env.Append(CPPFLAGS=['-mfix-cortex-a53-835769']) + env.Append(CCFLAGS=['-mfix-cortex-a53-835769']) + env.Append(CPPDEFINES=['__ARM_ARCH_8A__']) - 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++']) - else: - 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"]) + # Link flags + + ndk_version = get_ndk_version(env["ANDROID_NDK_ROOT"]) + if ndk_version != None and LooseVersion(ndk_version) >= LooseVersion("17.1.4828580"): + env.Append(LINKFLAGS=['-Wl,--exclude-libs,libgcc.a', '-Wl,--exclude-libs,libatomic.a', '-nostdlib++']) else: - env.Append(LINKFLAGS=['-shared', '--sysroot=' + lib_sysroot, '-Wl,--warn-shared-textrel']) - if mt_link: - env.Append(LINKFLAGS=['-Wl,--threads']) - + 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"]) + if env["android_arch"] == "armv7": env.Append(LINKFLAGS='-Wl,--fix-cortex-a8'.split()) env.Append(LINKFLAGS='-Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now'.split()) env.Append(LINKFLAGS='-Wl,-soname,libgodot_android.so -Wl,--gc-sections'.split()) - + env.Append(LINKFLAGS=target_opts) env.Append(LINKFLAGS=common_opts) @@ -281,19 +280,14 @@ 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']) + env.Prepend(CPPPATH=['#platform/android']) + env.Append(CPPDEFINES=['ANDROID_ENABLED', 'UNIX_ENABLED', 'NO_FCNTL']) + env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'GLESv2', '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" # Return NDK version string in source.properties (adapted from the Chromium project). def get_ndk_version(path): - if path == None: + if path is None: return None prop_file_path = os.path.join(path, "source.properties") try: diff --git a/platform/android/dir_access_android.cpp b/platform/android/dir_access_android.cpp deleted file mode 100644 index 402da4527e..0000000000 --- a/platform/android/dir_access_android.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/*************************************************************************/ -/* dir_access_android.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 ANDROID_NATIVE_ACTIVITY -#include "dir_access_android.h" -#include "file_access_android.h" - -DirAccess *DirAccessAndroid::create_fs() { - - return memnew(DirAccessAndroid); -} - -Error DirAccessAndroid::list_dir_begin() { - - list_dir_end(); - - AAssetDir *aad = AAssetManager_openDir(FileAccessAndroid::asset_manager, current_dir.utf8().get_data()); - if (!aad) - return ERR_CANT_OPEN; //nothing - - return OK; -} - -String DirAccessAndroid::get_next() { - - const char *fn = AAssetDir_getNextFileName(aad); - if (!fn) - return ""; - String s; - s.parse_utf8(fn); - current = s; - return s; -} - -bool DirAccessAndroid::current_is_dir() const { - - String sd; - if (current_dir == "") - sd = current; - else - sd = current_dir + "/" + current; - - AAssetDir *aad2 = AAssetManager_openDir(FileAccessAndroid::asset_manager, sd.utf8().get_data()); - if (aad2) { - - AAssetDir_close(aad2); - return true; - } - - return false; -} - -bool DirAccessAndroid::current_is_hidden() const { - return current != "." && current != ".." && current.begins_with("."); -} - -void DirAccessAndroid::list_dir_end() { - - if (aad == NULL) - return; - - AAssetDir_close(aad); - aad = NULL; -} - -int DirAccessAndroid::get_drive_count() { - - return 0; -} - -String DirAccessAndroid::get_drive(int p_drive) { - - return ""; -} - -Error DirAccessAndroid::change_dir(String p_dir) { - - p_dir = p_dir.simplify_path(); - - if (p_dir == "" || p_dir == "." || (p_dir == ".." && current_dir == "")) - return OK; - - String new_dir; - - if (p_dir.begins_with("/")) - new_dir = p_dir.substr(1, p_dir.length()); - else if (p_dir.begins_with("res://")) - new_dir = p_dir.substr(6, p_dir.length()); - else //relative - new_dir = new_dir + "/" + p_dir; - - //test if newdir exists - new_dir = new_dir.simplify_path(); - - AAssetDir *aad = AAssetManager_openDir(FileAccessAndroid::asset_manager, new_dir.utf8().get_data()); - if (aad) { - - current_dir = new_dir; - AAssetDir_close(aad); - return OK; - } - - return ERR_INVALID_PARAMETER; -} - -String DirAccessAndroid::get_current_dir() { - - return "/" + current_dir; -} - -bool DirAccessAndroid::file_exists(String p_file) { - - String sd; - if (current_dir == "") - sd = p_file; - else - sd = current_dir + "/" + p_file; - - AAsset *a = AAssetManager_open(FileAccessAndroid::asset_manager, sd.utf8().get_data(), AASSET_MODE_STREAMING); - if (a) { - AAsset_close(a); - return true; - } - - return false; -} - -Error DirAccessAndroid::make_dir(String p_dir) { - - ERR_FAIL_V(ERR_UNAVAILABLE); -} - -Error DirAccessAndroid::rename(String p_from, String p_to) { - - ERR_FAIL_V(ERR_UNAVAILABLE); -} - -Error DirAccessAndroid::remove(String p_name) { - - ERR_FAIL_V(ERR_UNAVAILABLE); -} - -//FileType get_file_type() const; -size_t DirAccessAndroid::get_space_left() { - - return 0; -} - -void DirAccessAndroid::make_default() { - - instance_func = create_fs; -} - -DirAccessAndroid::DirAccessAndroid() { - - aad = NULL; -} - -DirAccessAndroid::~DirAccessAndroid() { - - list_dir_end(); -} -#endif diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 6a95277585..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 */ @@ -28,11 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef ANDROID_NATIVE_ACTIVITY - #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; @@ -71,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; } @@ -214,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() { @@ -245,4 +249,3 @@ DirAccessJAndroid::~DirAccessJAndroid() { list_dir_end(); } -#endif diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index 1653fb0aa5..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 */ @@ -31,10 +31,8 @@ #ifndef DIR_ACCESS_JANDROID_H #define DIR_ACCESS_JANDROID_H -#ifndef ANDROID_NATIVE_ACTIVITY - #include "core/os/dir_access.h" -#include "java_glue.h" +#include "java_godot_lib_jni.h" #include <stdio.h> class DirAccessJAndroid : public DirAccess { @@ -77,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(); @@ -87,4 +87,3 @@ public: }; #endif // DIR_ACCESS_JANDROID_H -#endif diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 6ec7d27464..b61575e2aa 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 */ @@ -32,6 +32,7 @@ #include "core/io/marshalls.h" #include "core/io/zip_io.h" +#include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" #include "core/project_settings.h" @@ -194,8 +195,8 @@ static const char *android_perms[] = { }; struct LauncherIcon { - char *option_id; - char *export_path; + const char *option_id; + const char *export_path; }; static const LauncherIcon launcher_icons[] = { @@ -206,9 +207,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 +236,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 +302,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; @@ -395,7 +396,7 @@ class EditorExportAndroid : public EditorExportPlatform { return aname; } - String get_package_name(const String &p_package) { + String get_package_name(const String &p_package) const { String pname = p_package; String basename = ProjectSettings::get_singleton()->get("application/config/name"); @@ -417,9 +418,74 @@ class EditorExportAndroid : public EditorExportPlatform { name = "noname"; pname = pname.replace("$genname", name); + return pname; } + bool is_package_name_valid(const String &p_package, String *r_error = NULL) const { + + String pname = p_package; + + if (pname.length() == 0) { + if (r_error) { + *r_error = TTR("Package name is missing."); + } + return false; + } + + int segments = 0; + bool first = true; + for (int i = 0; i < pname.length(); i++) { + CharType c = pname[i]; + if (first && c == '.') { + if (r_error) { + *r_error = TTR("Package segments must be of non-zero length."); + } + return false; + } + if (c == '.') { + segments++; + first = true; + continue; + } + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) { + if (r_error) { + *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 = TTR("A digit cannot be the first character in a package segment."); + } + return false; + } + if (first && c == '_') { + if (r_error) { + *r_error = vformat(TTR("The character '%s' cannot be the first character in a package segment."), String::chr(c)); + } + return false; + } + first = false; + } + + if (segments == 0) { + if (r_error) { + *r_error = TTR("The package must have at least one '.' separator."); + } + return false; + } + + if (first) { + if (r_error) { + *r_error = TTR("Package segments must be of non-zero length."); + } + return false; + } + + return true; + } + static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data) { /* @@ -487,7 +553,6 @@ class EditorExportAndroid : public EditorExportPlatform { } static Vector<String> get_abis() { - // mips and armv6 are dead (especially for games), so not including them Vector<String> abis; abis.push_back("armeabi-v7a"); abis.push_back("arm64-v8a"); @@ -518,7 +583,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; @@ -530,16 +595,16 @@ class EditorExportAndroid : public EditorExportPlatform { if (abi_index != -1) { exported = true; String abi = abis[abi_index]; - String dst_path = "lib/" + abi + "/" + p_so.path.get_file(); + String dst_path = String("lib").plus_file(abi).plus_file(p_so.path.get_file()); Vector<uint8_t> array = FileAccess::get_file_as_array(p_so.path); Error store_err = store_in_apk(ed, dst_path, array); - ERR_FAIL_COND_V(store_err, store_err); + ERR_FAIL_COND_V_MSG(store_err, store_err, "Cannot store in apk file '" + dst_path + "'."); } } 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; @@ -550,7 +615,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; } @@ -577,11 +644,11 @@ class EditorExportAndroid : public EditorExportPlatform { uint32_t ofs = 8; uint32_t string_count = 0; - uint32_t styles_count = 0; + //uint32_t styles_count = 0; uint32_t string_flags = 0; uint32_t string_data_offset = 0; - uint32_t styles_offset = 0; + //uint32_t styles_offset = 0; uint32_t string_table_begins = 0; uint32_t string_table_ends = 0; Vector<uint8_t> stable_extra; @@ -592,11 +659,15 @@ 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"); bool screen_support_xlarge = p_preset->get("screen/support_xlarge"); + int xr_mode_index = p_preset->get("graphics/xr_mode"); + Vector<String> perms; const char **aperms = android_perms; @@ -608,10 +679,13 @@ class EditorExportAndroid : public EditorExportPlatform { aperms++; } - for (int i = 0; i < MAX_USER_PERMISSIONS; i++) { - String user_perm = p_preset->get("user_permissions/" + itos(i)); - if (user_perm.strip_edges() != "" && user_perm.strip_edges() != "False") - perms.push_back(user_perm.strip_edges()); + PoolStringArray user_perms = p_preset->get("permissions/custom_permissions"); + + for (int i = 0; i < user_perms.size(); i++) { + String user_perm = user_perms[i].strip_edges(); + if (!user_perm.empty()) { + perms.push_back(user_perm); + } } if (p_give_internet) { @@ -631,16 +705,16 @@ class EditorExportAndroid : public EditorExportPlatform { int iofs = ofs + 8; string_count = decode_uint32(&p_manifest[iofs]); - styles_count = decode_uint32(&p_manifest[iofs + 4]); + //styles_count = decode_uint32(&p_manifest[iofs + 4]); string_flags = decode_uint32(&p_manifest[iofs + 8]); string_data_offset = decode_uint32(&p_manifest[iofs + 12]); - styles_offset = decode_uint32(&p_manifest[iofs + 16]); + //styles_offset = decode_uint32(&p_manifest[iofs + 16]); /* printf("string count: %i\n",string_count); printf("flags: %i\n",string_flags); printf("sdata ofs: %i\n",string_data_offset); printf("styles ofs: %i\n",styles_offset); - */ + */ uint32_t st_offset = iofs + 20; string_table.resize(string_count); uint32_t string_end = 0; @@ -652,8 +726,7 @@ class EditorExportAndroid : public EditorExportPlatform { uint32_t string_at = decode_uint32(&p_manifest[st_offset + i * 4]); string_at += st_offset + string_count * 4; - ERR_EXPLAIN("Unimplemented, can't read utf8 string table."); - ERR_FAIL_COND(string_flags & UTF8_FLAG); + ERR_FAIL_COND_MSG(string_flags & UTF8_FLAG, "Unimplemented, can't read UTF-8 string table."); if (string_flags & UTF8_FLAG) { @@ -716,11 +789,15 @@ class EditorExportAndroid : public EditorExportPlatform { if (tname == "manifest" && attrname == "versionName") { if (attr_value == 0xFFFFFFFF) { - WARN_PRINT("Version name in a resource, should be plaintext") + WARN_PRINT("Version name in a resource, should be plain text"); } else string_table.write[attr_value] = version_name; } + if (tname == "instrumentation" && attrname == "targetPackage") { + string_table.write[attr_value] = get_package_name(package_name); + } + if (tname == "activity" && attrname == "screenOrientation") { encode_uint32(orientation == 0 ? 0 : 1, &p_manifest.write[iofs + 16]); @@ -746,6 +823,25 @@ class EditorExportAndroid : public EditorExportPlatform { } } + if (tname == "uses-feature" && attrname == "glEsVersion") { + + encode_uint32(min_gles3 ? 0x00030000 : 0x00020000, &p_manifest.write[iofs + 16]); + } + + if (tname == "meta-data" && attrname == "name" && string_table[attr_value] == "xr_mode_metadata_name") { + // Update the meta-data 'android:name' attribute based on the selected XR mode. + if (xr_mode_index == 1 /* XRMode.OVR */) { + string_table.write[attr_value] = "com.samsung.android.vr.application.mode"; + } + } + + if (tname == "meta-data" && attrname == "value" && string_table[attr_value] == "xr_mode_metadata_value") { + // Update the meta-data 'android:value' attribute based on the selected XR mode. + if (xr_mode_index == 1 /* XRMode.OVR */) { + string_table.write[attr_value] = "vr_only"; + } + } + iofs += 20; } @@ -755,23 +851,150 @@ class EditorExportAndroid : public EditorExportPlatform { uint32_t name = decode_uint32(&p_manifest[iofs + 12]); String tname = string_table[name]; + int dof_index = p_preset->get("graphics/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof + + if (tname == "uses-feature" && dof_index > 0) { + if (xr_mode_index == 0) { + WARN_PRINT("VR DOF feature setting is only valid for oculus HMDs with an XR mode set to VR"); + } + ofs += 24; // skip over end tag + + // save manifest ending so we can restore it + Vector<uint8_t> manifest_end; + uint32_t manifest_cur_size = p_manifest.size(); + + manifest_end.resize(p_manifest.size() - ofs); + memcpy(manifest_end.ptrw(), &p_manifest[ofs], manifest_end.size()); + + int32_t attr_name_string = string_table.find("name"); + ERR_FAIL_COND_MSG(attr_name_string == -1, "Template does not have 'name' attribute."); + + int32_t ns_android_string = string_table.find("http://schemas.android.com/apk/res/android"); + if (ns_android_string == -1) { + string_table.push_back("http://schemas.android.com/apk/res/android"); + ns_android_string = string_table.size() - 1; + } + + int32_t attr_uses_permission_string = string_table.find("uses-feature"); + if (attr_uses_permission_string == -1) { + string_table.push_back("uses-feature"); + attr_uses_permission_string = string_table.size() - 1; + } + + int32_t attr_required_string = string_table.find("required"); + if (attr_required_string == -1) { + string_table.push_back("required"); + attr_required_string = string_table.size() - 1; + } + + int32_t attr_version_string = string_table.find("version"); + if (attr_version_string == -1) { + string_table.push_back("version"); + attr_version_string = string_table.size() - 1; + } + + String required_value_string; + if (dof_index == 1) { + required_value_string = "false"; + } else if (dof_index == 2) { + required_value_string = "true"; + } else { + ERR_FAIL_MSG("Unknown DoF index: " + itos(dof_index) + "."); + } + int32_t required_value = string_table.find(required_value_string); + if (required_value == -1) { + string_table.push_back(required_value_string); + required_value = string_table.size() - 1; + } + + int32_t version_value = string_table.find("1"); + if (version_value == -1) { + string_table.push_back("1"); + version_value = string_table.size() - 1; + } + + int32_t feature_string = string_table.find("android.hardware.vr.headtracking"); + if (feature_string == -1) { + string_table.push_back("android.hardware.vr.headtracking"); + feature_string = string_table.size() - 1; + } + + { + manifest_cur_size += 96 + 20; // node and three attrs + end node + p_manifest.resize(manifest_cur_size); + + // start tag + encode_uint16(0x102, &p_manifest.write[ofs]); // type + encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize + encode_uint32(96, &p_manifest.write[ofs + 4]); // size + encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno + encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment + encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns + encode_uint32(attr_uses_permission_string, &p_manifest.write[ofs + 20]); // name + encode_uint16(20, &p_manifest.write[ofs + 24]); // attr_start + encode_uint16(20, &p_manifest.write[ofs + 26]); // attr_size + encode_uint16(3, &p_manifest.write[ofs + 28]); // num_attrs + encode_uint16(0, &p_manifest.write[ofs + 30]); // id_index + encode_uint16(0, &p_manifest.write[ofs + 32]); // class_index + encode_uint16(0, &p_manifest.write[ofs + 34]); // style_index + + // android:name attribute + encode_uint32(ns_android_string, &p_manifest.write[ofs + 36]); // ns + encode_uint32(attr_name_string, &p_manifest.write[ofs + 40]); // 'name' + encode_uint32(feature_string, &p_manifest.write[ofs + 44]); // raw_value + encode_uint16(8, &p_manifest.write[ofs + 48]); // typedvalue_size + p_manifest.write[ofs + 50] = 0; // typedvalue_always0 + p_manifest.write[ofs + 51] = 0x03; // typedvalue_type (string) + encode_uint32(feature_string, &p_manifest.write[ofs + 52]); // typedvalue reference + + // android:required attribute + encode_uint32(ns_android_string, &p_manifest.write[ofs + 56]); // ns + encode_uint32(attr_required_string, &p_manifest.write[ofs + 60]); // 'name' + encode_uint32(required_value, &p_manifest.write[ofs + 64]); // raw_value + encode_uint16(8, &p_manifest.write[ofs + 68]); // typedvalue_size + p_manifest.write[ofs + 70] = 0; // typedvalue_always0 + p_manifest.write[ofs + 71] = 0x03; // typedvalue_type (string) + encode_uint32(required_value, &p_manifest.write[ofs + 72]); // typedvalue reference + + // android:version attribute + encode_uint32(ns_android_string, &p_manifest.write[ofs + 76]); // ns + encode_uint32(attr_version_string, &p_manifest.write[ofs + 80]); // 'name' + encode_uint32(version_value, &p_manifest.write[ofs + 84]); // raw_value + encode_uint16(8, &p_manifest.write[ofs + 88]); // typedvalue_size + p_manifest.write[ofs + 90] = 0; // typedvalue_always0 + p_manifest.write[ofs + 91] = 0x03; // typedvalue_type (string) + encode_uint32(version_value, &p_manifest.write[ofs + 92]); // typedvalue reference + + ofs += 96; + + // end tag + encode_uint16(0x103, &p_manifest.write[ofs]); // type + encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize + encode_uint32(24, &p_manifest.write[ofs + 4]); // size + encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno + encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment + encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns + encode_uint32(attr_uses_permission_string, &p_manifest.write[ofs + 20]); // name + + ofs += 24; + } + memcpy(&p_manifest.write[ofs], manifest_end.ptr(), manifest_end.size()); + ofs -= 24; // go back over back end + } if (tname == "manifest") { // save manifest ending so we can restore it Vector<uint8_t> manifest_end; uint32_t manifest_cur_size = p_manifest.size(); - uint32_t node_size = size; manifest_end.resize(p_manifest.size() - ofs); memcpy(manifest_end.ptrw(), &p_manifest[ofs], manifest_end.size()); int32_t attr_name_string = string_table.find("name"); - ERR_EXPLAIN("Template does not have 'name' attribute"); - ERR_FAIL_COND(attr_name_string == -1); + ERR_FAIL_COND_MSG(attr_name_string == -1, "Template does not have 'name' attribute."); int32_t ns_android_string = string_table.find("android"); - ERR_EXPLAIN("Template does not have 'android' namespace"); - ERR_FAIL_COND(ns_android_string == -1); + ERR_FAIL_COND_MSG(ns_android_string == -1, "Template does not have 'android' namespace."); int32_t attr_uses_permission_string = string_table.find("uses-permission"); if (attr_uses_permission_string == -1) { @@ -1041,21 +1264,20 @@ class EditorExportAndroid : public EditorExportPlatform { } public: - enum { - MAX_USER_PERMISSIONS = 20 - }; - typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total); 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) + String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); + if (driver == "GLES2") { r_features->push_back("etc"); - else*/ - r_features->push_back("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> abis = get_enabled_abis(p_preset); for (int i = 0; i < abis.size(); ++i) { @@ -1065,15 +1287,18 @@ public: virtual void get_export_options(List<ExportOption> *r_options) { + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "graphics/xr_mode", PROPERTY_HINT_ENUM, "Regular,Oculus Mobile VR"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "graphics/degrees_of_freedom", PROPERTY_HINT_ENUM, "None,3DOF and 6DOF,6DOF"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/32_bits_framebuffer"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false)); 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)); @@ -1081,11 +1306,15 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_normal"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_large"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_xlarge"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/opengl_debug"), false)); - for (int i = 0; i < sizeof(launcher_icons) / sizeof(launcher_icons[0]); ++i) { + for (uint64_t i = 0; i < sizeof(launcher_icons) / sizeof(launcher_icons[0]); ++i) { 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"), "")); @@ -1096,21 +1325,18 @@ 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)); } + r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "permissions/custom_permissions"), PoolStringArray())); + const char **perms = android_perms; while (*perms) { r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "permissions/" + String(*perms).to_lower()), false)); perms++; } - - for (int i = 0; i < MAX_USER_PERMISSIONS; i++) { - - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "user_permissions/" + itos(i)), false)); - } } virtual String get_name() const { @@ -1177,8 +1403,11 @@ public: return ERR_UNCONFIGURED; } - //export_temp - ep.step("Exporting APK", 0); + // Export_temp APK. + if (ep.step("Exporting APK", 0)) { + device_lock->unlock(); + 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; @@ -1186,11 +1415,20 @@ public: if (use_reverse) p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST; - String export_to = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport.apk"); - Error err = export_project(p_preset, true, export_to, p_debug_flags); - if (err) { - device_lock->unlock(); - return err; + String tmp_export_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport.apk"); + +#define CLEANUP_AND_RETURN(m_err) \ + { \ + DirAccess::remove_file_or_error(tmp_export_path); \ + device_lock->unlock(); \ + return m_err; \ + } + + // Export to temporary APK before sending to device. + Error err = export_project(p_preset, true, tmp_export_path, p_debug_flags); + + if (err != OK) { + CLEANUP_AND_RETURN(err); } List<String> args; @@ -1201,7 +1439,9 @@ public: String package_name = p_preset->get("package/unique_name"); if (remove_prev) { - ep.step("Uninstalling...", 1); + if (ep.step("Uninstalling...", 1)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } print_line("Uninstalling previous version: " + devices[p_device].name); @@ -1214,27 +1454,28 @@ 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)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } args.clear(); args.push_back("-s"); args.push_back(devices[p_device].id); args.push_back("install"); args.push_back("-r"); - args.push_back(export_to); + args.push_back(tmp_export_path); err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv); if (err || rv != 0) { EditorNode::add_io_error("Could not install to device."); - device_lock->unlock(); - return ERR_CANT_CREATE; + CLEANUP_AND_RETURN(ERR_CANT_CREATE); } if (use_remote) { if (use_reverse) { - static const char *const msg = "** Device API >= 21; debugging over USB **"; - EditorNode::get_singleton()->get_log()->add_message(msg); + static const char *const msg = "--- Device API >= 21; debugging over USB ---"; + EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR); print_line(String(msg).to_upper()); args.clear(); @@ -1274,13 +1515,15 @@ public: } } else { - static const char *const msg = "** Device API < 21; debugging over Wi-Fi **"; - EditorNode::get_singleton()->get_log()->add_message(msg); + static const char *const msg = "--- Device API < 21; debugging over Wi-Fi ---"; + EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR); print_line(String(msg).to_upper()); } } - ep.step("Running on Device...", 3); + if (ep.step("Running on Device...", 3)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } args.clear(); args.push_back("-s"); args.push_back(devices[p_device].id); @@ -1294,16 +1537,16 @@ public: args.push_back("-a"); args.push_back("android.intent.action.MAIN"); args.push_back("-n"); - args.push_back(get_package_name(package_name) + "/org.godotengine.godot.Godot"); + args.push_back(get_package_name(package_name) + "/com.godot.game.GodotApp"); err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv); if (err || rv != 0) { EditorNode::add_io_error("Could not execute on device."); - device_lock->unlock(); - return ERR_CANT_CREATE; + CLEANUP_AND_RETURN(ERR_CANT_CREATE); } - device_lock->unlock(); - return OK; + + CLEANUP_AND_RETURN(OK); +#undef CLEANUP_AND_RETURN } virtual Ref<Texture> get_run_icon() const { @@ -1313,21 +1556,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"; + } } } @@ -1338,7 +1585,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"); @@ -1346,43 +1593,331 @@ 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; + DirAccessRef 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 (!FileAccess::exists("res://android/build/build.gradle")) { + + err += TTR("Android build template not installed in the project. Install it from the Project 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"; } } + String pn = p_preset->get("package/unique_name"); + String pn_err; + + if (!is_package_name_valid(get_package_name(pn), &pn_err)) { + + valid = false; + 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; return valid; } - virtual String get_binary_extension(const Ref<EditorExportPreset> &p_preset) const { - return "apk"; + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + List<String> list; + list.push_back("apk"); + return list; + } + + void _update_custom_build_project() { + + DirAccessRef da = DirAccess::open("res://android"); + + ERR_FAIL_COND_MSG(!da, "Cannot open directory 'res://android'."); + 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.xml 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) { + ERR_PRINTS("Not adding application attributes as the expected tag was not found in '<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) { @@ -1391,30 +1926,101 @@ public: String src_apk; - EditorProgress ep("export", "Exporting for Android", 105); + EditorProgress ep("export", "Exporting for Android", 105, true); - if (p_debug) - src_apk = p_preset->get("custom_package/debug"); - else - src_apk = p_preset->get("custom_package/release"); + if (bool(p_preset->get("custom_package/use_custom_build"))) { //custom build + //re-generate build.gradle and AndroidManifest.xml - src_apk = src_apk.strip_edges(); - if (src_apk == "") { + { //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_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'."); + + _update_custom_build_project(); + + OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required + + String build_command; +#ifdef WINDOWS_ENABLED + build_command = "gradlew.bat"; +#else + build_command = "gradlew"; +#endif + + 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/android_debug.apk"); } else { - src_apk = find_export_template("android_release.apk"); + src_apk = build_path.plus_file("build/outputs/apk/release/android_release.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) { @@ -1423,17 +2029,25 @@ public: return ERR_FILE_NOT_FOUND; } - ERR_FAIL_COND_V(!pkg, ERR_CANT_OPEN); int ret = unzGoToFirstFile(pkg); zlib_filefunc_def io2 = io; FileAccess *dst_f = NULL; io2.opaque = &dst_f; - String unaligned_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned.apk"); - zipFile unaligned_apk = zipOpen2(unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2); + + String tmp_unaligned_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned.apk"); + +#define CLEANUP_AND_RETURN(m_err) \ + { \ + DirAccess::remove_file_or_error(tmp_unaligned_path); \ + return m_err; \ + } + + zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2); bool use_32_fb = p_preset->get("graphics/32_bits_framebuffer"); bool immersive = p_preset->get("screen/immersive_mode"); + bool debug_opengl = p_preset->get("screen/opengl_debug"); bool _signed = p_preset->get("package/signed"); @@ -1484,9 +2098,9 @@ public: _fix_resources(p_preset, data); } - if (file == "res/drawable/icon.png") { + if (file == "res/drawable-nodpi-v4/icon.png") { bool found = false; - for (int i = 0; i < sizeof(launcher_icons) / sizeof(launcher_icons[0]); ++i) { + for (uint64_t 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(); if (icon_path != "" && icon_path.ends_with(".png")) { FileAccess *f = FileAccess::open(icon_path, FileAccess::READ); @@ -1556,7 +2170,9 @@ public: ret = unzGoToNextFile(pkg); } - ep.step("Adding Files...", 1); + if (ep.step("Adding Files...", 1)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } Error err = OK; Vector<String> cl = cmdline.strip_edges().split(" "); for (int i = 0; i < cl.size(); i++) { @@ -1570,16 +2186,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; @@ -1597,7 +2203,7 @@ public: unzClose(pkg); EditorNode::add_io_error("Could not write expansion package file: " + apkfname); - return OK; + CLEANUP_AND_RETURN(ERR_SKIP); } cl.push_back("--use_apk_expansion"); @@ -1620,7 +2226,7 @@ public: APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - for (int i = 0; i < sizeof(launcher_icons) / sizeof(launcher_icons[0]); ++i) { + for (uint64_t 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(); if (icon_path != "" && icon_path.ends_with(".png") && FileAccess::exists(icon_path)) { Vector<uint8_t> data = FileAccess::get_file_as_array(icon_path); @@ -1629,12 +2235,23 @@ public: } } + int xr_mode_index = p_preset->get("graphics/xr_mode"); + if (xr_mode_index == 1 /* XRMode.OVR */) { + cl.push_back("--xr_mode_ovr"); + } else { + // XRMode.REGULAR is the default. + cl.push_back("--xr_mode_regular"); + } + if (use_32_fb) cl.push_back("--use_depth_32"); if (immersive) cl.push_back("--use_immersive"); + if (debug_opengl) + cl.push_back("--debug_opengl"); + if (cl.size()) { //add comandline Vector<uint8_t> clf; @@ -1673,8 +2290,8 @@ public: zipClose(unaligned_apk, NULL); unzClose(pkg); - if (err) { - return err; + if (err != OK) { + CLEANUP_AND_RETURN(err); } if (_signed) { @@ -1682,30 +2299,42 @@ public: String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner"); if (!FileAccess::exists(jarsigner)) { EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned."); - return OK; + CLEANUP_AND_RETURN(OK); } String keystore; 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)) { + CLEANUP_AND_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)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } } if (!FileAccess::exists(keystore)) { EditorNode::add_io_error("Could not find keystore, unable to export."); - return ERR_FILE_CANT_OPEN; + CLEANUP_AND_RETURN(ERR_FILE_CANT_OPEN); } List<String> args; @@ -1723,28 +2352,30 @@ public: args.push_back(keystore); args.push_back("-storepass"); args.push_back(password); - args.push_back(unaligned_path); + args.push_back(tmp_unaligned_path); args.push_back(user); int retval; OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval); if (retval) { EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval)); - return ERR_CANT_CREATE; + CLEANUP_AND_RETURN(ERR_CANT_CREATE); } - ep.step("Verifying APK...", 104); + if (ep.step("Verifying APK...", 104)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } args.clear(); args.push_back("-verify"); args.push_back("-keystore"); args.push_back(keystore); - args.push_back(unaligned_path); + args.push_back(tmp_unaligned_path); args.push_back("-verbose"); OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval); if (retval) { EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8."); - return ERR_CANT_CREATE; + CLEANUP_AND_RETURN(ERR_CANT_CREATE); } } @@ -1752,16 +2383,17 @@ public: static const int ZIP_ALIGNMENT = 4; - ep.step("Aligning APK...", 105); + if (ep.step("Aligning APK...", 105)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } - unzFile tmp_unaligned = unzOpen2(unaligned_path.utf8().get_data(), &io); + unzFile tmp_unaligned = unzOpen2(tmp_unaligned_path.utf8().get_data(), &io); if (!tmp_unaligned) { - EditorNode::add_io_error("Could not find temp unaligned APK."); - return ERR_FILE_NOT_FOUND; + EditorNode::add_io_error("Could not unzip temporary unaligned APK."); + CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND); } - ERR_FAIL_COND_V(!tmp_unaligned, ERR_CANT_OPEN); ret = unzGoToFirstFile(tmp_unaligned); io2 = io; @@ -1829,11 +2461,7 @@ public: zipClose(final_apk, NULL); unzClose(tmp_unaligned); - if (err) { - return err; - } - - return OK; + CLEANUP_AND_RETURN(OK); } virtual void get_platform_features(List<String> *r_features) { @@ -1845,7 +2473,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(); @@ -1861,7 +2489,7 @@ public: device_thread = Thread::create(_device_poll_thread, this); } - ~EditorExportAndroid() { + ~EditorExportPlatformAndroid() { quit_request = true; Thread::wait_to_finish(device_thread); memdelete(device_lock); @@ -1885,10 +2513,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)); 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 573200bcf9..d4c2a23aa0 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 */ @@ -28,8 +28,6 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef ANDROID_NATIVE_ACTIVITY - #include "file_access_jandroid.h" #include "core/os/os.h" #include "thread_jandroid.h" @@ -64,7 +62,7 @@ Error FileAccessJAndroid::_open(const String &p_path, int p_mode_flags) { JNIEnv *env = ThreadAndroid::get_env(); jstring js = env->NewStringUTF(path.utf8().get_data()); - int res = env->CallIntMethod(io, _file_open, js, p_mode_flags & WRITE ? true : false); + int res = env->CallIntMethod(io, _file_open, js, (p_mode_flags & WRITE) ? true : false); env->DeleteLocalRef(js); OS::get_singleton()->print("fopen: '%s' ret %i\n", path.utf8().get_data(), res); @@ -96,13 +94,13 @@ void FileAccessJAndroid::seek(size_t p_position) { JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND(!is_open()); + ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); env->CallVoidMethod(io, _file_seek, id, p_position); } void FileAccessJAndroid::seek_end(int64_t p_position) { - ERR_FAIL_COND(!is_open()); + ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); seek(get_len()); } @@ -110,34 +108,34 @@ void FileAccessJAndroid::seek_end(int64_t p_position) { size_t FileAccessJAndroid::get_position() const { JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V(!is_open(), 0); + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); return env->CallIntMethod(io, _file_tell, id); } size_t FileAccessJAndroid::get_len() const { JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V(!is_open(), 0); + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); return env->CallIntMethod(io, _file_get_size, id); } bool FileAccessJAndroid::eof_reached() const { JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V(!is_open(), 0); + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); return env->CallIntMethod(io, _file_eof, id); } uint8_t FileAccessJAndroid::get_8() const { - ERR_FAIL_COND_V(!is_open(), 0); + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); uint8_t byte; get_buffer(&byte, 1); return byte; } int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const { - ERR_FAIL_COND_V(!is_open(), 0); + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); if (p_length == 0) return 0; JNIEnv *env = ThreadAndroid::get_env(); @@ -212,5 +210,3 @@ FileAccessJAndroid::~FileAccessJAndroid() { if (is_open()) close(); } - -#endif diff --git a/platform/android/file_access_jandroid.h b/platform/android/file_access_jandroid.h index 39c201ba85..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 */ @@ -31,10 +31,8 @@ #ifndef FILE_ACCESS_JANDROID_H #define FILE_ACCESS_JANDROID_H -#ifndef ANDROID_NATIVE_ACTIVITY - #include "core/os/file_access.h" -#include "java_glue.h" +#include "java_godot_lib_jni.h" class FileAccessJAndroid : public FileAccess { static jobject io; @@ -76,11 +74,11 @@ 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(); }; -#endif - #endif // FILE_ACCESS_JANDROID_H diff --git a/platform/android/godot_android.cpp b/platform/android/godot_android.cpp deleted file mode 100644 index 54692dc831..0000000000 --- a/platform/android/godot_android.cpp +++ /dev/null @@ -1,937 +0,0 @@ -/*************************************************************************/ -/* godot_android.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 ANDROID_NATIVE_ACTIVITY - -#include "core/engine.h" -#include "core/project_settings.h" -#include "file_access_android.h" -#include "main/main.h" -#include "os_android.h" - -#include <EGL/egl.h> -#include <android/log.h> -#include <android/sensor.h> -#include <android/window.h> -#include <android_native_app_glue.h> -#include <errno.h> -#include <jni.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "godot", __VA_ARGS__)) -#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "godot", __VA_ARGS__)) - -extern "C" { -JNIEXPORT void JNICALL Java_org_godotengine_godot_Godot_registerSingleton(JNIEnv *env, jobject obj, jstring name, jobject p_object); -JNIEXPORT void JNICALL Java_org_godotengine_godot_Godot_registerMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args); -JNIEXPORT jstring JNICALL Java_org_godotengine_godot_Godot_getGlobal(JNIEnv *env, jobject obj, jstring path); -}; - -class JNISingleton : public Object { - - GDCLASS(JNISingleton, Object); - - struct MethodData { - - jmethodID method; - Variant::Type ret_type; - Vector<Variant::Type> argtypes; - }; - - jobject instance; - Map<StringName, MethodData> method_map; - JNIEnv *env; - -public: - void update_env(JNIEnv *p_env) { env = p_env; } - - virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { - - r_error.error = Variant::CallError::CALL_OK; - - Map<StringName, MethodData>::Element *E = method_map.find(p_method); - if (!E) { - - r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } - - int ac = E->get().argtypes.size(); - if (ac < p_argcount) { - - r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = ac; - return Variant(); - } - - if (ac > p_argcount) { - - r_error.error = Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = ac; - return Variant(); - } - - for (int i = 0; i < p_argcount; i++) { - - if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) { - - r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = i; - r_error.expected = E->get().argtypes[i]; - } - } - - jvalue *v = NULL; - - if (p_argcount) { - - v = (jvalue *)alloca(sizeof(jvalue) * p_argcount); - } - - for (int i = 0; i < p_argcount; i++) { - - switch (E->get().argtypes[i]) { - - case Variant::BOOL: { - - v[i].z = *p_args[i]; - } break; - case Variant::INT: { - - v[i].i = *p_args[i]; - } break; - case Variant::REAL: { - - v[i].f = *p_args[i]; - } break; - case Variant::STRING: { - - String s = *p_args[i]; - jstring jStr = env->NewStringUTF(s.utf8().get_data()); - v[i].l = jStr; - } break; - case Variant::STRING_ARRAY: { - - PoolVector<String> sarray = *p_args[i]; - jobjectArray arr = env->NewObjectArray(sarray.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); - - for (int j = 0; j < sarray.size(); j++) { - - env->SetObjectArrayElement(arr, j, env->NewStringUTF(sarray[i].utf8().get_data())); - } - v[i].l = arr; - - } break; - case Variant::INT_ARRAY: { - - PoolVector<int> array = *p_args[i]; - jintArray arr = env->NewIntArray(array.size()); - PoolVector<int>::Read r = array.read(); - env->SetIntArrayRegion(arr, 0, array.size(), r.ptr()); - v[i].l = arr; - - } break; - case Variant::REAL_ARRAY: { - - PoolVector<float> array = *p_args[i]; - jfloatArray arr = env->NewFloatArray(array.size()); - PoolVector<float>::Read r = array.read(); - env->SetFloatArrayRegion(arr, 0, array.size(), r.ptr()); - v[i].l = arr; - - } break; - default: { - - ERR_FAIL_V(Variant()); - } break; - } - } - - Variant ret; - - switch (E->get().ret_type) { - - case Variant::NIL: { - - env->CallVoidMethodA(instance, E->get().method, v); - } break; - case Variant::BOOL: { - - ret = env->CallBooleanMethodA(instance, E->get().method, v); - } break; - case Variant::INT: { - - ret = env->CallIntMethodA(instance, E->get().method, v); - } break; - case Variant::REAL: { - - ret = env->CallFloatMethodA(instance, E->get().method, v); - } break; - case Variant::STRING: { - - jobject o = env->CallObjectMethodA(instance, E->get().method, v); - String singname = env->GetStringUTFChars((jstring)o, NULL); - } break; - case Variant::STRING_ARRAY: { - - jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v); - - int stringCount = env->GetArrayLength(arr); - PoolVector<String> sarr; - - for (int i = 0; i < stringCount; i++) { - jstring string = (jstring)env->GetObjectArrayElement(arr, i); - const char *rawString = env->GetStringUTFChars(string, 0); - sarr.push_back(String(rawString)); - } - - ret = sarr; - - } break; - case Variant::INT_ARRAY: { - - jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v); - - int fCount = env->GetArrayLength(arr); - PoolVector<int> sarr; - sarr.resize(fCount); - - PoolVector<int>::Write w = sarr.write(); - env->GetIntArrayRegion(arr, 0, fCount, w.ptr()); - w = PoolVector<int>::Write(); - ret = sarr; - } break; - case Variant::REAL_ARRAY: { - - jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v); - - int fCount = env->GetArrayLength(arr); - PoolVector<float> sarr; - sarr.resize(fCount); - - PoolVector<float>::Write w = sarr.write(); - env->GetFloatArrayRegion(arr, 0, fCount, w.ptr()); - w = PoolVector<float>::Write(); - ret = sarr; - } break; - default: { - - ERR_FAIL_V(Variant()); - } break; - } - - return ret; - } - - jobject get_instance() const { - - return instance; - } - void set_instance(jobject p_instance) { - - instance = p_instance; - } - - void add_method(const StringName &p_name, jmethodID p_method, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) { - - MethodData md; - md.method = p_method; - md.argtypes = p_args; - md.ret_type = p_ret_type; - method_map[p_name] = md; - } - - JNISingleton() {} -}; - -//JNIEnv *JNISingleton::env=NULL; - -static HashMap<String, JNISingleton *> jni_singletons; - -struct engine { - struct android_app *app; - OS_Android *os; - JNIEnv *jni; - - ASensorManager *sensorManager; - const ASensor *accelerometerSensor; - const ASensor *magnetometerSensor; - const ASensor *gyroscopeSensor; - ASensorEventQueue *sensorEventQueue; - - bool display_active; - bool requested_quit; - int animating; - EGLDisplay display; - EGLSurface surface; - EGLContext context; - int32_t width; - int32_t height; -}; - -/** - * Initialize an EGL context for the current display. - */ -static int engine_init_display(struct engine *engine, bool p_gl2) { - // initialize OpenGL ES and EGL - - /* - * Here specify the attributes of the desired configuration. - * Below, we select an EGLConfig with at least 8 bits per color - * component compatible with on-screen windows - */ - const EGLint gl2_attribs[] = { - // EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_BLUE_SIZE, 4, - EGL_GREEN_SIZE, 4, - EGL_RED_SIZE, 4, - EGL_ALPHA_SIZE, 0, - EGL_DEPTH_SIZE, 16, - EGL_STENCIL_SIZE, EGL_DONT_CARE, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_NONE - }; - - const EGLint gl1_attribs[] = { - // EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_BLUE_SIZE, 4, - EGL_GREEN_SIZE, 4, - EGL_RED_SIZE, 4, - EGL_ALPHA_SIZE, 0, - EGL_DEPTH_SIZE, 16, - EGL_STENCIL_SIZE, EGL_DONT_CARE, - EGL_NONE - }; - - const EGLint *attribs = p_gl2 ? gl2_attribs : gl1_attribs; - - EGLint w, h, dummy, format; - EGLint numConfigs; - EGLConfig config; - EGLSurface surface; - EGLContext context; - - EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - - eglInitialize(display, 0, 0); - - /* Here, the application chooses the configuration it desires. In this - * sample, we have a very simplified selection process, where we pick - * the first EGLConfig that matches our criteria */ - - eglChooseConfig(display, attribs, &config, 1, &numConfigs); - - LOGI("Num configs: %i\n", numConfigs); - - /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is - * guaranteed to be accepted by ANativeWindow_setBuffersGeometry(). - * As soon as we picked a EGLConfig, we can safely reconfigure the - * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */ - eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); - - ANativeWindow_setBuffersGeometry(engine->app->window, 0, 0, format); - //ANativeWindow_setFlags(engine->app->window, 0, 0, format|); - - surface = eglCreateWindowSurface(display, config, engine->app->window, NULL); - - const EGLint context_attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - context = eglCreateContext(display, config, EGL_NO_CONTEXT, p_gl2 ? context_attribs : NULL); - - if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { - LOGW("Unable to eglMakeCurrent"); - return -1; - } - - eglQuerySurface(display, surface, EGL_WIDTH, &w); - eglQuerySurface(display, surface, EGL_HEIGHT, &h); - - //engine->os->set_egl_extensions(eglQueryString(display,EGL_EXTENSIONS)); - engine->os->init_video_mode(w, h); - - engine->display = display; - engine->context = context; - engine->surface = surface; - engine->width = w; - engine->height = h; - engine->display_active = true; - - //engine->state.angle = 0; - - // Initialize GL state. - //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); - glEnable(GL_CULL_FACE); - // glShadeModel(GL_SMOOTH); - glDisable(GL_DEPTH_TEST); - LOGI("GL Version: %s - %s %s\n", glGetString(GL_VERSION), glGetString(GL_VENDOR), glGetString(GL_RENDERER)); - - return 0; -} - -static void engine_draw_frame(struct engine *engine) { - if (engine->display == NULL) { - // No display. - return; - } - - // Just fill the screen with a color. - //glClearColor(0,1,0,1); - //glClear(GL_COLOR_BUFFER_BIT); - if (engine->os && engine->os->main_loop_iterate() == true) { - - engine->requested_quit = true; - return; //should exit instead - } - - eglSwapBuffers(engine->display, engine->surface); -} - -static void engine_term_display(struct engine *engine) { - if (engine->display != EGL_NO_DISPLAY) { - eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (engine->context != EGL_NO_CONTEXT) { - eglDestroyContext(engine->display, engine->context); - } - if (engine->surface != EGL_NO_SURFACE) { - eglDestroySurface(engine->display, engine->surface); - } - eglTerminate(engine->display); - } - - engine->animating = 0; - engine->display = EGL_NO_DISPLAY; - engine->context = EGL_NO_CONTEXT; - engine->surface = EGL_NO_SURFACE; - engine->display_active = false; -} - -/** - * Process the next input event. - */ -static int32_t engine_handle_input(struct android_app *app, AInputEvent *event) { - struct engine *engine = (struct engine *)app->userData; - - if (!engine->os) - return 0; - - switch (AInputEvent_getType(event)) { - - case AINPUT_EVENT_TYPE_KEY: { - - int ac = AKeyEvent_getAction(event); - switch (ac) { - - case AKEY_EVENT_ACTION_DOWN: { - - int32_t code = AKeyEvent_getKeyCode(event); - if (code == AKEYCODE_BACK) { - - //AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled); - if (engine->os) - engine->os->main_loop_request_quit(); - return 1; - } - - } break; - case AKEY_EVENT_ACTION_UP: { - - } break; - } - - } break; - case AINPUT_EVENT_TYPE_MOTION: { - - Vector<OS_Android::TouchPos> touchvec; - - int pc = AMotionEvent_getPointerCount(event); - - touchvec.resize(pc); - - for (int i = 0; i < pc; i++) { - - touchvec[i].pos.x = AMotionEvent_getX(event, i); - touchvec[i].pos.y = AMotionEvent_getY(event, i); - touchvec[i].id = AMotionEvent_getPointerId(event, i); - } - - //System.out.printf("gaction: %d\n",event.getAction()); - int pidx = (AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> 8; - switch (AMotionEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK) { - - case AMOTION_EVENT_ACTION_DOWN: { - engine->os->process_touch(0, 0, touchvec); - - //System.out.printf("action down at: %f,%f\n", event.getX(),event.getY()); - } break; - case AMOTION_EVENT_ACTION_MOVE: { - engine->os->process_touch(1, 0, touchvec); - /* - for(int i=0;i<event.getPointerCount();i++) { - System.out.printf("%d - moved to: %f,%f\n",i, event.getX(i),event.getY(i)); - } - */ - } break; - case AMOTION_EVENT_ACTION_POINTER_UP: { - - engine->os->process_touch(4, pidx, touchvec); - //System.out.printf("%d - s.up at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx)); - } break; - case AMOTION_EVENT_ACTION_POINTER_DOWN: { - engine->os->process_touch(3, pidx, touchvec); - //System.out.printf("%d - s.down at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx)); - } break; - case AMOTION_EVENT_ACTION_CANCEL: - case AMOTION_EVENT_ACTION_UP: { - engine->os->process_touch(2, 0, touchvec); - /* - for(int i=0;i<event.getPointerCount();i++) { - System.out.printf("%d - up! %f,%f\n",i, event.getX(i),event.getY(i)); - } - */ - } break; - } - - return 1; - } break; - } - - return 0; -} - -/** - * Process the next main command. - */ - -static void _gfx_init(void *ud, bool p_gl2) { - - struct engine *engine = (struct engine *)ud; - engine_init_display(engine, p_gl2); -} - -static void engine_handle_cmd(struct android_app *app, int32_t cmd) { - struct engine *engine = (struct engine *)app->userData; - // LOGI("**** CMD %i\n",cmd); - switch (cmd) { - case APP_CMD_SAVE_STATE: - // The system has asked us to save our current state. Do so. - //engine->app->savedState = malloc(sizeof(struct saved_state)); - //*((struct saved_state*)engine->app->savedState) = engine->state; - //engine->app->savedStateSize = sizeof(struct saved_state); - break; - case APP_CMD_CONFIG_CHANGED: - case APP_CMD_WINDOW_RESIZED: { - - if (engine->display_active) { - - EGLint w, h; - eglQuerySurface(engine->display, engine->surface, EGL_WIDTH, &w); - eglQuerySurface(engine->display, engine->surface, EGL_HEIGHT, &h); - // if (w==engine->os->get_video_mode().width && h==engine->os->get_video_mode().height) - // break; - - engine_term_display(engine); - } - - engine->os->reload_gfx(); - engine_draw_frame(engine); - engine->animating = 1; - - } break; - case APP_CMD_INIT_WINDOW: - //The window is being shown, get it ready. - //LOGI("INIT WINDOW"); - if (engine->app->window != NULL) { - - if (engine->os == NULL) { - - //do initialization here, when there's OpenGL! hackish but the only way - engine->os = new OS_Android(_gfx_init, engine); - - __android_log_print(ANDROID_LOG_INFO, "godot", "pre asdasd setup..."); - - Error err = Main::setup("apk", 0, NULL); - - String modules = ProjectSettings::get_singleton()->get("android/modules"); - Vector<String> mods = modules.split(",", false); - mods.push_back("GodotOS"); - __android_log_print(ANDROID_LOG_INFO, "godot", "mod count: %i", mods.size()); - - if (mods.size()) { - - jclass activityClass = engine->jni->FindClass("android/app/NativeActivity"); - - jmethodID getClassLoader = engine->jni->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); - - jobject cls = engine->jni->CallObjectMethod(app->activity->clazz, getClassLoader); - - jclass classLoader = engine->jni->FindClass("java/lang/ClassLoader"); - - jmethodID findClass = engine->jni->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); - - static JNINativeMethod methods[] = { - { "registerSingleton", "(Ljava/lang/String;Ljava/lang/Object;)V", (void *)&Java_org_godotengine_godot_Godot_registerSingleton }, - { "registerMethod", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V", (void *)&Java_org_godotengine_godot_Godot_registerMethod }, - { "getGlobal", "(Ljava/lang/String;)Ljava/lang/String;", (void *)&Java_org_godotengine_godot_Godot_getGlobal }, - }; - - jstring gstrClassName = engine->jni->NewStringUTF("org/godotengine/godot/Godot"); - jclass GodotClass = (jclass)engine->jni->CallObjectMethod(cls, findClass, gstrClassName); - - __android_log_print(ANDROID_LOG_INFO, "godot", "godot ****^*^*?^*^*class data %x", GodotClass); - - engine->jni->RegisterNatives(GodotClass, methods, sizeof(methods) / sizeof(methods[0])); - - for (int i = 0; i < mods.size(); i++) { - - String m = mods[i]; - //jclass singletonClass = engine->jni->FindClass(m.utf8().get_data()); - - jstring strClassName = engine->jni->NewStringUTF(m.utf8().get_data()); - jclass singletonClass = (jclass)engine->jni->CallObjectMethod(cls, findClass, strClassName); - - __android_log_print(ANDROID_LOG_INFO, "godot", "****^*^*?^*^*class data %x", singletonClass); - jmethodID initialize = engine->jni->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lorg/godotengine/godot/Godot$SingletonBase;"); - - jobject obj = engine->jni->CallStaticObjectMethod(singletonClass, initialize, app->activity->clazz); - __android_log_print(ANDROID_LOG_INFO, "godot", "****^*^*?^*^*class instance %x", obj); - jobject gob = engine->jni->NewGlobalRef(obj); - } - } - - if (!Main::start()) - return; //should exit instead and print the error - - engine->os->main_loop_begin(); - } else { - //i guess recreate resources? - engine->os->reload_gfx(); - } - - engine->animating = 1; - engine_draw_frame(engine); - } - break; - case APP_CMD_TERM_WINDOW: - // The window is being hidden or closed, clean it up. - //LOGI("TERM WINDOW"); - engine_term_display(engine); - break; - case APP_CMD_GAINED_FOCUS: - // When our app gains focus, we start monitoring the accelerometer. - if (engine->accelerometerSensor != NULL) { - ASensorEventQueue_enableSensor(engine->sensorEventQueue, - engine->accelerometerSensor); - // We'd like to get 60 events per second (in us). - ASensorEventQueue_setEventRate(engine->sensorEventQueue, - engine->accelerometerSensor, (1000L / 60) * 1000); - } - // start monitoring gravity - if (engine->gravitySensor != NULL) { - ASensorEventQueue_enableSensor(engine->sensorEventQueue, - engine->gravitySensor); - // We'd like to get 60 events per second (in us). - ASensorEventQueue_setEventRate(engine->sensorEventQueue, - engine->gravitySensor, (1000L / 60) * 1000); - } - // Also start monitoring the magnetometer. - if (engine->magnetometerSensor != NULL) { - ASensorEventQueue_enableSensor(engine->sensorEventQueue, - engine->magnetometerSensor); - // We'd like to get 60 events per second (in us). - ASensorEventQueue_setEventRate(engine->sensorEventQueue, - engine->magnetometerSensor, (1000L / 60) * 1000); - } - // And the gyroscope. - if (engine->gyroscopeSensor != NULL) { - ASensorEventQueue_enableSensor(engine->sensorEventQueue, - engine->gyroscopeSensor); - // We'd like to get 60 events per second (in us). - ASensorEventQueue_setEventRate(engine->sensorEventQueue, - engine->gyroscopeSensor, (1000L / 60) * 1000); - } - engine->animating = 1; - break; - case APP_CMD_LOST_FOCUS: - // When our app loses focus, we stop monitoring the sensors. - // This is to avoid consuming battery while not being used. - if (engine->accelerometerSensor != NULL) { - ASensorEventQueue_disableSensor(engine->sensorEventQueue, - engine->accelerometerSensor); - } - if (engine->gravitySensor != NULL) { - ASensorEventQueue_disableSensor(engine->sensorEventQueue, - engine->gravitySensor); - } - if (engine->magnetometerSensor != NULL) { - ASensorEventQueue_disableSensor(engine->sensorEventQueue, - engine->magnetometerSensor); - } - if (engine->gyroscopeSensor != NULL) { - ASensorEventQueue_disableSensor(engine->sensorEventQueue, - engine->gyroscopeSensor); - } - // Also stop animating. - engine->animating = 0; - engine_draw_frame(engine); - break; - } -} - -void android_main(struct android_app *app) { - struct engine engine; - // Make sure glue isn't stripped. - app_dummy(); - - memset(&engine, 0, sizeof(engine)); - app->userData = &engine; - app->onAppCmd = engine_handle_cmd; - app->onInputEvent = engine_handle_input; - engine.app = app; - engine.requested_quit = false; - engine.os = NULL; - engine.display_active = false; - - FileAccessAndroid::asset_manager = app->activity->assetManager; - - // Prepare to monitor sensors - engine.sensorManager = ASensorManager_getInstance(); - engine.accelerometerSensor = ASensorManager_getDefaultSensor(engine.sensorManager, - ASENSOR_TYPE_ACCELEROMETER); - engine.gravitySensor = ASensorManager_getDefaultSensor(engine.sensorManager, - ASENSOR_TYPE_GRAVITY); - engine.magnetometerSensor = ASensorManager_getDefaultSensor(engine.sensorManager, - ASENSOR_TYPE_MAGNETIC_FIELD); - engine.gyroscopeSensor = ASensorManager_getDefaultSensor(engine.sensorManager, - ASENSOR_TYPE_GYROSCOPE); - engine.sensorEventQueue = ASensorManager_createEventQueue(engine.sensorManager, - app->looper, LOOPER_ID_USER, NULL, NULL); - - ANativeActivity_setWindowFlags(app->activity, AWINDOW_FLAG_FULLSCREEN | AWINDOW_FLAG_KEEP_SCREEN_ON, 0); - - app->activity->vm->AttachCurrentThread(&engine.jni, NULL); - - // loop waiting for stuff to do. - - while (1) { - // Read all pending events. - int ident; - int events; - struct android_poll_source *source; - - // If not animating, we will block forever waiting for events. - // If animating, we loop until all events are read, then continue - // to draw the next frame of animation. - - int nullmax = 50; - while ((ident = ALooper_pollAll(engine.animating ? 0 : -1, NULL, &events, - (void **)&source)) >= 0) { - - // Process this event. - - if (source != NULL) { - // LOGI("process\n"); - source->process(app, source); - } else { - nullmax--; - if (nullmax < 0) - break; - } - - // If a sensor has data, process it now. - // LOGI("events\n"); - if (ident == LOOPER_ID_USER) { - if (engine.accelerometerSensor != NULL || engine.magnetometerSensor != NULL || engine.gyroscopeSensor != NULL) { - ASensorEvent event; - while (ASensorEventQueue_getEvents(engine.sensorEventQueue, - &event, 1) > 0) { - - if (engine.os) { - if (event.acceleration != NULL) { - engine.os->process_accelerometer(Vector3(event.acceleration.x, event.acceleration.y, - event.acceleration.z)); - } - if (event.magnetic != NULL) { - engine.os->process_magnetometer(Vector3(event.magnetic.x, event.magnetic.y, - event.magnetic.z)); - } - if (event.vector != NULL) { - engine.os->process_gyroscope(Vector3(event.vector.x, event.vector.y, - event.vector.z)); - } - } - } - } - } - - // Check if we are exiting. - if (app->destroyRequested != 0) { - if (engine.os) { - engine.os->main_loop_request_quit(); - } - app->destroyRequested = 0; - } - - if (engine.requested_quit) { - engine_term_display(&engine); - exit(0); - } - - // LOGI("end\n"); - } - - // LOGI("engine animating? %i\n",engine.animating); - - if (engine.animating) { - //do os render - - engine_draw_frame(&engine); - //LOGI("TERM WINDOW"); - } - } -} - -JNIEXPORT void JNICALL Java_org_godotengine_godot_Godot_registerSingleton(JNIEnv *env, jobject obj, jstring name, jobject p_object) { - - String singname = env->GetStringUTFChars(name, NULL); - JNISingleton *s = memnew(JNISingleton); - s->update_env(env); - s->set_instance(env->NewGlobalRef(p_object)); - jni_singletons[singname] = s; - - Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s)); -} - -static Variant::Type get_jni_type(const String &p_type) { - - static struct { - const char *name; - Variant::Type type; - } _type_to_vtype[] = { - { "void", Variant::NIL }, - { "boolean", Variant::BOOL }, - { "int", Variant::INT }, - { "float", Variant::REAL }, - { "java.lang.String", Variant::STRING }, - { "[I", Variant::INT_ARRAY }, - { "[F", Variant::REAL_ARRAY }, - { "[Ljava.lang.String;", Variant::STRING_ARRAY }, - { NULL, Variant::NIL } - }; - - int idx = 0; - - while (_type_to_vtype[idx].name) { - - if (p_type == _type_to_vtype[idx].name) - return _type_to_vtype[idx].type; - - idx++; - } - - return Variant::NIL; -} - -static const char *get_jni_sig(const String &p_type) { - - static struct { - const char *name; - const char *sig; - } _type_to_vtype[] = { - { "void", "V" }, - { "boolean", "Z" }, - { "int", "I" }, - { "float", "F" }, - { "java.lang.String", "Ljava/lang/String;" }, - { "[I", "[I" }, - { "[F", "[F" }, - { "[Ljava.lang.String;", "[Ljava/lang/String;" }, - { NULL, "V" } - }; - - int idx = 0; - - while (_type_to_vtype[idx].name) { - - if (p_type == _type_to_vtype[idx].name) - return _type_to_vtype[idx].sig; - - idx++; - } - - return ""; -} - -JNIEXPORT jstring JNICALL Java_org_godotengine_godot_Godot_getGlobal(JNIEnv *env, jobject obj, jstring path) { - - String js = env->GetStringUTFChars(path, NULL); - - return env->NewStringUTF(ProjectSettings::get_singleton()->get(js).operator String().utf8().get_data()); -} - -JNIEXPORT void JNICALL Java_org_godotengine_godot_Godot_registerMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) { - - String singname = env->GetStringUTFChars(sname, NULL); - - 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); - Vector<Variant::Type> types; - String cs = "("; - - int stringCount = env->GetArrayLength(args); - - 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)); - } - - cs += ")"; - cs += get_jni_sig(retval); - jclass cls = env->GetObjectClass(s->get_instance()); - jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data()); - if (!mid) { - - print_line("RegisterMethod: Failed getting method ID: " + mname); - } - - s->add_method(mname, mid, types, get_jni_type(retval)); -} - -#endif 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/aidl/com/android/vending/billing/IInAppBillingService.aidl b/platform/android/java/aidl/com/android/vending/billing/IInAppBillingService.aidl deleted file mode 100644 index 2a492f7845..0000000000 --- a/platform/android/java/aidl/com/android/vending/billing/IInAppBillingService.aidl +++ /dev/null @@ -1,144 +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.android.vending.billing; - -import android.os.Bundle; - -/** - * InAppBillingService is the service that provides in-app billing version 3 and beyond. - * This service provides the following features: - * 1. Provides a new API to get details of in-app items published for the app including - * price, type, title and description. - * 2. The purchase flow is synchronous and purchase information is available immediately - * after it completes. - * 3. Purchase information of in-app purchases is maintained within the Google Play system - * till the purchase is consumed. - * 4. An API to consume a purchase of an inapp item. All purchases of one-time - * in-app items are consumable and thereafter can be purchased again. - * 5. An API to get current purchases of the user immediately. This will not contain any - * consumed purchases. - * - * All calls will give a response code with the following possible values - * RESULT_OK = 0 - success - * RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog - * RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested - * RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase - * RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API - * RESULT_ERROR = 6 - Fatal error during the API action - * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned - * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned - */ -interface IInAppBillingService { - /** - * Checks support for the requested billing API version, package and in-app type. - * Minimum API version supported by this interface is 3. - * @param apiVersion the billing version which the app is using - * @param packageName the package name of the calling app - * @param type type of the in-app item being purchased "inapp" for one-time purchases - * and "subs" for subscription. - * @return RESULT_OK(0) on success, corresponding result code on failures - */ - int isBillingSupported(int apiVersion, String packageName, String type); - - /** - * Provides details of a list of SKUs - * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle - * with a list JSON strings containing the productId, price, title and description. - * This API can be called with a maximum of 20 SKUs. - * @param apiVersion billing API version that the Third-party is using - * @param packageName the package name of the calling app - * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on - * failure as listed above. - * "DETAILS_LIST" with a StringArrayList containing purchase information - * in JSON format similar to: - * '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00", - * "title : "Example Title", "description" : "This is an example description" }' - */ - Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle); - - /** - * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, - * the type, a unique purchase token and an optional developer payload. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type the type of the in-app item ("inapp" for one-time purchases - * and "subs" for subscription). - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on - * failure as listed above. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on - * failure as listed above. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - * TODO: change this to app-specific keys. - */ - Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type, - String developerPayload); - - /** - * Returns the current SKUs owned by the user of the type and package name specified along with - * purchase information and a signature of the data to be validated. - * This will return all SKUs that have been purchased in V3 and managed items purchased using - * V1 and V2 that have not been consumed. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param type the type of the in-app items being requested - * ("inapp" for one-time purchases and "subs" for subscription). - * @param continuationToken to be set as null for the first call, if the number of owned - * skus are too many, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on - * failure as listed above. - * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken); - - /** - * Consume the last purchase of the given SKU. This will result in this item being removed - * from all subsequent responses to getPurchases() and allow re-purchase of this item. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param purchaseToken token in the purchase information JSON that identifies the purchase - * to be consumed - * @return 0 if consumption succeeded. Appropriate error values for failures. - */ - int consumePurchase(int apiVersion, String packageName, String purchaseToken); -} diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml new file mode 100644 index 0000000000..ba01ec313b --- /dev/null +++ b/platform/android/java/app/AndroidManifest.xml @@ -0,0 +1,62 @@ +<?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" > + + <!-- Adding custom text to the manifest is fine, but do it outside the custom USER and APPLICATION BEGIN/END comments, --> + <!-- as that gets rewritten. --> + + <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" /> + +<!-- Custom user 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--> + + <!-- Any tag in this line after android:icon will be erased when doing custom builds. --> + <!-- If you want to add tags manually, do before it. --> + <!-- WARNING: This should stay on a single line until the parsing code is improved. See GH-32414. --> + <application android:label="@string/godot_project_name_string" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" android:icon="@drawable/icon" > + + <!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. --> + <!-- Do these changes in the export preset. Adding new ones is fine. --> + + <!-- XR mode metadata. This is modified by the exporter based on the selected xr mode. DO NOT CHANGE the values here. --> + <meta-data + android:name="xr_mode_metadata_name" + android:value="xr_mode_metadata_value" /> + + <activity + android:name=".GodotApp" + android:label="@string/godot_project_name_string" + android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" + android:launchMode="singleTask" + android:screenOrientation="landscape" + android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode" + 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> + +<!-- Custom application XML added by add-ons. --> +<!--CHUNK_APPLICATION_BEGIN--> +<!--CHUNK_APPLICATION_END--> + + </application> + +</manifest> diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle new file mode 100644 index 0000000000..9f64c3dc8a --- /dev/null +++ b/platform/android/java/app/build.gradle @@ -0,0 +1,121 @@ +// Gradle build config for Godot Engine's Android port. +// +// Do not remove/modify comments ending with BEGIN/END, they are used to inject +// addon-specific configuration. +apply from: 'config.gradle' + +buildscript { + apply from: 'config.gradle' + + repositories { + google() + jcenter() +//CHUNK_BUILDSCRIPT_REPOSITORIES_BEGIN +//CHUNK_BUILDSCRIPT_REPOSITORIES_END + } + dependencies { + classpath libraries.androidGradlePlugin +//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 { + if (rootProject.findProject(":lib")) { + implementation project(":lib") + } else { + // Custom build mode. In this scenario this project is the only one around and the Godot + // library is available through the pre-generated godot-lib.*.aar android archive files. + debugImplementation fileTree(dir: 'libs/debug', include: ['*.jar', '*.aar']) + releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar']) + } +//CHUNK_DEPENDENCIES_BEGIN +//CHUNK_DEPENDENCIES_END +} + +android { + compileSdkVersion versions.compileSdk + buildToolsVersion versions.buildTools + + defaultConfig { + // Feel free to modify the application id to your own. + applicationId "com.godot.game" + minSdkVersion versions.minSdk + targetSdkVersion versions.targetSdk +//CHUNK_ANDROID_DEFAULTCONFIG_BEGIN +//CHUNK_ANDROID_DEFAULTCONFIG_END + } + + lintOptions { + abortOnError false + disable 'MissingTranslation', 'UnusedResources' + } + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + } + + // 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 + ] + } + + applicationVariants.all { variant -> + variant.outputs.all { output -> + output.outputFileName = "android_${variant.name}.apk" + } + } +} + +//CHUNK_GLOBAL_BEGIN +//CHUNK_GLOBAL_END diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle new file mode 100644 index 0000000000..20c3123221 --- /dev/null +++ b/platform/android/java/app/config.gradle @@ -0,0 +1,12 @@ +ext.versions = [ + androidGradlePlugin : '3.4.2', + compileSdk : 28, + minSdk : 18, + targetSdk : 28, + buildTools : '28.0.3', + +] + +ext.libraries = [ + androidGradlePlugin : "com.android.tools.build:gradle:$versions.androidGradlePlugin" +] diff --git a/platform/android/globals/global_defaults.cpp b/platform/android/java/app/src/com/godot/game/GodotApp.java index efeb8598e5..d7469a8765 100644 --- a/platform/android/globals/global_defaults.cpp +++ b/platform/android/java/app/src/com/godot/game/GodotApp.java @@ -1,12 +1,12 @@ /*************************************************************************/ -/* global_defaults.cpp */ +/* GodotApp.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,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "global_defaults.h" -#include "core/project_settings.h" +package com.godot.game; -void register_android_global_defaults() { +import org.godotengine.godot.Godot; + +/** + * Template activity for Godot Android custom builds. + * Feel free to extend and modify this class for your custom logic. + */ +public class GodotApp extends Godot { } diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle new file mode 100644 index 0000000000..2052017888 --- /dev/null +++ b/platform/android/java/build.gradle @@ -0,0 +1,152 @@ +apply from: 'app/config.gradle' + +buildscript { + apply from: 'app/config.gradle' + + repositories { + google() + jcenter() + } + dependencies { + classpath libraries.androidGradlePlugin + } +} + +allprojects { + repositories { + google() + jcenter() + mavenCentral() + } +} + +ext { + sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : "" + + supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"] + supportedTargets = ['release':"release", 'debug':"release_debug"] + + // Used by gradle to specify which architecture to build for by default when running `./gradlew build`. + // This command is usually used by Android Studio. + // If building manually on the command line, it's recommended to use the + // `./gradlew generateGodotTemplates` build command instead after running the `scons` command. + // The defaultAbi must be one of the {supportedAbis} values. + defaultAbi = "arm64v8" +} + +def rootDir = "../../.." +def binDir = "$rootDir/bin/" + +def getSconsTaskName(String buildType) { + return "compileGodotNativeLibs" + buildType.capitalize() +} + +/** + * Copy the generated 'android_debug.apk' binary template into the Godot bin directory. + * Depends on the app build task to ensure the binary is generated prior to copying. + */ +task copyDebugBinaryToBin(type: Copy) { + dependsOn ':app:assembleDebug' + from('app/build/outputs/apk/debug') + into(binDir) + include('android_debug.apk') +} + +/** + * Copy the generated 'android_release.apk' binary template into the Godot bin directory. + * Depends on the app build task to ensure the binary is generated prior to copying. + */ +task copyReleaseBinaryToBin(type: Copy) { + dependsOn ':app:assembleRelease' + from('app/build/outputs/apk/release') + into(binDir) + include('android_release.apk') +} + +/** + * Copy the Godot android library archive debug file into the app debug libs directory. + * Depends on the library build task to ensure the AAR file is generated prior to copying. + */ +task copyDebugAAR(type: Copy) { + dependsOn ':lib:assembleDebug' + from('lib/build/outputs/aar') + into('app/libs/debug') + include('godot-lib.debug.aar') +} + +/** + * Copy the Godot android library archive release file into the app release libs directory. + * Depends on the library build task to ensure the AAR file is generated prior to copying. + */ +task copyReleaseAAR(type: Copy) { + dependsOn ':lib:assembleRelease' + from('lib/build/outputs/aar') + into('app/libs/release') + include('godot-lib.release.aar') +} + +/** + * Generate Godot custom build template by zipping the source files from the app directory, as well + * as the AAR files generated by 'copyDebugAAR' and 'copyReleaseAAR'. + * The zip file also includes some gradle tools to allow building of the custom build. + */ +task zipCustomBuild(type: Zip) { + dependsOn ':generateGodotTemplates' + doFirst { + logger.lifecycle("Generating Godot custom build template") + } + from(fileTree(dir: 'app', excludes: ['**/build/**', '**/.gradle/**', '**/*.iml']), fileTree(dir: '.', includes: ['gradle.properties','gradlew', 'gradlew.bat', 'gradle/**'])) + include '**/*' + archiveName 'android_source.zip' + destinationDir(file(binDir)) +} + +/** + * Master task used to coordinate the tasks defined above to generate the set of Godot templates. + */ +task generateGodotTemplates(type: GradleBuild) { + // We exclude these gradle tasks so we can run the scons command manually. + for (String buildType : supportedTargets.keySet()) { + startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType) + } + + tasks = [] + + // Only build the apks and aar files for which we have native shared libraries. + for (String target : supportedTargets.keySet()) { + File targetLibs = new File("lib/libs/" + target) + if (targetLibs != null && targetLibs.isDirectory()) { + File[] targetLibsContents = targetLibs.listFiles() + if (targetLibsContents != null && targetLibsContents.length > 0) { + // Copy the generated aar library files to the custom build directory. + tasks += "copy" + target.capitalize() + "AAR" + // Copy the prebuilt binary templates to the bin directory. + tasks += "copy" + target.capitalize() + "BinaryToBin" + } + } + } + + finalizedBy 'zipCustomBuild' +} + +/** + * Clean the generated artifacts. + */ +task cleanGodotTemplates(type: Delete) { + // Delete the generated native libs + delete("lib/libs") + + // Delete the library generated AAR files + delete("lib/build/outputs/aar") + + // Delete the app libs directory contents + delete("app/libs") + + // Delete the generated binary apks + delete("app/build/outputs/apk") + + // Delete the Godot templates in the Godot bin directory + delete("$binDir/android_debug.apk") + delete("$binDir/android_release.apk") + delete("$binDir/android_source.zip") +} 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 fe37fa74a9..bf50265715 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Jul 29 16:10:03 ICT 2017 +#Mon Sep 02 02:44:30 PDT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-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/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml new file mode 100644 index 0000000000..517fc403b2 --- /dev/null +++ b/platform/android/java/lib/AndroidManifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.godotengine.godot" + android:versionCode="1" + android:versionName="1.0"> + + <application> + + <service android:name=".GodotDownloaderService" /> + + </application> + + <instrumentation + android:icon="@drawable/icon" + android:label="@string/godot_project_name_string" + android:name=".GodotInstrumentation" + android:targetPackage="org.godotengine.godot" /> + +</manifest> diff --git a/platform/android/java/lib/CMakeLists.txt b/platform/android/java/lib/CMakeLists.txt new file mode 100644 index 0000000000..d3bdf6a5f2 --- /dev/null +++ b/platform/android/java/lib/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.6) +project(godot) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(GODOT_ROOT_DIR ../../../..) + +# Get sources +file(GLOB_RECURSE SOURCES ${GODOT_ROOT_DIR}/*.c**) +file(GLOB_RECURSE HEADERS ${GODOT_ROOT_DIR}/*.h**) + +add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) +target_include_directories(${PROJECT_NAME} + SYSTEM PUBLIC + ${GODOT_ROOT_DIR} + ${GODOT_ROOT_DIR}/modules/gdnative/include) diff --git a/platform/android/java/lib/THIRDPARTY.md b/platform/android/java/lib/THIRDPARTY.md new file mode 100644 index 0000000000..2496b59263 --- /dev/null +++ b/platform/android/java/lib/THIRDPARTY.md @@ -0,0 +1,39 @@ +# Third-party libraries + +This file list third-party libraries used in the Android source folder, +with their provenance and, when relevant, modifications made to those files. + +## com.android.vending.billing + +- Upstream: https://github.com/googlesamples/android-play-billing/tree/master/TrivialDrive/app/src/main +- Version: git (7a94c69, 2019) +- License: Apache 2.0 + +Overwrite the file `aidl/com/android/vending/billing/IInAppBillingService.aidl`. + +## com.google.android.vending.expansion.downloader + +- Upstream: https://github.com/google/play-apk-expansion/tree/master/apkx_library +- Version: git (9ecf54e, 2017) +- License: Apache 2.0 + +Overwrite all files under: + +- `src/com/google/android/vending/expansion/downloader` + +Some files have been modified for yet unclear reasons. +See the `patches/com.google.android.vending.expansion.downloader.patch` file. + +## com.google.android.vending.licensing + +- Upstream: https://github.com/google/play-licensing/tree/master/lvl_library/ +- Version: git (eb57657, 2018) with modifications +- License: Apache 2.0 + +Overwrite all files under: + +- `aidl/com/android/vending/licensing` +- `src/com/google/android/vending/licensing` + +Some files have been modified to silence linter errors or fix downstream issues. +See the `patches/com.google.android.vending.licensing.patch` file. diff --git a/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl b/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl new file mode 100644 index 0000000000..0f2bcae338 --- /dev/null +++ b/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.vending.billing; + +import android.os.Bundle; + +/** + * InAppBillingService is the service that provides in-app billing version 3 and beyond. + * This service provides the following features: + * 1. Provides a new API to get details of in-app items published for the app including + * price, type, title and description. + * 2. The purchase flow is synchronous and purchase information is available immediately + * after it completes. + * 3. Purchase information of in-app purchases is maintained within the Google Play system + * till the purchase is consumed. + * 4. An API to consume a purchase of an inapp item. All purchases of one-time + * in-app items are consumable and thereafter can be purchased again. + * 5. An API to get current purchases of the user immediately. This will not contain any + * consumed purchases. + * + * All calls will give a response code with the following possible values + * RESULT_OK = 0 - success + * RESULT_USER_CANCELED = 1 - User pressed back or canceled a dialog + * RESULT_SERVICE_UNAVAILABLE = 2 - The network connection is down + * RESULT_BILLING_UNAVAILABLE = 3 - This billing API version is not supported for the type requested + * RESULT_ITEM_UNAVAILABLE = 4 - Requested SKU is not available for purchase + * RESULT_DEVELOPER_ERROR = 5 - Invalid arguments provided to the API + * RESULT_ERROR = 6 - Fatal error during the API action + * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned + * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned + */ +interface IInAppBillingService { + /** + * Checks support for the requested billing API version, package and in-app type. + * Minimum API version supported by this interface is 3. + * @param apiVersion billing API version that the app is using + * @param packageName the package name of the calling app + * @param type type of the in-app item being purchased ("inapp" for one-time purchases + * and "subs" for subscriptions) + * @return RESULT_OK(0) on success and appropriate response code on failures. + */ + int isBillingSupported(int apiVersion, String packageName, String type); + + /** + * Provides details of a list of SKUs + * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle + * with a list JSON strings containing the productId, price, title and description. + * This API can be called with a maximum of 20 SKUs. + * @param apiVersion billing API version that the app is using + * @param packageName the package name of the calling app + * @param type of the in-app items ("inapp" for one-time purchases + * and "subs" for subscriptions) + * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes + * on failures. + * "DETAILS_LIST" with a StringArrayList containing purchase information + * in JSON format similar to: + * '{ "productId" : "exampleSku", + * "type" : "inapp", + * "price" : "$5.00", + * "price_currency": "USD", + * "price_amount_micros": 5000000, + * "title : "Example Title", + * "description" : "This is an example description" }' + */ + Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle); + + /** + * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, + * the type, a unique purchase token and an optional developer payload. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param sku the SKU of the in-app item as published in the developer console + * @param type of the in-app item being purchased ("inapp" for one-time purchases + * and "subs" for subscriptions) + * @param developerPayload optional argument to be sent back with the purchase information + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes + * on failures. + * "BUY_INTENT" - PendingIntent to start the purchase flow + * + * The Pending intent should be launched with startIntentSenderForResult. When purchase flow + * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. + * If the purchase is successful, the result data will contain the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response + * codes on failures. + * "INAPP_PURCHASE_DATA" - String in JSON format similar to + * '{"orderId":"12999763169054705758.1371079406387615", + * "packageName":"com.example.app", + * "productId":"exampleSku", + * "purchaseTime":1345678900000, + * "purchaseToken" : "122333444455555", + * "developerPayload":"example developer payload" }' + * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that + * was signed with the private key of the developer + */ + Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type, + String developerPayload); + + /** + * Returns the current SKUs owned by the user of the type and package name specified along with + * purchase information and a signature of the data to be validated. + * This will return all SKUs that have been purchased in V3 and managed items purchased using + * V1 and V2 that have not been consumed. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param type of the in-app items being requested ("inapp" for one-time purchases + * and "subs" for subscriptions) + * @param continuationToken to be set as null for the first call, if the number of owned + * skus are too many, a continuationToken is returned in the response bundle. + * This method can be called again with the continuation token to get the next set of + * owned skus. + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes + on failures. + * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs + * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information + * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures + * of the purchase information + * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the + * next set of in-app purchases. Only set if the + * user has more owned skus than the current list. + */ + Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken); + + /** + * Consume the last purchase of the given SKU. This will result in this item being removed + * from all subsequent responses to getPurchases() and allow re-purchase of this item. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param purchaseToken token in the purchase information JSON that identifies the purchase + * to be consumed + * @return RESULT_OK(0) if consumption succeeded, appropriate response codes on failures. + */ + int consumePurchase(int apiVersion, String packageName, String purchaseToken); + + /** + * This API is currently under development. + */ + int stub(int apiVersion, String packageName, String type); + + /** + * Returns a pending intent to launch the purchase flow for upgrading or downgrading a + * subscription. The existing owned SKU(s) should be provided along with the new SKU that + * the user is upgrading or downgrading to. + * @param apiVersion billing API version that the app is using, must be 5 or later + * @param packageName package name of the calling app + * @param oldSkus the SKU(s) that the user is upgrading or downgrading from, + * if null or empty this method will behave like {@link #getBuyIntent} + * @param newSku the SKU that the user is upgrading or downgrading to + * @param type of the item being purchased, currently must be "subs" + * @param developerPayload optional argument to be sent back with the purchase information + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes + * on failures. + * "BUY_INTENT" - PendingIntent to start the purchase flow + * + * The Pending intent should be launched with startIntentSenderForResult. When purchase flow + * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. + * If the purchase is successful, the result data will contain the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response + * codes on failures. + * "INAPP_PURCHASE_DATA" - String in JSON format similar to + * '{"orderId":"12999763169054705758.1371079406387615", + * "packageName":"com.example.app", + * "productId":"exampleSku", + * "purchaseTime":1345678900000, + * "purchaseToken" : "122333444455555", + * "developerPayload":"example developer payload" }' + * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that + * was signed with the private key of the developer + */ + Bundle getBuyIntentToReplaceSkus(int apiVersion, String packageName, + in List<String> oldSkus, String newSku, String type, String developerPayload); + + /** + * Returns a pending intent to launch the purchase flow for an in-app item. This method is + * a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams} + * parameter. This parameter is a Bundle of optional keys and values that affect the + * operation of the method. + * @param apiVersion billing API version that the app is using, must be 6 or later + * @param packageName package name of the calling app + * @param sku the SKU of the in-app item as published in the developer console + * @param type of the in-app item being purchased ("inapp" for one-time purchases + * and "subs" for subscriptions) + * @param developerPayload optional argument to be sent back with the purchase information + * @extraParams a Bundle with the following optional keys: + * "skusToReplace" - List<String> - an optional list of SKUs that the user is + * upgrading or downgrading from. + * Pass this field if the purchase is upgrading or downgrading + * existing subscriptions. + * The specified SKUs are replaced with the SKUs that the user is + * purchasing. Google Play replaces the specified SKUs at the start of + * the next billing cycle. + * "replaceSkusProration" - Boolean - whether the user should be credited for any unused + * subscription time on the SKUs they are upgrading or downgrading. + * If you set this field to true, Google Play swaps out the old SKUs + * and credits the user with the unused value of their subscription + * time on a pro-rated basis. + * Google Play applies this credit to the new subscription, and does + * not begin billing the user for the new subscription until after + * the credit is used up. + * If you set this field to false, the user does not receive credit for + * any unused subscription time and the recurrence date does not + * change. + * Default value is true. Ignored if you do not pass skusToReplace. + * "accountId" - String - an optional obfuscated string that is uniquely + * associated with the user's account in your app. + * If you pass this value, Google Play can use it to detect irregular + * activity, such as many devices making purchases on the same + * account in a short period of time. + * Do not use the developer ID or the user's Google ID for this field. + * In addition, this field should not contain the user's ID in + * cleartext. + * We recommend that you use a one-way hash to generate a string from + * the user's ID, and store the hashed string in this field. + * "vr" - Boolean - an optional flag indicating whether the returned intent + * should start a VR purchase flow. The apiVersion must also be 7 or + * later to use this flag. + */ + Bundle getBuyIntentExtraParams(int apiVersion, String packageName, String sku, + String type, String developerPayload, in Bundle extraParams); + + /** + * Returns the most recent purchase made by the user for each SKU, even if that purchase is + * expired, canceled, or consumed. + * @param apiVersion billing API version that the app is using, must be 6 or later + * @param packageName package name of the calling app + * @param type of the in-app items being requested ("inapp" for one-time purchases + * and "subs" for subscriptions) + * @param continuationToken to be set as null for the first call, if the number of owned + * skus is too large, a continuationToken is returned in the response bundle. + * This method can be called again with the continuation token to get the next set of + * owned skus. + * @param extraParams a Bundle with extra params that would be appended into http request + * query string. Not used at this moment. Reserved for future functionality. + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value: RESULT_OK(0) if success, + * {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures. + * + * "INAPP_PURCHASE_ITEM_LIST" - ArrayList<String> containing the list of SKUs + * "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information + * "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> containing the signatures + * of the purchase information + * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the + * next set of in-app purchases. Only set if the + * user has more owned skus than the current list. + */ + Bundle getPurchaseHistory(int apiVersion, String packageName, String type, + String continuationToken, in Bundle extraParams); + + /** + * This method is a variant of {@link #isBillingSupported}} that takes an additional + * {@code extraParams} parameter. + * @param apiVersion billing API version that the app is using, must be 7 or later + * @param packageName package name of the calling app + * @param type of the in-app item being purchased ("inapp" for one-time purchases and "subs" + * for subscriptions) + * @param extraParams a Bundle with the following optional keys: + * "vr" - Boolean - an optional flag to indicate whether {link #getBuyIntentExtraParams} + * supports returning a VR purchase flow. + * @return RESULT_OK(0) on success and appropriate response code on failures. + */ + int isBillingSupportedExtraParams(int apiVersion, String packageName, String type, + in Bundle extraParams); +} diff --git a/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.aidl b/platform/android/java/lib/aidl/com/android/vending/licensing/ILicenseResultListener.aidl index c816558afc..869cb16f68 100644 --- a/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.aidl +++ b/platform/android/java/lib/aidl/com/android/vending/licensing/ILicenseResultListener.aidl @@ -16,8 +16,6 @@ package com.android.vending.licensing; -// Android library projects do not yet support AIDL, so this has been -// precompiled into the src directory. oneway interface ILicenseResultListener { void verifyLicense(int responseCode, String signedData, String signature); } diff --git a/platform/android/java/src/com/android/vending/licensing/ILicensingService.aidl b/platform/android/java/lib/aidl/com/android/vending/licensing/ILicensingService.aidl index 664510ce0c..9541a2090c 100644 --- a/platform/android/java/src/com/android/vending/licensing/ILicensingService.aidl +++ b/platform/android/java/lib/aidl/com/android/vending/licensing/ILicensingService.aidl @@ -18,8 +18,6 @@ package com.android.vending.licensing; import com.android.vending.licensing.ILicenseResultListener; -// Android library projects do not yet support AIDL, so this has been -// precompiled into the src directory. oneway interface ILicensingService { void checkLicense(long nonce, String packageName, in ILicenseResultListener listener); } diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle new file mode 100644 index 0000000000..13a14422ed --- /dev/null +++ b/platform/android/java/lib/build.gradle @@ -0,0 +1,82 @@ +apply plugin: 'com.android.library' + +dependencies { + implementation "com.android.support:support-core-utils:28.0.0" +} + +def pathToRootDir = "../../../../" + +android { + compileSdkVersion versions.compileSdk + buildToolsVersion versions.buildTools + useLibrary 'org.apache.http.legacy' + + defaultConfig { + minSdkVersion versions.minSdk + targetSdkVersion versions.targetSdk + } + + lintOptions { + abortOnError false + disable 'MissingTranslation', 'UnusedResources' + } + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + aidl.srcDirs = ['aidl'] + assets.srcDirs = ['assets'] + } + debug.jniLibs.srcDirs = ['libs/debug'] + release.jniLibs.srcDirs = ['libs/release'] + } + + libraryVariants.all { variant -> + variant.outputs.all { output -> + output.outputFileName = "godot-lib.${variant.name}.aar" + } + + def buildType = variant.buildType.name.capitalize() + + def taskPrefix = "" + if (project.path != ":") { + taskPrefix = project.path + ":" + } + + // Disable the externalNativeBuild* task as it would cause build failures since the cmake build + // files is only setup for editing support. + gradle.startParameter.excludedTaskNames += taskPrefix + "externalNativeBuild" + buildType + + def releaseTarget = supportedTargets[buildType.toLowerCase()] + if (releaseTarget == null || releaseTarget == "") { + throw new GradleException("Invalid build type: " + buildType) + } + + if (!supportedAbis.contains(defaultAbi)) { + throw new GradleException("Invalid default abi: " + defaultAbi) + } + + // Creating gradle task to generate the native libraries for the default abi. + def taskName = getSconsTaskName(buildType) + tasks.create(name: taskName, type: Exec) { + executable "scons" + sconsExt + args "--directory=${pathToRootDir}", "platform=android", "target=${releaseTarget}", "android_arch=${defaultAbi}", "-j" + Runtime.runtime.availableProcessors() + } + + // Schedule the tasks so the generated libs are present before the aar file is packaged. + tasks["merge${buildType}JniLibFolders"].dependsOn taskName + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } +} diff --git a/platform/android/java/lib/patches/com.google.android.vending.expansion.downloader.patch b/platform/android/java/lib/patches/com.google.android.vending.expansion.downloader.patch new file mode 100644 index 0000000000..49cc41e817 --- /dev/null +++ b/platform/android/java/lib/patches/com.google.android.vending.expansion.downloader.patch @@ -0,0 +1,300 @@ +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 ad6ea0de6..452c7d148 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,6 +32,9 @@ import android.os.Messenger; + import android.os.RemoteException; + import android.util.Log; + ++// -- GODOT start -- ++import java.lang.ref.WeakReference; ++// -- GODOT end -- + + + /** +@@ -118,29 +121,46 @@ public class DownloaderClientMarshaller { + /** + * Target we publish for clients to send messages to IncomingHandler. + */ +- final Messenger mMessenger = new Messenger(new Handler() { ++ // -- GODOT start -- ++ 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) { +- 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; ++ Stub downloader = mDownloader.get(); ++ if (downloader != null) { ++ downloader.handleMessage(msg); + } + } +- }); ++ } ++ ++ 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; ++ } ++ } ++ // -- GODOT end -- + + public Stub(IDownloaderClient itf, Class<?> downloaderService) { + mItf = itf; +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 979352299..3771d19c9 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,6 +25,9 @@ import android.os.Message; + import android.os.Messenger; + import android.os.RemoteException; + ++// -- GODOT start -- ++import java.lang.ref.WeakReference; ++// -- GODOT end -- + + + /** +@@ -108,32 +111,49 @@ public class DownloaderServiceMarshaller { + + private static class Stub implements IStub { + private IDownloaderService mItf = null; +- final Messenger mMessenger = new Messenger(new Handler() { ++ // -- GODOT start -- ++ 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) { +- 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; ++ 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; ++ } ++ } ++ // -- GODOT end -- + + public Stub(IDownloaderService itf) { + mItf = 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 e4b1b0f1c..36cd6aacf 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 +@@ -24,7 +24,10 @@ import android.os.StatFs; + import android.os.SystemClock; + import android.util.Log; + +-import com.android.vending.expansion.downloader.R; ++// -- GODOT start -- ++//import com.android.vending.expansion.downloader.R; ++import org.godotengine.godot.R; ++// -- GODOT end -- + + import java.io.File; + import java.text.SimpleDateFormat; +@@ -146,12 +149,14 @@ public class Helpers { + } + return ""; + } +- return String.format("%.2f", ++ // -- GODOT start -- ++ return String.format(Locale.ENGLISH, "%.2f", + (float) overallProgress / (1024.0f * 1024.0f)) + + "MB /" + +- String.format("%.2f", (float) overallTotal / ++ String.format(Locale.ENGLISH, "%.2f", (float) overallTotal / + (1024.0f * 1024.0f)) + + "MB"; ++ // -- GODOT end -- + } + + /** +@@ -184,7 +189,9 @@ public class Helpers { + } + + public static String getSpeedString(float bytesPerMillisecond) { +- return String.format("%.2f", bytesPerMillisecond * 1000 / 1024); ++ // -- GODOT start -- ++ return String.format(Locale.ENGLISH, "%.2f", bytesPerMillisecond * 1000 / 1024); ++ // -- GODOT end -- + } + + public static String getTimeRemaining(long durationInMilliseconds) { +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 12edd97ab..a0e1165cc 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 +@@ -26,6 +26,10 @@ import android.net.NetworkInfo; + import android.telephony.TelephonyManager; + import android.util.Log; + ++// -- GODOT start -- ++import android.annotation.SuppressLint; ++// -- GODOT end -- ++ + /** + * Contains useful helper functions, typically tied to the application context. + */ +@@ -51,6 +55,7 @@ class SystemFacade { + return null; + } + ++ @SuppressLint("MissingPermission") + NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); + if (activeInfo == null) { + if (Constants.LOGVV) { +@@ -69,6 +74,7 @@ class SystemFacade { + return false; + } + ++ @SuppressLint("MissingPermission") + NetworkInfo info = connectivity.getActiveNetworkInfo(); + boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE); + TelephonyManager tm = (TelephonyManager) mContext +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 f1536e80e..4b214b22d 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 +@@ -16,7 +16,11 @@ + + package com.google.android.vending.expansion.downloader.impl; + +-import com.android.vending.expansion.downloader.R; ++// -- GODOT start -- ++//import com.android.vending.expansion.downloader.R; ++import org.godotengine.godot.R; ++// -- GODOT end -- ++ + import com.google.android.vending.expansion.downloader.DownloadProgressInfo; + import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; + import com.google.android.vending.expansion.downloader.Helpers; +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 b2e0e7af0..c114b8a64 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 +@@ -146,8 +146,12 @@ public class DownloadThread { + + try { + PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); +- wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); +- wakeLock.acquire(); ++ // -- GODOT start -- ++ //wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); ++ //wakeLock.acquire(); ++ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "org.godot.game:wakelock"); ++ wakeLock.acquire(20 * 60 * 1000L /*20 minutes*/); ++ // -- GODOT end -- + + if (Constants.LOGV) { + Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName); +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 4babe476f..8d41a7690 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 +@@ -50,6 +50,10 @@ import android.provider.Settings.Secure; + import android.telephony.TelephonyManager; + import android.util.Log; + ++// -- GODOT start -- ++import android.annotation.SuppressLint; ++// -- GODOT end -- ++ + import java.io.File; + + /** +@@ -578,6 +582,7 @@ public abstract class DownloaderService extends CustomIntentService implements I + Log.w(Constants.TAG, + "couldn't get connectivity manager to poll network state"); + } else { ++ @SuppressLint("MissingPermission") + NetworkInfo activeInfo = mConnectivityManager + .getActiveNetworkInfo(); + updateNetworkState(activeInfo); diff --git a/platform/android/java/lib/patches/com.google.android.vending.licensing.patch b/platform/android/java/lib/patches/com.google.android.vending.licensing.patch new file mode 100644 index 0000000000..4adb81d951 --- /dev/null +++ b/platform/android/java/lib/patches/com.google.android.vending.licensing.patch @@ -0,0 +1,42 @@ +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 +index 7c42bfc28..feb579af0 100644 +--- a/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java ++++ b/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java +@@ -45,6 +45,9 @@ public class PreferenceObfuscator { + public void putString(String key, String value) { + if (mEditor == null) { + mEditor = mPreferences.edit(); ++ // -- GODOT start -- ++ mEditor.apply(); ++ // -- GODOT end -- + } + String obfuscatedValue = mObfuscator.obfuscate(value, key); + mEditor.putString(key, obfuscatedValue); +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 +index a0d2779af..a8bf65f9c 100644 +--- 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 +@@ -31,6 +31,10 @@ package com.google.android.vending.licensing.util; + * @version 1.3 + */ + ++// -- GODOT start -- +import org.godotengine.godot.BuildConfig; ++// -- GODOT end -- ++ + /** + * Base64 converter class. This code is not a full-blown MIME encoder; + * it simply converts binary data to base64 data and back. +@@ -341,7 +345,11 @@ public class Base64 { + e += 4; + } + +- assert (e == outBuff.length); ++ // -- GODOT start -- ++ //assert (e == outBuff.length); ++ if (BuildConfig.DEBUG && e != outBuff.length) ++ throw new RuntimeException(); ++ // -- GODOT end -- + return outBuff; + } + diff --git a/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png Binary files differnew file mode 100644 index 0000000000..2c246b04a4 --- /dev/null +++ b/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png Binary files differnew file mode 100644 index 0000000000..8bcd464bed --- /dev/null +++ b/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/res/drawable/icon.png b/platform/android/java/lib/res/drawable-nodpi/icon.png Binary files differindex 6ad9b43117..6ad9b43117 100644 --- a/platform/android/java/res/drawable/icon.png +++ b/platform/android/java/lib/res/drawable-nodpi/icon.png diff --git a/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-xhdpi/notify_panel_notification_icon_bg.png Binary files differindex 372b763ec5..372b763ec5 100644 --- a/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/lib/res/drawable-xhdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png Binary files differnew file mode 100644 index 0000000000..b458ff3057 --- /dev/null +++ b/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/res/layout/downloading_expansion.xml b/platform/android/java/lib/res/layout/downloading_expansion.xml index d678d94eac..4a9700965f 100644 --- a/platform/android/java/res/layout/downloading_expansion.xml +++ b/platform/android/java/lib/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/lib/res/layout/status_bar_ongoing_event_progress_bar.xml index 23bac02294..fae1faeb60 100644 --- a/platform/android/java/res/layout/status_bar_ongoing_event_progress_bar.xml +++ b/platform/android/java/lib/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"> @@ -32,17 +33,18 @@ android:id="@+id/appIcon" android:layout_width="fill_parent" android:layout_height="25dp" - android:scaleType="centerInside" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" - android:src="@android:drawable/stat_sys_download" /> + android:scaleType="centerInside" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + 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-ar/strings.xml b/platform/android/java/lib/res/values-ar/strings.xml index 9f3dc6d6ac..9f3dc6d6ac 100644 --- a/platform/android/java/res/values-ar/strings.xml +++ b/platform/android/java/lib/res/values-ar/strings.xml diff --git a/platform/android/java/res/values-bg/strings.xml b/platform/android/java/lib/res/values-bg/strings.xml index bd8109277e..bd8109277e 100644 --- a/platform/android/java/res/values-bg/strings.xml +++ b/platform/android/java/lib/res/values-bg/strings.xml diff --git a/platform/android/java/res/values-ca/strings.xml b/platform/android/java/lib/res/values-ca/strings.xml index 494cb88468..494cb88468 100644 --- a/platform/android/java/res/values-ca/strings.xml +++ b/platform/android/java/lib/res/values-ca/strings.xml diff --git a/platform/android/java/res/values-cs/strings.xml b/platform/android/java/lib/res/values-cs/strings.xml index 30ce00f895..30ce00f895 100644 --- a/platform/android/java/res/values-cs/strings.xml +++ b/platform/android/java/lib/res/values-cs/strings.xml diff --git a/platform/android/java/res/values-da/strings.xml b/platform/android/java/lib/res/values-da/strings.xml index 4c2a1cf0f4..4c2a1cf0f4 100644 --- a/platform/android/java/res/values-da/strings.xml +++ b/platform/android/java/lib/res/values-da/strings.xml diff --git a/platform/android/java/res/values-de/strings.xml b/platform/android/java/lib/res/values-de/strings.xml index 52946d4cce..52946d4cce 100644 --- a/platform/android/java/res/values-de/strings.xml +++ b/platform/android/java/lib/res/values-de/strings.xml diff --git a/platform/android/java/res/values-el/strings.xml b/platform/android/java/lib/res/values-el/strings.xml index 181dc51762..181dc51762 100644 --- a/platform/android/java/res/values-el/strings.xml +++ b/platform/android/java/lib/res/values-el/strings.xml diff --git a/platform/android/java/res/values-en/strings.xml b/platform/android/java/lib/res/values-en/strings.xml index 976a565013..976a565013 100644 --- a/platform/android/java/res/values-en/strings.xml +++ b/platform/android/java/lib/res/values-en/strings.xml diff --git a/platform/android/java/res/values-es-rES/strings.xml b/platform/android/java/lib/res/values-es-rES/strings.xml index 73f63a08f8..73f63a08f8 100644 --- a/platform/android/java/res/values-es-rES/strings.xml +++ b/platform/android/java/lib/res/values-es-rES/strings.xml diff --git a/platform/android/java/res/values-es/strings.xml b/platform/android/java/lib/res/values-es/strings.xml index 07b718a641..07b718a641 100644 --- a/platform/android/java/res/values-es/strings.xml +++ b/platform/android/java/lib/res/values-es/strings.xml diff --git a/platform/android/java/res/values-fa/strings.xml b/platform/android/java/lib/res/values-fa/strings.xml index f1e29013c4..f1e29013c4 100644 --- a/platform/android/java/res/values-fa/strings.xml +++ b/platform/android/java/lib/res/values-fa/strings.xml diff --git a/platform/android/java/res/values-fi/strings.xml b/platform/android/java/lib/res/values-fi/strings.xml index 323d82aff1..323d82aff1 100644 --- a/platform/android/java/res/values-fi/strings.xml +++ b/platform/android/java/lib/res/values-fi/strings.xml diff --git a/platform/android/java/res/values-fr/strings.xml b/platform/android/java/lib/res/values-fr/strings.xml index 32bead2661..32bead2661 100644 --- a/platform/android/java/res/values-fr/strings.xml +++ b/platform/android/java/lib/res/values-fr/strings.xml diff --git a/platform/android/java/res/values-hi/strings.xml b/platform/android/java/lib/res/values-hi/strings.xml index 8aab2a8c63..8aab2a8c63 100644 --- a/platform/android/java/res/values-hi/strings.xml +++ b/platform/android/java/lib/res/values-hi/strings.xml diff --git a/platform/android/java/res/values-hr/strings.xml b/platform/android/java/lib/res/values-hr/strings.xml index caf55e2241..caf55e2241 100644 --- a/platform/android/java/res/values-hr/strings.xml +++ b/platform/android/java/lib/res/values-hr/strings.xml diff --git a/platform/android/java/res/values-hu/strings.xml b/platform/android/java/lib/res/values-hu/strings.xml index e7f9e51226..e7f9e51226 100644 --- a/platform/android/java/res/values-hu/strings.xml +++ b/platform/android/java/lib/res/values-hu/strings.xml diff --git a/platform/android/java/res/values-in/strings.xml b/platform/android/java/lib/res/values-in/strings.xml index 9e9a8b0c03..9e9a8b0c03 100644 --- a/platform/android/java/res/values-in/strings.xml +++ b/platform/android/java/lib/res/values-in/strings.xml diff --git a/platform/android/java/res/values-it/strings.xml b/platform/android/java/lib/res/values-it/strings.xml index 1f5e5a049e..1f5e5a049e 100644 --- a/platform/android/java/res/values-it/strings.xml +++ b/platform/android/java/lib/res/values-it/strings.xml diff --git a/platform/android/java/res/values-iw/strings.xml b/platform/android/java/lib/res/values-iw/strings.xml index f52ede2085..f52ede2085 100644 --- a/platform/android/java/res/values-iw/strings.xml +++ b/platform/android/java/lib/res/values-iw/strings.xml diff --git a/platform/android/java/res/values-ja/strings.xml b/platform/android/java/lib/res/values-ja/strings.xml index 7f85f57df7..7f85f57df7 100644 --- a/platform/android/java/res/values-ja/strings.xml +++ b/platform/android/java/lib/res/values-ja/strings.xml diff --git a/platform/android/java/res/values-ko/strings.xml b/platform/android/java/lib/res/values-ko/strings.xml index b997b934b2..fab0bdd753 100644 --- a/platform/android/java/res/values-ko/strings.xml +++ b/platform/android/java/lib/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-lt/strings.xml b/platform/android/java/lib/res/values-lt/strings.xml index 6e3677fde7..6e3677fde7 100644 --- a/platform/android/java/res/values-lt/strings.xml +++ b/platform/android/java/lib/res/values-lt/strings.xml diff --git a/platform/android/java/res/values-lv/strings.xml b/platform/android/java/lib/res/values-lv/strings.xml index 701fc271ac..701fc271ac 100644 --- a/platform/android/java/res/values-lv/strings.xml +++ b/platform/android/java/lib/res/values-lv/strings.xml diff --git a/platform/android/java/res/values-nb/strings.xml b/platform/android/java/lib/res/values-nb/strings.xml index 73147ca1af..73147ca1af 100644 --- a/platform/android/java/res/values-nb/strings.xml +++ b/platform/android/java/lib/res/values-nb/strings.xml diff --git a/platform/android/java/res/values-nl/strings.xml b/platform/android/java/lib/res/values-nl/strings.xml index e501928a35..e501928a35 100644 --- a/platform/android/java/res/values-nl/strings.xml +++ b/platform/android/java/lib/res/values-nl/strings.xml diff --git a/platform/android/java/res/values-pl/strings.xml b/platform/android/java/lib/res/values-pl/strings.xml index ea5da73b6f..ea5da73b6f 100644 --- a/platform/android/java/res/values-pl/strings.xml +++ b/platform/android/java/lib/res/values-pl/strings.xml diff --git a/platform/android/java/res/values-pt/strings.xml b/platform/android/java/lib/res/values-pt/strings.xml index bdda7cd2c7..bdda7cd2c7 100644 --- a/platform/android/java/res/values-pt/strings.xml +++ b/platform/android/java/lib/res/values-pt/strings.xml diff --git a/platform/android/java/res/values-ro/strings.xml b/platform/android/java/lib/res/values-ro/strings.xml index 3686da4c19..3686da4c19 100644 --- a/platform/android/java/res/values-ro/strings.xml +++ b/platform/android/java/lib/res/values-ro/strings.xml diff --git a/platform/android/java/res/values-ru/strings.xml b/platform/android/java/lib/res/values-ru/strings.xml index 954067658b..954067658b 100644 --- a/platform/android/java/res/values-ru/strings.xml +++ b/platform/android/java/lib/res/values-ru/strings.xml diff --git a/platform/android/java/res/values-sk/strings.xml b/platform/android/java/lib/res/values-sk/strings.xml index 37d1283124..37d1283124 100644 --- a/platform/android/java/res/values-sk/strings.xml +++ b/platform/android/java/lib/res/values-sk/strings.xml diff --git a/platform/android/java/res/values-sl/strings.xml b/platform/android/java/lib/res/values-sl/strings.xml index 0bb249c375..0bb249c375 100644 --- a/platform/android/java/res/values-sl/strings.xml +++ b/platform/android/java/lib/res/values-sl/strings.xml diff --git a/platform/android/java/res/values-sr/strings.xml b/platform/android/java/lib/res/values-sr/strings.xml index 0e83cab1a1..0e83cab1a1 100644 --- a/platform/android/java/res/values-sr/strings.xml +++ b/platform/android/java/lib/res/values-sr/strings.xml diff --git a/platform/android/java/res/values-sv/strings.xml b/platform/android/java/lib/res/values-sv/strings.xml index e3a04ac2ec..e3a04ac2ec 100644 --- a/platform/android/java/res/values-sv/strings.xml +++ b/platform/android/java/lib/res/values-sv/strings.xml diff --git a/platform/android/java/res/values-th/strings.xml b/platform/android/java/lib/res/values-th/strings.xml index 0aa893b8bf..0aa893b8bf 100644 --- a/platform/android/java/res/values-th/strings.xml +++ b/platform/android/java/lib/res/values-th/strings.xml diff --git a/platform/android/java/res/values-tl/strings.xml b/platform/android/java/lib/res/values-tl/strings.xml index e7e2af4909..e7e2af4909 100644 --- a/platform/android/java/res/values-tl/strings.xml +++ b/platform/android/java/lib/res/values-tl/strings.xml diff --git a/platform/android/java/res/values-tr/strings.xml b/platform/android/java/lib/res/values-tr/strings.xml index 97af1243a6..97af1243a6 100644 --- a/platform/android/java/res/values-tr/strings.xml +++ b/platform/android/java/lib/res/values-tr/strings.xml diff --git a/platform/android/java/res/values-uk/strings.xml b/platform/android/java/lib/res/values-uk/strings.xml index 3dea6908a9..3dea6908a9 100644 --- a/platform/android/java/res/values-uk/strings.xml +++ b/platform/android/java/lib/res/values-uk/strings.xml diff --git a/platform/android/java/res/values-vi/strings.xml b/platform/android/java/lib/res/values-vi/strings.xml index a6552130b0..a6552130b0 100644 --- a/platform/android/java/res/values-vi/strings.xml +++ b/platform/android/java/lib/res/values-vi/strings.xml diff --git a/platform/android/java/res/values-zh-rCN/strings.xml b/platform/android/java/lib/res/values-zh-rCN/strings.xml index 6668c56bd9..6668c56bd9 100644 --- a/platform/android/java/res/values-zh-rCN/strings.xml +++ b/platform/android/java/lib/res/values-zh-rCN/strings.xml diff --git a/platform/android/java/res/values-zh-rHK/strings.xml b/platform/android/java/lib/res/values-zh-rHK/strings.xml index 8a6269da0f..8a6269da0f 100644 --- a/platform/android/java/res/values-zh-rHK/strings.xml +++ b/platform/android/java/lib/res/values-zh-rHK/strings.xml diff --git a/platform/android/java/res/values-zh-rTW/strings.xml b/platform/android/java/lib/res/values-zh-rTW/strings.xml index b1bb39d5d6..b1bb39d5d6 100644 --- a/platform/android/java/res/values-zh-rTW/strings.xml +++ b/platform/android/java/lib/res/values-zh-rTW/strings.xml diff --git a/platform/android/java/res/values/strings.xml b/platform/android/java/lib/res/values/strings.xml index f0ea56148f..a1b81a6186 100644 --- a/platform/android/java/res/values/strings.xml +++ b/platform/android/java/lib/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/res/values/styles.xml b/platform/android/java/lib/res/values/styles.xml index a442f61e7e..a442f61e7e 100644 --- a/platform/android/java/res/values/styles.xml +++ b/platform/android/java/lib/res/values/styles.xml diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java index 2af33b96b9..1dcc370d83 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java @@ -23,7 +23,7 @@ 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 { +public class Constants { /** Tag used for debugging/logging */ public static final String TAG = "LVLDL"; @@ -32,11 +32,7 @@ public class Constants { */ 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"; @@ -74,7 +70,7 @@ public class Constants { * 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 @@ -105,11 +101,11 @@ public class Constants { /** Enable verbose logging */ public static final boolean LOGV = false; - + /** Enable super-verbose logging */ private static final boolean LOCAL_LOGVV = false; public static final boolean LOGVV = LOCAL_LOGVV && LOGV; - + /** * This download has successfully completed. * Warning: there might be other status values that indicate success @@ -230,11 +226,11 @@ public class Constants { /** * 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/lib/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java index 9cb294d721..9cb294d721 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java index 2201751254..452c7d1483 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java @@ -32,13 +32,16 @@ import android.os.Messenger; import android.os.RemoteException; import android.util.Log; +// -- GODOT start -- +import java.lang.ref.WeakReference; +// -- GODOT end -- /** * 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 @@ -96,7 +99,7 @@ public class DownloaderClientMarshaller { e.printStackTrace(); } } - + public Proxy(Messenger msg) { mServiceMessenger = msg; } @@ -118,29 +121,46 @@ public class DownloaderClientMarshaller { /** * Target we publish for clients to send messages to IncomingHandler. */ - final Messenger mMessenger = new Messenger(new Handler() { + // -- GODOT start -- + 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) { - 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; + Stub downloader = mDownloader.get(); + if (downloader != null) { + downloader.handleMessage(msg); } } - }); + } + + 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; + } + } + // -- GODOT end -- public Stub(IDownloaderClient itf, Class<?> downloaderService) { mItf = itf; @@ -181,7 +201,7 @@ public class DownloaderClientMarshaller { } else { mBound = true; } - + } @Override @@ -201,7 +221,7 @@ public class DownloaderClientMarshaller { /** * Returns a proxy that will marshal calls to IDownloaderClient methods - * + * * @param msg * @return */ @@ -213,7 +233,7 @@ public class DownloaderClientMarshaller { * 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 @@ -224,7 +244,7 @@ public class DownloaderClientMarshaller { 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 @@ -237,7 +257,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 +268,30 @@ public class DownloaderClientMarshaller { * #DOWNLOAD_REQUIRED}. * @throws NameNotFoundException */ - public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, + 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, + 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/lib/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java index 054eaa9895..3771d19c9b 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java @@ -25,6 +25,9 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +// -- GODOT start -- +import java.lang.ref.WeakReference; +// -- GODOT end -- /** @@ -108,32 +111,49 @@ public class DownloaderServiceMarshaller { private static class Stub implements IStub { private IDownloaderService mItf = null; - final Messenger mMessenger = new Messenger(new Handler() { + // -- GODOT start -- + 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) { - 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; + 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; + } + } + // -- GODOT end -- public Stub(IDownloaderService itf) { mItf = itf; @@ -157,7 +177,7 @@ public class DownloaderServiceMarshaller { /** * Returns a proxy that will marshall calls to IDownloaderService methods - * + * * @param ctx * @return */ @@ -169,7 +189,7 @@ public class DownloaderServiceMarshaller { * 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 diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java index fb56f917be..2a72c9818d 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java +++ b/platform/android/java/lib/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,11 @@ import android.os.StatFs; import android.os.SystemClock; import android.util.Log; +// -- GODOT start -- +//import com.android.vending.expansion.downloader.R; +import org.godotengine.godot.R; +// -- GODOT end -- + import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; @@ -49,10 +53,10 @@ public class 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 { @@ -87,7 +91,7 @@ public class Helpers { if (!Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { // No SD card found. - if ( Constants.LOGVV ) { + if (Constants.LOGVV) { Log.d(Constants.TAG, "no external storage"); } return false; @@ -96,8 +100,7 @@ public class Helpers { } /** - * @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()); @@ -130,10 +133,10 @@ public class Helpers { } /** - * 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 @@ -141,21 +144,24 @@ public class Helpers { static public String getDownloadProgressString(long overallProgress, long overallTotal) { if (overallTotal == 0) { - if ( Constants.LOGVV ) { + if (Constants.LOGVV) { Log.e(Constants.TAG, "Notification called when total is zero"); } return ""; } - return String.format("%.2f", + // -- GODOT start -- + return String.format(Locale.ENGLISH, "%.2f", (float) overallProgress / (1024.0f * 1024.0f)) + "MB /" + - String.format("%.2f", (float) overallTotal / - (1024.0f * 1024.0f)) + "MB"; + String.format(Locale.ENGLISH, "%.2f", (float) overallTotal / + (1024.0f * 1024.0f)) + + "MB"; + // -- GODOT end -- } /** * Adds a percentile to getDownloadProgressString. - * + * * @param overallProgress * @param overallTotal * @return @@ -163,7 +169,7 @@ public class Helpers { static public String getDownloadProgressStringNotification(long overallProgress, long overallTotal) { if (overallTotal == 0) { - if ( Constants.LOGVV ) { + if (Constants.LOGVV) { Log.e(Constants.TAG, "Notification called when total is zero"); } return ""; @@ -174,7 +180,7 @@ public class Helpers { public static String getDownloadProgressPercent(long overallProgress, long overallTotal) { if (overallTotal == 0) { - if ( Constants.LOGVV ) { + if (Constants.LOGVV) { Log.e(Constants.TAG, "Notification called when total is zero"); } return ""; @@ -183,7 +189,9 @@ public class Helpers { } public static String getSpeedString(float bytesPerMillisecond) { - return String.format("%.2f", bytesPerMillisecond * 1000 / 1024); + // -- GODOT start -- + return String.format(Locale.ENGLISH, "%.2f", bytesPerMillisecond * 1000 / 1024); + // -- GODOT end -- } public static String getTimeRemaining(long durationInMilliseconds) { @@ -197,9 +205,8 @@ public class Helpers { } /** - * 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 @@ -210,8 +217,7 @@ public class Helpers { } /** - * 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) @@ -219,30 +225,32 @@ public class Helpers { return path; } + @TargetApi(Build.VERSION_CODES.HONEYCOMB) 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(); + // 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 Market --- let's make sure + // 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()) { @@ -258,10 +266,58 @@ public class Helpers { 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 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. */ diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java index b8511a62a0..cef3794701 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java @@ -86,7 +86,7 @@ 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. */ @@ -109,7 +109,7 @@ 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); @@ -118,7 +118,7 @@ public interface IDownloaderClient { * 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. */ diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/IDownloaderService.java index 4789afe19c..4de9de0c62 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/IDownloaderService.java @@ -61,7 +61,7 @@ public interface IDownloaderService { /** * Set the flags for this download (e.g. * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}). - * + * * @param flags */ void setDownloadFlags(int flags); @@ -76,7 +76,7 @@ public interface IDownloaderService { * IDownloaderClient.onServiceConnected(Messenger m)} from the * DownloaderClient to register the client with the service. It will * automatically send the current status to the client. - * + * * @param clientMessenger */ void onClientUpdated(Messenger clientMessenger); diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/IStub.java index d5bc3a843e..d5bc3a843e 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/IStub.java diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/SystemFacade.java index 12edd97ab2..a0e1165cc4 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/SystemFacade.java @@ -26,6 +26,10 @@ import android.net.NetworkInfo; import android.telephony.TelephonyManager; import android.util.Log; +// -- GODOT start -- +import android.annotation.SuppressLint; +// -- GODOT end -- + /** * Contains useful helper functions, typically tied to the application context. */ @@ -51,6 +55,7 @@ class SystemFacade { return null; } + @SuppressLint("MissingPermission") NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); if (activeInfo == null) { if (Constants.LOGVV) { @@ -69,6 +74,7 @@ class SystemFacade { return false; } + @SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo(); boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE); TelephonyManager tm = (TelephonyManager) mContext diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java index b77af7e085..3ccc191c60 100755..100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java @@ -36,7 +36,7 @@ public abstract class CustomIntentService extends Service { private boolean mRedelivery; private volatile ServiceHandler mServiceHandler; private volatile Looper mServiceLooper; - private static final String LOG_TAG = "CancellableIntentService"; + private static final String LOG_TAG = "CustomIntentService"; private static final int WHAT_MESSAGE = -10; public CustomIntentService(String paramString) { diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java index 45111b16a3..45111b16a3 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java index a9f674803c..0abaf2e052 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java @@ -16,17 +16,22 @@ package com.google.android.vending.expansion.downloader.impl; -import com.godot.game.R; +// -- GODOT start -- +//import com.android.vending.expansion.downloader.R; +import org.godotengine.godot.R; +// -- GODOT end -- + import com.google.android.vending.expansion.downloader.DownloadProgressInfo; import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; import com.google.android.vending.expansion.downloader.Helpers; import com.google.android.vending.expansion.downloader.IDownloaderClient; -import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; +import android.os.Build; import android.os.Messenger; +import android.support.v4.app.NotificationCompat; /** * This class handles displaying the notification associated with the download @@ -44,16 +49,16 @@ public class DownloadNotification implements IDownloaderClient { private int mState; private final Context mContext; private final NotificationManager mNotificationManager; - private String mCurrentTitle; + private CharSequence mCurrentTitle; private IDownloaderClient mClientProxy; - final ICustomNotification mCustomNotification; - private Notification.Builder mNotificationBuilder; - private Notification.Builder mCurrentNotificationBuilder; + private NotificationCompat.Builder mActiveDownloadBuilder; + private NotificationCompat.Builder mBuilder; + private NotificationCompat.Builder mCurrentBuilder; private CharSequence mLabel; private String mCurrentText; - private PendingIntent mContentIntent; private DownloadProgressInfo mProgressInfo; + private PendingIntent mContentIntent; static final String LOGTAG = "DownloadNotification"; static final int NOTIFICATION_ID = LOGTAG.hashCode(); @@ -62,8 +67,10 @@ public class DownloadNotification implements IDownloaderClient { return mContentIntent; } - public void setClientIntent(PendingIntent mClientIntent) { - this.mContentIntent = mClientIntent; + public void setClientIntent(PendingIntent clientIntent) { + this.mBuilder.setContentIntent(clientIntent); + this.mActiveDownloadBuilder.setContentIntent(clientIntent); + this.mContentIntent = clientIntent; } public void resendState() { @@ -130,16 +137,20 @@ public class DownloadNotification implements IDownloaderClient { 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()); + 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()); } } @@ -151,47 +162,28 @@ public class DownloadNotification implements IDownloaderClient { } 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; + mBuilder.setTicker(mCurrentTitle); + mBuilder.setSmallIcon(android.R.drawable.stat_sys_download); + mBuilder.setContentTitle(mCurrentTitle); + mBuilder.setContentText(mCurrentText); + mCurrentBuilder = mBuilder; } 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); + 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, 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); + 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) { @@ -206,7 +198,7 @@ public class DownloadNotification implements IDownloaderClient { /** * Constructor - * + * * @param ctx The context to use to obtain access to the Notification * Service */ @@ -216,11 +208,18 @@ public class DownloadNotification implements IDownloaderClient { mLabel = applicationLabel; mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - mCustomNotification = CustomNotificationFactory - .createCustomNotification(); - mNotificationBuilder = new Notification.Builder(ctx); - mCurrentNotificationBuilder = mNotificationBuilder; + 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 diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java index 056d1eca0b..c114b8a64a 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java +++ b/platform/android/java/lib/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; /** @@ -117,9 +110,7 @@ public class DownloadThread { * headers, or destination filename. */ private class StopRequest extends Throwable { - /** - * - */ + private static final long serialVersionUID = 6338592678988347973L; public int mFinalStatus; @@ -140,86 +131,33 @@ public class DownloadThread { */ private class RetryDownload extends Throwable { - /** - * - */ private static final long serialVersionUID = 6196036036517540229L; } /** - * Returns the preferred proxy to be used by clients. This is a wrapper - * around {@link android.net.Proxy#getHost()}. Currently no proxy will be - * returned for localhost or if the active network is Wi-Fi. - * - * @param context the context which will be passed to - * {@link android.net.Proxy#getHost()} - * @param url the target URL for the request - * @note Calling this method requires permission - * android.permission.ACCESS_NETWORK_STATE - * @return The preferred proxy to be used by clients, or null if there is no - * proxy. - */ - public HttpHost getPreferredHttpHost(Context context, - String url) { - if (!isLocalHost(url) && !mService.isWiFi()) { - final String proxyHost = Proxy.getHost(context); - if (proxyHost != null) { - return new HttpHost(proxyHost, Proxy.getPort(context), "http"); - } - } - - return null; - } - - static final private boolean isLocalHost(String url) { - if (url == null) { - return false; - } - - try { - final URI uri = URI.create(url); - final String host = uri.getHost(); - if (host != null) { - // TODO: InetAddress.isLoopbackAddress should be used to check - // for localhost. However no public factory methods exist which - // can be used without triggering DNS lookup if host is not - // localhost. - if (host.equalsIgnoreCase("localhost") || - host.equals("127.0.0.1") || - host.equals("[::1]")) { - return true; - } - } - } catch (IllegalArgumentException iex) { - // Ignore (URI.create) - } - - return false; - } - - /** * Executes the download in a separate thread */ public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); State state = new State(mInfo, mService); - AndroidHttpClient client = null; PowerManager.WakeLock wakeLock = null; int finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR; try { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); - wakeLock.acquire(); + // -- GODOT start -- + //wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); + //wakeLock.acquire(); + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "org.godot.game:wakelock"); + wakeLock.acquire(20 * 60 * 1000L /*20 minutes*/); + // -- GODOT end -- 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) { @@ -229,16 +167,16 @@ public class DownloadThread { // 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); + URL url = new URL(state.mRequestUri); + HttpURLConnection request = (HttpURLConnection)url.openConnection(); + request.setRequestProperty("User-Agent", userAgent()); try { - executeDownload(state, client, request); + executeDownload(state, request); finished = true; } catch (RetryDownload exc) { // fall through } finally { - request.abort(); + request.disconnect(); request = null; } } @@ -266,10 +204,6 @@ public class DownloadThread { 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); @@ -280,7 +214,7 @@ public class DownloadThread { * 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) + private void executeDownload(State state, HttpURLConnection request) throws StopRequest, RetryDownload { InnerState innerState = new InnerState(); byte data[] = new byte[Constants.BUFFER_SIZE]; @@ -295,15 +229,15 @@ public class DownloadThread { checkConnectivity(state); mNotification.onDownloadStateChanged(IDownloaderClient.STATE_CONNECTING); - HttpResponse response = sendRequest(state, client, request); - handleExceptionalStatus(state, innerState, response); + int responseCode = sendRequest(state, request); + handleExceptionalStatus(state, innerState, request, responseCode); if (Constants.LOGV) { Log.v(Constants.TAG, "received response for " + mInfo.mUri); } - processResponseHeaders(state, innerState, response); - InputStream entityStream = openResponseEntity(state, response); + processResponseHeaders(state, innerState, request); + InputStream entityStream = openResponseEntity(state, request); mNotification.onDownloadStateChanged(IDownloaderClient.STATE_DOWNLOADING); transferData(state, innerState, data, entityStream); } @@ -333,7 +267,7 @@ public class DownloadThread { /** * 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 */ @@ -484,7 +418,7 @@ public class DownloadThread { /** * 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 */ @@ -548,7 +482,7 @@ public class DownloadThread { /** * 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 @@ -576,13 +510,13 @@ public class DownloadThread { /** * 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) + private InputStream openResponseEntity(State state, HttpURLConnection response) throws StopRequest { try { - return response.getEntity().getContent(); + return response.getInputStream(); } catch (IOException ex) { logNetworkState(); throw new StopRequest(getFinalStatusForHttpError(state), @@ -603,7 +537,7 @@ public class DownloadThread { * 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) + private void processResponseHeaders(State state, InnerState innerState, HttpURLConnection response) throws StopRequest { if (innerState.mContinuingDownload) { // ignore response headers on resume requests @@ -652,29 +586,29 @@ public class DownloadThread { /** * Read headers from the HTTP response and store them into local state. */ - private void readResponseHeaders(State state, InnerState innerState, HttpResponse response) + private void readResponseHeaders(State state, InnerState innerState, HttpURLConnection response) throws StopRequest { - Header header = response.getFirstHeader("Content-Disposition"); - if (header != null) { - innerState.mHeaderContentDisposition = header.getValue(); + String value = response.getHeaderField("Content-Disposition"); + if (value != null) { + innerState.mHeaderContentDisposition = value; } - header = response.getFirstHeader("Content-Location"); - if (header != null) { - innerState.mHeaderContentLocation = header.getValue(); + value = response.getHeaderField("Content-Location"); + if (value != null) { + innerState.mHeaderContentLocation = value; } - header = response.getFirstHeader("ETag"); - if (header != null) { - innerState.mHeaderETag = header.getValue(); + value = response.getHeaderField("ETag"); + if (value != null) { + innerState.mHeaderETag = value; } String headerTransferEncoding = null; - header = response.getFirstHeader("Transfer-Encoding"); - if (header != null) { - headerTransferEncoding = header.getValue(); + value = response.getHeaderField("Transfer-Encoding"); + if (value != null) { + headerTransferEncoding = value; } String headerContentType = null; - header = response.getFirstHeader("Content-Type"); - if (header != null) { - headerContentType = header.getValue(); + 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"); @@ -682,11 +616,9 @@ public class DownloadThread { } if (headerTransferEncoding == null) { - header = response.getFirstHeader("Content-Length"); - if (header != null) { - innerState.mHeaderContentLength = header.getValue(); + long contentLength = response.getContentLength(); + if (value != null) { // 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 @@ -694,6 +626,8 @@ public class DownloadThread { // enough // to tell us that something is wrong here Log.e(Constants.TAG, "Incorrect file size delivered."); + } else { + innerState.mHeaderContentLength = Long.toString(contentLength); } } } else { @@ -725,20 +659,15 @@ public class DownloadThread { * Check the HTTP response status and handle anything unusual (e.g. not * 200/206). */ - private void handleExceptionalStatus(State state, InnerState innerState, HttpResponse response) + private void handleExceptionalStatus(State state, InnerState innerState, HttpURLConnection connection, int responseCode) 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); + if (responseCode == 503 && mInfo.mNumFailed < Constants.MAX_RETRIES) { + handleServiceUnavailable(state, connection); } - int expectedStatus = innerState.mContinuingDownload ? 206 : DownloaderService.STATUS_SUCCESS; - if (statusCode != expectedStatus) { - handleOtherStatus(state, innerState, statusCode); + if (responseCode != expectedStatus) { + handleOtherStatus(state, innerState, responseCode); } else { // no longer redirected state.mRedirectCount = 0; @@ -764,54 +693,14 @@ public class DownloadThread { } /** - * Handle a 3xx redirect status. - */ - private void handleRedirect(State state, HttpResponse response, int statusCode) - throws StopRequest, RetryDownload { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "got HTTP redirect " + statusCode); - } - if (state.mRedirectCount >= Constants.MAX_REDIRECTS) { - throw new StopRequest(DownloaderService.STATUS_TOO_MANY_REDIRECTS, "too many redirects"); - } - Header header = response.getFirstHeader("Location"); - if (header == null) { - return; - } - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Location :" + header.getValue()); - } - - String newUri; - try { - newUri = new URI(mInfo.mUri).resolve(new URI(header.getValue())).toString(); - } catch (URISyntaxException ex) { - if (Constants.LOGV) { - Log.d(Constants.TAG, "Couldn't resolve redirect URI " + header.getValue() - + " for " + mInfo.mUri); - } - throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, - "Couldn't resolve redirect URI"); - } - ++state.mRedirectCount; - state.mRequestUri = newUri; - if (statusCode == 301 || statusCode == 303) { - // use the new URI for all future requests (should a retry/resume be - // necessary) - state.mNewUri = newUri; - } - throw new RetryDownload(); - } - - /** * Add headers for this download to the HTTP request to allow for resume. */ - private void addRequestHeaders(InnerState innerState, HttpGet request) { + private void addRequestHeaders(InnerState innerState, HttpURLConnection request) { if (innerState.mContinuingDownload) { if (innerState.mHeaderETag != null) { - request.addHeader("If-Match", innerState.mHeaderETag); + request.setRequestProperty("If-Match", innerState.mHeaderETag); } - request.addHeader("Range", "bytes=" + innerState.mBytesSoFar + "-"); + request.setRequestProperty("Range", "bytes=" + innerState.mBytesSoFar + "-"); } } @@ -819,18 +708,18 @@ public class DownloadThread { * Handle a 503 Service Unavailable status by processing the Retry-After * header. */ - private void handleServiceUnavailable(State state, HttpResponse response) throws StopRequest { + private void handleServiceUnavailable(State state, HttpURLConnection connection) 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) { + String retryAfterValue = connection.getHeaderField("Retry-After"); + if (retryAfterValue != null) { try { if (Constants.LOGVV) { - Log.v(Constants.TAG, "Retry-After :" + header.getValue()); + Log.v(Constants.TAG, "Retry-After :" + retryAfterValue); } - state.mRetryAfter = Integer.parseInt(header.getValue()); + state.mRetryAfter = Integer.parseInt(retryAfterValue); if (state.mRetryAfter < 0) { state.mRetryAfter = 0; } else { @@ -853,10 +742,10 @@ public class DownloadThread { /** * Send the request to the server, handling any I/O exceptions. */ - private HttpResponse sendRequest(State state, AndroidHttpClient client, HttpGet request) + private int sendRequest(State state, HttpURLConnection request) throws StopRequest { try { - return client.execute(request); + return request.getResponseCode(); } catch (IllegalArgumentException ex) { throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, "while trying to execute request: " + ex.toString(), ex); diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java index e83faa2756..8d41a76900 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java @@ -50,6 +50,10 @@ import android.provider.Settings.Secure; import android.telephony.TelephonyManager; import android.util.Log; +// -- GODOT start -- +import android.annotation.SuppressLint; +// -- GODOT end -- + import java.io.File; /** @@ -229,7 +233,7 @@ public abstract class DownloaderService extends CustomIntentService implements I * 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; @@ -256,7 +260,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * This download was canceled - * + * * @hide */ public static final int STATUS_CANCELED = 490; @@ -273,7 +277,7 @@ public abstract class DownloaderService extends CustomIntentService implements I * 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; @@ -281,7 +285,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * 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; @@ -289,7 +293,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * This download couldn't be completed because of an unspecified unhandled * HTTP code. - * + * * @hide */ public static final int STATUS_UNHANDLED_HTTP_CODE = 494; @@ -297,7 +301,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * 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; @@ -305,7 +309,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * This download couldn't be completed because of an HttpException while * setting up the request. - * + * * @hide */ public static final int STATUS_HTTP_EXCEPTION = 496; @@ -313,7 +317,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * This download couldn't be completed because there were too many * redirects. - * + * * @hide */ public static final int STATUS_TOO_MANY_REDIRECTS = 497; @@ -321,7 +325,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * 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; @@ -329,21 +333,21 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * This download couldn't be completed because no external storage device * was found. Typically, this is because the SD card is not mounted. - * + * * @hide */ public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; /** * This download is allowed to run. - * + * * @hide */ public static final int CONTROL_RUN = 0; /** * This download must pause at the first opportunity. - * + * * @hide */ public static final int CONTROL_PAUSED = 1; @@ -351,7 +355,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * This download is visible but only shows in the notifications while it's * in progress. - * + * * @hide */ public static final int VISIBILITY_VISIBLE = 0; @@ -359,26 +363,26 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * This download is visible and shows in the notifications while in progress * and after completion. - * + * * @hide */ public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; /** * This download doesn't show in the UI or in the notifications. - * + * * @hide */ public static final int VISIBILITY_HIDDEN = 2; /** - * Bit flag for {@link #setAllowedNetworkTypes} corresponding to + * Bit flag for setAllowedNetworkTypes corresponding to * {@link ConnectivityManager#TYPE_MOBILE}. */ 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; @@ -456,7 +460,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * 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 */ @@ -569,7 +573,7 @@ public abstract class DownloaderService extends CustomIntentService implements I */ void pollNetworkState() { if (null == mConnectivityManager) { - mConnectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); + mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); } if (null == mWifiManager) { mWifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); @@ -578,6 +582,7 @@ public abstract class DownloaderService extends CustomIntentService implements I Log.w(Constants.TAG, "couldn't get connectivity manager to poll network state"); } else { + @SuppressLint("MissingPermission") NetworkInfo activeInfo = mConnectivityManager .getActiveNetworkInfo(); updateNetworkState(activeInfo); @@ -594,7 +599,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * 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 @@ -610,7 +615,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * Careful! Only use this internally. - * + * * @return whether we think the service is running */ private static synchronized boolean isServiceRunning() { @@ -652,9 +657,9 @@ 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 @@ -897,7 +902,7 @@ public abstract class DownloaderService extends CustomIntentService implements I /** * Updates the LVL information from the server. - * + * * @param context */ public void updateLVL(final Context context) { @@ -912,7 +917,7 @@ public abstract class DownloaderService extends CustomIntentService implements I * 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 diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java index 250299c400..c658b4cc43 100755..100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java @@ -319,7 +319,7 @@ public class DownloadsDB { /** * 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 */ diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java index 3f440e9893..3f440e9893 100644 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java diff --git a/platform/android/java/src/com/android/vending/licensing/AESObfuscator.java b/platform/android/java/lib/src/com/google/android/vending/licensing/AESObfuscator.java index ee12c68deb..d6ccb0c5e4 100644 --- a/platform/android/java/src/com/android/vending/licensing/AESObfuscator.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/AESObfuscator.java @@ -41,7 +41,7 @@ public class AESObfuscator implements Obfuscator { 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 static final String header = "com.google.android.vending.licensing.AESObfuscator-1|"; private Cipher mEncryptor; private Cipher mDecryptor; @@ -55,7 +55,7 @@ public class AESObfuscator implements Obfuscator { public AESObfuscator(byte[] salt, String applicationId, String deviceId) { try { SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM); - KeySpec keySpec = + KeySpec keySpec = new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256); SecretKey tmp = factory.generateSecret(keySpec); SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); diff --git a/platform/android/java/src/com/android/vending/licensing/APKExpansionPolicy.java b/platform/android/java/lib/src/com/google/android/vending/licensing/APKExpansionPolicy.java index 17cc7a7cfd..37fad8926a 100644 --- a/platform/android/java/src/com/android/vending/licensing/APKExpansionPolicy.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/APKExpansionPolicy.java @@ -17,17 +17,15 @@ package com.google.android.vending.licensing; * 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 com.google.android.vending.licensing.util.URIQueryDecoder; + 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; @@ -35,11 +33,11 @@ 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. + * 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 Android Market publishing console, such as whether the application is + * 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> @@ -49,12 +47,13 @@ import java.util.Vector; 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 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"; @@ -68,6 +67,7 @@ public class APKExpansionPolicy implements Policy { 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>(); @@ -96,6 +96,7 @@ public class APKExpansionPolicy implements Policy { 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); } /** @@ -121,8 +122,10 @@ public class APKExpansionPolicy implements Policy { * 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 */ @@ -136,10 +139,12 @@ public class APKExpansionPolicy implements Policy { setRetryCount(mRetryCount + 1); } + // Update server policy data + Map<String, String> extras = decodeExtras(rawData); if (response == Policy.LICENSED) { - // Update server policy data - Map<String, String> extras = decodeExtras(rawData.extra); 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) { @@ -161,10 +166,12 @@ public class APKExpansionPolicy implements Policy { } } } else if (response == Policy.NOT_LICENSED) { - // Clear out stale policy data + // 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); @@ -175,7 +182,7 @@ public class APKExpansionPolicy implements Policy { * 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) { @@ -187,7 +194,7 @@ public class APKExpansionPolicy implements Policy { /** * 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) { @@ -203,7 +210,7 @@ public class APKExpansionPolicy implements Policy { * 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) { @@ -229,7 +236,7 @@ public class APKExpansionPolicy implements Policy { * 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) { @@ -255,7 +262,7 @@ public class APKExpansionPolicy implements Policy { * 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) { @@ -278,10 +285,24 @@ public class APKExpansionPolicy implements Policy { } /** + * 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() { @@ -292,10 +313,9 @@ public class APKExpansionPolicy implements Policy { * 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()) { @@ -308,7 +328,7 @@ public class APKExpansionPolicy implements Policy { * 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 @@ -375,19 +395,16 @@ public class APKExpansionPolicy implements Policy { return false; } - private Map<String, String> decodeExtras(String extras) { + 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("?" + 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()); - } + 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."); } diff --git a/platform/android/java/src/com/android/vending/licensing/DeviceLimiter.java b/platform/android/java/lib/src/com/google/android/vending/licensing/DeviceLimiter.java index e5c5e2d7ca..e5c5e2d7ca 100644 --- a/platform/android/java/src/com/android/vending/licensing/DeviceLimiter.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/DeviceLimiter.java diff --git a/platform/android/java/src/com/android/vending/licensing/LicenseChecker.java b/platform/android/java/lib/src/com/google/android/vending/licensing/LicenseChecker.java index 531cb22f8c..15017b3425 100644 --- a/platform/android/java/src/com/android/vending/licensing/LicenseChecker.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/LicenseChecker.java @@ -16,14 +16,12 @@ 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.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -31,6 +29,11 @@ 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.util.Base64; +import com.google.android.vending.licensing.util.Base64DecoderException; + import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; @@ -44,15 +47,15 @@ import java.util.Queue; import java.util.Set; /** - * Client library for Android Market license verifications. + * 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. + * 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. + * 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"; @@ -71,8 +74,8 @@ public class LicenseChecker implements ServiceConnection { 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. + * 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; @@ -98,9 +101,8 @@ public class LicenseChecker implements ServiceConnection { } /** - * Generates a PublicKey instance from a string containing the - * Base64-encoded public key. - * + * 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 */ @@ -123,14 +125,15 @@ public class LicenseChecker implements ServiceConnection { } /** - * Checks if the user should have access to the app. Binds the service if necessary. + * 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. + * 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) { @@ -146,14 +149,35 @@ public class LicenseChecker implements ServiceConnection { 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, + 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 { @@ -172,6 +196,20 @@ public class LicenseChecker implements ServiceConnection { } } + /** + * 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) { @@ -287,8 +325,8 @@ public class LicenseChecker implements ServiceConnection { } /** - * Generates policy response for service connection errors, as a result of - * disconnections or timeouts. + * 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); @@ -315,12 +353,12 @@ public class LicenseChecker implements ServiceConnection { } /** - * Inform the library that the context is about to be destroyed, so that any - * open connections can be cleaned up. + * 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. + * 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(); @@ -334,15 +372,15 @@ public class LicenseChecker implements ServiceConnection { /** * 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); + 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/lib/src/com/google/android/vending/licensing/LicenseCheckerCallback.java index b250a7147b..8b869ddaaf 100644 --- a/platform/android/java/src/com/android/vending/licensing/LicenseCheckerCallback.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/LicenseCheckerCallback.java @@ -36,7 +36,7 @@ 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) */ @@ -44,7 +44,7 @@ public interface LicenseCheckerCallback { /** * 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) diff --git a/platform/android/java/src/com/android/vending/licensing/LicenseValidator.java b/platform/android/java/lib/src/com/google/android/vending/licensing/LicenseValidator.java index 61d3c7e79e..11a00786d0 100644 --- a/platform/android/java/src/com/android/vending/licensing/LicenseValidator.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/LicenseValidator.java @@ -94,6 +94,13 @@ class LicenseValidator { 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()); diff --git a/platform/android/java/src/com/android/vending/licensing/NullDeviceLimiter.java b/platform/android/java/lib/src/com/google/android/vending/licensing/NullDeviceLimiter.java index d87af3153f..d87af3153f 100644 --- a/platform/android/java/src/com/android/vending/licensing/NullDeviceLimiter.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/NullDeviceLimiter.java diff --git a/platform/android/java/src/com/android/vending/licensing/Obfuscator.java b/platform/android/java/lib/src/com/google/android/vending/licensing/Obfuscator.java index 88891728e6..008c150a8e 100644 --- a/platform/android/java/src/com/android/vending/licensing/Obfuscator.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/Obfuscator.java @@ -20,7 +20,7 @@ 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. @@ -39,9 +39,9 @@ public interface Obfuscator { /** * 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; diff --git a/platform/android/java/src/com/android/vending/licensing/Policy.java b/platform/android/java/lib/src/com/google/android/vending/licensing/Policy.java index fa267fc71a..b672a078b7 100644 --- a/platform/android/java/src/com/android/vending/licensing/Policy.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/Policy.java @@ -46,7 +46,7 @@ public interface Policy { * 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 */ @@ -56,4 +56,10 @@ public interface Policy { * Check if the user should be allowed access to the application. */ 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/android/vending/licensing/PreferenceObfuscator.java b/platform/android/java/lib/src/com/google/android/vending/licensing/PreferenceObfuscator.java index 7c42bfc28a..feb579af04 100644 --- a/platform/android/java/src/com/android/vending/licensing/PreferenceObfuscator.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/PreferenceObfuscator.java @@ -45,6 +45,9 @@ public class PreferenceObfuscator { public void putString(String key, String value) { if (mEditor == null) { mEditor = mPreferences.edit(); + // -- GODOT start -- + mEditor.apply(); + // -- GODOT end -- } String obfuscatedValue = mObfuscator.obfuscate(value, key); mEditor.putString(key, obfuscatedValue); diff --git a/platform/android/java/src/com/android/vending/licensing/ResponseData.java b/platform/android/java/lib/src/com/google/android/vending/licensing/ResponseData.java index 2adef3709e..3b5d557e76 100644 --- a/platform/android/java/src/com/android/vending/licensing/ResponseData.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/ResponseData.java @@ -16,10 +16,10 @@ package com.google.android.vending.licensing; -import java.util.regex.Pattern; - import android.text.TextUtils; +import java.util.regex.Pattern; + /** * ResponseData from licensing server. */ @@ -43,17 +43,17 @@ public class ResponseData { */ 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); - } + 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("|")); + String[] fields = TextUtils.split(mainData, Pattern.quote("|")); if (fields.length < 6) { throw new IllegalArgumentException("Wrong number of fields."); } @@ -73,7 +73,9 @@ public class ResponseData { @Override public String toString() { - return TextUtils.join("|", new Object [] { responseCode, nonce, packageName, versionCode, - userId, timestamp }); + 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/lib/src/com/google/android/vending/licensing/ServerManagedPolicy.java index fbf8cf6d00..e2f0bfdca8 100644 --- a/platform/android/java/src/com/android/vending/licensing/ServerManagedPolicy.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/ServerManagedPolicy.java @@ -16,27 +16,25 @@ 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; +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, and - * error retry count. + * 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 Android Market publishing console, such as whether the application is + * 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> @@ -46,12 +44,13 @@ import android.util.Log; 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 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"; @@ -65,6 +64,7 @@ public class ServerManagedPolicy implements Policy { private long mRetryCount; private long mLastResponseTime = 0; private int mLastResponse; + private String mLicensingUrl; private PreferenceObfuscator mPreferences; /** @@ -82,6 +82,7 @@ public class ServerManagedPolicy implements Policy { 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); } /** @@ -90,10 +91,12 @@ public class ServerManagedPolicy implements Policy { * 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>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 @@ -108,18 +111,22 @@ public class ServerManagedPolicy implements Policy { setRetryCount(mRetryCount + 1); } + // Update server policy data + Map<String, String> extras = decodeExtras(rawData); if (response == Policy.LICENSED) { - // Update server policy data - Map<String, String> extras = decodeExtras(rawData.extra); 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 policy data + // 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); @@ -233,6 +240,21 @@ public class ServerManagedPolicy implements Policy { } /** + * 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> @@ -259,16 +281,18 @@ public class ServerManagedPolicy implements Policy { return false; } - private Map<String, String> decodeExtras(String extras) { + 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("?" + extras); - List<NameValuePair> extraList = URLEncodedUtils.parse(rawExtras, "UTF-8"); - for (NameValuePair item : extraList) { - results.put(item.getName(), item.getValue()); - } + 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."); + 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/lib/src/com/google/android/vending/licensing/StrictPolicy.java index d8d83b4e4b..c2d55c37f1 100644 --- a/platform/android/java/src/com/android/vending/licensing/StrictPolicy.java +++ b/platform/android/java/lib/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,28 +33,38 @@ 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 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; + 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; + + if (response == Policy.NOT_LICENSED) { + Map<String, String> extras = decodeExtras(rawData); + mLicensingUrl = extras.get("LU"); + } } /** @@ -60,4 +77,24 @@ public class StrictPolicy implements Policy { 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/lib/src/com/google/android/vending/licensing/ValidationException.java index ee4df47c68..ee4df47c68 100644 --- a/platform/android/java/src/com/android/vending/licensing/ValidationException.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/ValidationException.java diff --git a/platform/android/java/src/com/android/vending/licensing/util/Base64.java b/platform/android/java/lib/src/com/google/android/vending/licensing/util/Base64.java index a0d2779af2..79efca9621 100644 --- a/platform/android/java/src/com/android/vending/licensing/util/Base64.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/util/Base64.java @@ -31,6 +31,10 @@ package com.google.android.vending.licensing.util; * @version 1.3 */ +// -- GODOT start -- +import org.godotengine.godot.BuildConfig; +// -- GODOT end -- + /** * Base64 converter class. This code is not a full-blown MIME encoder; * it simply converts binary data to base64 data and back. @@ -341,7 +345,11 @@ public class Base64 { e += 4; } - assert (e == outBuff.length); + // -- GODOT start -- + //assert (e == outBuff.length); + if (BuildConfig.DEBUG && e != outBuff.length) + throw new RuntimeException(); + // -- GODOT end -- return outBuff; } diff --git a/platform/android/java/src/com/android/vending/licensing/util/Base64DecoderException.java b/platform/android/java/lib/src/com/google/android/vending/licensing/util/Base64DecoderException.java index 1aef1b54b8..1aef1b54b8 100644 --- a/platform/android/java/src/com/android/vending/licensing/util/Base64DecoderException.java +++ b/platform/android/java/lib/src/com/google/android/vending/licensing/util/Base64DecoderException.java diff --git a/platform/android/java/lib/src/com/google/android/vending/licensing/util/URIQueryDecoder.java b/platform/android/java/lib/src/com/google/android/vending/licensing/util/URIQueryDecoder.java new file mode 100644 index 0000000000..5155bf5ac3 --- /dev/null +++ b/platform/android/java/lib/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/lib/src/org/godotengine/godot/Dictionary.java index de6b4af568..588d9ae646 100644 --- a/platform/android/java/src/org/godotengine/godot/Dictionary.java +++ b/platform/android/java/lib/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/lib/src/org/godotengine/godot/Godot.java index 92c9be5d43..739aa285bf 100644 --- a/platform/android/java/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/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,52 @@ package org.godotengine.godot; -import android.R; +import android.Manifest; +import android.annotation.SuppressLint; 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.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings.Secure; +import android.support.annotation.Keep; +import android.support.v4.content.ContextCompat; +import android.view.Display; +import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.Surface; 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,16 +83,27 @@ 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 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; +import org.godotengine.godot.xr.XRMode; -import android.os.Bundle; -import android.os.Messenger; -import android.os.SystemClock; - -public class Godot extends Activity implements SensorEventListener, IDownloaderClient { +public abstract 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; + static final int REQUEST_VIBRATE_PERMISSION = 3; private IStub mDownloaderClientStub; - private IDownloaderService mRemoteService; private TextView mStatusText; private TextView mProgressFraction; private TextView mProgressPercent; @@ -114,11 +118,13 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC private Button mPauseButton; private Button mWiFiSettingsButton; + private XRMode xrMode = XRMode.REGULAR; private boolean use_32_bits = false; private boolean use_immersive = false; + private boolean use_debug_opengl = false; private boolean mStatePaused; + private boolean activityResumed; private int mState; - private boolean keep_screen_on = true; static private Intent mCurrentIntent; @@ -140,8 +146,8 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC private void setButtonPausedState(boolean paused) { mStatePaused = paused; - int stringResourceID = paused ? com.godot.game.R.string.text_button_resume : - com.godot.game.R.string.text_button_pause; + int stringResourceID = paused ? R.string.text_button_resume : + R.string.text_button_pause; mPauseButton.setText(stringResourceID); } @@ -184,6 +190,9 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC protected void onMainActivityResult(int requestCode, int resultCode, Intent data) { } + protected void onMainRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + } + protected void onMainPause() {} protected void onMainResume() {} protected void onMainDestroy() {} @@ -217,16 +226,9 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC private Sensor mMagnetometer; private Sensor mGyroscope; - public FrameLayout layout; - public RelativeLayout adLayout; + public static GodotIO io; - static public GodotIO io; - - public static void setWindowTitle(String title) { - //setTitle(title); - } - - static SingletonBase singletons[] = new SingletonBase[MAX_SINGLETONS]; + static SingletonBase[] singletons = new SingletonBase[MAX_SINGLETONS]; static int singleton_count = 0; public interface ResultCallback { @@ -251,25 +253,36 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC } }; - public void onVideoInit() { + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + for (int i = 0; i < singleton_count; i++) { + singletons[i].onMainRequestPermissionsResult(requestCode, permissions, grantResults); + } - boolean use_gl3 = getGLESVersionCode() >= 0x00030000; + for (int i = 0; i < permissions.length; i++) { + GodotLib.requestPermissionResult(permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED); + } + }; - //mView = new GodotView(getApplication(),io,use_gl3); - //setContentView(mView); + /** + * Used by the native code (java_godot_lib_jni.cpp) to complete initialization of the GLSurfaceView view and renderer. + */ + @Keep + private void onVideoInit() { + boolean use_gl3 = getGLESVersionCode() >= 0x00030000; - layout = new FrameLayout(this); - layout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); + final FrameLayout layout = new FrameLayout(this); + 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, this); - layout.addView(mView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); + mView = new GodotView(this, xrMode, use_gl3, use_32_bits, use_debug_opengl); + layout.addView(mView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); edittext.setView(mView); io.setEdit(edittext); @@ -287,44 +300,72 @@ 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); } - }); + } + }); + } + + /** + * Used by the native code (java_godot_wrapper.h) to vibrate the device. + * @param durationMs + */ + @SuppressLint("MissingPermission") + @Keep + private void vibrate(int durationMs) { + if (requestPermission("VIBRATE")) { + Vibrator v = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE)); + } else { + //deprecated in API 26 + v.vibrate(durationMs); + } + } } } + 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(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", @@ -339,14 +380,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; } @@ -386,6 +421,31 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC } } + /** + * Used by the native code (java_godot_wrapper.h) to check whether the activity is resumed or paused. + */ + @Keep + private boolean isActivityResumed() { + return activityResumed; + } + + /** + * Used by the native code (java_godot_wrapper.h) to access the Android surface. + */ + @Keep + private Surface getSurface() { + return mView.getHolder().getSurface(); + } + + /** + * Used by the native code (java_godot_wrapper.h) to access the input fallback mapping. + * @return The input fallback mapping for the current XR mode. + */ + @Keep + private String getInputFallbackMapping() { + return xrMode.inputFallbackMapping; + } + String expansion_pack_path; private void initializeGodot() { @@ -422,7 +482,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; @@ -433,17 +493,15 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC @Override public void onServiceConnected(Messenger m) { - mRemoteService = DownloaderServiceMarshaller.CreateProxy(m); - mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger()); + IDownloaderService remoteService = DownloaderServiceMarshaller.CreateProxy(m); + remoteService.onClientUpdated(mDownloaderClientStub.getMessenger()); } @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - _self = this; Window window = getWindow(); - //window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); mClipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); @@ -459,11 +517,17 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC for (int i = 0; i < command_line.length; i++) { boolean has_extra = i < command_line.length - 1; - if (command_line[i].equals("--use_depth_32")) { + if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) { + xrMode = XRMode.REGULAR; + } else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) { + xrMode = XRMode.OVR; + } else if (command_line[i].equals("--use_depth_32")) { use_32_bits = true; + } else if (command_line[i].equals("--debug_opengl")) { + 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 | @@ -485,7 +549,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]); @@ -550,20 +614,19 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, GodotDownloaderService.class); - setContentView(com.godot.game.R.layout.downloading_expansion); - mPB = (ProgressBar)findViewById(com.godot.game.R.id.progressBar); - mStatusText = (TextView)findViewById(com.godot.game.R.id.statusText); - mProgressFraction = (TextView)findViewById(com.godot.game.R.id.progressAsFraction); - mProgressPercent = (TextView)findViewById(com.godot.game.R.id.progressAsPercentage); - mAverageSpeed = (TextView)findViewById(com.godot.game.R.id.progressAverageSpeed); - mTimeRemaining = (TextView)findViewById(com.godot.game.R.id.progressTimeRemaining); - mDashboard = findViewById(com.godot.game.R.id.downloaderDashboard); - mCellMessage = findViewById(com.godot.game.R.id.approveCellular); - mPauseButton = (Button)findViewById(com.godot.game.R.id.pauseButton); - mWiFiSettingsButton = (Button)findViewById(com.godot.game.R.id.wifiSettingsButton); + setContentView(R.layout.downloading_expansion); + mPB = (ProgressBar)findViewById(R.id.progressBar); + mStatusText = (TextView)findViewById(R.id.statusText); + mProgressFraction = (TextView)findViewById(R.id.progressAsFraction); + mProgressPercent = (TextView)findViewById(R.id.progressAsPercentage); + mAverageSpeed = (TextView)findViewById(R.id.progressAverageSpeed); + mTimeRemaining = (TextView)findViewById(R.id.progressTimeRemaining); + mDashboard = findViewById(R.id.downloaderDashboard); + mCellMessage = findViewById(R.id.approveCellular); + mPauseButton = (Button)findViewById(R.id.pauseButton); + mWiFiSettingsButton = (Button)findViewById(R.id.wifiSettingsButton); return; - } else { } } catch (NameNotFoundException e) { // TODO Auto-generated catch block @@ -575,8 +638,6 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC mCurrentIntent = getIntent(); initializeGodot(); - - //instanceSingleton( new GodotFacebook(this) ); } @Override @@ -586,12 +647,21 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC for (int i = 0; i < singleton_count; i++) { singletons[i].onMainDestroy(); } + + GodotLib.ondestroy(this); + super.onDestroy(); + + // TODO: This is a temp solution. The proper fix will involve tracking down and properly shutting down each + // native Godot components that is started in Godot#onVideoInit. + forceQuit(); } @Override protected void onPause() { super.onPause(); + activityResumed = false; + if (!godot_initialized) { if (null != mDownloaderClientStub) { mDownloaderClientStub.disconnect(this); @@ -599,12 +669,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC return; } mView.onPause(); - mView.queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.focusout(); - } - }); + mSensorManager.unregisterListener(this); for (int i = 0; i < singleton_count; i++) { @@ -633,6 +698,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC @Override protected void onResume() { super.onResume(); + activityResumed = true; if (!godot_initialized) { if (null != mDownloaderClientStub) { mDownloaderClientStub.connect(this); @@ -641,18 +707,13 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC } mView.onResume(); - mView.queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.focusin(); - } - }); + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); mSensorManager.registerListener(this, mGravity, SensorManager.SENSOR_DELAY_GAME); 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 | @@ -675,13 +736,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); + } } } }); @@ -772,8 +835,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC } } - public void forceQuit() { - + private void forceQuit() { System.exit(0); } @@ -820,7 +882,6 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC } } - //@Override public boolean dispatchTouchEvent (MotionEvent event) { public boolean gotTouchEvent(final MotionEvent event) { final int evcount = event.getPointerCount(); @@ -891,8 +952,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC for (int i = cc.length; --i >= 0; cnt += cc[i] != 0 ? 1 : 0) ; if (cnt == 0) return super.onKeyMultiple(inKeyCode, repeatCount, event); - final Activity me = this; - queueEvent(new Runnable() { + mView.queueEvent(new Runnable() { // This method will be called on the rendering thread: public void run() { for (int i = 0, n = cc.length; i < n; i++) { @@ -908,27 +968,44 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC return true; } - private void queueEvent(Runnable runnable) { - // TODO Auto-generated method stub - } - public PaymentsManager getPaymentsManager() { return mPaymentsManager; } - /* - public void setPaymentsManager(PaymentsManager mPaymentsManager) { - this.mPaymentsManager = mPaymentsManager; - } - */ + 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; + } + } - // Audio + 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; + } + } + + if (p_name.equals("VIBRATE")) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_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); @@ -939,7 +1016,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC switch (newState) { case IDownloaderClient.STATE_IDLE: // STATE_IDLE means the service is listening, so it's - // safe to start making calls via mRemoteService. + // safe to start making remote service calls. paused = false; indeterminate = true; break; @@ -1006,18 +1083,18 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC @Override public void onDownloadProgress(DownloadProgressInfo progress) { - mAverageSpeed.setText(getString(com.godot.game.R.string.kilobytes_per_second, + mAverageSpeed.setText(getString(R.string.kilobytes_per_second, Helpers.getSpeedString(progress.mCurrentSpeed))); - mTimeRemaining.setText(getString(com.godot.game.R.string.time_remaining, + mTimeRemaining.setText(getString(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/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java index 4701bac9df..e7e2a3f808 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java +++ b/platform/android/java/lib/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/lib/src/org/godotengine/godot/GodotDownloaderService.java index 3a94354843..8e10710c9f 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotDownloaderService.java +++ b/platform/android/java/lib/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/lib/src/org/godotengine/godot/GodotIO.java index a95c508d21..04566cf62c 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/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,19 @@ /*************************************************************************/ package org.godotengine.godot; -import java.util.HashMap; -import java.util.Locale; -import android.net.Uri; +import android.content.*; import android.content.Intent; +import android.content.pm.ActivityInfo; 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.media.*; +import android.net.Uri; import android.os.*; -import android.util.Log; import android.util.DisplayMetrics; -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.util.Log; +import android.util.SparseArray; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; import org.godotengine.godot.input.*; //android.os.Build @@ -61,7 +53,6 @@ public class GodotIO { Godot activity; GodotEditText edit; - Context applicationContext; MediaPlayer mediaPlayer; final int SCREEN_LANDSCAPE = 0; @@ -87,7 +78,7 @@ public class GodotIO { public int pos; } - HashMap<Integer, AssetData> streams; + SparseArray<AssetData> streams; public int file_open(String path, boolean write) { @@ -125,7 +116,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 +125,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 +165,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 +175,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 +186,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 +234,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 +255,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 +284,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 +311,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 +330,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 +342,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 +356,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 +487,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 +502,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 +542,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/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java b/platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java new file mode 100644 index 0000000000..0466f380e8 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* GodotInstrumentation.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot; + +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/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java new file mode 100644 index 0000000000..067fa6f4b9 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -0,0 +1,226 @@ +/*************************************************************************/ +/* GodotLib.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot; + +import android.app.Activity; +import android.hardware.SensorEvent; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +/** + * Wrapper for native library + */ +public class GodotLib { + + public static GodotIO io; + + static { + System.loadLibrary("godot_android"); + } + + /** + * Invoked on the main thread to initialize Godot native layer. + */ + public static native void initialize(Godot p_instance, Object p_asset_manager, boolean use_apk_expansion); + + /** + * Invoked on the main thread to clean up Godot native layer. + * @see Activity#onDestroy() + */ + public static native void ondestroy(Godot p_instance); + + /** + * Invoked on the GL thread to complete setup for the Godot native layer logic. + * @param p_cmdline Command line arguments used to configure Godot native layer components. + */ + public static native void setup(String[] p_cmdline); + + /** + * Invoked on the GL thread when the underlying Android surface has changed size. + * @param width + * @param height + * @see android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(GL10, int, int) + */ + public static native void resize(int width, int height); + + /** + * Invoked on the GL thread when the underlying Android surface is created or recreated. + * @param p_32_bits + * @see android.opengl.GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig) + */ + public static native void newcontext(boolean p_32_bits); + + /** + * Forward {@link Activity#onBackPressed()} event from the main thread to the GL thread. + */ + public static native void back(); + + /** + * Invoked on the GL thread to draw the current frame. + * @see android.opengl.GLSurfaceView.Renderer#onDrawFrame(GL10) + */ + public static native void step(); + + /** + * Forward touch events from the main thread to the GL thread. + */ + public static native void touch(int what, int pointer, int howmany, int[] arr); + + /** + * Forward accelerometer sensor events from the main thread to the GL thread. + * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent) + */ + public static native void accelerometer(float x, float y, float z); + + /** + * Forward gravity sensor events from the main thread to the GL thread. + * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent) + */ + public static native void gravity(float x, float y, float z); + + /** + * Forward magnetometer sensor events from the main thread to the GL thread. + * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent) + */ + public static native void magnetometer(float x, float y, float z); + + /** + * Forward gyroscope sensor events from the main thread to the GL thread. + * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent) + */ + public static native void gyroscope(float x, float y, float z); + + /** + * Forward regular key events from the main thread to the GL thread. + */ + public static native void key(int p_scancode, int p_unicode_char, boolean p_pressed); + + /** + * Forward game device's key events from the main thread to the GL thread. + */ + public static native void joybutton(int p_device, int p_but, boolean p_pressed); + + /** + * Forward joystick devices axis motion events from the main thread to the GL thread. + */ + public static native void joyaxis(int p_device, int p_axis, float p_value); + + /** + * Forward joystick devices hat motion events from the main thread to the GL thread. + */ + public static native void joyhat(int p_device, int p_hat_x, int p_hat_y); + + /** + * Fires when a joystick device is added or removed. + */ + public static native void joyconnectionchanged(int p_device, boolean p_connected, String p_name); + + /** + * Invoked when the Android activity resumes. + * @see Activity#onResume() + */ + public static native void focusin(); + + /** + * Invoked when the Android activity pauses. + * @see Activity#onPause() + */ + public static native void focusout(); + + /** + * Invoked when the audio thread is started. + */ + public static native void audio(); + + /** + * Used to setup a {@link org.godotengine.godot.Godot.SingletonBase} instance. + * @param p_name Name of the instance. + * @param p_object Reference to the singleton instance. + */ + public static native void singleton(String p_name, Object p_object); + + /** + * Used to complete registration of the {@link org.godotengine.godot.Godot.SingletonBase} instance's methods. + * @param p_sname Name of the instance + * @param p_name Name of the method to register + * @param p_ret Return type of the registered method + * @param p_params Method parameters types + */ + public static native void method(String p_sname, String p_name, String p_ret, String[] p_params); + + /** + * Used to access Godot global properties. + * @param p_key Property key + * @return String value of the property + */ + public static native String getGlobal(String p_key); + + /** + * Invoke method |p_method| on the Godot object specified by |p_id| + * @param p_id Id of the Godot object to invoke + * @param p_method Name of the method to invoke + * @param p_params Parameters to use for method invocation + */ + public static native void callobject(int p_id, String p_method, Object[] p_params); + + /** + * Invoke method |p_method| on the Godot object specified by |p_id| during idle time. + * @param p_id Id of the Godot object to invoke + * @param p_method Name of the method to invoke + * @param p_params Parameters to use for method invocation + */ + public static native void calldeferred(int p_id, String p_method, Object[] p_params); + + /** + * Forward the results from a permission request. + * @see Activity#onRequestPermissionsResult(int, String[], int[]) + * @param p_permission Request permission + * @param p_result True if the permission was granted, false otherwise + */ + public static native void requestPermissionResult(String p_permission, boolean p_result); + + /** + * Invoked on the GL thread to configure the height of the virtual keyboard. + */ + public static native void setVirtualKeyboardHeight(int p_height); + + /** + * Invoked on the GL thread when the {@link GodotRenderer} has been resumed. + * @see GodotRenderer#onActivityResumed() + */ + public static native void onRendererResumed(); + + /** + * Invoked on the GL thread when the {@link GodotRenderer} has been paused. + * @see GodotRenderer#onActivityPaused() + */ + public static native void onRendererPaused(); +} diff --git a/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java b/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java index bde4221644..1432cd3a67 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java +++ b/platform/android/java/lib/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/lib/src/org/godotengine/godot/GodotRenderer.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java new file mode 100644 index 0000000000..56ba88656e --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java @@ -0,0 +1,78 @@ +/*************************************************************************/ +/* GodotRenderer.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot; + +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; +import org.godotengine.godot.utils.GLUtils; + +/** + * Godot's renderer implementation. + */ +class GodotRenderer implements GLSurfaceView.Renderer { + + private boolean activityJustResumed = false; + + public void onDrawFrame(GL10 gl) { + if (activityJustResumed) { + GodotLib.onRendererResumed(); + activityJustResumed = false; + } + + GodotLib.step(); + for (int i = 0; i < Godot.singleton_count; i++) { + Godot.singletons[i].onGLDrawFrame(gl); + } + } + + public void onSurfaceChanged(GL10 gl, int width, int height) { + + GodotLib.resize(width, height); + for (int i = 0; i < Godot.singleton_count; i++) { + Godot.singletons[i].onGLSurfaceChanged(gl, width, height); + } + } + + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + GodotLib.newcontext(GLUtils.use_32); + } + + void onActivityResumed() { + // We defer invoking GodotLib.onRendererResumed() until the first draw frame call. + // This ensures we have a valid GL context and surface when we do so. + activityJustResumed = true; + } + + void onActivityPaused() { + GodotLib.onRendererPaused(); + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotView.java new file mode 100644 index 0000000000..5511e5d782 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotView.java @@ -0,0 +1,200 @@ +/*************************************************************************/ +/* GodotView.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot; +import android.annotation.SuppressLint; +import android.graphics.PixelFormat; +import android.opengl.GLSurfaceView; +import android.view.KeyEvent; +import android.view.MotionEvent; +import org.godotengine.godot.input.GodotInputHandler; +import org.godotengine.godot.utils.GLUtils; +import org.godotengine.godot.xr.XRMode; +import org.godotengine.godot.xr.ovr.OvrConfigChooser; +import org.godotengine.godot.xr.ovr.OvrContextFactory; +import org.godotengine.godot.xr.ovr.OvrWindowSurfaceFactory; +import org.godotengine.godot.xr.regular.RegularConfigChooser; +import org.godotengine.godot.xr.regular.RegularContextFactory; +import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser; + +/** + * A simple GLSurfaceView sub-class that demonstrate how to perform + * OpenGL ES 2.0 rendering into a GL Surface. Note the following important + * details: + * + * - The class must use a custom context factory to enable 2.0 rendering. + * See ContextFactory class definition below. + * + * - The class must use a custom EGLConfigChooser to be able to select + * an EGLConfig that supports 2.0. This is done by providing a config + * specification to eglChooseConfig() that has the attribute + * EGL10.ELG_RENDERABLE_TYPE containing the EGL_OPENGL_ES2_BIT flag + * set. See ConfigChooser class definition below. + * + * - The class must select the surface's format, then choose an EGLConfig + * that matches it exactly (with regards to red/green/blue/alpha channels + * bit depths). Failure to do so would result in an EGL_BAD_MATCH error. + */ +public class GodotView extends GLSurfaceView { + + private static String TAG = GodotView.class.getSimpleName(); + + private final Godot activity; + private final GodotInputHandler inputHandler; + private final GodotRenderer godotRenderer; + + public GodotView(Godot activity, XRMode xrMode, boolean p_use_gl3, boolean p_use_32_bits, boolean p_use_debug_opengl) { + super(activity); + GLUtils.use_gl3 = p_use_gl3; + GLUtils.use_32 = p_use_32_bits; + GLUtils.use_debug_opengl = p_use_debug_opengl; + + this.activity = activity; + this.inputHandler = new GodotInputHandler(this); + this.godotRenderer = new GodotRenderer(); + init(xrMode, false, 16, 0); + } + + public void initInputDevices() { + this.inputHandler.initInputDevices(); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + return activity.gotTouchEvent(event); + } + + @Override + public boolean onKeyUp(final int keyCode, KeyEvent event) { + return inputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyDown(final int keyCode, KeyEvent event) { + return inputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + return inputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); + } + + private void init(XRMode xrMode, boolean translucent, int depth, int stencil) { + + setPreserveEGLContextOnPause(true); + setFocusableInTouchMode(true); + switch (xrMode) { + + case OVR: + // Replace the default egl config chooser. + setEGLConfigChooser(new OvrConfigChooser()); + + // Replace the default context factory. + setEGLContextFactory(new OvrContextFactory()); + + // Replace the default window surface factory. + setEGLWindowSurfaceFactory(new OvrWindowSurfaceFactory()); + break; + + case REGULAR: + default: + /* By default, GLSurfaceView() creates a RGB_565 opaque surface. + * If we want a translucent one, we should change the surface's + * format here, using PixelFormat.TRANSLUCENT for GL Surfaces + * is interpreted as any 32-bit surface with alpha by SurfaceFlinger. + */ + if (translucent) { + this.getHolder().setFormat(PixelFormat.TRANSLUCENT); + } + + /* Setup the context factory for 2.0 rendering. + * See ContextFactory class definition below + */ + setEGLContextFactory(new RegularContextFactory()); + + /* We need to choose an EGLConfig that matches the format of + * our surface exactly. This is going to be done in our + * custom config chooser. See ConfigChooser class definition + * below. + */ + + if (GLUtils.use_32) { + setEGLConfigChooser(translucent ? + new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil, + new RegularConfigChooser(8, 8, 8, 8, 16, stencil)) : + new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil, + new RegularConfigChooser(5, 6, 5, 0, 16, stencil))); + + } else { + setEGLConfigChooser(translucent ? + new RegularConfigChooser(8, 8, 8, 8, 16, stencil) : + new RegularConfigChooser(5, 6, 5, 0, 16, stencil)); + } + break; + } + + /* Set the renderer responsible for frame rendering */ + setRenderer(godotRenderer); + } + + public void onBackPressed() { + activity.onBackPressed(); + } + + @Override + public void onResume() { + super.onResume(); + + queueEvent(new Runnable() { + @Override + public void run() { + // Resume the renderer + godotRenderer.onActivityResumed(); + GodotLib.focusin(); + } + }); + } + + @Override + public void onPause() { + super.onPause(); + + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.focusout(); + // Pause the renderer + godotRenderer.onActivityPaused(); + } + }); + } +} diff --git a/platform/android/java/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/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/lib/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/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java new file mode 100644 index 0000000000..2beca67922 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -0,0 +1,358 @@ +/*************************************************************************/ +/* GodotInputHandler.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot.input; + +import static org.godotengine.godot.utils.GLUtils.DEBUG; + +import android.util.Log; +import android.view.InputDevice; +import android.view.InputDevice.MotionRange; +import android.view.KeyEvent; +import android.view.MotionEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.GodotView; +import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener; + +/** + * Handles input related events for the {@link GodotView} view. + */ +public class GodotInputHandler implements InputDeviceListener { + + private final ArrayList<Joystick> joysticksDevices = new ArrayList<Joystick>(); + + private final GodotView godotView; + private final InputManagerCompat inputManager; + + public GodotInputHandler(GodotView godotView) { + this.godotView = godotView; + this.inputManager = InputManagerCompat.Factory.getInputManager(godotView.getContext()); + this.inputManager.registerInputDeviceListener(this, null); + } + + private void queueEvent(Runnable task) { + godotView.queueEvent(task); + } + + private boolean isKeyEvent_GameDevice(int source) { + // Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD) + if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD)) + return false; + + return (source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD; + } + + public boolean onKeyUp(final int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + return true; + } + + if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + return false; + }; + + int source = event.getSource(); + if (isKeyEvent_GameDevice(source)) { + + final int button = getGodotButton(keyCode); + final int device_id = findJoystickDevice(event.getDeviceId()); + + // Check if the device exists + if (device_id > -1) { + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joybutton(device_id, button, false); + } + }); + } + } else { + final int chr = event.getUnicodeChar(0); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.key(keyCode, chr, false); + } + }); + }; + + return true; + } + + public boolean onKeyDown(final int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + godotView.onBackPressed(); + // press 'back' button should not terminate program + //normal handle 'back' event in game logic + return true; + } + + if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + return false; + }; + + int source = event.getSource(); + //Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD))); + + if (isKeyEvent_GameDevice(source)) { + + if (event.getRepeatCount() > 0) // ignore key echo + return true; + + final int button = getGodotButton(keyCode); + final int device_id = findJoystickDevice(event.getDeviceId()); + + // Check if the device exists + if (device_id > -1) { + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joybutton(device_id, button, true); + } + }); + } + } else { + final int chr = event.getUnicodeChar(0); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.key(keyCode, chr, true); + } + }); + }; + + return true; + } + + public boolean onGenericMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) { + + final int device_id = findJoystickDevice(event.getDeviceId()); + + // Check if the device exists + if (device_id > -1) { + Joystick joy = joysticksDevices.get(device_id); + + for (int i = 0; i < joy.axes.size(); i++) { + InputDevice.MotionRange range = joy.axes.get(i); + final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f; + final int idx = i; + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyaxis(device_id, idx, value); + } + }); + } + + for (int i = 0; i < joy.hats.size(); i += 2) { + final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis())); + final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis())); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyhat(device_id, hatX, hatY); + } + }); + } + return true; + } + }; + + return false; + } + + public void initInputDevices() { + /* initially add input devices*/ + int[] deviceIds = inputManager.getInputDeviceIds(); + for (int deviceId : deviceIds) { + InputDevice device = inputManager.getInputDevice(deviceId); + if (DEBUG) { + Log.v("GodotView", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName())); + } + onInputDeviceAdded(deviceId); + } + } + + @Override + public void onInputDeviceAdded(int deviceId) { + int id = findJoystickDevice(deviceId); + + // Check if the device has not been already added + if (id < 0) { + InputDevice device = inputManager.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 = joysticksDevices.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); + } + } + + joysticksDevices.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); + } + }); + } + } + } + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + final int device_id = findJoystickDevice(deviceId); + + // Check if the evice has not been already removed + if (device_id > -1) { + joysticksDevices.remove(device_id); + + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyconnectionchanged(device_id, false, ""); + } + }); + } + } + + @Override + public void onInputDeviceChanged(int deviceId) { + onInputDeviceRemoved(deviceId); + onInputDeviceAdded(deviceId); + } + + private static class RangeComparator implements Comparator<MotionRange> { + @Override + public int compare(MotionRange arg0, MotionRange arg1) { + return arg0.getAxis() - arg1.getAxis(); + } + } + + public static int getGodotButton(int keyCode) { + int button; + switch (keyCode) { + case KeyEvent.KEYCODE_BUTTON_A: // Android A is SNES B + button = 0; + break; + case KeyEvent.KEYCODE_BUTTON_B: + button = 1; + break; + case KeyEvent.KEYCODE_BUTTON_X: // Android X is SNES Y + button = 2; + break; + case KeyEvent.KEYCODE_BUTTON_Y: + button = 3; + break; + case KeyEvent.KEYCODE_BUTTON_L1: + button = 9; + break; + case KeyEvent.KEYCODE_BUTTON_L2: + button = 15; + break; + case KeyEvent.KEYCODE_BUTTON_R1: + button = 10; + break; + case KeyEvent.KEYCODE_BUTTON_R2: + button = 16; + break; + case KeyEvent.KEYCODE_BUTTON_SELECT: + button = 4; + break; + case KeyEvent.KEYCODE_BUTTON_START: + button = 6; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBL: + button = 7; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBR: + button = 8; + break; + case KeyEvent.KEYCODE_DPAD_UP: + button = 11; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + button = 12; + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + button = 13; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + button = 14; + break; + case KeyEvent.KEYCODE_BUTTON_C: + button = 17; + break; + case KeyEvent.KEYCODE_BUTTON_Z: + button = 18; + break; + + default: + button = keyCode - KeyEvent.KEYCODE_BUTTON_1 + 20; + break; + } + return button; + } + + private int findJoystickDevice(int device_id) { + for (int i = 0; i < joysticksDevices.size(); i++) { + if (joysticksDevices.get(i).device_id == device_id) { + return i; + } + } + + return -1; + } +} diff --git a/platform/android/java/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java index 5d13f17ffb..9b372c75e3 100644 --- a/platform/android/java/src/org/godotengine/godot/input/GodotTextInputWrapper.java +++ b/platform/android/java/lib/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 */ @@ -32,7 +32,6 @@ package org.godotengine.godot.input; import android.content.Context; import android.text.Editable; import android.text.TextWatcher; -import android.util.Log; import android.view.KeyEvent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; diff --git a/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java b/platform/android/java/lib/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/lib/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/lib/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/lib/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/globals/global_defaults.h b/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java index 99da2dd527..ff95bfb0c5 100644 --- a/platform/android/globals/global_defaults.h +++ b/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java @@ -1,12 +1,12 @@ /*************************************************************************/ -/* global_defaults.h */ +/* Joystick.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,4 +28,17 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -void register_android_global_defaults(); +package org.godotengine.godot.input; + +import android.view.InputDevice.MotionRange; +import java.util.ArrayList; + +/** + * POJO class to represent a Joystick input device. + */ +class Joystick { + int device_id; + String name; + ArrayList<MotionRange> axes; + ArrayList<MotionRange> hats; +} diff --git a/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/ConsumeTask.java index 5d94e77cd7..4c1050c948 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java +++ b/platform/android/java/lib/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,85 @@ 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/HandlePurchaseTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/HandlePurchaseTask.java index aaf18c74bf..1a914967a2 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/HandlePurchaseTask.java +++ b/platform/android/java/lib/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,23 +30,10 @@ 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; -import android.content.Context; import android.content.Intent; -import android.content.IntentSender.SendIntentException; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.RemoteException; -import android.util.Log; +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/lib/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/lib/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/lib/src/org/godotengine/godot/payments/PaymentsManager.java index d4c7380424..c079c55854 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java +++ b/platform/android/java/lib/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,13 @@ import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; - import com.android.vending.billing.IInAppBillingService; - -import org.godotengine.godot.Godot; +import java.util.ArrayList; +import java.util.Arrays; 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 +108,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 +155,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/lib/src/org/godotengine/godot/payments/PurchaseTask.java index e1d9bcee65..9adc85e521 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/PurchaseTask.java +++ b/platform/android/java/lib/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,23 +30,14 @@ 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; -import android.content.Context; import android.content.Intent; import android.content.IntentSender.SendIntentException; -import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; +import com.android.vending.billing.IInAppBillingService; abstract public class PurchaseTask { diff --git a/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java b/platform/android/java/lib/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/lib/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/lib/src/org/godotengine/godot/payments/ValidateTask.java index 0626e50bb1..17a2a197ad 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java +++ b/platform/android/java/lib/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,91 +30,112 @@ 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; -import android.content.Context; -import android.content.Intent; -import android.content.IntentSender.SendIntentException; import android.os.AsyncTask; -import android.os.Bundle; -import android.os.RemoteException; -import android.util.Log; +import java.lang.ref.WeakReference; +import org.godotengine.godot.GodotPaymentV3; +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/lib/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/lib/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/lib/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java index 7216d8b5a4..b61007faa3 100644 --- a/platform/android/java/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java +++ b/platform/android/java/lib/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,14 +37,12 @@ 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; /** - * + * * @author Luis Linietsky <luis.linietsky@gmail.com> */ public class CustomSSLSocketFactory extends SSLSocketFactory { diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java new file mode 100644 index 0000000000..6c95494f8b --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java @@ -0,0 +1,157 @@ +/*************************************************************************/ +/* GLUtils.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot.utils; + +import android.util.Log; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +/** + * Contains GL utilities methods. + */ +public class GLUtils { + + private static final String TAG = GLUtils.class.getSimpleName(); + + public static final boolean DEBUG = false; + + public static boolean use_gl3 = false; + public static boolean use_32 = false; + public static boolean use_debug_opengl = false; + + private static final String[] ATTRIBUTES_NAMES = new String[] { + "EGL_BUFFER_SIZE", + "EGL_ALPHA_SIZE", + "EGL_BLUE_SIZE", + "EGL_GREEN_SIZE", + "EGL_RED_SIZE", + "EGL_DEPTH_SIZE", + "EGL_STENCIL_SIZE", + "EGL_CONFIG_CAVEAT", + "EGL_CONFIG_ID", + "EGL_LEVEL", + "EGL_MAX_PBUFFER_HEIGHT", + "EGL_MAX_PBUFFER_PIXELS", + "EGL_MAX_PBUFFER_WIDTH", + "EGL_NATIVE_RENDERABLE", + "EGL_NATIVE_VISUAL_ID", + "EGL_NATIVE_VISUAL_TYPE", + "EGL_PRESERVED_RESOURCES", + "EGL_SAMPLES", + "EGL_SAMPLE_BUFFERS", + "EGL_SURFACE_TYPE", + "EGL_TRANSPARENT_TYPE", + "EGL_TRANSPARENT_RED_VALUE", + "EGL_TRANSPARENT_GREEN_VALUE", + "EGL_TRANSPARENT_BLUE_VALUE", + "EGL_BIND_TO_TEXTURE_RGB", + "EGL_BIND_TO_TEXTURE_RGBA", + "EGL_MIN_SWAP_INTERVAL", + "EGL_MAX_SWAP_INTERVAL", + "EGL_LUMINANCE_SIZE", + "EGL_ALPHA_MASK_SIZE", + "EGL_COLOR_BUFFER_TYPE", + "EGL_RENDERABLE_TYPE", + "EGL_CONFORMANT" + }; + + private static final int[] ATTRIBUTES = new int[] { + EGL10.EGL_BUFFER_SIZE, + EGL10.EGL_ALPHA_SIZE, + EGL10.EGL_BLUE_SIZE, + EGL10.EGL_GREEN_SIZE, + EGL10.EGL_RED_SIZE, + EGL10.EGL_DEPTH_SIZE, + EGL10.EGL_STENCIL_SIZE, + EGL10.EGL_CONFIG_CAVEAT, + EGL10.EGL_CONFIG_ID, + EGL10.EGL_LEVEL, + EGL10.EGL_MAX_PBUFFER_HEIGHT, + EGL10.EGL_MAX_PBUFFER_PIXELS, + EGL10.EGL_MAX_PBUFFER_WIDTH, + EGL10.EGL_NATIVE_RENDERABLE, + EGL10.EGL_NATIVE_VISUAL_ID, + EGL10.EGL_NATIVE_VISUAL_TYPE, + 0x3030, // EGL10.EGL_PRESERVED_RESOURCES, + EGL10.EGL_SAMPLES, + EGL10.EGL_SAMPLE_BUFFERS, + EGL10.EGL_SURFACE_TYPE, + EGL10.EGL_TRANSPARENT_TYPE, + EGL10.EGL_TRANSPARENT_RED_VALUE, + EGL10.EGL_TRANSPARENT_GREEN_VALUE, + EGL10.EGL_TRANSPARENT_BLUE_VALUE, + 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB, + 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA, + 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL, + 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL, + EGL10.EGL_LUMINANCE_SIZE, + EGL10.EGL_ALPHA_MASK_SIZE, + EGL10.EGL_COLOR_BUFFER_TYPE, + EGL10.EGL_RENDERABLE_TYPE, + 0x3042 // EGL10.EGL_CONFORMANT + }; + + private GLUtils() {} + + public static void checkEglError(String tag, String prompt, EGL10 egl) { + int error; + while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) { + Log.e(tag, String.format("%s: EGL error: 0x%x", prompt, error)); + } + } + + public static void printConfigs(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + int numConfigs = configs.length; + Log.v(TAG, String.format("%d configurations", numConfigs)); + for (int i = 0; i < numConfigs; i++) { + Log.v(TAG, String.format("Configuration %d:\n", i)); + printConfig(egl, display, configs[i]); + } + } + + private static void printConfig(EGL10 egl, EGLDisplay display, + EGLConfig config) { + int[] value = new int[1]; + for (int i = 0; i < ATTRIBUTES.length; i++) { + int attribute = ATTRIBUTES[i]; + String name = ATTRIBUTES_NAMES[i]; + if (egl.eglGetConfigAttrib(display, config, attribute, value)) { + Log.i(TAG, String.format(" %s: %d\n", name, value[0])); + } else { + // Log.w(TAG, String.format(" %s: failed\n", name)); + while (egl.eglGetError() != EGL10.EGL_SUCCESS) + ; + } + } + } +} diff --git a/platform/android/java/src/org/godotengine/godot/utils/HttpRequester.java b/platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java index b84f5cce2e..02ae753b3e 100644 --- a/platform/android/java/src/org/godotengine/godot/utils/HttpRequester.java +++ b/platform/android/java/lib/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,19 +30,18 @@ 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; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.security.KeyStore; -import java.util.ArrayList; import java.util.Date; -import java.util.List; - import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; -import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; @@ -56,7 +55,6 @@ import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; @@ -64,12 +62,8 @@ 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> */ public class HttpRequester { @@ -105,7 +99,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 +194,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/lib/src/org/godotengine/godot/utils/RequestParams.java index 2368766afa..b9fe0dd0c9 100644 --- a/platform/android/java/src/org/godotengine/godot/utils/RequestParams.java +++ b/platform/android/java/lib/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,12 +34,11 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; - import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; /** - * + * * @author Luis Linietsky <luis.linietsky@gmail.com> */ public class RequestParams { diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java b/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java new file mode 100644 index 0000000000..5896b23ac3 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* XRMode.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot.xr; + +/** + * Godot available XR modes. + */ +public enum XRMode { + REGULAR(0, "Regular", "--xr_mode_regular", "Default Android Gamepad"), // Regular/flatscreen + OVR(1, "Oculus Mobile VR", "--xr_mode_ovr", ""); + + final int index; + final String label; + public final String cmdLineArg; + public final String inputFallbackMapping; + + XRMode(int index, String label, String cmdLineArg, String inputFallbackMapping) { + this.index = index; + this.label = label; + this.cmdLineArg = cmdLineArg; + this.inputFallbackMapping = inputFallbackMapping; + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java new file mode 100644 index 0000000000..ff836a31ca --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java @@ -0,0 +1,112 @@ +/*************************************************************************/ +/* OvrConfigChooser.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.ovr; + +import android.opengl.EGLExt; +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +/** + * EGL config chooser for the Oculus Mobile VR SDK. + */ +public class OvrConfigChooser implements GLSurfaceView.EGLConfigChooser { + + private static final int[] CONFIG_ATTRIBS = { + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 8, // Need alpha for the multi-pass timewarp compositor + EGL10.EGL_DEPTH_SIZE, 0, + EGL10.EGL_STENCIL_SIZE, 0, + EGL10.EGL_SAMPLES, 0, + EGL10.EGL_NONE + }; + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + // Do NOT use eglChooseConfig, because the Android EGL code pushes in + // multisample flags in eglChooseConfig if the user has selected the "force 4x + // MSAA" option in settings, and that is completely wasted for our warp + // target. + int[] numConfig = new int[1]; + if (!egl.eglGetConfigs(display, null, 0, numConfig)) { + throw new IllegalArgumentException("eglGetConfigs failed."); + } + + int configsCount = numConfig[0]; + if (configsCount <= 0) { + throw new IllegalArgumentException("No configs match configSpec"); + } + + EGLConfig[] configs = new EGLConfig[configsCount]; + if (!egl.eglGetConfigs(display, configs, configsCount, numConfig)) { + throw new IllegalArgumentException("eglGetConfigs #2 failed."); + } + + int[] value = new int[1]; + for (EGLConfig config : configs) { + egl.eglGetConfigAttrib(display, config, EGL10.EGL_RENDERABLE_TYPE, value); + if ((value[0] & EGLExt.EGL_OPENGL_ES3_BIT_KHR) != EGLExt.EGL_OPENGL_ES3_BIT_KHR) { + continue; + } + + // The pbuffer config also needs to be compatible with normal window rendering + // so it can share textures with the window context. + egl.eglGetConfigAttrib(display, config, EGL10.EGL_SURFACE_TYPE, value); + if ((value[0] & (EGL10.EGL_WINDOW_BIT | EGL10.EGL_PBUFFER_BIT)) != (EGL10.EGL_WINDOW_BIT | EGL10.EGL_PBUFFER_BIT)) { + continue; + } + + // Check each attribute in CONFIG_ATTRIBS (which are the attributes we care about) + // and ensure the value in config matches. + int attribIndex = 0; + while (CONFIG_ATTRIBS[attribIndex] != EGL10.EGL_NONE) { + egl.eglGetConfigAttrib(display, config, CONFIG_ATTRIBS[attribIndex], value); + if (value[0] != CONFIG_ATTRIBS[attribIndex + 1]) { + // Attribute key's value does not match the configs value. + // Start checking next config. + break; + } + + // Step by two because CONFIG_ATTRIBS is in key/value pairs. + attribIndex += 2; + } + + if (CONFIG_ATTRIBS[attribIndex] == EGL10.EGL_NONE) { + // All relevant attributes match, set the config and stop checking the rest. + return config; + } + } + return null; + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java new file mode 100644 index 0000000000..5f6da8c672 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* OvrContextFactory.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.ovr; + +import android.opengl.EGL14; +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; + +/** + * EGL Context factory for the Oculus mobile VR SDK. + */ +public class OvrContextFactory implements GLSurfaceView.EGLContextFactory { + + private static final int[] CONTEXT_ATTRIBS = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE + }; + + @Override + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { + return egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, CONTEXT_ATTRIBS); + } + + @Override + public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { + egl.eglDestroyContext(display, context); + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java new file mode 100644 index 0000000000..f1e38c35d8 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java @@ -0,0 +1,60 @@ +/*************************************************************************/ +/* OvrWindowSurfaceFactory.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.ovr; + +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +/** + * EGL window surface factory for the Oculus mobile VR SDK. + */ +public class OvrWindowSurfaceFactory implements GLSurfaceView.EGLWindowSurfaceFactory { + + private final static int[] SURFACE_ATTRIBS = { + EGL10.EGL_WIDTH, 16, + EGL10.EGL_HEIGHT, 16, + EGL10.EGL_NONE + }; + + @Override + public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, + Object nativeWindow) { + return egl.eglCreatePbufferSurface(display, config, SURFACE_ATTRIBS); + } + + @Override + public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) { + egl.eglDestroySurface(display, surface); + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java new file mode 100644 index 0000000000..3836967f86 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* RegularConfigChooser.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.regular; + +import android.opengl.GLSurfaceView; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; +import org.godotengine.godot.utils.GLUtils; + +/** + * Used to select the egl config for pancake games. + */ +public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { + + private static final String TAG = RegularConfigChooser.class.getSimpleName(); + + private int[] mValue = new int[1]; + + /* This EGL config specification is used to specify 2.0 rendering. + * We use a minimum size of 4 bits for red/green/blue, but will + * 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 + }; + + public RegularConfigChooser(int r, int g, int b, int a, int depth, int stencil) { + mRedSize = r; + mGreenSize = g; + mBlueSize = b; + mAlphaSize = a; + mDepthSize = depth; + mStencilSize = stencil; + } + + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + + /* Get the number of minimally matching EGL configurations + */ + int[] num_config = new int[1]; + egl.eglChooseConfig(display, GLUtils.use_gl3 ? s_configAttribs3 : s_configAttribs2, null, 0, num_config); + + int numConfigs = num_config[0]; + + if (numConfigs <= 0) { + throw new IllegalArgumentException("No configs match configSpec"); + } + + /* Allocate then read the array of minimally matching EGL configs + */ + EGLConfig[] configs = new EGLConfig[numConfigs]; + egl.eglChooseConfig(display, GLUtils.use_gl3 ? s_configAttribs3 : s_configAttribs2, configs, numConfigs, num_config); + + if (GLUtils.DEBUG) { + GLUtils.printConfigs(egl, display, configs); + } + /* Now return the "best" one + */ + return chooseConfig(egl, display, configs); + } + + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + for (EGLConfig config : configs) { + int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + + // We need at least mDepthSize and mStencilSize bits + if (d < mDepthSize || s < mStencilSize) + continue; + + // We want an *exact* match for red/green/blue/alpha + int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + + if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize) + return config; + } + return null; + } + + private int findConfigAttrib(EGL10 egl, EGLDisplay display, + EGLConfig config, int attribute, int defaultValue) { + + if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { + return mValue[0]; + } + return defaultValue; + } + + // Subclasses can adjust these values: + protected int mRedSize; + protected int mGreenSize; + protected int mBlueSize; + protected int mAlphaSize; + protected int mDepthSize; + protected int mStencilSize; +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java new file mode 100644 index 0000000000..4f1e9a696b --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java @@ -0,0 +1,81 @@ +/*************************************************************************/ +/* RegularContextFactory.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.regular; + +import android.opengl.GLSurfaceView; +import android.util.Log; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.utils.GLUtils; + +/** + * Factory used to setup the opengl context for pancake games. + */ +public class RegularContextFactory implements GLSurfaceView.EGLContextFactory { + private static final String TAG = RegularContextFactory.class.getSimpleName(); + + private static final int _EGL_CONTEXT_FLAGS_KHR = 0x30FC; + private static final int _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR = 0x00000001; + + 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 (GLUtils.use_gl3 && !driver_name.equals("GLES3")) { + GLUtils.use_gl3 = false; + } + if (GLUtils.use_gl3) + Log.w(TAG, "creating OpenGL ES 3.0 context :"); + else + Log.w(TAG, "creating OpenGL ES 2.0 context :"); + + GLUtils.checkEglError(TAG, "Before eglCreateContext", egl); + EGLContext context; + if (GLUtils.use_debug_opengl) { + int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; + int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; + context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, GLUtils.use_gl3 ? attrib_list3 : attrib_list2); + } else { + int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; + int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE }; + context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, GLUtils.use_gl3 ? attrib_list3 : attrib_list2); + } + GLUtils.checkEglError(TAG, "After eglCreateContext", egl); + return context; + } + + public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { + egl.eglDestroyContext(display, context); + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java new file mode 100644 index 0000000000..f5718ef2b3 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* RegularFallbackConfigChooser.java */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.godot.xr.regular; + +import android.util.Log; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; +import org.godotengine.godot.utils.GLUtils; + +/* Fallback if 32bit View is not supported*/ +public class RegularFallbackConfigChooser extends RegularConfigChooser { + + private static final String TAG = RegularFallbackConfigChooser.class.getSimpleName(); + + private RegularConfigChooser fallback; + + public RegularFallbackConfigChooser(int r, int g, int b, int a, int depth, int stencil, RegularConfigChooser fallback) { + super(r, g, b, a, depth, stencil); + this.fallback = fallback; + } + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { + EGLConfig ec = super.chooseConfig(egl, display, configs); + if (ec == null) { + Log.w(TAG, "Trying ConfigChooser fallback"); + ec = fallback.chooseConfig(egl, display, configs); + GLUtils.use_32 = false; + } + return ec; + } +} 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 differdeleted file mode 100644 index c61c440636..0000000000 --- a/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png +++ /dev/null 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/settings.gradle b/platform/android/java/settings.gradle new file mode 100644 index 0000000000..f6921c70aa --- /dev/null +++ b/platform/android/java/settings.gradle @@ -0,0 +1,5 @@ +// Configure the root project. +rootProject.name = "Godot" + +include ':app' +include ':lib' 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/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/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/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/org/godotengine/godot/GodotLib.java b/platform/android/java/src/org/godotengine/godot/GodotLib.java deleted file mode 100644 index 45eb188327..0000000000 --- a/platform/android/java/src/org/godotengine/godot/GodotLib.java +++ /dev/null @@ -1,74 +0,0 @@ -/*************************************************************************/ -/* GodotLib.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; - -// Wrapper for native library - -public class GodotLib { - - public static GodotIO io; - - static { - System.loadLibrary("godot_android"); - } - - /** - * @param width the current view width - * @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 setup(String[] p_cmdline); - public static native void resize(int width, int height, boolean reload); - public static native void newcontext(boolean p_32_bits); - public static native void back(); - public static native void step(); - public static native void touch(int what, int pointer, int howmany, int[] arr); - public static native void accelerometer(float x, float y, float z); - public static native void gravity(float x, float y, float z); - public static native void magnetometer(float x, float y, float z); - public static native void gyroscope(float x, float y, float z); - public static native void key(int p_scancode, int p_unicode_char, boolean p_pressed); - public static native void joybutton(int p_device, int p_but, boolean p_pressed); - public static native void joyaxis(int p_device, int p_axis, float p_value); - public static native void joyhat(int p_device, int p_hat_x, int p_hat_y); - public static native void joyconnectionchanged(int p_device, boolean p_connected, String p_name); - public static native void focusin(); - public static native void focusout(); - public static native void audio(); - public static native void singleton(String p_name, Object p_object); - public static native void method(String p_sname, String p_name, String p_ret, String[] p_params); - public static native String getGlobal(String p_key); - public static native void callobject(int p_ID, String p_method, Object[] p_params); - public static native void calldeferred(int p_ID, String p_method, Object[] p_params); - - public static native void setVirtualKeyboardHeight(int p_height); -} diff --git a/platform/android/java/src/org/godotengine/godot/GodotView.java b/platform/android/java/src/org/godotengine/godot/GodotView.java deleted file mode 100644 index 23723c2696..0000000000 --- a/platform/android/java/src/org/godotengine/godot/GodotView.java +++ /dev/null @@ -1,685 +0,0 @@ -/*************************************************************************/ -/* GodotView.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; -import android.content.Context; -import android.graphics.PixelFormat; -import android.opengl.GLSurfaceView; -import android.util.AttributeSet; -import android.util.Log; -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; -import java.util.Comparator; -import java.util.List; -import javax.microedition.khronos.egl.EGL10; -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; -/** - * A simple GLSurfaceView sub-class that demonstrate how to perform - * OpenGL ES 2.0 rendering into a GL Surface. Note the following important - * details: - * - * - The class must use a custom context factory to enable 2.0 rendering. - * See ContextFactory class definition below. - * - * - The class must use a custom EGLConfigChooser to be able to select - * an EGLConfig that supports 2.0. This is done by providing a config - * specification to eglChooseConfig() that has the attribute - * EGL10.ELG_RENDERABLE_TYPE containing the EGL_OPENGL_ES2_BIT flag - * set. See ConfigChooser class definition below. - * - * - The class must select the surface's format, then choose an EGLConfig - * that matches it exactly (with regards to red/green/blue/alpha channels - * bit depths). Failure to do so would result in an EGL_BAD_MATCH error. - */ -public class GodotView extends GLSurfaceView implements InputDeviceListener { - - private static String TAG = "GodotView"; - private static final boolean DEBUG = false; - private static Context ctx; - - private static GodotIO io; - private static boolean firsttime = true; - private static boolean use_gl3 = false; - private static boolean use_32 = false; - - private Godot activity; - - private InputManagerCompat mInputManager; - public GodotView(Context context, GodotIO p_io, boolean p_use_gl3, boolean p_use_32_bits, Godot p_activity) { - super(context); - ctx = context; - io = p_io; - use_gl3 = p_use_gl3; - use_32 = p_use_32_bits; - - activity = p_activity; - - if (!p_io.needsReloadHooks()) { - //will only work on SDK 11+!! - setPreserveEGLContextOnPause(true); - } - mInputManager = InputManagerCompat.Factory.getInputManager(this.getContext()); - mInputManager.registerInputDeviceListener(this, null); - init(false, 16, 0); - } - - public GodotView(Context context, boolean translucent, int depth, int stencil) { - super(context); - init(translucent, depth, stencil); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - super.onTouchEvent(event); - return activity.gotTouchEvent(event); - }; - - public int get_godot_button(int keyCode) { - - int button = 0; - switch (keyCode) { - case KeyEvent.KEYCODE_BUTTON_A: // Android A is SNES B - button = 0; - break; - case KeyEvent.KEYCODE_BUTTON_B: - button = 1; - break; - case KeyEvent.KEYCODE_BUTTON_X: // Android X is SNES Y - button = 2; - break; - case KeyEvent.KEYCODE_BUTTON_Y: - button = 3; - break; - case KeyEvent.KEYCODE_BUTTON_L1: - button = 9; - break; - case KeyEvent.KEYCODE_BUTTON_L2: - button = 15; - break; - case KeyEvent.KEYCODE_BUTTON_R1: - button = 10; - break; - case KeyEvent.KEYCODE_BUTTON_R2: - button = 16; - break; - case KeyEvent.KEYCODE_BUTTON_SELECT: - button = 4; - break; - case KeyEvent.KEYCODE_BUTTON_START: - button = 6; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBL: - button = 7; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBR: - button = 8; - break; - case KeyEvent.KEYCODE_DPAD_UP: - button = 11; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - button = 12; - break; - case KeyEvent.KEYCODE_DPAD_LEFT: - button = 13; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - button = 14; - break; - case KeyEvent.KEYCODE_BUTTON_C: - button = 17; - break; - case KeyEvent.KEYCODE_BUTTON_Z: - button = 18; - break; - - default: - button = keyCode - KeyEvent.KEYCODE_BUTTON_1 + 20; - break; - }; - return button; - }; - - private static class joystick { - public int device_id; - public String name; - public ArrayList<InputDevice.MotionRange> axes; - public ArrayList<InputDevice.MotionRange> hats; - } - - private static class RangeComparator implements Comparator<InputDevice.MotionRange> { - @Override - public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { - return arg0.getAxis() - arg1.getAxis(); - } - } - - ArrayList<joystick> joy_devices = new ArrayList<joystick>(); - - private int find_joy_device(int device_id) { - for (int i = 0; i < joy_devices.size(); i++) { - if (joy_devices.get(i).device_id == device_id) { - return i; - } - } - onInputDeviceAdded(device_id); - return joy_devices.size() - 1; - } - - @Override - public void onInputDeviceAdded(int deviceId) { - joystick joy = new joystick(); - joy.device_id = deviceId; - final int id = joy_devices.size(); - InputDevice device = mInputManager.getInputDevice(deviceId); - final String name = device.getName(); - 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); - } - } - joy_devices.add(joy); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyconnectionchanged(id, true, name); - } - }); - } - - @Override - public void onInputDeviceRemoved(int deviceId) { - final int id = find_joy_device(deviceId); - joy_devices.remove(id); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyconnectionchanged(id, false, ""); - } - }); - } - - @Override - public void onInputDeviceChanged(int deviceId) { - } - @Override - public boolean onKeyUp(final int keyCode, KeyEvent event) { - - if (keyCode == KeyEvent.KEYCODE_BACK) { - return true; - } - - if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { - return super.onKeyUp(keyCode, event); - }; - - int source = event.getSource(); - if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { - - final int button = get_godot_button(keyCode); - final int device = find_joy_device(event.getDeviceId()); - - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joybutton(device, button, false); - } - }); - return true; - } else { - final int chr = event.getUnicodeChar(0); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.key(keyCode, chr, false); - } - }); - }; - return super.onKeyUp(keyCode, event); - }; - - @Override - public boolean onKeyDown(final int keyCode, KeyEvent event) { - - if (keyCode == KeyEvent.KEYCODE_BACK) { - activity.onBackPressed(); - // press 'back' button should not terminate program - //normal handle 'back' event in game logic - return true; - } - - if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { - return super.onKeyDown(keyCode, event); - }; - - int source = event.getSource(); - //Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD))); - - if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { - - if (event.getRepeatCount() > 0) // ignore key echo - return true; - final int button = get_godot_button(keyCode); - final int device = find_joy_device(event.getDeviceId()); - - //Log.e(TAG, String.format("joy button down! button %x, %d, device %d", keyCode, button, device)); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joybutton(device, button, true); - } - }); - return true; - - } else { - final int chr = event.getUnicodeChar(0); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.key(keyCode, chr, true); - } - }); - }; - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - - if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) { - - final int device_id = find_joy_device(event.getDeviceId()); - joystick joy = joy_devices.get(device_id); - - for (int i = 0; i < joy.axes.size(); i++) { - InputDevice.MotionRange range = joy.axes.get(i); - final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f; - //Log.e(TAG, String.format("axis event: %d, value %f", i, value)); - final int idx = i; - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyaxis(device_id, idx, value); - } - }); - } - - for (int i = 0; i < joy.hats.size(); i += 2) { - final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis())); - final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis())); - //Log.e(TAG, String.format("HAT EVENT %d, %d", hatX, hatY)); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyhat(device_id, hatX, hatY); - } - }); - } - return true; - }; - - return super.onGenericMotionEvent(event); - }; - - private void init(boolean translucent, int depth, int stencil) { - - this.setFocusableInTouchMode(true); - /* By default, GLSurfaceView() creates a RGB_565 opaque surface. - * If we want a translucent one, we should change the surface's - * format here, using PixelFormat.TRANSLUCENT for GL Surfaces - * is interpreted as any 32-bit surface with alpha by SurfaceFlinger. - */ - if (translucent) { - this.getHolder().setFormat(PixelFormat.TRANSLUCENT); - } - - /* Setup the context factory for 2.0 rendering. - * See ContextFactory class definition below - */ - setEGLContextFactory(new ContextFactory()); - - /* We need to choose an EGLConfig that matches the format of - * our surface exactly. This is going to be done in our - * custom config chooser. See ConfigChooser class definition - * below. - */ - - if (use_32) { - setEGLConfigChooser(translucent ? - new FallbackConfigChooser(8, 8, 8, 8, 24, stencil, new ConfigChooser(8, 8, 8, 8, 16, stencil)) : - new FallbackConfigChooser(8, 8, 8, 8, 24, stencil, new ConfigChooser(5, 6, 5, 0, 16, stencil))); - - } else { - setEGLConfigChooser(translucent ? - new ConfigChooser(8, 8, 8, 8, 16, stencil) : - new ConfigChooser(5, 6, 5, 0, 16, stencil)); - } - - /* Set the renderer responsible for frame rendering */ - setRenderer(new Renderer()); - } - - private static class ContextFactory implements GLSurfaceView.EGLContextFactory { - private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { - if (use_gl3) - Log.w(TAG, "creating OpenGL ES 3.0 context :"); - else - Log.w(TAG, "creating OpenGL ES 2.0 context :"); - - checkEglError("Before eglCreateContext", egl); - int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; - int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE }; - EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, use_gl3 ? attrib_list3 : attrib_list2); - checkEglError("After eglCreateContext", egl); - return context; - } - - public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { - egl.eglDestroyContext(display, context); - } - } - - private static void checkEglError(String prompt, EGL10 egl) { - int error; - while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) { - Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error)); - } - } - /* Fallback if 32bit View is not supported*/ - private static class FallbackConfigChooser extends ConfigChooser { - private ConfigChooser fallback; - - public FallbackConfigChooser(int r, int g, int b, int a, int depth, int stencil, ConfigChooser fallback) { - super(r, g, b, a, depth, stencil); - this.fallback = fallback; - } - - @Override - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { - EGLConfig ec = super.chooseConfig(egl, display, configs); - if (ec == null) { - Log.w(TAG, "Trying ConfigChooser fallback"); - ec = fallback.chooseConfig(egl, display, configs); - use_32 = false; - } - return ec; - } - } - - private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser { - - public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) { - mRedSize = r; - mGreenSize = g; - mBlueSize = b; - mAlphaSize = a; - mDepthSize = depth; - mStencilSize = stencil; - } - - /* This EGL config specification is used to specify 2.0 rendering. - * We use a minimum size of 4 bits for red/green/blue, but will - * 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 - }; - - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { - - /* Get the number of minimally matching EGL configurations - */ - int[] num_config = new int[1]; - egl.eglChooseConfig(display, use_gl3 ? s_configAttribs3 : s_configAttribs2, null, 0, num_config); - - int numConfigs = num_config[0]; - - if (numConfigs <= 0) { - throw new IllegalArgumentException("No configs match configSpec"); - } - - /* Allocate then read the array of minimally matching EGL configs - */ - EGLConfig[] configs = new EGLConfig[numConfigs]; - egl.eglChooseConfig(display, use_gl3 ? s_configAttribs3 : s_configAttribs2, configs, numConfigs, num_config); - - if (DEBUG) { - printConfigs(egl, display, configs); - } - /* Now return the "best" one - */ - return chooseConfig(egl, display, configs); - } - - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs) { - for (EGLConfig config : configs) { - int d = findConfigAttrib(egl, display, config, - EGL10.EGL_DEPTH_SIZE, 0); - int s = findConfigAttrib(egl, display, config, - EGL10.EGL_STENCIL_SIZE, 0); - - // We need at least mDepthSize and mStencilSize bits - if (d < mDepthSize || s < mStencilSize) - continue; - - // We want an *exact* match for red/green/blue/alpha - int r = findConfigAttrib(egl, display, config, - EGL10.EGL_RED_SIZE, 0); - int g = findConfigAttrib(egl, display, config, - EGL10.EGL_GREEN_SIZE, 0); - int b = findConfigAttrib(egl, display, config, - EGL10.EGL_BLUE_SIZE, 0); - int a = findConfigAttrib(egl, display, config, - EGL10.EGL_ALPHA_SIZE, 0); - - if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize) - return config; - } - return null; - } - - private int findConfigAttrib(EGL10 egl, EGLDisplay display, - EGLConfig config, int attribute, int defaultValue) { - - if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { - return mValue[0]; - } - return defaultValue; - } - - private void printConfigs(EGL10 egl, EGLDisplay display, - EGLConfig[] configs) { - int numConfigs = configs.length; - Log.w(TAG, String.format("%d configurations", numConfigs)); - for (int i = 0; i < numConfigs; i++) { - Log.w(TAG, String.format("Configuration %d:\n", i)); - printConfig(egl, display, configs[i]); - } - } - - private void printConfig(EGL10 egl, EGLDisplay display, - EGLConfig config) { - int[] attributes = { - EGL10.EGL_BUFFER_SIZE, - EGL10.EGL_ALPHA_SIZE, - EGL10.EGL_BLUE_SIZE, - EGL10.EGL_GREEN_SIZE, - EGL10.EGL_RED_SIZE, - EGL10.EGL_DEPTH_SIZE, - EGL10.EGL_STENCIL_SIZE, - EGL10.EGL_CONFIG_CAVEAT, - EGL10.EGL_CONFIG_ID, - EGL10.EGL_LEVEL, - EGL10.EGL_MAX_PBUFFER_HEIGHT, - EGL10.EGL_MAX_PBUFFER_PIXELS, - EGL10.EGL_MAX_PBUFFER_WIDTH, - EGL10.EGL_NATIVE_RENDERABLE, - EGL10.EGL_NATIVE_VISUAL_ID, - EGL10.EGL_NATIVE_VISUAL_TYPE, - 0x3030, // EGL10.EGL_PRESERVED_RESOURCES, - EGL10.EGL_SAMPLES, - EGL10.EGL_SAMPLE_BUFFERS, - EGL10.EGL_SURFACE_TYPE, - EGL10.EGL_TRANSPARENT_TYPE, - EGL10.EGL_TRANSPARENT_RED_VALUE, - EGL10.EGL_TRANSPARENT_GREEN_VALUE, - EGL10.EGL_TRANSPARENT_BLUE_VALUE, - 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB, - 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA, - 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL, - 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL, - EGL10.EGL_LUMINANCE_SIZE, - EGL10.EGL_ALPHA_MASK_SIZE, - EGL10.EGL_COLOR_BUFFER_TYPE, - EGL10.EGL_RENDERABLE_TYPE, - 0x3042 // EGL10.EGL_CONFORMANT - }; - String[] names = { - "EGL_BUFFER_SIZE", - "EGL_ALPHA_SIZE", - "EGL_BLUE_SIZE", - "EGL_GREEN_SIZE", - "EGL_RED_SIZE", - "EGL_DEPTH_SIZE", - "EGL_STENCIL_SIZE", - "EGL_CONFIG_CAVEAT", - "EGL_CONFIG_ID", - "EGL_LEVEL", - "EGL_MAX_PBUFFER_HEIGHT", - "EGL_MAX_PBUFFER_PIXELS", - "EGL_MAX_PBUFFER_WIDTH", - "EGL_NATIVE_RENDERABLE", - "EGL_NATIVE_VISUAL_ID", - "EGL_NATIVE_VISUAL_TYPE", - "EGL_PRESERVED_RESOURCES", - "EGL_SAMPLES", - "EGL_SAMPLE_BUFFERS", - "EGL_SURFACE_TYPE", - "EGL_TRANSPARENT_TYPE", - "EGL_TRANSPARENT_RED_VALUE", - "EGL_TRANSPARENT_GREEN_VALUE", - "EGL_TRANSPARENT_BLUE_VALUE", - "EGL_BIND_TO_TEXTURE_RGB", - "EGL_BIND_TO_TEXTURE_RGBA", - "EGL_MIN_SWAP_INTERVAL", - "EGL_MAX_SWAP_INTERVAL", - "EGL_LUMINANCE_SIZE", - "EGL_ALPHA_MASK_SIZE", - "EGL_COLOR_BUFFER_TYPE", - "EGL_RENDERABLE_TYPE", - "EGL_CONFORMANT" - }; - int[] value = new int[1]; - for (int i = 0; i < attributes.length; i++) { - int attribute = attributes[i]; - String name = names[i]; - if (egl.eglGetConfigAttrib(display, config, attribute, value)) { - Log.w(TAG, String.format(" %s: %d\n", name, value[0])); - } else { - // Log.w(TAG, String.format(" %s: failed\n", name)); - while (egl.eglGetError() != EGL10.EGL_SUCCESS) - ; - } - } - } - - // Subclasses can adjust these values: - protected int mRedSize; - protected int mGreenSize; - protected int mBlueSize; - protected int mAlphaSize; - protected int mDepthSize; - protected int mStencilSize; - private int[] mValue = new int[1]; - } - - private static class Renderer implements GLSurfaceView.Renderer { - - public void onDrawFrame(GL10 gl) { - GodotLib.step(); - for (int i = 0; i < Godot.singleton_count; i++) { - Godot.singletons[i].onGLDrawFrame(gl); - } - } - - public void onSurfaceChanged(GL10 gl, int width, int height) { - - GodotLib.resize(width, height, !firsttime); - firsttime = false; - for (int i = 0; i < Godot.singleton_count; i++) { - Godot.singletons[i].onGLSurfaceChanged(gl, width, height); - } - } - - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - GodotLib.newcontext(use_32); - } - } -} 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_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/dir_access_android.h b/platform/android/java_godot_io_wrapper.h index 3ac0bd6332..100e50fd66 100644 --- a/platform/android/dir_access_android.h +++ b/platform/android/java_godot_io_wrapper.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* dir_access_android.h */ +/* java_godot_io_wrapper.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,53 +28,61 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef DIR_ACCESS_ANDROID_H -#define DIR_ACCESS_ANDROID_H +// note, swapped java and godot around in the file name so all the java +// wrappers are together -#ifdef ANDROID_NATIVE_ACTIVITY +#ifndef JAVA_GODOT_IO_WRAPPER_H +#define JAVA_GODOT_IO_WRAPPER_H -#include "core/os/dir_access.h" -#include <android/asset_manager.h> #include <android/log.h> -#include <android_native_app_glue.h> -#include <stdio.h> - -class DirAccessAndroid : public DirAccess { - - AAssetDir *aad; - String current_dir; - String current; - - static DirAccess *create_fs(); +#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: - virtual Error list_dir_begin(); ///< This starts dir listing - virtual String get_next(); - virtual bool current_is_dir() const; - virtual bool current_is_hidden() const; - virtual void list_dir_end(); ///< - - virtual int get_drive_count(); - virtual String get_drive(int p_drive); - - virtual Error change_dir(String p_dir); ///< can be relative or absolute, return false on success - virtual String get_current_dir(); ///< return current dir location - - virtual bool file_exists(String p_file); - - virtual Error make_dir(String p_dir); - - virtual Error rename(String p_from, String p_to); - virtual Error remove(String p_name); - - //virtual FileType get_file_type() const; - size_t get_space_left(); - - static void make_default(); - - DirAccessAndroid(); - ~DirAccessAndroid(); + 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 -#endif // DIR_ACCESS_ANDROID_H +#endif /* !JAVA_GODOT_IO_WRAPPER_H */ diff --git a/platform/android/java_glue.cpp b/platform/android/java_godot_lib_jni.cpp index 6cf49758bc..30676783db 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,9 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef ANDROID_NATIVE_ACTIVITY +#include "java_godot_lib_jni.h" +#include "java_godot_io_wrapper.h" +#include "java_godot_wrapper.h" -#include "java_glue.h" #include "android/asset_manager_jni.h" #include "audio_driver_jandroid.h" #include "core/engine.h" @@ -43,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 { @@ -225,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; @@ -243,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;") { @@ -254,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); } @@ -268,11 +272,11 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { return ret; }; - if (name == "java.lang.Integer") { + if (name == "java.lang.Integer" || name == "java.lang.Long") { jclass nclass = env->FindClass("java/lang/Number"); - jmethodID intValue = env->GetMethodID(nclass, "intValue", "()I"); - int ret = env->CallIntMethod(obj, intValue); + jmethodID longValue = env->GetMethodID(nclass, "longValue", "()J"); + jlong ret = env->CallLongMethod(obj, longValue); return ret; }; @@ -285,7 +289,7 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { PoolVector<int>::Write w = sarr.write(); env->GetIntArrayRegion(arr, 0, fCount, w.ptr()); - w = PoolVector<int>::Write(); + w.release(); return sarr; }; @@ -298,7 +302,7 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { PoolVector<uint8_t>::Write w = sarr.write(); env->GetByteArrayRegion(arr, 0, fCount, reinterpret_cast<signed char *>(w.ptr())); - w = PoolVector<uint8_t>::Write(); + w.release(); return sarr; }; @@ -489,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: { @@ -510,7 +514,7 @@ public: PoolVector<int>::Write w = sarr.write(); env->GetIntArrayRegion(arr, 0, fCount, w.ptr()); - w = PoolVector<int>::Write(); + w.release(); ret = sarr; env->DeleteLocalRef(arr); } break; @@ -524,7 +528,7 @@ public: PoolVector<float>::Write w = sarr.write(); env->GetFloatArrayRegion(arr, 0, fCount, w.ptr()); - w = PoolVector<float>::Write(); + w.release(); ret = sarr; env->DeleteLocalRef(arr); } break; @@ -589,254 +593,73 @@ TST tst; static bool initialized = false; static int step = 0; -static bool resized = false; -static bool resized_reload = false; + 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 - - 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"); - - jclass clsio = env->FindClass("org/godotengine/godot/Godot"); - 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"); - } + // 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)); - 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, jobject obj, jobject activity) { + // 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; } @@ -848,46 +671,28 @@ 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); - - if (!singletonClass) { - - ERR_EXPLAIN("Couldn't find singleton for class: " + m); - ERR_CONTINUE(!singletonClass); - } - //singletonClass=(jclass)env->NewGlobalRef(singletonClass); + ERR_CONTINUE_MSG(!singletonClass, "Couldn't find singleton for class: " + m + "."); jmethodID initialize = env->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lorg/godotengine/godot/Godot$SingletonBase;"); + ERR_CONTINUE_MSG(!initialize, "Couldn't find proper initialize function 'public static Godot.SingletonBase Class::initialize(Activity p_activity)' initializer for singleton class: " + m + "."); - if (!initialize) { - - ERR_EXPLAIN("Couldn't find proper initialize function 'public static Godot.SingletonBase Class::initialize(Activity p_activity)' initializer for singleton class: " + m); - ERR_CONTINUE(!initialize); - } - jobject obj = env->CallStaticObjectMethod(singletonClass, initialize, _godot_instance); - jobject gob = env->NewGlobalRef(obj); + jobject obj = env->CallStaticObjectMethod(singletonClass, initialize, godot_java->get_activity()); + env->NewGlobalRef(obj); } } } @@ -896,12 +701,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++) { @@ -909,12 +716,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); } @@ -922,33 +736,29 @@ 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)); - - /*input_mutex->lock(); - resized=true; - if (reload) - resized_reload=true; - new_size=Size2(width,height); - input_mutex->unlock();*/ } 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 + } } } @@ -960,6 +770,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, @@ -979,18 +792,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() == true) { + 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); } } @@ -1325,7 +1133,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); } } @@ -1398,7 +1206,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; @@ -1475,21 +1283,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 = "("; @@ -1498,9 +1306,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 += ")"; @@ -1523,7 +1331,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); @@ -1555,7 +1363,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]; @@ -1573,7 +1381,27 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv * env->PopLocalFrame(NULL); } -//Main::cleanup(); +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(); + } +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) { + if (step == 0) + return; -//return os.get_exit_code(); -#endif + if (os_android->get_main_loop()) { + os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_RESUMED); + } +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz) { + if (step == 0) + return; + + if (os_android->get_main_loop()) { + os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_PAUSED); + } +} diff --git a/platform/android/java_glue.h b/platform/android/java_godot_lib_jni.h index d433b5f0d8..11bda94f8d 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,18 +28,19 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef ANDROID_NATIVE_ACTIVITY - -#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, jobject obj, jobject activity); 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); @@ -62,7 +63,9 @@ 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); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz); } -#endif -#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..8194ee6ecf --- /dev/null +++ b/platform/android/java_godot_wrapper.cpp @@ -0,0 +1,232 @@ +/*************************************************************************/ +/* 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"); + _get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;"); + _is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z"); + _vibrate = p_env->GetMethodID(cls, "vibrate", "(I)V"); + _get_input_fallback_mapping = p_env->GetMethodID(cls, "getInputFallbackMapping", "()Ljava/lang/String;"); +} + +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(); + } +} + +String GodotJavaWrapper::get_input_fallback_mapping() { + if (_get_input_fallback_mapping) { + JNIEnv *env = ThreadAndroid::get_env(); + jstring fallback_mapping = (jstring)env->CallObjectMethod(godot_instance, _get_input_fallback_mapping); + return jstring_to_string(fallback_mapping, 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); + } +} + +jobject GodotJavaWrapper::get_surface() { + if (_get_surface) { + JNIEnv *env = ThreadAndroid::get_env(); + return env->CallObjectMethod(godot_instance, _get_surface); + } else { + return NULL; + } +} + +bool GodotJavaWrapper::is_activity_resumed() { + if (_is_activity_resumed) { + JNIEnv *env = ThreadAndroid::get_env(); + return env->CallBooleanMethod(godot_instance, _is_activity_resumed); + } else { + return false; + } +} + +void GodotJavaWrapper::vibrate(int p_duration_ms) { + if (_vibrate) { + JNIEnv *env = ThreadAndroid::get_env(); + env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms); + } +} diff --git a/platform/android/java/src/org/godotengine/godot/payments/GenericConsumeTask.java b/platform/android/java_godot_wrapper.h index 8b48193ae2..b1bd9b7f48 100644 --- a/platform/android/java/src/org/godotengine/godot/payments/GenericConsumeTask.java +++ b/platform/android/java_godot_wrapper.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* GenericConsumeTask.java */ +/* java_godot_wrapper.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,52 +28,64 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.payments; +// note, swapped java and godot around in the file name so all the java +// wrappers are together -import com.android.vending.billing.IInAppBillingService; +#ifndef JAVA_GODOT_WRAPPER_H +#define JAVA_GODOT_WRAPPER_H -import android.content.Context; -import android.os.AsyncTask; -import android.os.RemoteException; -import android.util.Log; +#include <android/log.h> +#include <jni.h> -abstract public class GenericConsumeTask extends AsyncTask<String, String, String> { +#include "string_android.h" - private Context context; - private IInAppBillingService mService; +// Class that makes functions in java/src/org/godotengine/godot/Godot.java callable from C++ +class GodotJavaWrapper { +private: + jobject godot_instance; + jclass cls; - 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; - } + 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; + jmethodID _get_surface = 0; + jmethodID _is_activity_resumed = 0; + jmethodID _vibrate = 0; + jmethodID _get_input_fallback_mapping = 0; - private String sku; - private String receipt; - private String signature; - private String token; +public: + GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance); + ~GodotJavaWrapper(); - @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; - } + jobject get_activity(); + jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = NULL); - protected void onPostExecute(String sarasa) { - onSuccess(sku, receipt, signature, token); - } + jobject get_class_loader(); - abstract public void onSuccess(String sku, String receipt, String signature, String token); -} + 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(); + jobject get_surface(); + bool is_activity_resumed(); + void vibrate(int p_duration_ms); + String get_input_fallback_mapping(); +}; + +#endif /* !JAVA_GODOT_WRAPPER_H */ diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 484ca4fff8..49ab0ea84a 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 */ @@ -39,18 +39,16 @@ #include "file_access_android.h" #include "main/main.h" #include "servers/visual/visual_server_raster.h" -//#include "servers/visual/visual_server_wrap_mt.h" +#include "servers/visual/visual_server_wrap_mt.h" -#ifdef ANDROID_NATIVE_ACTIVITY -#include "dir_access_android.h" -#include "file_access_android.h" -#else #include "dir_access_jandroid.h" #include "file_access_jandroid.h" -#endif #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) { @@ -73,8 +71,7 @@ const char *OS_Android::get_video_driver_name(int p_driver) const { case VIDEO_DRIVER_GLES2: return "GLES2"; } - ERR_EXPLAIN("Invalid video driver index " + itos(p_driver)); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Invalid video driver index: " + itos(p_driver) + "."); } int OS_Android::get_audio_driver_count() const { @@ -90,18 +87,6 @@ void OS_Android::initialize_core() { OS_Unix::initialize_core(); -#ifdef ANDROID_NATIVE_ACTIVITY - - FileAccess::make_default<FileAccessAndroid>(FileAccess::ACCESS_RESOURCES); - FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA); - FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM); - //FileAccessBufferedFA<FileAccessUnix>::make_default(); - DirAccess::make_default<DirAccessAndroid>(DirAccess::ACCESS_RESOURCES); - DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA); - DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM); - -#else - if (use_apk_expansion) FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES); else { @@ -121,8 +106,6 @@ void OS_Android::initialize_core() { DirAccess::make_default<DirAccessJAndroid>(DirAccess::ACCESS_RESOURCES); DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA); DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM); - -#endif } void OS_Android::set_opengl_extensions(const char *p_gl_extensions) { @@ -137,20 +120,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; @@ -161,8 +143,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; @@ -183,20 +164,21 @@ Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int video_driver_index = p_video_driver; visual_server = memnew(VisualServerRaster); - /* if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { - + if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { visual_server = memnew(VisualServerWrapMT(visual_server, false)); - };*/ + } visual_server->init(); - // visual_server->cursor_set_visible(false, 0); AudioDriverManager::initialize(p_audio_driver); input = memnew(InputDefault); - input->set_fallback_mapping("Default Android Gamepad"); + input->set_fallback_mapping(godot_java->get_input_fallback_mapping()); + + ///@TODO implement a subclass for Android and instantiate that instead + camera_server = memnew(CameraServer); - //power_manager = memnew(power_android); + //power_manager = memnew(PowerAndroid); return OK; } @@ -213,22 +195,34 @@ void OS_Android::delete_main_loop() { } void OS_Android::finalize() { + + memdelete(camera_server); + 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) { p_library_handle = dlopen(p_path.utf8().get_data(), RTLD_NOW); - if (!p_library_handle) { - ERR_EXPLAIN("Can't open dynamic library: " + p_path + ". Error: " + dlerror()); - ERR_FAIL_V(ERR_CANT_OPEN); - } + ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + "."); return OK; } @@ -259,6 +253,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) { @@ -277,9 +275,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 { @@ -287,7 +283,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"; } @@ -302,14 +298,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) @@ -520,18 +508,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"); @@ -540,9 +526,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"); @@ -569,45 +555,41 @@ 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 { - return "/"; //android has it's own filesystem for resources inside the APK + return "/"; //android has its own filesystem for resources inside the APK } 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(); @@ -615,22 +597,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 { @@ -638,8 +614,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]; @@ -666,44 +642,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) { - if (video_play_func) - video_play_func(p_path); +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 + + godot_io_java->play_video(p_path); return OK; } -bool OS_Android::native_video_is_playing() { - if (video_is_playing_func) - return video_is_playing_func(); - return false; +bool OS_Android::native_video_is_playing() const { + + 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) { @@ -725,8 +700,12 @@ String OS_Android::get_joy_guid(int p_device) const { return input->get_joy_guid_remapped(p_device); } +void OS_Android::vibrate_handheld(int p_duration_ms) { + godot_java->vibrate(p_duration_ms); +} + 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; } @@ -746,7 +725,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; @@ -754,38 +733,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 9594c6fdf4..a17941f7c0 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 */ @@ -39,36 +39,11 @@ #include "main/input_default.h" //#include "power_android.h" #include "servers/audio_server.h" +#include "servers/camera_server.h" #include "servers/visual/rasterizer.h" -#ifdef ANDROID_NATIVE_ACTIVITY -#include <android/log.h> -#include <android/sensor.h> -#include <android_native_app_glue.h> -#endif - -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: @@ -96,17 +71,15 @@ 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; VisualServer *visual_server; + CameraServer *camera_server; + mutable String data_dir_cache; //AudioDriverAndroid audio_driver_android; @@ -118,29 +91,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: @@ -164,8 +119,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); @@ -184,14 +142,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(); @@ -209,10 +164,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); @@ -237,17 +190,18 @@ public: void process_event(Ref<InputEvent> p_event); void init_video_mode(int p_video_width, int p_video_height); - virtual Error native_video_play(String p_path, float p_volume); - virtual bool native_video_is_playing(); + virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); + virtual bool native_video_is_playing() const; virtual void native_video_pause(); virtual void native_video_stop(); virtual bool is_joy_known(int p_device); virtual String get_joy_guid(int p_device) const; void joy_connection_changed(int p_device, bool p_connected, String p_name); + void vibrate_handheld(int p_duration_ms); 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 51283183df..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 */ @@ -98,7 +98,7 @@ ANativeWindow *Android_JNI_GetNativeWindow(void) { return anw; } -/* +/* * CODE CHUNK IMPORTED FROM SDL 2.0 * returns 0 on success or -1 on error (others undefined then) * returns truthy or falsy value in plugged, charged and battery @@ -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/sign.sh b/platform/android/sign.sh deleted file mode 100755 index 830da05a37..0000000000 --- a/platform/android/sign.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore my-release-key.keystore "$1" reduz - -echo "" -echo "" -echo "Checking if APK is verified..." -jarsigner -verify "$1" -verbose -certs - diff --git a/platform/android/string_android.h b/platform/android/string_android.h new file mode 100644 index 0000000000..fe627a3e0c --- /dev/null +++ b/platform/android/string_android.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* string_android.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. */ +/*************************************************************************/ + +#ifndef STRING_ANDROID_H +#define STRING_ANDROID_H +#include "core/ustring.h" +#include "thread_jandroid.h" +#include <jni.h> + +/** + * 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..0b6e1f4b4a 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 */ @@ -31,10 +31,6 @@ #ifndef THREAD_POSIX_H #define THREAD_POSIX_H -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - #include "core/os/thread.h" #include <jni.h> #include <pthread.h> |