no message
This commit is contained in:
133
cocos/platform/android/AndroidKeyCodes.cpp
Normal file
133
cocos/platform/android/AndroidKeyCodes.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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 <android/input.h>
|
||||
#include <android/keycodes.h>
|
||||
#include <unordered_map>
|
||||
#include "base/std/container/unordered_map.h"
|
||||
|
||||
namespace cc {
|
||||
ccstd::unordered_map<int32_t, const char *> androidKeyCodes = {
|
||||
{AKEYCODE_BACK, "Backspace"},
|
||||
{AKEYCODE_TAB, "Tab"},
|
||||
{AKEYCODE_ENTER, "Enter"},
|
||||
{AKEYCODE_SHIFT_LEFT, "ShiftLeft"},
|
||||
{AKEYCODE_CTRL_LEFT, "ControlLeft"},
|
||||
{AKEYCODE_ALT_LEFT, "AltLeft"},
|
||||
{AKEYCODE_SHIFT_RIGHT, "ShiftRight"},
|
||||
{AKEYCODE_CTRL_RIGHT, "ControlRight"},
|
||||
{AKEYCODE_ALT_RIGHT, "AltRight"},
|
||||
{AKEYCODE_MEDIA_PLAY_PAUSE, "Pause"},
|
||||
{AKEYCODE_CAPS_LOCK, "CapsLock"},
|
||||
{AKEYCODE_ESCAPE, "Escape"},
|
||||
{AKEYCODE_SPACE, "Space"},
|
||||
{AKEYCODE_PAGE_UP, "PageUp"},
|
||||
{AKEYCODE_PAGE_DOWN, "PageDown"},
|
||||
{AKEYCODE_MOVE_END, "End"},
|
||||
{AKEYCODE_MOVE_HOME, "Home"},
|
||||
{AKEYCODE_DPAD_LEFT, "ArrowLeft"},
|
||||
{AKEYCODE_DPAD_UP, "ArrowUp"},
|
||||
{AKEYCODE_DPAD_RIGHT, "ArrowRight"},
|
||||
{AKEYCODE_DPAD_DOWN, "ArrowDown"},
|
||||
{AKEYCODE_INSERT, "Insert"},
|
||||
{AKEYCODE_DEL, "Delete"},
|
||||
{AKEYCODE_0, "Digit0"},
|
||||
{AKEYCODE_1, "Digit1"},
|
||||
{AKEYCODE_2, "Digit2"},
|
||||
{AKEYCODE_3, "Digit3"},
|
||||
{AKEYCODE_4, "Digit4"},
|
||||
{AKEYCODE_5, "Digit5"},
|
||||
{AKEYCODE_6, "Digit6"},
|
||||
{AKEYCODE_7, "Digit7"},
|
||||
{AKEYCODE_8, "Digit8"},
|
||||
{AKEYCODE_9, "Digit9"},
|
||||
{AKEYCODE_A, "KeyA"},
|
||||
{AKEYCODE_B, "KeyB"},
|
||||
{AKEYCODE_C, "KeyC"},
|
||||
{AKEYCODE_D, "KeyD"},
|
||||
{AKEYCODE_E, "KeyE"},
|
||||
{AKEYCODE_F, "KeyF"},
|
||||
{AKEYCODE_G, "KeyG"},
|
||||
{AKEYCODE_H, "KeyH"},
|
||||
{AKEYCODE_I, "KeyI"},
|
||||
{AKEYCODE_J, "KeyJ"},
|
||||
{AKEYCODE_K, "KeyK"},
|
||||
{AKEYCODE_L, "KeyL"},
|
||||
{AKEYCODE_M, "KeyM"},
|
||||
{AKEYCODE_N, "KeyN"},
|
||||
{AKEYCODE_O, "KeyO"},
|
||||
{AKEYCODE_P, "KeyP"},
|
||||
{AKEYCODE_Q, "KeyQ"},
|
||||
{AKEYCODE_R, "KeyR"},
|
||||
{AKEYCODE_S, "KeyS"},
|
||||
{AKEYCODE_T, "KeyT"},
|
||||
{AKEYCODE_U, "KeyU"},
|
||||
{AKEYCODE_V, "KeyV"},
|
||||
{AKEYCODE_W, "KeyW"},
|
||||
{AKEYCODE_X, "KeyX"},
|
||||
{AKEYCODE_Y, "KeyY"},
|
||||
{AKEYCODE_Z, "KeyZ"},
|
||||
{AKEYCODE_NUMPAD_0, "Numpad0"},
|
||||
{AKEYCODE_NUMPAD_1, "Numpad1"},
|
||||
{AKEYCODE_NUMPAD_2, "Numpad2"},
|
||||
{AKEYCODE_NUMPAD_3, "Numpad3"},
|
||||
{AKEYCODE_NUMPAD_4, "Numpad4"},
|
||||
{AKEYCODE_NUMPAD_5, "Numpad5"},
|
||||
{AKEYCODE_NUMPAD_6, "Numpad6"},
|
||||
{AKEYCODE_NUMPAD_7, "Numpad7"},
|
||||
{AKEYCODE_NUMPAD_8, "Numpad8"},
|
||||
{AKEYCODE_NUMPAD_9, "Numpad9"},
|
||||
{AKEYCODE_NUMPAD_MULTIPLY, "NumpadMultiply"},
|
||||
{AKEYCODE_NUMPAD_ADD, "NumpadAdd"},
|
||||
{AKEYCODE_NUMPAD_SUBTRACT, "NumpadSubtract"},
|
||||
{AKEYCODE_NUMPAD_DOT, "NumpadDecimal"},
|
||||
{AKEYCODE_NUMPAD_DIVIDE, "NumpadDivide"},
|
||||
{AKEYCODE_NUMPAD_ENTER, "NumpadEnter"},
|
||||
{AKEYCODE_F1, "F1"},
|
||||
{AKEYCODE_F2, "F2"},
|
||||
{AKEYCODE_F3, "F3"},
|
||||
{AKEYCODE_F4, "F4"},
|
||||
{AKEYCODE_F5, "F5"},
|
||||
{AKEYCODE_F6, "F6"},
|
||||
{AKEYCODE_F7, "F7"},
|
||||
{AKEYCODE_F8, "F8"},
|
||||
{AKEYCODE_F9, "F9"},
|
||||
{AKEYCODE_F10, "F10"},
|
||||
{AKEYCODE_F11, "F11"},
|
||||
{AKEYCODE_F12, "F12"},
|
||||
{AKEYCODE_NUM_LOCK, "NumLock"},
|
||||
{AKEYCODE_SCROLL_LOCK, "ScrollLock"},
|
||||
{AKEYCODE_SEMICOLON, "Semicolon"},
|
||||
{AKEYCODE_EQUALS, "Equal"},
|
||||
{AKEYCODE_COMMA, "Comma"},
|
||||
{AKEYCODE_MINUS, "Minus"},
|
||||
{AKEYCODE_PERIOD, "Period"},
|
||||
{AKEYCODE_SLASH, "Slash"},
|
||||
{AKEYCODE_GRAVE, "Backquote"},
|
||||
{AKEYCODE_LEFT_BRACKET, "BracketLeft"},
|
||||
{AKEYCODE_BACKSLASH, "Backslash"},
|
||||
{AKEYCODE_RIGHT_BRACKET, "BracketRight"},
|
||||
{AKEYCODE_APOSTROPHE, "Quote"},
|
||||
};
|
||||
} // namespace cc
|
||||
884
cocos/platform/android/AndroidPlatform.cpp
Normal file
884
cocos/platform/android/AndroidPlatform.cpp
Normal file
@@ -0,0 +1,884 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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 "platform/android/AndroidPlatform.h"
|
||||
#include <android/native_window_jni.h>
|
||||
#include <thread>
|
||||
#include "application/ApplicationManager.h"
|
||||
#include "base/Log.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#include "base/std/container/unordered_map.h"
|
||||
#include "game-activity/native_app_glue/android_native_app_glue.h"
|
||||
#include "java/jni/JniHelper.h"
|
||||
#include "modules/Screen.h"
|
||||
#include "modules/System.h"
|
||||
#include "platform/BasePlatform.h"
|
||||
#include "platform/android/FileUtils-android.h"
|
||||
#include "platform/java/jni/JniImp.h"
|
||||
#include "platform/java/modules/Accelerometer.h"
|
||||
#include "platform/java/modules/Battery.h"
|
||||
#include "platform/java/modules/Network.h"
|
||||
#include "platform/java/modules/SystemWindow.h"
|
||||
#include "platform/java/modules/SystemWindowManager.h"
|
||||
#include "platform/java/modules/Vibrator.h"
|
||||
|
||||
#include "platform/interfaces/modules/IXRInterface.h"
|
||||
#if CC_USE_XR
|
||||
#include "platform/java/modules/XRInterface.h"
|
||||
#endif
|
||||
|
||||
#include "base/StringUtil.h"
|
||||
#include "engine/EngineEvents.h"
|
||||
#include "paddleboat.h"
|
||||
|
||||
#define ABORT_GAME \
|
||||
{ \
|
||||
CC_LOG_ERROR("*** GAME ABORTING."); \
|
||||
*((volatile char *)0) = 'a'; \
|
||||
}
|
||||
|
||||
#define ABORT_IF(cond) \
|
||||
{ \
|
||||
if (!(cond)) { \
|
||||
CC_LOG_ERROR("ASSERTION FAILED: %s", #cond); \
|
||||
ABORT_GAME; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define INPUT_ACTION_COUNT 6
|
||||
|
||||
// Interval time per frame, in milliseconds
|
||||
#define LOW_FREQUENCY_TIME_INTERVAL 50
|
||||
|
||||
// Maximum runtime of game threads while in the background, in seconds
|
||||
#define LOW_FREQUENCY_EXPIRED_DURATION_SECONDS 60
|
||||
|
||||
#define CC_ENABLE_SUSPEND_GAME_THREAD true
|
||||
|
||||
extern int cocos_main(int argc, const char **argv); // NOLINT(readability-identifier-naming)
|
||||
|
||||
namespace cc {
|
||||
|
||||
struct cc::KeyboardEvent keyboardEvent;
|
||||
|
||||
struct InputAction {
|
||||
uint32_t buttonMask{0};
|
||||
int32_t actionCode{-1};
|
||||
};
|
||||
|
||||
extern ccstd::unordered_map<int32_t, const char *> androidKeyCodes;
|
||||
|
||||
static const InputAction PADDLEBOAT_ACTIONS[INPUT_ACTION_COUNT] = {
|
||||
{PADDLEBOAT_BUTTON_DPAD_UP, static_cast<int>(KeyCode::DPAD_UP)},
|
||||
{PADDLEBOAT_BUTTON_DPAD_LEFT, static_cast<int>(KeyCode::DPAD_LEFT)},
|
||||
{PADDLEBOAT_BUTTON_DPAD_DOWN, static_cast<int>(KeyCode::DPAD_DOWN)},
|
||||
{PADDLEBOAT_BUTTON_DPAD_RIGHT, static_cast<int>(KeyCode::DPAD_RIGHT)},
|
||||
};
|
||||
|
||||
struct ControllerKeyRemap {
|
||||
Paddleboat_Buttons buttonMask;
|
||||
StickKeyCode actionCode{StickKeyCode::UNDEFINE};
|
||||
const char *name;
|
||||
};
|
||||
|
||||
#define REMAP_WITH_NAME(btn, key) \
|
||||
{ btn, key, #btn }
|
||||
|
||||
static const ControllerKeyRemap PADDLEBOAT_MAPKEY[] = {
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_A, StickKeyCode::A),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_B, StickKeyCode::B),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_X, StickKeyCode::X),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_Y, StickKeyCode::Y),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_L1, StickKeyCode::L1),
|
||||
// REMAP_WITH_NAME(PADDLEBOAT_BUTTON_L2, StickKeyCode::TRIGGER_LEFT),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_L3, StickKeyCode::L3),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_R1, StickKeyCode::R1),
|
||||
// REMAP_WITH_NAME(PADDLEBOAT_BUTTON_R2, StickKeyCode::TRIGGER_RIGHT),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_R3, StickKeyCode::R3),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_SELECT, StickKeyCode::MINUS),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_START, StickKeyCode::PLUS),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_SYSTEM, StickKeyCode::MENU),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_TOUCHPAD, StickKeyCode::UNDEFINE),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_AUX1, StickKeyCode::UNDEFINE),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_AUX2, StickKeyCode::UNDEFINE),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_AUX3, StickKeyCode::UNDEFINE),
|
||||
REMAP_WITH_NAME(PADDLEBOAT_BUTTON_AUX4, StickKeyCode::UNDEFINE),
|
||||
};
|
||||
#undef REMAP_WITH_NAME
|
||||
|
||||
const int INPUT_ACTION_REMAP_COUNT = sizeof(PADDLEBOAT_MAPKEY) / sizeof(ControllerKeyRemap);
|
||||
|
||||
static const InputAction INPUT_KEY_ACTIONS[] = {
|
||||
{AKEYCODE_BACK, static_cast<int>(KeyCode::MOBILE_BACK)},
|
||||
{AKEYCODE_ENTER, static_cast<int>(KeyCode::ENTER)},
|
||||
{AKEYCODE_MENU, static_cast<int>(KeyCode::ALT_LEFT)},
|
||||
{AKEYCODE_DPAD_UP, static_cast<int>(KeyCode::DPAD_UP)},
|
||||
{AKEYCODE_DPAD_DOWN, static_cast<int>(KeyCode::DPAD_DOWN)},
|
||||
{AKEYCODE_DPAD_LEFT, static_cast<int>(KeyCode::DPAD_LEFT)},
|
||||
{AKEYCODE_DPAD_RIGHT, static_cast<int>(KeyCode::DPAD_RIGHT)},
|
||||
{AKEYCODE_DPAD_CENTER, static_cast<int>(KeyCode::DPAD_CENTER)},
|
||||
};
|
||||
|
||||
static bool keyState[INPUT_ACTION_COUNT] = {false};
|
||||
|
||||
extern void gameControllerStatusCallback(int32_t controllerIndex,
|
||||
Paddleboat_ControllerStatus status,
|
||||
void *userData);
|
||||
|
||||
class GameInputProxy {
|
||||
public:
|
||||
explicit GameInputProxy(AndroidPlatform *platform) {
|
||||
_androidPlatform = platform;
|
||||
if (0 != platform->_app->activity->vm->AttachCurrentThread(&_jniEnv, nullptr)) {
|
||||
CC_LOG_FATAL("*** FATAL ERROR: Failed to attach thread to JNI.");
|
||||
ABORT_GAME
|
||||
}
|
||||
ABORT_IF(_jniEnv != nullptr)
|
||||
|
||||
Paddleboat_init(_jniEnv, platform->_app->activity->javaGameActivity);
|
||||
Paddleboat_setControllerStatusCallback(gameControllerStatusCallback, this);
|
||||
// This is needed to allow controller events through to us.
|
||||
// By default, only touch-screen events are passed through, to match the
|
||||
// behaviour of NativeActivity.
|
||||
android_app_set_motion_event_filter(platform->_app, nullptr);
|
||||
|
||||
// Flags to control how the IME behaves.
|
||||
static constexpr int INPUTTYPE_DOT_TYPE_CLASS_TEXT = 1;
|
||||
static constexpr int IME_ACTION_NONE = 1;
|
||||
static constexpr int IME_FLAG_NO_FULLSCREEN = 33554432;
|
||||
|
||||
GameActivity_setImeEditorInfo(platform->_app->activity, INPUTTYPE_DOT_TYPE_CLASS_TEXT,
|
||||
IME_ACTION_NONE, IME_FLAG_NO_FULLSCREEN);
|
||||
}
|
||||
|
||||
~GameInputProxy() {
|
||||
Paddleboat_setControllerStatusCallback(nullptr, nullptr);
|
||||
Paddleboat_destroy(_jniEnv);
|
||||
if (_jniEnv) {
|
||||
CC_LOG_INFO("Detaching current thread from JNI.");
|
||||
_androidPlatform->_app->activity->vm->DetachCurrentThread();
|
||||
CC_LOG_INFO("Current thread detached from JNI.");
|
||||
_jniEnv = nullptr;
|
||||
}
|
||||
}
|
||||
void checkForNewAxis() {
|
||||
// Tell GameActivity about any new axis ids so it reports
|
||||
// their events
|
||||
const uint64_t activeAxisIds = Paddleboat_getActiveAxisMask();
|
||||
uint64_t newAxisIds = activeAxisIds ^ _activeAxisIds;
|
||||
if (newAxisIds != 0) {
|
||||
_activeAxisIds = activeAxisIds;
|
||||
int32_t currentAxisId = 0;
|
||||
while (newAxisIds != 0) {
|
||||
if ((newAxisIds & 1) != 0) {
|
||||
CC_LOG_INFO("Enable Axis: %d", currentAxisId);
|
||||
GameActivityPointerAxes_enableAxis(currentAxisId);
|
||||
}
|
||||
++currentAxisId;
|
||||
newAxisIds >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleInput() {
|
||||
checkForNewAxis();
|
||||
Paddleboat_update(_jniEnv);
|
||||
// If we get any key or motion events that were handled by a game controller,
|
||||
// read controller data and cook it into an event
|
||||
bool controllerEvent = false;
|
||||
|
||||
// Swap input buffers so we don't miss any events while processing inputBuffer.
|
||||
android_input_buffer *inputBuffer = android_app_swap_input_buffers(
|
||||
_androidPlatform->_app);
|
||||
// Early exit if no events.
|
||||
if (inputBuffer == nullptr) return;
|
||||
|
||||
if (inputBuffer->keyEventsCount != 0) {
|
||||
for (uint64_t i = 0; i < inputBuffer->keyEventsCount; ++i) {
|
||||
GameActivityKeyEvent *keyEvent = &inputBuffer->keyEvents[i];
|
||||
if (_gameControllerIndex >= 0 && Paddleboat_processGameActivityKeyInputEvent(keyEvent,
|
||||
sizeof(GameActivityKeyEvent))) {
|
||||
controllerEvent = true;
|
||||
} else {
|
||||
cookGameActivityKeyEvent(keyEvent);
|
||||
}
|
||||
}
|
||||
android_app_clear_key_events(inputBuffer);
|
||||
}
|
||||
if (inputBuffer->motionEventsCount != 0) {
|
||||
for (uint64_t i = 0; i < inputBuffer->motionEventsCount; ++i) {
|
||||
GameActivityMotionEvent *motionEvent = &inputBuffer->motionEvents[i];
|
||||
|
||||
if (_gameControllerIndex >= 0 && Paddleboat_processGameActivityMotionInputEvent(motionEvent,
|
||||
sizeof(GameActivityMotionEvent))) {
|
||||
controllerEvent = true;
|
||||
} else {
|
||||
// Didn't belong to a game controller, process it ourselves if it is a touch event
|
||||
bool isMouseEvent = motionEvent->pointerCount > 0 && (motionEvent->pointers[0].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || motionEvent->pointers[0].toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE);
|
||||
if (isMouseEvent) {
|
||||
cookGameActivityMouseEvent(motionEvent);
|
||||
} else {
|
||||
cookGameActivityMotionEvent(motionEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
android_app_clear_motion_events(inputBuffer);
|
||||
}
|
||||
|
||||
if (controllerEvent) {
|
||||
cookGameControllerEvent(_gameControllerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
struct ButtonState {
|
||||
uint32_t buttonsDown;
|
||||
uint32_t &prevState;
|
||||
#define DEF_ATTR(name, code) \
|
||||
bool name##Pressed() const { return buttonsDown & PADDLEBOAT_BUTTON_##code; } \
|
||||
bool name##Rel() const { return !name##Pressed() && (prevState & PADDLEBOAT_BUTTON_##code); } \
|
||||
bool name() const { return name##Pressed() || name##Rel(); }
|
||||
|
||||
DEF_ATTR(dpadLeft, DPAD_LEFT)
|
||||
DEF_ATTR(dpadRight, DPAD_RIGHT)
|
||||
DEF_ATTR(dpadUp, DPAD_UP)
|
||||
DEF_ATTR(dpadDown, DPAD_DOWN)
|
||||
|
||||
DEF_ATTR(l2, L2)
|
||||
DEF_ATTR(r2, R2)
|
||||
|
||||
#undef DEF_ATTR
|
||||
};
|
||||
|
||||
bool cookGameControllerEvent(const int32_t gameControllerIndex) {
|
||||
static std::vector<uint32_t> prevStates{4};
|
||||
bool addedControllerEvent = false;
|
||||
cc::ControllerInfo info;
|
||||
if (gameControllerIndex >= 0) {
|
||||
if (gameControllerIndex >= prevStates.size()) {
|
||||
prevStates.resize(gameControllerIndex * 2 + 1);
|
||||
}
|
||||
uint32_t &prevButtonsDown = prevStates[gameControllerIndex];
|
||||
|
||||
info.napdId = gameControllerIndex;
|
||||
Paddleboat_Controller_Data controllerData;
|
||||
if (Paddleboat_getControllerData(gameControllerIndex, &controllerData) ==
|
||||
PADDLEBOAT_NO_ERROR) {
|
||||
addedControllerEvent = true;
|
||||
// Generate events from buttons
|
||||
for (auto inputAction : PADDLEBOAT_ACTIONS) {
|
||||
if (controllerData.buttonsDown & inputAction.buttonMask) {
|
||||
reportKeyState(inputAction.actionCode, true);
|
||||
} else if (prevButtonsDown & inputAction.buttonMask) {
|
||||
reportKeyState(inputAction.actionCode, false);
|
||||
}
|
||||
}
|
||||
for (auto remap : PADDLEBOAT_MAPKEY) {
|
||||
auto code = remap.actionCode;
|
||||
if (controllerData.buttonsDown & remap.buttonMask) {
|
||||
if (code == StickKeyCode::UNDEFINE) {
|
||||
CC_LOG_ERROR("key \"%s\" is unhandled", remap.name);
|
||||
}
|
||||
cc::ControllerInfo::ButtonInfo buttonInfo{code, true};
|
||||
info.buttonInfos.emplace_back(buttonInfo);
|
||||
} else if (prevButtonsDown & remap.buttonMask) {
|
||||
cc::ControllerInfo::ButtonInfo buttonInfo{code, false};
|
||||
buttonInfo.key = code;
|
||||
buttonInfo.isPress = false;
|
||||
info.buttonInfos.emplace_back(buttonInfo);
|
||||
}
|
||||
}
|
||||
const ButtonState bts{controllerData.buttonsDown, prevButtonsDown};
|
||||
if (bts.dpadLeft() || bts.dpadRight()) {
|
||||
float dLeft = bts.dpadLeftRel() ? 0.0F : (bts.dpadLeftPressed() ? -1.0F : 0.0F);
|
||||
float dRight = bts.dpadRightRel() ? 0.0F : (bts.dpadRightPressed() ? 1.0F : 0.0F);
|
||||
const ControllerInfo::AxisInfo axisInfo(StickAxisCode::X, dLeft + dRight);
|
||||
info.axisInfos.emplace_back(axisInfo);
|
||||
}
|
||||
if (bts.dpadUp() || bts.dpadDown()) {
|
||||
float dUp = bts.dpadUpRel() ? 0.0F : (bts.dpadUp() ? 1.0F : 0.0F);
|
||||
float dDown = bts.dpadDownRel() ? 0.0F : (bts.dpadDown() ? -1.0F : 0.0F);
|
||||
const ControllerInfo::AxisInfo axisInfo(StickAxisCode::Y, dUp + dDown);
|
||||
info.axisInfos.emplace_back(axisInfo);
|
||||
}
|
||||
if (bts.l2()) {
|
||||
const ControllerInfo::AxisInfo axisInfo(StickAxisCode::L2, bts.l2Rel() ? 0.0F : (bts.l2Pressed() ? controllerData.triggerL2 : 0.0F));
|
||||
info.axisInfos.emplace_back(axisInfo);
|
||||
}
|
||||
if (bts.r2()) {
|
||||
const ControllerInfo::AxisInfo axisInfo(StickAxisCode::R2, bts.r2Rel() ? 0.0F : (bts.r2Pressed() ? controllerData.triggerR2 : 0.0F));
|
||||
info.axisInfos.emplace_back(axisInfo);
|
||||
}
|
||||
|
||||
auto lx = controllerData.leftStick.stickX;
|
||||
auto ly = -controllerData.leftStick.stickY;
|
||||
auto rx = controllerData.rightStick.stickX;
|
||||
auto ry = -controllerData.rightStick.stickY;
|
||||
|
||||
info.axisInfos.emplace_back(StickAxisCode::LEFT_STICK_X, lx);
|
||||
info.axisInfos.emplace_back(StickAxisCode::LEFT_STICK_Y, ly);
|
||||
info.axisInfos.emplace_back(StickAxisCode::RIGHT_STICK_X, rx);
|
||||
info.axisInfos.emplace_back(StickAxisCode::RIGHT_STICK_Y, ry);
|
||||
|
||||
ControllerEvent controllerEvent;
|
||||
controllerEvent.type = ControllerEvent::Type::GAMEPAD;
|
||||
controllerEvent.controllerInfos.emplace_back(std::make_unique<ControllerInfo>(std::move(info)));
|
||||
events::Controller::broadcast(controllerEvent);
|
||||
|
||||
// Update our prev variable so we can det
|
||||
// ect delta changes from down to up
|
||||
prevButtonsDown = controllerData.buttonsDown;
|
||||
}
|
||||
}
|
||||
return addedControllerEvent;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
bool cookGameActivityMouseEvent(GameActivityMotionEvent *motionEvent) {
|
||||
cc::MouseEvent mouseEvent;
|
||||
if (motionEvent->pointerCount > 0) {
|
||||
mouseEvent.windowId = ISystemWindow::mainWindowId; // must be main window here
|
||||
|
||||
int action = motionEvent->action;
|
||||
int actionMasked = action & AMOTION_EVENT_ACTION_MASK;
|
||||
int button = motionEvent->buttonState;
|
||||
|
||||
setMousePosition(mouseEvent, motionEvent);
|
||||
switch (button) {
|
||||
case AMOTION_EVENT_BUTTON_PRIMARY:
|
||||
mouseEvent.button = 0;
|
||||
break;
|
||||
case AMOTION_EVENT_BUTTON_SECONDARY:
|
||||
mouseEvent.button = 2;
|
||||
break;
|
||||
case AMOTION_EVENT_BUTTON_TERTIARY:
|
||||
mouseEvent.button = 1;
|
||||
break;
|
||||
case AMOTION_EVENT_BUTTON_BACK:
|
||||
mouseEvent.button = 3;
|
||||
break;
|
||||
case AMOTION_EVENT_BUTTON_FORWARD:
|
||||
mouseEvent.button = 4;
|
||||
break;
|
||||
default:
|
||||
mouseEvent.button = 0;
|
||||
}
|
||||
|
||||
if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
|
||||
actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
|
||||
mouseEvent.type = cc::MouseEvent::Type::DOWN;
|
||||
} else if (actionMasked == AMOTION_EVENT_ACTION_UP ||
|
||||
actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
|
||||
mouseEvent.type = cc::MouseEvent::Type::UP;
|
||||
} else if (actionMasked == AMOTION_EVENT_ACTION_SCROLL) {
|
||||
mouseEvent.type = cc::MouseEvent::Type::WHEEL;
|
||||
// TODO(): wheel delta
|
||||
} else if (actionMasked == AMOTION_EVENT_ACTION_MOVE) {
|
||||
mouseEvent.type = cc::MouseEvent::Type::MOVE;
|
||||
} else if (actionMasked == AMOTION_EVENT_ACTION_HOVER_MOVE) {
|
||||
mouseEvent.type = cc::MouseEvent::Type::MOVE;
|
||||
} else if (actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
|
||||
return false;
|
||||
} else if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT) {
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
events::Mouse::broadcast(mouseEvent);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
bool cookGameActivityMotionEvent(GameActivityMotionEvent *motionEvent) {
|
||||
cc::TouchEvent touchEvent;
|
||||
if (motionEvent->pointerCount > 0) {
|
||||
touchEvent.windowId = ISystemWindow::mainWindowId; // must be main window here
|
||||
|
||||
int action = motionEvent->action;
|
||||
int actionMasked = action & AMOTION_EVENT_ACTION_MASK;
|
||||
int eventChangedIndex = -1;
|
||||
|
||||
bool isMouseEvent = motionEvent->pointerCount > 0 && (motionEvent->pointers[0].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || motionEvent->pointers[0].toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE);
|
||||
|
||||
if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
|
||||
actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
|
||||
if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
|
||||
eventChangedIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
|
||||
>> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
|
||||
} else {
|
||||
eventChangedIndex = 0;
|
||||
}
|
||||
touchEvent.type = cc::TouchEvent::Type::BEGAN;
|
||||
} else if (actionMasked == AMOTION_EVENT_ACTION_UP ||
|
||||
actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
|
||||
touchEvent.type = cc::TouchEvent::Type::ENDED;
|
||||
if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
|
||||
eventChangedIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
|
||||
>> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
|
||||
} else {
|
||||
eventChangedIndex = 0;
|
||||
}
|
||||
} else if (actionMasked == AMOTION_EVENT_ACTION_CANCEL) {
|
||||
touchEvent.type = cc::TouchEvent::Type::CANCELLED;
|
||||
} else if (actionMasked == AMOTION_EVENT_ACTION_MOVE) {
|
||||
touchEvent.type = cc::TouchEvent::Type::MOVED;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool touchHandled = true;
|
||||
if (eventChangedIndex >= 0) {
|
||||
touchHandled = addTouchEvent(touchEvent, eventChangedIndex, motionEvent);
|
||||
} else {
|
||||
for (int i = 0; i < motionEvent->pointerCount; i++) {
|
||||
addTouchEvent(touchEvent, i, motionEvent);
|
||||
}
|
||||
}
|
||||
events::Touch::broadcast(touchEvent);
|
||||
touchEvent.touches.clear();
|
||||
return touchHandled;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
bool cookGameActivityKeyEvent(GameActivityKeyEvent *keyEvent) {
|
||||
if (_gameControllerIndex >= 0) {
|
||||
for (const auto &action : INPUT_KEY_ACTIONS) {
|
||||
if (action.buttonMask != keyEvent->keyCode) {
|
||||
continue;
|
||||
}
|
||||
keyboardEvent.action = 0 == keyEvent->action ? cc::KeyboardEvent::Action::PRESS
|
||||
: cc::KeyboardEvent::Action::RELEASE;
|
||||
keyboardEvent.key = action.actionCode;
|
||||
events::Keyboard::broadcast(keyboardEvent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
keyboardEvent.action = 0 == keyEvent->action ? cc::KeyboardEvent::Action::PRESS
|
||||
: cc::KeyboardEvent::Action::RELEASE;
|
||||
keyboardEvent.key = keyEvent->keyCode;
|
||||
auto keyCodeItr = androidKeyCodes.find(keyEvent->keyCode);
|
||||
if (keyCodeItr == androidKeyCodes.end()) {
|
||||
keyboardEvent.code.clear();
|
||||
} else {
|
||||
keyboardEvent.code = keyCodeItr->second;
|
||||
}
|
||||
events::Keyboard::broadcast(keyboardEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
void reportKeyState(int keyCode, bool state) {
|
||||
bool wentDown = !keyState[keyCode] && state;
|
||||
bool wentUp = keyState[keyCode] && !state;
|
||||
keyState[keyCode] = state;
|
||||
|
||||
if (wentUp) {
|
||||
keyboardEvent.key = keyCode;
|
||||
keyboardEvent.action = cc::KeyboardEvent::Action::RELEASE;
|
||||
events::Keyboard::broadcast(keyboardEvent);
|
||||
} else if (wentDown) {
|
||||
keyboardEvent.key = keyCode;
|
||||
keyboardEvent.action = cc::KeyboardEvent::Action::PRESS;
|
||||
events::Keyboard::broadcast(keyboardEvent);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t getActiveGameControllerIndex() const {
|
||||
return _gameControllerIndex;
|
||||
}
|
||||
|
||||
void setActiveGameControllerIndex(const int32_t controllerIndex) {
|
||||
_gameControllerIndex = controllerIndex;
|
||||
}
|
||||
|
||||
void handleAppCommand(int32_t cmd) {
|
||||
switch (cmd) {
|
||||
case APP_CMD_SAVE_STATE:
|
||||
// The system has asked us to save our current state.
|
||||
CC_LOG_DEBUG("AndroidPlatform: APP_CMD_SAVE_STATE");
|
||||
break;
|
||||
case APP_CMD_INIT_WINDOW: {
|
||||
_hasWindow = true;
|
||||
ANativeWindow *nativeWindow = _androidPlatform->_app->window;
|
||||
|
||||
// We have a window!
|
||||
CC_LOG_DEBUG("AndroidPlatform: APP_CMD_INIT_WINDOW");
|
||||
if (!_launched) {
|
||||
_launched = true;
|
||||
|
||||
ISystemWindowInfo info;
|
||||
info.width = ANativeWindow_getWidth(nativeWindow);
|
||||
info.height = ANativeWindow_getHeight(nativeWindow);
|
||||
info.externalHandle = nativeWindow;
|
||||
_androidPlatform->getInterface<SystemWindowManager>()->createWindow(info);
|
||||
|
||||
if (cocos_main(0, nullptr) != 0) {
|
||||
CC_LOG_ERROR("AndroidPlatform: Launch game failed!");
|
||||
} else {
|
||||
IXRInterface *xr = CC_GET_XR_INTERFACE();
|
||||
if (xr) {
|
||||
xr->onRenderResume();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
IXRInterface *xr = CC_GET_XR_INTERFACE();
|
||||
if (xr) {
|
||||
xr->onRenderResume();
|
||||
}
|
||||
|
||||
auto *windowMgr = _androidPlatform->getInterface<SystemWindowManager>();
|
||||
auto *window = static_cast<cc::SystemWindow *>(windowMgr->getWindow(ISystemWindow::mainWindowId));
|
||||
window->setWindowHandle(nativeWindow);
|
||||
events::WindowRecreated::broadcast(ISystemWindow::mainWindowId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_CMD_TERM_WINDOW: {
|
||||
_hasWindow = false;
|
||||
// The window is going away -- kill the surface
|
||||
CC_LOG_DEBUG("AndroidPlatform: APP_CMD_TERM_WINDOW");
|
||||
IXRInterface *xr = CC_GET_XR_INTERFACE();
|
||||
if (xr) {
|
||||
xr->onRenderPause();
|
||||
}
|
||||
// NOLINTNEXTLINE
|
||||
events::WindowDestroy::broadcast(ISystemWindow::mainWindowId);
|
||||
break;
|
||||
}
|
||||
case APP_CMD_GAINED_FOCUS:
|
||||
_isActive = true;
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_GAINED_FOCUS");
|
||||
break;
|
||||
case APP_CMD_LOST_FOCUS:
|
||||
_isActive = false;
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_LOST_FOCUS");
|
||||
break;
|
||||
case APP_CMD_PAUSE:
|
||||
_isActive = false;
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_PAUSE");
|
||||
break;
|
||||
case APP_CMD_RESUME: {
|
||||
_isActive = true;
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_RESUME");
|
||||
break;
|
||||
}
|
||||
case APP_CMD_DESTROY: {
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_DESTROY");
|
||||
IXRInterface *xr = CC_GET_XR_INTERFACE();
|
||||
if (xr) {
|
||||
xr->onRenderDestroy();
|
||||
}
|
||||
WindowEvent ev;
|
||||
ev.type = WindowEvent::Type::CLOSE;
|
||||
events::WindowEvent::broadcast(ev);
|
||||
break;
|
||||
}
|
||||
case APP_CMD_STOP: {
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_STOP");
|
||||
_isVisible = false;
|
||||
Paddleboat_onStop(_jniEnv);
|
||||
WindowEvent ev;
|
||||
ev.type = WindowEvent::Type::HIDDEN;
|
||||
events::WindowEvent::broadcast(ev);
|
||||
break;
|
||||
}
|
||||
case APP_CMD_START: {
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_START");
|
||||
_isVisible = true;
|
||||
Paddleboat_onStart(_jniEnv);
|
||||
WindowEvent ev;
|
||||
ev.type = WindowEvent::Type::SHOW;
|
||||
events::WindowEvent::broadcast(ev);
|
||||
break;
|
||||
}
|
||||
case APP_CMD_WINDOW_RESIZED: {
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_WINDOW_RESIZED");
|
||||
cc::WindowEvent ev;
|
||||
ev.type = cc::WindowEvent::Type::SIZE_CHANGED;
|
||||
ev.width = ANativeWindow_getWidth(_androidPlatform->_app->window);
|
||||
ev.height = ANativeWindow_getHeight(_androidPlatform->_app->window);
|
||||
ev.windowId = ISystemWindow::mainWindowId;
|
||||
events::WindowEvent::broadcast(ev);
|
||||
break;
|
||||
}
|
||||
case APP_CMD_CONFIG_CHANGED:
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_CONFIG_CHANGED");
|
||||
// Window was resized or some other configuration changed.
|
||||
// Note: we don't handle this event because we check the surface dimensions
|
||||
// every frame, so that's how we know it was resized. If you are NOT doing that,
|
||||
// then you need to handle this event!
|
||||
break;
|
||||
case APP_CMD_LOW_MEMORY: {
|
||||
// system told us we have low memory. So if we are not visible, let's
|
||||
// cooperate by deallocating all of our graphic resources.
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_LOW_MEMORY");
|
||||
events::LowMemory::broadcast();
|
||||
break;
|
||||
}
|
||||
case APP_CMD_CONTENT_RECT_CHANGED:
|
||||
CC_LOG_DEBUG("AndroidPlatform: APP_CMD_CONTENT_RECT_CHANGED");
|
||||
break;
|
||||
case APP_CMD_WINDOW_REDRAW_NEEDED:
|
||||
CC_LOG_INFO("AndroidPlatform: APP_CMD_WINDOW_REDRAW_NEEDED");
|
||||
break;
|
||||
case APP_CMD_WINDOW_INSETS_CHANGED:
|
||||
CC_LOG_DEBUG("AndroidPlatform: APP_CMD_WINDOW_INSETS_CHANGED");
|
||||
// ARect insets;
|
||||
// // Log all the insets types
|
||||
// for (int type = 0; type < GAMECOMMON_INSETS_TYPE_COUNT; ++type) {
|
||||
// GameActivity_getWindowInsets(_app->activity, (GameCommonInsetsType)type, &insets);
|
||||
// }
|
||||
break;
|
||||
default:
|
||||
CC_LOG_INFO("AndroidPlatform: (unknown command).");
|
||||
break;
|
||||
}
|
||||
if (_eventCallback) {
|
||||
_eventCallback(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
using AppEventCallback = std::function<void(int32_t)>;
|
||||
void registerAppEventCallback(AppEventCallback callback) {
|
||||
_eventCallback = std::move(callback);
|
||||
}
|
||||
|
||||
inline bool isAnimating() const {
|
||||
return _isVisible && _hasWindow;
|
||||
}
|
||||
|
||||
inline bool isActive() const {
|
||||
return _isActive;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool addTouchEvent(cc::TouchEvent &touchEvent, int index, GameActivityMotionEvent *motionEvent) {
|
||||
if (index < 0 || index >= motionEvent->pointerCount) {
|
||||
return false;
|
||||
}
|
||||
int id = motionEvent->pointers[index].id;
|
||||
float x = GameActivityPointerAxes_getX(&motionEvent->pointers[index]);
|
||||
float y = GameActivityPointerAxes_getY(&motionEvent->pointers[index]);
|
||||
touchEvent.touches.emplace_back(x, y, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void setMousePosition(cc::MouseEvent &mouseEvent, GameActivityMotionEvent *motionEvent) {
|
||||
if (motionEvent->pointerCount == 0) {
|
||||
ABORT_IF(false);
|
||||
}
|
||||
mouseEvent.x = GameActivityPointerAxes_getX(&motionEvent->pointers[0]);
|
||||
mouseEvent.y = GameActivityPointerAxes_getY(&motionEvent->pointers[0]);
|
||||
}
|
||||
|
||||
AppEventCallback _eventCallback{nullptr};
|
||||
AndroidPlatform *_androidPlatform{nullptr};
|
||||
JNIEnv *_jniEnv{nullptr}; // JNI environment
|
||||
uint64_t _activeAxisIds{0};
|
||||
int32_t _gameControllerIndex{-1}; // Most recently connected game controller index
|
||||
bool _launched{false};
|
||||
bool _isVisible{false};
|
||||
bool _hasWindow{false};
|
||||
bool _isActive{false};
|
||||
};
|
||||
|
||||
static void handleCmdProxy(struct android_app *app, int32_t cmd) {
|
||||
auto *proxy = static_cast<GameInputProxy *>(app->userData);
|
||||
proxy->handleAppCommand(cmd);
|
||||
}
|
||||
|
||||
void gameControllerStatusCallback(const int32_t controllerIndex,
|
||||
const Paddleboat_ControllerStatus status,
|
||||
void *userData) {
|
||||
auto *inputProxy = static_cast<GameInputProxy *>(userData);
|
||||
if (inputProxy) {
|
||||
// Always make the most recently connected controller the active one
|
||||
if (status == PADDLEBOAT_CONTROLLER_JUST_CONNECTED) {
|
||||
inputProxy->setActiveGameControllerIndex(controllerIndex);
|
||||
} else if (status == PADDLEBOAT_CONTROLLER_JUST_DISCONNECTED) {
|
||||
// We only care if the controller that disconnected was the one
|
||||
// we are currently using
|
||||
if (controllerIndex == inputProxy->getActiveGameControllerIndex()) {
|
||||
// Default to no fallback controller, loop and look for another connected
|
||||
// one
|
||||
int32_t newControllerIndex = -1;
|
||||
|
||||
for (int32_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) {
|
||||
if (i != controllerIndex &&
|
||||
Paddleboat_getControllerStatus(i) == PADDLEBOAT_CONTROLLER_ACTIVE) {
|
||||
newControllerIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
inputProxy->setActiveGameControllerIndex(newControllerIndex);
|
||||
}
|
||||
}
|
||||
ControllerChangeEvent event;
|
||||
for (int32_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) {
|
||||
if (Paddleboat_getControllerStatus(i) == PADDLEBOAT_CONTROLLER_ACTIVE) {
|
||||
event.controllerIds.emplace_back(i);
|
||||
}
|
||||
}
|
||||
events::ControllerChange::broadcast(event);
|
||||
}
|
||||
}
|
||||
|
||||
AndroidPlatform::~AndroidPlatform() = default;
|
||||
|
||||
int AndroidPlatform::init() {
|
||||
#if CC_USE_XR
|
||||
registerInterface(std::make_shared<XRInterface>());
|
||||
#endif
|
||||
IXRInterface *xr = CC_GET_XR_INTERFACE();
|
||||
if (xr) {
|
||||
JniHelper::getEnv();
|
||||
xr->initialize(JniHelper::getJavaVM(), getActivity());
|
||||
}
|
||||
cc::FileUtilsAndroid::setAssetManager(_app->activity->assetManager);
|
||||
_inputProxy = ccnew GameInputProxy(this);
|
||||
_inputProxy->registerAppEventCallback([this](int32_t cmd) {
|
||||
IXRInterface *xr = CC_GET_XR_INTERFACE();
|
||||
if (xr) {
|
||||
xr->handleAppCommand(cmd);
|
||||
}
|
||||
|
||||
if (APP_CMD_START == cmd || APP_CMD_INIT_WINDOW == cmd) {
|
||||
if (_inputProxy->isAnimating()) {
|
||||
_isLowFrequencyLoopEnabled = false;
|
||||
_loopTimeOut = 0;
|
||||
}
|
||||
} else if (APP_CMD_STOP == cmd) {
|
||||
_lowFrequencyTimer.reset();
|
||||
_loopTimeOut = LOW_FREQUENCY_TIME_INTERVAL;
|
||||
_isLowFrequencyLoopEnabled = true;
|
||||
if (xr && !xr->getXRConfig(xr::XRConfigKey::INSTANCE_CREATED).getBool()) {
|
||||
// xr will sleep, -1 we will block forever waiting for events.
|
||||
_loopTimeOut = -1;
|
||||
_isLowFrequencyLoopEnabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
_app->userData = _inputProxy;
|
||||
_app->onAppCmd = handleCmdProxy;
|
||||
|
||||
registerInterface(std::make_shared<Accelerometer>());
|
||||
registerInterface(std::make_shared<Battery>());
|
||||
registerInterface(std::make_shared<Network>());
|
||||
registerInterface(std::make_shared<Screen>());
|
||||
registerInterface(std::make_shared<System>());
|
||||
registerInterface(std::make_shared<SystemWindowManager>());
|
||||
registerInterface(std::make_shared<Vibrator>());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AndroidPlatform::onDestroy() {
|
||||
UniversalPlatform::onDestroy();
|
||||
unregisterAllInterfaces();
|
||||
JniHelper::onDestroy();
|
||||
CC_SAFE_DELETE(_inputProxy)
|
||||
}
|
||||
|
||||
cc::ISystemWindow *AndroidPlatform::createNativeWindow(uint32_t windowId, void *externalHandle) {
|
||||
return ccnew SystemWindow(windowId, externalHandle);
|
||||
}
|
||||
|
||||
int AndroidPlatform::getSdkVersion() const {
|
||||
return AConfiguration_getSdkVersion(_app->config);
|
||||
}
|
||||
|
||||
int32_t AndroidPlatform::run(int /*argc*/, const char ** /*argv*/) {
|
||||
loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AndroidPlatform::exit() {
|
||||
_app->destroyRequested = 1;
|
||||
}
|
||||
|
||||
int32_t AndroidPlatform::loop() {
|
||||
IXRInterface *xr = CC_GET_XR_INTERFACE();
|
||||
while (true) {
|
||||
int events;
|
||||
struct android_poll_source *source;
|
||||
|
||||
// suspend thread while _loopTimeOut set to -1
|
||||
while ((ALooper_pollAll(_loopTimeOut, nullptr, &events,
|
||||
reinterpret_cast<void **>(&source))) >= 0) {
|
||||
// process event
|
||||
if (source != nullptr) {
|
||||
source->process(_app, source);
|
||||
}
|
||||
|
||||
// Exit the game loop when the Activity is destroyed
|
||||
if (_app->destroyRequested) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Exit the game loop when the Activity is destroyed
|
||||
if (_app->destroyRequested) {
|
||||
break;
|
||||
}
|
||||
if (xr && !xr->platformLoopStart()) continue;
|
||||
_inputProxy->handleInput();
|
||||
if (_inputProxy->isAnimating() && (xr ? xr->getXRConfig(xr::XRConfigKey::SESSION_RUNNING).getBool() : true)) {
|
||||
runTask();
|
||||
if (_inputProxy->isActive()) {
|
||||
flushTasksOnGameThreadAtForegroundJNI();
|
||||
}
|
||||
}
|
||||
flushTasksOnGameThreadJNI();
|
||||
|
||||
#if CC_ENABLE_SUSPEND_GAME_THREAD
|
||||
if (_isLowFrequencyLoopEnabled) {
|
||||
// Suspend a game thread after it has been running in the background for a specified amount of time
|
||||
if (_lowFrequencyTimer.getSeconds() > LOW_FREQUENCY_EXPIRED_DURATION_SECONDS) {
|
||||
_isLowFrequencyLoopEnabled = false;
|
||||
_loopTimeOut = -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (xr) xr->platformLoopEnd();
|
||||
}
|
||||
onDestroy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AndroidPlatform::pollEvent() {
|
||||
//
|
||||
}
|
||||
|
||||
void *AndroidPlatform::getActivity() { // Dangerous
|
||||
return _app->activity->javaGameActivity;
|
||||
}
|
||||
|
||||
void *AndroidPlatform::getEnv() {
|
||||
return JniHelper::getEnv();
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
72
cocos/platform/android/AndroidPlatform.h
Normal file
72
cocos/platform/android/AndroidPlatform.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "base/Timer.h"
|
||||
#include "platform/UniversalPlatform.h"
|
||||
|
||||
struct android_app;
|
||||
|
||||
namespace cc {
|
||||
class GameInputProxy;
|
||||
|
||||
class CC_DLL AndroidPlatform : public UniversalPlatform {
|
||||
public:
|
||||
AndroidPlatform() = default;
|
||||
|
||||
~AndroidPlatform() override;
|
||||
|
||||
int init() override;
|
||||
|
||||
void pollEvent() override;
|
||||
|
||||
int32_t run(int argc, const char **argv) override;
|
||||
|
||||
int getSdkVersion() const override;
|
||||
|
||||
int32_t loop() override;
|
||||
void exit() override;
|
||||
|
||||
void *getActivity();
|
||||
|
||||
static void *getEnv();
|
||||
|
||||
void onDestroy() override;
|
||||
|
||||
inline void setAndroidApp(android_app *app) {
|
||||
_app = app;
|
||||
}
|
||||
|
||||
ISystemWindow *createNativeWindow(uint32_t windowId, void *externalHandle) override;
|
||||
|
||||
private:
|
||||
bool _isLowFrequencyLoopEnabled{false};
|
||||
utils::Timer _lowFrequencyTimer;
|
||||
int _loopTimeOut{-1};
|
||||
GameInputProxy *_inputProxy{nullptr};
|
||||
android_app *_app{nullptr};
|
||||
friend class GameInputProxy;
|
||||
};
|
||||
} // namespace cc
|
||||
236
cocos/platform/android/FileUtils-android.cpp
Normal file
236
cocos/platform/android/FileUtils-android.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2012 cocos2d-x.org
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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 "platform/android/FileUtils-android.h"
|
||||
#include <android/log.h>
|
||||
#include <sys/stat.h>
|
||||
#include <cstdlib>
|
||||
#include "android/asset_manager.h"
|
||||
#include "android/asset_manager_jni.h"
|
||||
#include "base/Log.h"
|
||||
#include "base/ZipUtils.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#include "platform/java/jni/JniHelper.h"
|
||||
#include "platform/java/jni/JniImp.h"
|
||||
|
||||
#define LOG_TAG "FileUtils-android.cpp"
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
#define ASSETS_FOLDER_NAME "@assets/"
|
||||
|
||||
#ifndef JCLS_HELPER
|
||||
#define JCLS_HELPER "com/cocos/lib/CocosHelper"
|
||||
#endif
|
||||
|
||||
namespace cc {
|
||||
|
||||
AAssetManager *FileUtilsAndroid::assetmanager = nullptr;
|
||||
ZipFile *FileUtilsAndroid::obbfile = nullptr;
|
||||
|
||||
FileUtils *createFileUtils() {
|
||||
return ccnew FileUtilsAndroid();
|
||||
}
|
||||
|
||||
void FileUtilsAndroid::setAssetManager(AAssetManager *a) {
|
||||
if (nullptr == a) {
|
||||
LOGD("setAssetManager : received unexpected nullptr parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
cc::FileUtilsAndroid::assetmanager = a;
|
||||
}
|
||||
|
||||
FileUtilsAndroid::FileUtilsAndroid() {
|
||||
init();
|
||||
}
|
||||
|
||||
FileUtilsAndroid::~FileUtilsAndroid() {
|
||||
CC_SAFE_DELETE(obbfile);
|
||||
}
|
||||
|
||||
bool FileUtilsAndroid::init() {
|
||||
_defaultResRootPath = ASSETS_FOLDER_NAME;
|
||||
|
||||
ccstd::string assetsPath(getObbFilePathJNI());
|
||||
if (assetsPath.find("/obb/") != ccstd::string::npos) {
|
||||
obbfile = ccnew ZipFile(assetsPath);
|
||||
}
|
||||
|
||||
return FileUtils::init();
|
||||
}
|
||||
|
||||
bool FileUtilsAndroid::isFileExistInternal(const ccstd::string &strFilePath) const {
|
||||
if (strFilePath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bFound = false;
|
||||
|
||||
// Check whether file exists in apk.
|
||||
if (strFilePath[0] != '/') {
|
||||
const char *s = strFilePath.c_str();
|
||||
|
||||
// Found "@assets/" at the beginning of the path and we don't want it
|
||||
if (strFilePath.find(ASSETS_FOLDER_NAME) == 0) s += strlen(ASSETS_FOLDER_NAME);
|
||||
if (obbfile && obbfile->fileExists(s)) {
|
||||
bFound = true;
|
||||
} else if (FileUtilsAndroid::assetmanager) {
|
||||
AAsset *aa = AAssetManager_open(FileUtilsAndroid::assetmanager, s, AASSET_MODE_UNKNOWN);
|
||||
if (aa) {
|
||||
bFound = true;
|
||||
AAsset_close(aa);
|
||||
} else {
|
||||
// CC_LOG_DEBUG("[AssetManager] ... in APK %s, found = false!", strFilePath.c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FILE *fp = fopen(strFilePath.c_str(), "r");
|
||||
if (fp) {
|
||||
bFound = true;
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
return bFound;
|
||||
}
|
||||
|
||||
bool FileUtilsAndroid::isDirectoryExistInternal(const ccstd::string &testDirPath) const {
|
||||
if (testDirPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ccstd::string dirPath = testDirPath;
|
||||
if (dirPath[dirPath.length() - 1] == '/') {
|
||||
dirPath[dirPath.length() - 1] = '\0';
|
||||
}
|
||||
|
||||
// find absolute path in flash memory
|
||||
if (dirPath[0] == '/') {
|
||||
CC_LOG_DEBUG("find in flash memory dirPath(%s)", dirPath.c_str());
|
||||
struct stat st;
|
||||
if (stat(dirPath.c_str(), &st) == 0) {
|
||||
return S_ISDIR(st.st_mode);
|
||||
}
|
||||
} else {
|
||||
// find it in apk's assets dir
|
||||
// Found "@assets/" at the beginning of the path and we don't want it
|
||||
CC_LOG_DEBUG("find in apk dirPath(%s)", dirPath.c_str());
|
||||
const char *s = dirPath.c_str();
|
||||
if (dirPath.find(_defaultResRootPath) == 0) {
|
||||
s += _defaultResRootPath.length();
|
||||
}
|
||||
if (FileUtilsAndroid::assetmanager) {
|
||||
AAssetDir *aa = AAssetManager_openDir(FileUtilsAndroid::assetmanager, s);
|
||||
if (aa && AAssetDir_getNextFileName(aa)) {
|
||||
AAssetDir_close(aa);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileUtilsAndroid::isAbsolutePath(const ccstd::string &strPath) const {
|
||||
// On Android, there are two situations for full path.
|
||||
// 1) Files in APK, e.g. assets/path/path/file.png
|
||||
// 2) Files not in APK, e.g. /data/data/org.cocos2dx.hellocpp/cache/path/path/file.png, or /sdcard/path/path/file.png.
|
||||
// So these two situations need to be checked on Android.
|
||||
return strPath[0] == '/' || strPath.find(ASSETS_FOLDER_NAME) == 0;
|
||||
}
|
||||
|
||||
FileUtils::Status FileUtilsAndroid::getContents(const ccstd::string &filename, ResizableBuffer *buffer) {
|
||||
if (filename.empty()) {
|
||||
return FileUtils::Status::NOT_EXISTS;
|
||||
}
|
||||
|
||||
ccstd::string fullPath = fullPathForFilename(filename);
|
||||
if (fullPath.empty()) {
|
||||
return FileUtils::Status::NOT_EXISTS;
|
||||
}
|
||||
|
||||
if (fullPath[0] == '/') {
|
||||
return FileUtils::getContents(fullPath, buffer);
|
||||
}
|
||||
|
||||
ccstd::string relativePath;
|
||||
size_t position = fullPath.find(ASSETS_FOLDER_NAME);
|
||||
if (0 == position) {
|
||||
// "@assets/" is at the beginning of the path and we don't want it
|
||||
relativePath += fullPath.substr(strlen(ASSETS_FOLDER_NAME));
|
||||
} else {
|
||||
relativePath = fullPath;
|
||||
}
|
||||
|
||||
if (obbfile) {
|
||||
if (obbfile->getFileData(relativePath, buffer)) {
|
||||
return FileUtils::Status::OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (nullptr == assetmanager) {
|
||||
LOGD("... FileUtilsAndroid::assetmanager is nullptr");
|
||||
return FileUtils::Status::NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
AAsset *asset = AAssetManager_open(assetmanager, relativePath.data(), AASSET_MODE_UNKNOWN);
|
||||
if (nullptr == asset) {
|
||||
LOGD("asset (%s) is nullptr", filename.c_str());
|
||||
return FileUtils::Status::OPEN_FAILED;
|
||||
}
|
||||
|
||||
auto size = AAsset_getLength(asset);
|
||||
buffer->resize(size);
|
||||
|
||||
int readsize = AAsset_read(asset, buffer->buffer(), size);
|
||||
AAsset_close(asset);
|
||||
|
||||
if (readsize < size) {
|
||||
if (readsize >= 0) {
|
||||
buffer->resize(readsize);
|
||||
}
|
||||
return FileUtils::Status::READ_FAILED;
|
||||
}
|
||||
|
||||
return FileUtils::Status::OK;
|
||||
}
|
||||
|
||||
ccstd::string FileUtilsAndroid::getWritablePath() const {
|
||||
if (!_writablePath.empty()) {
|
||||
return _writablePath;
|
||||
}
|
||||
// Fix for Nexus 10 (Android 4.2 multi-user environment)
|
||||
// the path is retrieved through Java Context.getCacheDir() method
|
||||
ccstd::string dir;
|
||||
ccstd::string tmp = JniHelper::callStaticStringMethod(JCLS_HELPER, "getWritablePath");
|
||||
|
||||
if (tmp.length() > 0) {
|
||||
dir.append(tmp).append("/");
|
||||
return dir;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
74
cocos/platform/android/FileUtils-android.h
Normal file
74
cocos/platform/android/FileUtils-android.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2012 cocos2d-x.org
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "android/asset_manager.h"
|
||||
#include "base/Macros.h"
|
||||
#include "base/std/container/string.h"
|
||||
#include "jni.h"
|
||||
#include "platform/FileUtils.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class ZipFile;
|
||||
|
||||
/**
|
||||
* @addtogroup platform
|
||||
* @{
|
||||
*/
|
||||
|
||||
//! @brief Helper class to handle file operations
|
||||
class CC_DLL FileUtilsAndroid : public FileUtils {
|
||||
friend class FileUtils;
|
||||
|
||||
public:
|
||||
FileUtilsAndroid();
|
||||
~FileUtilsAndroid() override;
|
||||
|
||||
static void setAssetManager(AAssetManager *a);
|
||||
static AAssetManager *getAssetManager() { return assetmanager; }
|
||||
static ZipFile *getObbFile() { return obbfile; }
|
||||
|
||||
/* override functions */
|
||||
bool init() override;
|
||||
FileUtils::Status getContents(const ccstd::string &filename, ResizableBuffer *buffer) override;
|
||||
|
||||
ccstd::string getWritablePath() const override;
|
||||
bool isAbsolutePath(const ccstd::string &strPath) const override;
|
||||
|
||||
private:
|
||||
bool isFileExistInternal(const ccstd::string &strFilePath) const override;
|
||||
bool isDirectoryExistInternal(const ccstd::string &dirPath) const override;
|
||||
|
||||
static AAssetManager *assetmanager;
|
||||
static ZipFile *obbfile;
|
||||
};
|
||||
|
||||
// end of platform group
|
||||
/// @}
|
||||
|
||||
} // namespace cc
|
||||
272
cocos/platform/android/adpf_manager.cpp
Normal file
272
cocos/platform/android/adpf_manager.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
#include "adpf_manager.h"
|
||||
#include "platform/BasePlatform.h"
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID && __ANDROID_API__ >= 30
|
||||
|
||||
#include <unistd.h>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include "java/jni/JniHelper.h"
|
||||
|
||||
#define ALOGI(...)
|
||||
#define ALOGE(...)
|
||||
|
||||
// Invoke the method periodically (once a frame) to monitor
|
||||
// the device's thermal throttling status.
|
||||
void ADPFManager::Monitor() {
|
||||
auto currentClock = std::chrono::high_resolution_clock::now();
|
||||
auto past = currentClock - last_clock_;
|
||||
auto pastMS = std::chrono::duration_cast<std::chrono::milliseconds>(past).count();
|
||||
|
||||
// if (current_clock - last_clock_ >= kThermalHeadroomUpdateThreshold) {
|
||||
if (past > kThermalHeadroomUpdateThreshold) {
|
||||
// Update thermal headroom.
|
||||
// CC_LOG_INFO(" Monitor past %d ms", static_cast<int>(pastMS));
|
||||
UpdateThermalStatusHeadRoom();
|
||||
last_clock_ = currentClock;
|
||||
}
|
||||
}
|
||||
|
||||
float ADPFManager::GetThermalStatusNormalized() const {
|
||||
if (thermal_manager_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
auto level = AThermal_getCurrentThermalStatus(thermal_manager_);
|
||||
auto levelValue = (static_cast<int>(level) - static_cast<int>(ATHERMAL_STATUS_NONE)) * 1.0f /
|
||||
static_cast<int>(ATHERMAL_STATUS_SHUTDOWN);
|
||||
return levelValue;
|
||||
}
|
||||
|
||||
// Invoke the API first to set the android_app instance.
|
||||
void ADPFManager::Initialize() {
|
||||
// Initialize PowerManager reference.
|
||||
InitializePowerManager();
|
||||
|
||||
// Initialize PowerHintManager reference.
|
||||
InitializePerformanceHintManager();
|
||||
|
||||
beforeTick.bind([&]() {
|
||||
this->BeginPerfHintSession();
|
||||
this->Monitor();
|
||||
});
|
||||
|
||||
afterTick.bind([&]() {
|
||||
auto fps = cc::BasePlatform::getPlatform()->getFps();
|
||||
auto frameDurationNS = 1000000000LL / fps;
|
||||
this->EndPerfHintSession(frameDurationNS);
|
||||
});
|
||||
|
||||
if (thermal_manager_) {
|
||||
auto ret = AThermal_registerThermalStatusListener(
|
||||
thermal_manager_, +[](void *data, AThermalStatus status) {
|
||||
ADPFManager::getInstance().SetThermalStatus(status);
|
||||
CC_LOG_INFO("Thermal Status :%d", static_cast<int>(status));
|
||||
},
|
||||
nullptr);
|
||||
ALOGI("Thermal Status callback registerred to:%d", ret);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize JNI calls for the powermanager.
|
||||
bool ADPFManager::InitializePowerManager() {
|
||||
#if __ANDROID_API__ >= 31
|
||||
if (android_get_device_api_level() >= 31) {
|
||||
// Initialize the powermanager using NDK API.
|
||||
thermal_manager_ = AThermal_acquireManager();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
JNIEnv *env = cc::JniHelper::getEnv();
|
||||
auto *javaGameActivity = cc::JniHelper::getActivity();
|
||||
|
||||
// Retrieve class information
|
||||
jclass context = env->FindClass("android/content/Context");
|
||||
|
||||
// Get the value of a constant
|
||||
jfieldID fid =
|
||||
env->GetStaticFieldID(context, "POWER_SERVICE", "Ljava/lang/String;");
|
||||
jobject str_svc = env->GetStaticObjectField(context, fid);
|
||||
|
||||
// Get the method 'getSystemService' and call it
|
||||
jmethodID mid_getss = env->GetMethodID(
|
||||
context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
|
||||
jobject obj_power_service = env->CallObjectMethod(javaGameActivity, mid_getss, str_svc);
|
||||
|
||||
// Add global reference to the power service object.
|
||||
obj_power_service_ = env->NewGlobalRef(obj_power_service);
|
||||
|
||||
jclass cls_power_service = env->GetObjectClass(obj_power_service_);
|
||||
get_thermal_headroom_ =
|
||||
env->GetMethodID(cls_power_service, "getThermalHeadroom", "(I)F");
|
||||
|
||||
// Free references
|
||||
env->DeleteLocalRef(cls_power_service);
|
||||
env->DeleteLocalRef(obj_power_service);
|
||||
env->DeleteLocalRef(str_svc);
|
||||
env->DeleteLocalRef(context);
|
||||
|
||||
if (get_thermal_headroom_ == 0) {
|
||||
// The API is not supported in the platform version.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Retrieve current thermal headroom using JNI call.
|
||||
float ADPFManager::UpdateThermalStatusHeadRoom() {
|
||||
#if __ANDROID_API__ >= 31
|
||||
if (android_get_device_api_level() >= 31) {
|
||||
// Use NDK API to retrieve thermal status headroom.
|
||||
auto seconds = kThermalHeadroomUpdateThreshold.count();
|
||||
thermal_headroom_ = AThermal_getThermalHeadroom(
|
||||
thermal_manager_, seconds);
|
||||
if (!std::isnan(thermal_headroom_)) {
|
||||
thermal_headroom_valid_ = thermal_headroom_;
|
||||
}
|
||||
return thermal_headroom_;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (get_thermal_headroom_ == 0) {
|
||||
return 0.f;
|
||||
}
|
||||
JNIEnv *env = cc::JniHelper::getEnv();
|
||||
|
||||
// Get thermal headroom!
|
||||
thermal_headroom_ =
|
||||
env->CallFloatMethod(obj_power_service_, get_thermal_headroom_,
|
||||
kThermalHeadroomUpdateThreshold);
|
||||
ALOGE("Current thermal Headroom %f", thermal_headroom_);
|
||||
return thermal_headroom_;
|
||||
}
|
||||
|
||||
// Initialize JNI calls for the PowerHintManager.
|
||||
bool ADPFManager::InitializePerformanceHintManager() {
|
||||
JNIEnv *env = cc::JniHelper::getEnv();
|
||||
auto *javaGameActivity = cc::JniHelper::getActivity();
|
||||
|
||||
// Retrieve class information
|
||||
jclass context = env->FindClass("android/content/Context");
|
||||
|
||||
// Get the value of a constant
|
||||
jfieldID fid = env->GetStaticFieldID(context, "PERFORMANCE_HINT_SERVICE",
|
||||
"Ljava/lang/String;");
|
||||
jobject str_svc = env->GetStaticObjectField(context, fid);
|
||||
|
||||
// Get the method 'getSystemService' and call it
|
||||
jmethodID mid_getss = env->GetMethodID(
|
||||
context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
|
||||
jobject obj_perfhint_service = env->CallObjectMethod(
|
||||
javaGameActivity, mid_getss, str_svc);
|
||||
|
||||
// Add global reference to the power service object.
|
||||
obj_perfhint_service_ = env->NewGlobalRef(obj_perfhint_service);
|
||||
|
||||
// Retrieve methods IDs for the APIs.
|
||||
jclass cls_perfhint_service = env->GetObjectClass(obj_perfhint_service_);
|
||||
jmethodID mid_createhintsession =
|
||||
env->GetMethodID(cls_perfhint_service, "createHintSession",
|
||||
"([IJ)Landroid/os/PerformanceHintManager$Session;");
|
||||
jmethodID mid_preferedupdaterate = env->GetMethodID(
|
||||
cls_perfhint_service, "getPreferredUpdateRateNanos", "()J");
|
||||
|
||||
// Create int array which contain current tid.
|
||||
jintArray array = env->NewIntArray(1);
|
||||
int32_t tid = getpid();
|
||||
env->SetIntArrayRegion(array, 0, 1, &tid);
|
||||
const jlong DEFAULT_TARGET_NS = 16666666;
|
||||
|
||||
// Create Hint session for the thread.
|
||||
jobject obj_hintsession = env->CallObjectMethod(
|
||||
obj_perfhint_service_, mid_createhintsession, array, DEFAULT_TARGET_NS);
|
||||
if (obj_hintsession == nullptr) {
|
||||
ALOGI("Failed to create a perf hint session.");
|
||||
} else {
|
||||
obj_perfhint_session_ = env->NewGlobalRef(obj_hintsession);
|
||||
preferred_update_rate_ =
|
||||
env->CallLongMethod(obj_perfhint_service_, mid_preferedupdaterate);
|
||||
|
||||
// Retrieve mid of Session APIs.
|
||||
jclass cls_perfhint_session = env->GetObjectClass(obj_perfhint_session_);
|
||||
report_actual_work_duration_ = env->GetMethodID(
|
||||
cls_perfhint_session, "reportActualWorkDuration", "(J)V");
|
||||
update_target_work_duration_ = env->GetMethodID(
|
||||
cls_perfhint_session, "updateTargetWorkDuration", "(J)V");
|
||||
}
|
||||
|
||||
// Free local references
|
||||
env->DeleteLocalRef(obj_hintsession);
|
||||
env->DeleteLocalRef(array);
|
||||
env->DeleteLocalRef(cls_perfhint_service);
|
||||
env->DeleteLocalRef(obj_perfhint_service);
|
||||
env->DeleteLocalRef(str_svc);
|
||||
env->DeleteLocalRef(context);
|
||||
|
||||
if (report_actual_work_duration_ == 0 || update_target_work_duration_ == 0) {
|
||||
// The API is not supported in the platform version.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
thermalStateChangeListener ADPFManager::thermalListener = NULL;
|
||||
|
||||
void ADPFManager::SetThermalStatus(int32_t i) {
|
||||
int32_t prev_status_ = thermal_status_;
|
||||
int32_t current_status_ = i;
|
||||
thermal_status_ = i;
|
||||
if (thermalListener != NULL) {
|
||||
thermalListener(prev_status_, current_status_);
|
||||
}
|
||||
}
|
||||
|
||||
void ADPFManager::SetThermalListener(thermalStateChangeListener listener) {
|
||||
thermalListener = listener;
|
||||
}
|
||||
|
||||
// Indicates the start and end of the performance intensive task.
|
||||
// The methods call performance hint API to tell the performance
|
||||
// hint to the system.
|
||||
void ADPFManager::BeginPerfHintSession() { perfhintsession_start_ = std::chrono::high_resolution_clock::now(); }
|
||||
|
||||
void ADPFManager::EndPerfHintSession(jlong target_duration_ns) {
|
||||
auto current_clock = std::chrono::high_resolution_clock::now();
|
||||
auto duration = current_clock - perfhintsession_start_;
|
||||
frame_time_ns_ = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count();
|
||||
if (obj_perfhint_session_) {
|
||||
jlong duration_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
duration * 100000000)
|
||||
.count();
|
||||
auto *env = cc::JniHelper::getEnv();
|
||||
|
||||
// Report and update the target work duration using JNI calls.
|
||||
env->CallVoidMethod(obj_perfhint_session_, report_actual_work_duration_,
|
||||
duration_ns);
|
||||
env->CallVoidMethod(obj_perfhint_session_, update_target_work_duration_,
|
||||
target_duration_ns);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
161
cocos/platform/android/adpf_manager.h
Normal file
161
cocos/platform/android/adpf_manager.h
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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 ADPF_MANAGER_H_
|
||||
#define ADPF_MANAGER_H_
|
||||
|
||||
#if CC_PLATFORM == CC_PLATFORM_ANDROID && __ANDROID_API__ >= 30
|
||||
#include <android/api-level.h>
|
||||
#include <android/log.h>
|
||||
#include <android/thermal.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include "3d/models/SkinningModel.h"
|
||||
#include "engine/EngineEvents.h"
|
||||
#include "platform/java/jni/JniHelper.h"
|
||||
|
||||
// Forward declarations of functions that need to be in C decl.
|
||||
|
||||
typedef void (*thermalStateChangeListener)(int32_t, int32_t);
|
||||
|
||||
/*
|
||||
* ADPFManager class anages the ADPF APIs.
|
||||
*/
|
||||
class ADPFManager {
|
||||
public:
|
||||
// Singleton function.
|
||||
static ADPFManager &getInstance() {
|
||||
static ADPFManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Dtor.
|
||||
~ADPFManager() {
|
||||
// Remove global reference.
|
||||
auto env = cc::JniHelper::getEnv();
|
||||
if (env != nullptr) {
|
||||
if (obj_power_service_ != nullptr) {
|
||||
env->DeleteGlobalRef(obj_power_service_);
|
||||
}
|
||||
if (obj_perfhint_service_ != nullptr) {
|
||||
env->DeleteGlobalRef(obj_perfhint_service_);
|
||||
}
|
||||
if (obj_perfhint_session_ != nullptr) {
|
||||
env->DeleteGlobalRef(obj_perfhint_session_);
|
||||
}
|
||||
if (thermal_manager_ != nullptr) {
|
||||
AThermal_releaseManager(thermal_manager_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete copy constructor since the class is used as a singleton.
|
||||
ADPFManager(ADPFManager const &) = delete;
|
||||
|
||||
void operator=(ADPFManager const &) = delete;
|
||||
|
||||
// Invoke the method periodically (once a frame) to monitor
|
||||
// the device's thermal throttling status.
|
||||
void Monitor();
|
||||
|
||||
// Invoke the API first to set the android_app instance.
|
||||
|
||||
// Method to set thermal status. Need to be public since the method
|
||||
// is called from C native listener.
|
||||
void SetThermalStatus(int32_t i);
|
||||
|
||||
// Get current thermal status and headroom.
|
||||
int32_t GetThermalStatus() { return thermal_status_; }
|
||||
float GetThermalStatusNormalized() const;
|
||||
|
||||
float GetFrameTimeMS() const { return frame_time_ns_ / 1000000.0F; }
|
||||
|
||||
float GetThermalHeadroom() { return thermal_headroom_; }
|
||||
|
||||
void SetThermalListener(thermalStateChangeListener listener);
|
||||
|
||||
// Indicates the start and end of the performance intensive task.
|
||||
// The methods call performance hint API to tell the performance
|
||||
// hint to the system.
|
||||
void BeginPerfHintSession();
|
||||
|
||||
void EndPerfHintSession(jlong target_duration_ns);
|
||||
|
||||
// Method to retrieve thermal manager. The API is used to register/unregister
|
||||
// callbacks from C API.
|
||||
AThermalManager *GetThermalManager() { return thermal_manager_; }
|
||||
|
||||
void Initialize();
|
||||
|
||||
private:
|
||||
// Update thermal headroom each sec.
|
||||
static constexpr auto kThermalHeadroomUpdateThreshold = std::chrono::seconds(1);
|
||||
|
||||
// Function pointer from the game, will be invoked when we receive state changed event from Thermal API
|
||||
static thermalStateChangeListener thermalListener;
|
||||
|
||||
// Ctor. It's private since the class is designed as a singleton.
|
||||
ADPFManager()
|
||||
: thermal_manager_(nullptr),
|
||||
thermal_status_(0),
|
||||
thermal_headroom_(0.f),
|
||||
obj_power_service_(nullptr),
|
||||
get_thermal_headroom_(0),
|
||||
obj_perfhint_service_(nullptr),
|
||||
obj_perfhint_session_(nullptr),
|
||||
report_actual_work_duration_(0),
|
||||
update_target_work_duration_(0),
|
||||
preferred_update_rate_(0) {
|
||||
last_clock_ = std::chrono::high_resolution_clock::now();
|
||||
perfhintsession_start_ = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
// Functions to initialize ADPF API's calls.
|
||||
bool InitializePowerManager();
|
||||
|
||||
float UpdateThermalStatusHeadRoom();
|
||||
|
||||
bool InitializePerformanceHintManager();
|
||||
|
||||
AThermalManager *thermal_manager_ = nullptr;
|
||||
int32_t thermal_status_;
|
||||
float thermal_headroom_ = 0;
|
||||
float thermal_headroom_valid_ = 0;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> last_clock_;
|
||||
jobject obj_power_service_;
|
||||
jmethodID get_thermal_headroom_;
|
||||
|
||||
jobject obj_perfhint_service_;
|
||||
jobject obj_perfhint_session_;
|
||||
jmethodID report_actual_work_duration_;
|
||||
jmethodID update_target_work_duration_;
|
||||
jlong preferred_update_rate_;
|
||||
|
||||
cc::events::BeforeTick::Listener beforeTick;
|
||||
cc::events::AfterTick::Listener afterTick;
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> perfhintsession_start_;
|
||||
int64_t frame_time_ns_{0};
|
||||
};
|
||||
|
||||
#define CC_SUPPORT_ADPF 1 // NOLINT
|
||||
#else
|
||||
#define CC_SUPPORT_ADPF 0 // NOLINT
|
||||
#endif // ADPF_MANAGER_H_
|
||||
|
||||
#endif
|
||||
9
cocos/platform/android/java/.classpath
Normal file
9
cocos/platform/android/java/.classpath
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
||||
33
cocos/platform/android/java/.project
Normal file
33
cocos/platform/android/java/.project
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>libcocos2dx</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
9
cocos/platform/android/java/AndroidManifest.xml
Normal file
9
cocos/platform/android/java/AndroidManifest.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.cocos.lib"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9"/>
|
||||
|
||||
</manifest>
|
||||
83
cocos/platform/android/java/build.xml
Normal file
83
cocos/platform/android/java/build.xml
Normal file
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="cocos2dxandroid" default="help">
|
||||
|
||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||
It contains the path to the SDK. It should *NOT* be checked into
|
||||
Version Control Systems. -->
|
||||
<property file="local.properties" />
|
||||
|
||||
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||
'android' tool to add properties to it.
|
||||
This is the place to change some Ant specific build properties.
|
||||
Here are some properties you may want to change/update:
|
||||
|
||||
source.dir
|
||||
The name of the source directory. Default is 'src'.
|
||||
out.dir
|
||||
The name of the output directory. Default is 'bin'.
|
||||
|
||||
For other overridable properties, look at the beginning of the rules
|
||||
files in the SDK, at tools/ant/build.xml
|
||||
|
||||
Properties related to the SDK location or the project target should
|
||||
be updated using the 'android' tool with the 'update' action.
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems.
|
||||
|
||||
-->
|
||||
<property file="ant.properties" />
|
||||
|
||||
<!-- The project.properties file is created and updated by the 'android'
|
||||
tool, as well as ADT.
|
||||
|
||||
This contains project specific properties such as project target, and library
|
||||
dependencies. Lower level build properties are stored in ant.properties
|
||||
(or in .classpath for Eclipse projects).
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems. -->
|
||||
<loadproperties srcFile="project.properties" />
|
||||
|
||||
<!-- quick check on sdk.dir -->
|
||||
<fail
|
||||
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
|
||||
unless="sdk.dir"
|
||||
/>
|
||||
|
||||
<!--
|
||||
Import per project custom build rules if present at the root of the project.
|
||||
This is the place to put custom intermediary targets such as:
|
||||
-pre-build
|
||||
-pre-compile
|
||||
-post-compile (This is typically used for code obfuscation.
|
||||
Compiled code location: ${out.classes.absolute.dir}
|
||||
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||
-post-package
|
||||
-post-build
|
||||
-pre-clean
|
||||
-->
|
||||
<import file="custom_rules.xml" optional="true" />
|
||||
|
||||
<!-- Import the actual build file.
|
||||
|
||||
To customize existing targets, there are two options:
|
||||
- Customize only one target:
|
||||
- copy/paste the target into this file, *before* the
|
||||
<import> task.
|
||||
- customize it to your needs.
|
||||
- Customize the whole content of build.xml
|
||||
- copy/paste the content of the rules files (minus the top node)
|
||||
into this file, replacing the <import> task.
|
||||
- customize to your needs.
|
||||
|
||||
***********************
|
||||
****** IMPORTANT ******
|
||||
***********************
|
||||
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||
in order to avoid having your file be overridden by tools such as "android update project"
|
||||
-->
|
||||
<!-- version-tag: 1 -->
|
||||
<import file="${sdk.dir}/tools/ant/build.xml" />
|
||||
|
||||
</project>
|
||||
Binary file not shown.
BIN
cocos/platform/android/java/libs/game-sdk.jar
Normal file
BIN
cocos/platform/android/java/libs/game-sdk.jar
Normal file
Binary file not shown.
BIN
cocos/platform/android/java/libs/okhttp-3.12.14.jar
Normal file
BIN
cocos/platform/android/java/libs/okhttp-3.12.14.jar
Normal file
Binary file not shown.
BIN
cocos/platform/android/java/libs/okio-1.15.0.jar
Normal file
BIN
cocos/platform/android/java/libs/okio-1.15.0.jar
Normal file
Binary file not shown.
13
cocos/platform/android/java/proguard-project.txt
Normal file
13
cocos/platform/android/java/proguard-project.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# To enable ProGuard in your project, edit project.properties
|
||||
# to define the proguard.config property as described in that file.
|
||||
#
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
15
cocos/platform/android/java/project.properties
Executable file
15
cocos/platform/android/java/project.properties
Executable file
@@ -0,0 +1,15 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
android.library=true
|
||||
# Project target.
|
||||
target=android-10
|
||||
@@ -0,0 +1,529 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.text.TextPaint;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class CanvasRenderingContext2DImpl {
|
||||
|
||||
private static final String TAG = "CanvasContext2D";
|
||||
|
||||
private static final int TEXT_ALIGN_LEFT = 0;
|
||||
private static final int TEXT_ALIGN_CENTER = 1;
|
||||
private static final int TEXT_ALIGN_RIGHT = 2;
|
||||
|
||||
private static final int TEXT_BASELINE_TOP = 0;
|
||||
private static final int TEXT_BASELINE_MIDDLE = 1;
|
||||
private static final int TEXT_BASELINE_BOTTOM = 2;
|
||||
private static final int TEXT_BASELINE_ALPHABETIC = 3;
|
||||
|
||||
private static WeakReference<Context> sContext;
|
||||
private TextPaint mTextPaint;
|
||||
private Paint mLinePaint;
|
||||
private Path mLinePath;
|
||||
private Canvas mCanvas = new Canvas();
|
||||
private Bitmap mBitmap;
|
||||
private int mTextAlign = TEXT_ALIGN_LEFT;
|
||||
private int mTextBaseline = TEXT_BASELINE_BOTTOM;
|
||||
private int mFillStyleR = 0;
|
||||
private int mFillStyleG = 0;
|
||||
private int mFillStyleB = 0;
|
||||
private int mFillStyleA = 255;
|
||||
|
||||
private int mStrokeStyleR = 0;
|
||||
private int mStrokeStyleG = 0;
|
||||
private int mStrokeStyleB = 0;
|
||||
private int mStrokeStyleA = 255;
|
||||
|
||||
private String mFontName = "Arial";
|
||||
private float mFontSize = 40.0f;
|
||||
private float mLineWidth = 0.0f;
|
||||
private float mShadowBlur = 0.0f;
|
||||
private float mShadowOffsetX = 0.0f;
|
||||
private float mShadowOffsetY = 0.0f;
|
||||
private int mShadowColorA = 0;
|
||||
private int mShadowColorB = 0;
|
||||
private int mShadowColorG = 0;
|
||||
private int mShadowColorR = 0;
|
||||
|
||||
private static float _sApproximatingOblique = -0.25f;//please check paint api documentation
|
||||
private boolean mIsBoldFont = false;
|
||||
private boolean mIsItalicFont = false;
|
||||
private boolean mIsObliqueFont = false;
|
||||
private boolean mIsSmallCapsFontVariant = false;
|
||||
private String mLineCap = "butt";
|
||||
private String mLineJoin = "miter";
|
||||
|
||||
private class Size {
|
||||
Size(float w, float h) {
|
||||
this.width = w;
|
||||
this.height = h;
|
||||
}
|
||||
|
||||
Size() {
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
}
|
||||
public float width;
|
||||
public float height;
|
||||
}
|
||||
|
||||
private class Point {
|
||||
Point(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
Point() {
|
||||
this.x = this.y = 0.0f;
|
||||
}
|
||||
|
||||
Point(Point pt) {
|
||||
this.x = pt.x;
|
||||
this.y = pt.y;
|
||||
}
|
||||
|
||||
void set(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public float x;
|
||||
public float y;
|
||||
}
|
||||
|
||||
static void init(Context context) {
|
||||
sContext = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
static void destroy() {
|
||||
sContext = null;
|
||||
}
|
||||
|
||||
private static HashMap<String, Typeface> sTypefaceCache = new HashMap<>();
|
||||
|
||||
// url is a full path started with '@assets/'
|
||||
private static void loadTypeface(String familyName, String url) {
|
||||
if (!sTypefaceCache.containsKey(familyName)) {
|
||||
try {
|
||||
Typeface typeface = null;
|
||||
if (url.startsWith("/")) {
|
||||
typeface = Typeface.createFromFile(url);
|
||||
} else if (sContext.get() != null) {
|
||||
final String prefix = "@assets/";
|
||||
if (url.startsWith(prefix)) {
|
||||
url = url.substring(prefix.length());
|
||||
}
|
||||
typeface = Typeface.createFromAsset(sContext.get().getAssets(), url);
|
||||
}
|
||||
|
||||
if (typeface != null) {
|
||||
sTypefaceCache.put(familyName, typeface);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// REFINE:: native should clear font cache before exiting game.
|
||||
private static void clearTypefaceCache() {
|
||||
sTypefaceCache.clear();
|
||||
}
|
||||
|
||||
private static TextPaint newPaint(String fontName, int fontSize, boolean enableBold, boolean enableItalic, boolean obliqueFont, boolean smallCapsFontVariant) {
|
||||
TextPaint paint = new TextPaint();
|
||||
paint.setTextSize(fontSize);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setSubpixelText(true);
|
||||
|
||||
int style = Typeface.NORMAL;
|
||||
if (enableBold && enableItalic) {
|
||||
paint.setFakeBoldText(true);
|
||||
style = Typeface.BOLD_ITALIC;
|
||||
} else if (enableBold) {
|
||||
paint.setFakeBoldText(true);
|
||||
style = Typeface.BOLD;
|
||||
} else if (enableItalic) {
|
||||
style = Typeface.ITALIC;
|
||||
}
|
||||
|
||||
Typeface typeFace = null;
|
||||
if (sTypefaceCache.containsKey(fontName)) {
|
||||
typeFace = sTypefaceCache.get(fontName);
|
||||
typeFace = Typeface.create(typeFace, style);
|
||||
} else {
|
||||
typeFace = Typeface.create(fontName, style);
|
||||
}
|
||||
paint.setTypeface(typeFace);
|
||||
if(obliqueFont) {
|
||||
paint.setTextSkewX(_sApproximatingOblique);
|
||||
}
|
||||
if(smallCapsFontVariant && Build.VERSION.SDK_INT >= 21) {
|
||||
CocosReflectionHelper.<Void>invokeInstanceMethod(paint,
|
||||
"setFontFeatureSettings",
|
||||
new Class[]{String.class},
|
||||
new Object[]{"smcp"});
|
||||
}
|
||||
return paint;
|
||||
}
|
||||
|
||||
private CanvasRenderingContext2DImpl() {
|
||||
// Log.d(TAG, "constructor");
|
||||
}
|
||||
|
||||
private void recreateBuffer(float w, float h) {
|
||||
// Log.d(TAG, "recreateBuffer:" + w + ", " + h);
|
||||
if (mBitmap != null) {
|
||||
mBitmap.recycle();
|
||||
}
|
||||
mBitmap = Bitmap.createBitmap((int)Math.ceil(w), (int)Math.ceil(h), Bitmap.Config.ARGB_8888);
|
||||
// FIXME: in MIX 2S, its API level is 28, but can not find invokeInstanceMethod. It seems
|
||||
// devices may not obey the specification, so comment the codes.
|
||||
// if (Build.VERSION.SDK_INT >= 19) {
|
||||
// CocosReflectionHelper.<Void>invokeInstanceMethod(mBitmap,
|
||||
// "setPremultiplied",
|
||||
// new Class[]{Boolean.class},
|
||||
// new Object[]{Boolean.FALSE});
|
||||
// }
|
||||
mCanvas.setBitmap(mBitmap);
|
||||
}
|
||||
|
||||
private void beginPath() {
|
||||
if (mLinePath == null) {
|
||||
mLinePath = new Path();
|
||||
}
|
||||
mLinePath.reset();
|
||||
}
|
||||
|
||||
private void closePath() {
|
||||
mLinePath.close();
|
||||
}
|
||||
|
||||
private void moveTo(float x, float y) {
|
||||
mLinePath.moveTo(x, y);
|
||||
}
|
||||
|
||||
private void lineTo(float x, float y) {
|
||||
mLinePath.lineTo(x, y);
|
||||
}
|
||||
|
||||
private void stroke() {
|
||||
if (mLinePaint == null) {
|
||||
mLinePaint = new Paint();
|
||||
mLinePaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
if(mLinePath == null) {
|
||||
mLinePath = new Path();
|
||||
}
|
||||
|
||||
mLinePaint.setARGB(mStrokeStyleA, mStrokeStyleR, mStrokeStyleG, mStrokeStyleB);
|
||||
mLinePaint.setStyle(Paint.Style.STROKE);
|
||||
mLinePaint.setStrokeWidth(mLineWidth);
|
||||
this.setStrokeCap(mLinePaint);
|
||||
this.setStrokeJoin(mLinePaint);
|
||||
mCanvas.drawPath(mLinePath, mLinePaint);
|
||||
}
|
||||
|
||||
private void setStrokeCap(Paint paint) {
|
||||
switch (mLineCap) {
|
||||
case "butt":
|
||||
paint.setStrokeCap(Paint.Cap.BUTT);
|
||||
break;
|
||||
case "round":
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
break;
|
||||
case "square":
|
||||
paint.setStrokeCap(Paint.Cap.SQUARE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setStrokeJoin(Paint paint) {
|
||||
switch (mLineJoin) {
|
||||
case "bevel":
|
||||
paint.setStrokeJoin(Paint.Join.BEVEL);
|
||||
break;
|
||||
case "round":
|
||||
paint.setStrokeJoin(Paint.Join.ROUND);
|
||||
break;
|
||||
case "miter":
|
||||
paint.setStrokeJoin(Paint.Join.MITER);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void fill() {
|
||||
if (mLinePaint == null) {
|
||||
mLinePaint = new Paint();
|
||||
}
|
||||
|
||||
if(mLinePath == null) {
|
||||
mLinePath = new Path();
|
||||
}
|
||||
|
||||
mLinePaint.setARGB(mFillStyleA, mFillStyleR, mFillStyleG, mFillStyleB);
|
||||
mLinePaint.setStyle(Paint.Style.FILL);
|
||||
mCanvas.drawPath(mLinePath, mLinePaint);
|
||||
// workaround: draw a hairline to cover the border
|
||||
mLinePaint.setStrokeWidth(0);
|
||||
this.setStrokeCap(mLinePaint);
|
||||
this.setStrokeJoin(mLinePaint);
|
||||
mLinePaint.setStyle(Paint.Style.STROKE);
|
||||
mCanvas.drawPath(mLinePath, mLinePaint);
|
||||
mLinePaint.setStrokeWidth(mLineWidth);
|
||||
}
|
||||
|
||||
private void setLineCap(String lineCap) {
|
||||
mLineCap = lineCap;
|
||||
}
|
||||
|
||||
private void setLineJoin(String lineJoin) {
|
||||
mLineJoin = lineJoin;
|
||||
}
|
||||
|
||||
private void setShadowBlur(float blur) {
|
||||
mShadowBlur = blur * 0.5f;
|
||||
}
|
||||
|
||||
private void setShadowColor(int r, int g, int b, int a) {
|
||||
mShadowColorR = r;
|
||||
mShadowColorG = g;
|
||||
mShadowColorB = b;
|
||||
mShadowColorA = a;
|
||||
}
|
||||
|
||||
private void setShadowOffsetX(float offsetX) {
|
||||
mShadowOffsetX = offsetX;
|
||||
}
|
||||
|
||||
private void setShadowOffsetY(float offsetY) {
|
||||
mShadowOffsetY = offsetY;
|
||||
}
|
||||
|
||||
private void saveContext() {
|
||||
mCanvas.save();
|
||||
}
|
||||
|
||||
private void restoreContext() {
|
||||
// If there is no saved state, this method should do nothing.
|
||||
if (mCanvas.getSaveCount() > 1){
|
||||
mCanvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
private void rect(float x, float y, float w, float h) {
|
||||
// Log.d(TAG, "this: " + this + ", rect: " + x + ", " + y + ", " + w + ", " + h);
|
||||
beginPath();
|
||||
moveTo(x, y);
|
||||
lineTo(x, y + h);
|
||||
lineTo(x + w, y + h);
|
||||
lineTo(x + w, y);
|
||||
closePath();
|
||||
}
|
||||
|
||||
private void clearRect(float x, float y, float w, float h) {
|
||||
// Log.d(TAG, "this: " + this + ", clearRect: " + x + ", " + y + ", " + w + ", " + h);
|
||||
int clearSize = (int)(w * h);
|
||||
int[] clearColor = new int[clearSize];
|
||||
for (int i = 0; i < clearSize; ++i) {
|
||||
clearColor[i] = Color.TRANSPARENT;
|
||||
}
|
||||
mBitmap.setPixels(clearColor, 0, (int) w, (int) x, (int) y, (int) w, (int) h);
|
||||
}
|
||||
|
||||
private void createTextPaintIfNeeded() {
|
||||
if (mTextPaint == null) {
|
||||
mTextPaint = newPaint(mFontName, (int) mFontSize, mIsBoldFont, mIsItalicFont, mIsObliqueFont, mIsSmallCapsFontVariant);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillRect(float x, float y, float w, float h) {
|
||||
// Log.d(TAG, "fillRect: " + x + ", " + y + ", " + ", " + w + ", " + h);
|
||||
int pixelValue = (mFillStyleA & 0xff) << 24 | (mFillStyleR & 0xff) << 16 | (mFillStyleG & 0xff) << 8 | (mFillStyleB & 0xff);
|
||||
int fillSize = (int)(w * h);
|
||||
int[] fillColors = new int[fillSize];
|
||||
for (int i = 0; i < fillSize; ++i) {
|
||||
fillColors[i] = pixelValue;
|
||||
}
|
||||
mBitmap.setPixels(fillColors, 0, (int) w, (int)x, (int)y, (int)w, (int)h);
|
||||
}
|
||||
|
||||
private void scaleX(TextPaint textPaint, String text, float maxWidth) {
|
||||
if(maxWidth < Float.MIN_VALUE) return;
|
||||
float measureWidth = this.measureText(text);
|
||||
if((measureWidth - maxWidth) < Float.MIN_VALUE) return;
|
||||
float scaleX = maxWidth/measureWidth;
|
||||
textPaint.setTextScaleX(scaleX);
|
||||
}
|
||||
|
||||
private void fillText(String text, float x, float y, float maxWidth) {
|
||||
// Log.d(TAG, "this: " + this + ", fillText: " + text + ", " + x + ", " + y + ", " + ", " + maxWidth);
|
||||
createTextPaintIfNeeded();
|
||||
configShadow(mTextPaint);
|
||||
mTextPaint.setARGB(mFillStyleA, mFillStyleR, mFillStyleG, mFillStyleB);
|
||||
mTextPaint.setStyle(Paint.Style.FILL);
|
||||
scaleX(mTextPaint, text, maxWidth);
|
||||
Point pt = convertDrawPoint(new Point(x, y), text);
|
||||
mCanvas.drawText(text, pt.x, pt.y, mTextPaint);
|
||||
}
|
||||
|
||||
private void strokeText(String text, float x, float y, float maxWidth) {
|
||||
// Log.d(TAG, "strokeText: " + text + ", " + x + ", " + y + ", " + ", " + maxWidth);
|
||||
createTextPaintIfNeeded();
|
||||
configShadow(mTextPaint);
|
||||
mTextPaint.setARGB(mStrokeStyleA, mStrokeStyleR, mStrokeStyleG, mStrokeStyleB);
|
||||
mTextPaint.setStyle(Paint.Style.STROKE);
|
||||
mTextPaint.setStrokeWidth(mLineWidth);
|
||||
scaleX(mTextPaint, text, maxWidth);
|
||||
Point pt = convertDrawPoint(new Point(x, y), text);
|
||||
mCanvas.drawText(text, pt.x, pt.y, mTextPaint);
|
||||
}
|
||||
|
||||
private void configShadow(Paint paint) {
|
||||
if ((Math.abs(mShadowOffsetX) > Float.MIN_VALUE || Math.abs(mShadowOffsetY) > Float.MIN_VALUE)) {
|
||||
if (mShadowBlur < 0) {
|
||||
return;
|
||||
}
|
||||
if (mShadowBlur < Float.MIN_VALUE) {
|
||||
mShadowBlur = 0.001f;//If shadowBlur is 0, the shadow effect is not consistent with the web.
|
||||
}
|
||||
paint.setShadowLayer(mShadowBlur, mShadowOffsetX, mShadowOffsetY,
|
||||
Color.argb(mShadowColorA, mShadowColorR, mShadowColorG, mShadowColorB));
|
||||
}
|
||||
}
|
||||
|
||||
private float measureText(String text) {
|
||||
createTextPaintIfNeeded();
|
||||
float ret = mTextPaint.measureText(text);
|
||||
// Log.d(TAG, "measureText: " + text + ", return: " + ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void updateFont(String fontName, float fontSize, boolean bold, boolean italic, boolean oblique, boolean smallCaps) {
|
||||
// Log.d(TAG, "updateFont: " + fontName + ", " + fontSize);
|
||||
mFontName = fontName;
|
||||
mFontSize = fontSize;
|
||||
mIsBoldFont = bold;
|
||||
mIsItalicFont = italic;
|
||||
mIsObliqueFont = oblique;
|
||||
mIsSmallCapsFontVariant = smallCaps;
|
||||
mTextPaint = null; // Reset paint to re-create paint object in createTextPaintIfNeeded
|
||||
}
|
||||
|
||||
private void setTextAlign(int align) {
|
||||
// Log.d(TAG, "setTextAlign: " + align);
|
||||
mTextAlign = align;
|
||||
}
|
||||
|
||||
private void setTextBaseline(int baseline) {
|
||||
// Log.d(TAG, "setTextBaseline: " + baseline);
|
||||
mTextBaseline = baseline;
|
||||
}
|
||||
|
||||
private void setFillStyle(int r, int g, int b, int a) {
|
||||
// Log.d(TAG, "setFillStyle: " + r + ", " + g + ", " + b + ", " + a);
|
||||
mFillStyleR = r;
|
||||
mFillStyleG = g;
|
||||
mFillStyleB = b;
|
||||
mFillStyleA = a;
|
||||
}
|
||||
|
||||
private void setStrokeStyle(int r, int g, int b, int a) {
|
||||
// Log.d(TAG, "setStrokeStyle: " + r + ", " + g + ", " + b + ", " + a);
|
||||
mStrokeStyleR = r;
|
||||
mStrokeStyleG = g;
|
||||
mStrokeStyleB = b;
|
||||
mStrokeStyleA = a;
|
||||
}
|
||||
|
||||
private void setLineWidth(float lineWidth) {
|
||||
mLineWidth = lineWidth;
|
||||
}
|
||||
|
||||
private void _fillImageData(int[] imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) {
|
||||
Log.d(TAG, "_fillImageData: ");
|
||||
int fillSize = (int) (imageWidth * imageHeight);
|
||||
int[] fillColors = new int[fillSize];
|
||||
int r, g, b, a;
|
||||
for (int i = 0; i < fillSize; ++i) {
|
||||
// imageData Pixel (RGBA) -> fillColors int (ARGB)
|
||||
fillColors[i] = Integer.rotateRight(imageData[i], 8);
|
||||
}
|
||||
mBitmap.setPixels(fillColors, 0, (int) imageWidth, (int) offsetX, (int) offsetY, (int) imageWidth, (int) imageHeight);
|
||||
}
|
||||
|
||||
private Point convertDrawPoint(final Point point, String text) {
|
||||
// The parameter 'point' is located at left-bottom position.
|
||||
// Need to adjust 'point' according 'text align' & 'text base line'.
|
||||
Point ret = new Point(point);
|
||||
createTextPaintIfNeeded();
|
||||
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
|
||||
float width = measureText(text);
|
||||
|
||||
if (mTextAlign == TEXT_ALIGN_CENTER)
|
||||
{
|
||||
ret.x -= width / 2;
|
||||
}
|
||||
else if (mTextAlign == TEXT_ALIGN_RIGHT)
|
||||
{
|
||||
ret.x -= width;
|
||||
}
|
||||
|
||||
// Canvas.drawText accepts the y parameter as the baseline position, not the most bottom
|
||||
if (mTextBaseline == TEXT_BASELINE_TOP)
|
||||
{
|
||||
ret.y += -fm.ascent;
|
||||
}
|
||||
else if (mTextBaseline == TEXT_BASELINE_MIDDLE)
|
||||
{
|
||||
ret.y += (fm.descent - fm.ascent) / 2 - fm.descent;
|
||||
}
|
||||
else if (mTextBaseline == TEXT_BASELINE_BOTTOM)
|
||||
{
|
||||
ret.y += -fm.descent;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Bitmap getBitmap() {
|
||||
return mBitmap;
|
||||
}
|
||||
}
|
||||
222
cocos/platform/android/java/src/com/cocos/lib/CocosActivity.java
Normal file
222
cocos/platform/android/java/src/com/cocos/lib/CocosActivity.java
Normal file
@@ -0,0 +1,222 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018-2022 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.google.androidgamesdk.GameActivity;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CocosActivity extends GameActivity {
|
||||
private static final String TAG = "CocosActivity";
|
||||
private CocosWebViewHelper mWebViewHelper = null;
|
||||
private CocosVideoHelper mVideoHelper = null;
|
||||
|
||||
private CocosSensorHandler mSensorHandler;
|
||||
private List<CocosSurfaceView> mSurfaceViewArray;
|
||||
private FrameLayout mRootLayout;
|
||||
|
||||
|
||||
|
||||
private native void onCreateNative();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
onLoadNativeLibraries();
|
||||
onCreateNative();
|
||||
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// GlobalObject.init should be initialized at first.
|
||||
GlobalObject.init(this, this);
|
||||
|
||||
CocosHelper.registerBatteryLevelReceiver(this);
|
||||
CocosHelper.init();
|
||||
CocosAudioFocusManager.registerAudioFocusListener(this);
|
||||
CanvasRenderingContext2DImpl.init(this);
|
||||
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
|
||||
initView();
|
||||
|
||||
|
||||
mSensorHandler = new CocosSensorHandler(this);
|
||||
|
||||
setImmersiveMode();
|
||||
|
||||
Utils.hideVirtualButton();
|
||||
|
||||
mSurfaceView.setOnTouchListener((v, event) -> processMotionEvent(event));
|
||||
}
|
||||
|
||||
private void setImmersiveMode() {
|
||||
WindowManager.LayoutParams lp = getWindow().getAttributes();
|
||||
try {
|
||||
Field field = lp.getClass().getField("layoutInDisplayCutoutMode");
|
||||
//Field constValue = lp.getClass().getDeclaredField("LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER");
|
||||
Field constValue = lp.getClass().getDeclaredField("LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES");
|
||||
field.setInt(lp, constValue.getInt(null));
|
||||
|
||||
// https://developer.android.com/training/system-ui/immersive
|
||||
int flag = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
|
||||
|
||||
flag |= View.class.getDeclaredField("SYSTEM_UI_FLAG_IMMERSIVE_STICKY").getInt(null);
|
||||
View view = getWindow().getDecorView();
|
||||
view.setSystemUiVisibility(flag);
|
||||
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected void initView() {
|
||||
mRootLayout = findViewById(contentViewId);
|
||||
if (mWebViewHelper == null) {
|
||||
mWebViewHelper = new CocosWebViewHelper(mRootLayout);
|
||||
}
|
||||
|
||||
if (mVideoHelper == null) {
|
||||
mVideoHelper = new CocosVideoHelper(this, mRootLayout);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public SurfaceView getSurfaceView() {
|
||||
return this.mSurfaceView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
CocosHelper.unregisterBatteryLevelReceiver(this);
|
||||
CocosAudioFocusManager.unregisterAudioFocusListener(this);
|
||||
CanvasRenderingContext2DImpl.destroy();
|
||||
GlobalObject.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
mSensorHandler.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mSensorHandler.onResume();
|
||||
Utils.hideVirtualButton();
|
||||
if (CocosAudioFocusManager.isAudioFocusLoss()) {
|
||||
CocosAudioFocusManager.registerAudioFocusListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
mSurfaceView.setVisibility(View.INVISIBLE);
|
||||
if (null != mSurfaceViewArray) {
|
||||
for (CocosSurfaceView surfaceView : mSurfaceViewArray) {
|
||||
surfaceView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
mSurfaceView.setVisibility(View.VISIBLE);
|
||||
if (null != mSurfaceViewArray) {
|
||||
for (CocosSurfaceView surfaceView : mSurfaceViewArray) {
|
||||
surfaceView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus && CocosAudioFocusManager.isAudioFocusLoss()) {
|
||||
CocosAudioFocusManager.registerAudioFocusListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
// invoke from native code
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
private void createSurface(int x, int y, int width, int height, int windowId) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosSurfaceView view = new CocosSurfaceView(CocosActivity.this, windowId);
|
||||
view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height);
|
||||
params.leftMargin = x;
|
||||
params.topMargin = y;
|
||||
//mSubsurfaceView.setBackgroundColor(Color.BLUE);
|
||||
mRootLayout.addView(view, params);
|
||||
if (null == mSurfaceViewArray) {
|
||||
mSurfaceViewArray = new ArrayList<>();
|
||||
}
|
||||
mSurfaceViewArray.add(view);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onLoadNativeLibraries() {
|
||||
try {
|
||||
ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
|
||||
|
||||
Bundle bundle = ai.metaData;
|
||||
String libName = bundle.getString("android.app.lib_name");
|
||||
if (TextUtils.isEmpty(libName)) {
|
||||
Log.e(TAG, "can not find library, please config android.app.lib_name at AndroidManifest.xml");
|
||||
}
|
||||
assert libName != null;
|
||||
System.loadLibrary(libName);
|
||||
getIntent().putExtra(GameActivity.META_DATA_LIB_NAME, libName);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2018-2022 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated engine source code (the "Software"), a limited,
|
||||
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
|
||||
to use Cocos Creator solely to develop games on your target platforms. You shall
|
||||
not use Cocos Creator software for developing other software or tools that's
|
||||
used for developing games. You are not granted to publish, distribute,
|
||||
sublicense, and/or sell copies of Cocos Creator.
|
||||
|
||||
The software or tools in this License Agreement are licensed, not sold.
|
||||
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioFocusRequest;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
class CocosAudioFocusManager {
|
||||
|
||||
private static final String _TAG = "CocosAudioFocusManager";
|
||||
private static boolean isAudioFocusLost = true;
|
||||
|
||||
private final static AudioManager.OnAudioFocusChangeListener sAfChangeListener = focusChange -> {
|
||||
Log.d(_TAG, "onAudioFocusChange: " + focusChange + ", thread: " + Thread.currentThread().getName());
|
||||
|
||||
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
|
||||
// Permanent loss of audio focus
|
||||
// Pause playback immediately
|
||||
Log.d(_TAG, "Pause music by AUDIOFOCUS_LOSS");
|
||||
isAudioFocusLost = true;
|
||||
CocosHelper.runOnGameThreadAtForeground(() -> nativeSetAudioVolumeFactor(0));
|
||||
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
|
||||
// Pause playback
|
||||
Log.d(_TAG, "Pause music by AUDIOFOCUS_LOSS_TRANSILENT");
|
||||
isAudioFocusLost = true;
|
||||
CocosHelper.runOnGameThreadAtForeground(() -> nativeSetAudioVolumeFactor(0));
|
||||
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
|
||||
// Lower the volume, keep playing
|
||||
Log.d(_TAG, "Lower the volume, keep playing by AUDIOFOCUS_LOSS_TRANSILENT_CAN_DUCK");
|
||||
isAudioFocusLost = false;
|
||||
CocosHelper.runOnGameThreadAtForeground(() -> nativeSetAudioVolumeFactor(0.1f));
|
||||
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
|
||||
// Your app has been granted audio focus again
|
||||
// Raise volume to normal, restart playback if necessary
|
||||
Log.d(_TAG, "Resume music by AUDIOFOCUS_GAIN");
|
||||
isAudioFocusLost = false;
|
||||
CocosHelper.runOnGameThreadAtForeground(() -> nativeSetAudioVolumeFactor(1.0f));
|
||||
}
|
||||
};
|
||||
|
||||
static void registerAudioFocusListener(Context context) {
|
||||
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
assert am != null;
|
||||
int result;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AudioAttributes playbackAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build();
|
||||
|
||||
// set the playback attributes for the focus requester
|
||||
AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
|
||||
.setAudioAttributes(playbackAttributes)
|
||||
.setWillPauseWhenDucked(true)
|
||||
.setOnAudioFocusChangeListener(sAfChangeListener)
|
||||
.build();
|
||||
|
||||
result = am.requestAudioFocus(focusRequest);
|
||||
} else {
|
||||
// Request audio focus for playback
|
||||
result = am.requestAudioFocus(sAfChangeListener,
|
||||
// Use the music stream.
|
||||
AudioManager.STREAM_MUSIC,
|
||||
// Request permanent focus.
|
||||
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
||||
}
|
||||
|
||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
Log.d(_TAG, "requestAudioFocus succeed");
|
||||
isAudioFocusLost = false;
|
||||
CocosHelper.runOnGameThreadAtForeground(() -> nativeSetAudioVolumeFactor(1.0f));
|
||||
return;
|
||||
}
|
||||
|
||||
Log.e(_TAG, "requestAudioFocus failed!");
|
||||
}
|
||||
|
||||
static void unregisterAudioFocusListener(Context context) {
|
||||
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
assert am != null;
|
||||
int result = am.abandonAudioFocus(sAfChangeListener);
|
||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
Log.d(_TAG, "abandonAudioFocus succeed!");
|
||||
} else {
|
||||
Log.e(_TAG, "abandonAudioFocus failed!");
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isAudioFocusLoss() {
|
||||
return isAudioFocusLost;
|
||||
}
|
||||
|
||||
private static native void nativeSetAudioVolumeFactor(float focus);
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated engine source code (the "Software"), a limited,
|
||||
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
|
||||
to use Cocos Creator solely to develop games on your target platforms. You shall
|
||||
not use Cocos Creator software for developing other software or tools that's
|
||||
used for developing games. You are not granted to publish, distribute,
|
||||
sublicense, and/or sell copies of Cocos Creator.
|
||||
|
||||
The software or tools in this License Agreement are licensed, not sold.
|
||||
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import org.cocos2dx.okhttp3.Call;
|
||||
import org.cocos2dx.okhttp3.Callback;
|
||||
import org.cocos2dx.okhttp3.Dispatcher;
|
||||
import org.cocos2dx.okhttp3.OkHttpClient;
|
||||
import org.cocos2dx.okhttp3.Protocol;
|
||||
import org.cocos2dx.okhttp3.Request;
|
||||
import org.cocos2dx.okhttp3.Response;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
// Rename package okhttp3 to org.cocos2dx.okhttp3
|
||||
// Github repo: https://github.com/PatriceJiang/okhttp/tree/cocos2dx-rename-3.12.x
|
||||
// and https://github.com/PatriceJiang/okio/tree/cocos2dx-rename-1.15.0
|
||||
|
||||
public class CocosDownloader {
|
||||
|
||||
private int _id;
|
||||
private OkHttpClient _httpClient = null;
|
||||
private static Dispatcher dispatcher = null;
|
||||
|
||||
private String _tempFileNameSuffix;
|
||||
private int _countOfMaxProcessingTasks;
|
||||
private ConcurrentHashMap<Integer,Call> _taskMap = new ConcurrentHashMap<>();
|
||||
private Queue<Runnable> _taskQueue = new LinkedList<>();
|
||||
private int _runningTaskCount = 0;
|
||||
|
||||
private void onProgress(final int id, final long downloadBytes, final long downloadNow, final long downloadTotal) {
|
||||
CocosHelper.runOnGameThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
nativeOnProgress(_id, id, downloadBytes, downloadNow, downloadTotal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onFinish(final int id, final int errCode, final String errStr, final byte[] data) {
|
||||
Call task =_taskMap.get(id);
|
||||
if (null == task) return;
|
||||
_taskMap.remove(id);
|
||||
_runningTaskCount -= 1;
|
||||
CocosHelper.runOnGameThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
nativeOnFinish(_id, id, errCode, errStr, data);
|
||||
}
|
||||
});
|
||||
runNextTaskIfExists();
|
||||
}
|
||||
|
||||
public static CocosDownloader createDownloader(int id, int timeoutInSeconds, String tempFileSuffix, int maxProcessingTasks) {
|
||||
CocosDownloader downloader = new CocosDownloader();
|
||||
downloader._id = id;
|
||||
|
||||
if(dispatcher == null) {
|
||||
dispatcher = new Dispatcher();
|
||||
}
|
||||
|
||||
if (timeoutInSeconds > 0) {
|
||||
downloader._httpClient = new OkHttpClient().newBuilder()
|
||||
.dispatcher(dispatcher)
|
||||
.followRedirects(true)
|
||||
.followSslRedirects(true)
|
||||
.connectTimeout(timeoutInSeconds, TimeUnit.SECONDS)
|
||||
.protocols(Collections.singletonList(Protocol.HTTP_1_1))
|
||||
.build();
|
||||
} else {
|
||||
downloader._httpClient = new OkHttpClient().newBuilder()
|
||||
.dispatcher(dispatcher)
|
||||
.followRedirects(true)
|
||||
.followSslRedirects(true)
|
||||
.protocols(Collections.singletonList(Protocol.HTTP_1_1))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
downloader._tempFileNameSuffix = tempFileSuffix;
|
||||
downloader._countOfMaxProcessingTasks = maxProcessingTasks;
|
||||
return downloader;
|
||||
}
|
||||
|
||||
public static void createTask(final CocosDownloader downloader, int id_, String url_, String path_, String []header_) {
|
||||
final int id = id_;
|
||||
final String url = url_;
|
||||
final String path = path_;
|
||||
final String pathWithUrlHash = path + url.hashCode();
|
||||
final String tempFilePath = pathWithUrlHash + downloader._tempFileNameSuffix;
|
||||
final String[] header = header_;
|
||||
|
||||
Runnable taskRunnable = new Runnable() {
|
||||
String domain = null;
|
||||
String host = null;
|
||||
File tempFile = null;
|
||||
File finalFile = null;
|
||||
long downloadStart = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Call task = null;
|
||||
|
||||
do {
|
||||
if (path.length() > 0) {
|
||||
try {
|
||||
URI uri = new URI(url);
|
||||
domain = uri.getHost();
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
} catch (NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
|
||||
// file task
|
||||
tempFile = new File(tempFilePath);
|
||||
if (tempFile.isDirectory()) break;
|
||||
|
||||
File parent = tempFile.getParentFile();
|
||||
if (parent == null) {
|
||||
String errStr = "Invalid path " + path + " : The current path is inaccessible.";
|
||||
Log.e("CocosDownloader", errStr);
|
||||
CocosHelper.runOnGameThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
downloader.nativeOnFinish(downloader._id, id, 0, errStr, null);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
if (!parent.isDirectory() && !parent.mkdirs()) break;
|
||||
|
||||
finalFile = new File(path);
|
||||
if (finalFile.isDirectory()) break;
|
||||
long fileLen = tempFile.length();
|
||||
|
||||
host = domain.startsWith("www.") ? domain.substring(4) : domain;
|
||||
if (fileLen > 0) {
|
||||
SharedPreferences sharedPreferences = GlobalObject.getContext().getSharedPreferences("breakpointDownloadSupport", Context.MODE_PRIVATE);
|
||||
if (sharedPreferences.contains(host) && sharedPreferences.getBoolean(host, false)) {
|
||||
downloadStart = fileLen;
|
||||
} else {
|
||||
// Remove previous downloaded context
|
||||
try {
|
||||
PrintWriter writer = new PrintWriter(tempFile);
|
||||
writer.print("");
|
||||
writer.close();
|
||||
}
|
||||
// Not found then nothing to do
|
||||
catch (FileNotFoundException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Request.Builder builder = new Request.Builder().url(url);
|
||||
for (int i = 0; i < header.length / 2; i++) {
|
||||
builder.addHeader(header[i * 2], header[(i * 2) + 1]);
|
||||
}
|
||||
if (downloadStart > 0) {
|
||||
builder.addHeader("RANGE", "bytes=" + downloadStart + "-");
|
||||
}
|
||||
|
||||
final Request request = builder.build();
|
||||
task = downloader._httpClient.newCall(request);
|
||||
if (null == task) {
|
||||
final String errStr = "Can't create DownloadTask for " + url;
|
||||
CocosHelper.runOnGameThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
downloader.nativeOnFinish(downloader._id, id, 0, errStr, null);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
downloader._taskMap.put(id, task);
|
||||
}
|
||||
task.enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
downloader.onFinish(id, 0, e.toString(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
InputStream is = null;
|
||||
byte[] buf = new byte[4096];
|
||||
FileOutputStream fos = null;
|
||||
|
||||
try {
|
||||
|
||||
if(!(response.code() >= 200 && response.code() <= 206)) {
|
||||
// it is encourage to delete the tmp file when requested range not satisfiable.
|
||||
if (response.code() == 416) {
|
||||
File file = new File(tempFilePath);
|
||||
if (file.exists() && file.isFile()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
downloader.onFinish(id, -2, response.message(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
// save breakpointDownloadSupport Data to SharedPreferences storage
|
||||
Context context = GlobalObject.getContext();
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences("breakpointDownloadSupport", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
long total = response.body().contentLength() + downloadStart;
|
||||
if (path.length() > 0 && !sharedPreferences.contains(host)) {
|
||||
if (total > 0) {
|
||||
editor.putBoolean(host, true);
|
||||
} else {
|
||||
editor.putBoolean(host, false);
|
||||
}
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
long current = downloadStart;
|
||||
is = response.body().byteStream();
|
||||
|
||||
if (path.length() > 0) {
|
||||
if (downloadStart > 0) {
|
||||
fos = new FileOutputStream(tempFile, true);
|
||||
} else {
|
||||
fos = new FileOutputStream(tempFile, false);
|
||||
}
|
||||
|
||||
int len;
|
||||
while ((len = is.read(buf)) != -1) {
|
||||
current += len;
|
||||
fos.write(buf, 0, len);
|
||||
downloader.onProgress(id, len, current, total);
|
||||
}
|
||||
fos.flush();
|
||||
|
||||
String errStr = null;
|
||||
do {
|
||||
// rename temp file to final file, if final file exist, remove it
|
||||
if (finalFile.exists()) {
|
||||
if (finalFile.isDirectory()) {
|
||||
break;
|
||||
}
|
||||
if (!finalFile.delete()) {
|
||||
errStr = "Can't remove old file:" + finalFile.getAbsolutePath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
tempFile.renameTo(finalFile);
|
||||
} while (false);
|
||||
|
||||
if (errStr == null) {
|
||||
downloader.onFinish(id, 0, null, null);
|
||||
downloader.runNextTaskIfExists();
|
||||
} else {
|
||||
downloader.onFinish(id, 0, errStr, null);
|
||||
}
|
||||
if (sharedPreferences.contains(host)) {
|
||||
editor.remove(host);
|
||||
editor.commit();
|
||||
}
|
||||
} else {
|
||||
// non-file
|
||||
ByteArrayOutputStream buffer;
|
||||
if(total > 0) {
|
||||
buffer = new ByteArrayOutputStream((int) total);
|
||||
} else {
|
||||
buffer = new ByteArrayOutputStream(4096);
|
||||
}
|
||||
|
||||
int len;
|
||||
while ((len = is.read(buf)) != -1) {
|
||||
current += len;
|
||||
buffer.write(buf, 0, len);
|
||||
downloader.onProgress(id, len, current, total);
|
||||
}
|
||||
downloader.onFinish(id, 0, null, buffer.toByteArray());
|
||||
downloader.runNextTaskIfExists();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
downloader.onFinish(id, 0, e.toString(), null);
|
||||
} finally {
|
||||
try {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
if (fos != null) {
|
||||
fos.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e("CocosDownloader", e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} while (false);
|
||||
}
|
||||
};
|
||||
downloader.enqueueTask(taskRunnable);
|
||||
}
|
||||
|
||||
public static void abort(final CocosDownloader downloader, final int id) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Iterator iter = downloader._taskMap.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry entry = (Map.Entry) iter.next();
|
||||
Object key = entry.getKey();
|
||||
Call task = (Call) entry.getValue();
|
||||
if (null != task && Integer.parseInt(key.toString()) == id) {
|
||||
task.cancel();
|
||||
downloader._taskMap.remove(id);
|
||||
downloader.runNextTaskIfExists();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void cancelAllRequests(final CocosDownloader downloader) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Object o : downloader._taskMap.entrySet()) {
|
||||
Map.Entry entry = (Map.Entry) o;
|
||||
Call task = (Call) entry.getValue();
|
||||
if (null != task) {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void enqueueTask(Runnable taskRunnable) {
|
||||
synchronized (_taskQueue) {
|
||||
if (_runningTaskCount < _countOfMaxProcessingTasks) {
|
||||
GlobalObject.runOnUiThread(taskRunnable);
|
||||
_runningTaskCount++;
|
||||
} else {
|
||||
_taskQueue.add(taskRunnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runNextTaskIfExists() {
|
||||
synchronized (_taskQueue) {
|
||||
while (_runningTaskCount < _countOfMaxProcessingTasks &&
|
||||
CocosDownloader.this._taskQueue.size() > 0) {
|
||||
|
||||
Runnable taskRunnable = CocosDownloader.this._taskQueue.poll();
|
||||
GlobalObject.runOnUiThread(taskRunnable);
|
||||
_runningTaskCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
native void nativeOnProgress(int id, int taskId, long dl, long dlnow, long dltotal);
|
||||
native void nativeOnFinish(int id, int taskId, int errCode, String errStr, final byte[] data);
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2012 cocos2d-x.org
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class CocosEditBoxActivity extends Activity {
|
||||
|
||||
// a color of dark green, was used for confirm button background
|
||||
private static final int DARK_GREEN = Color.parseColor("#1fa014");
|
||||
private static final int DARK_GREEN_PRESS = Color.parseColor("#008e26");
|
||||
|
||||
private static CocosEditBoxActivity sThis = null;
|
||||
private Cocos2dxEditText mEditText = null;
|
||||
private Button mButton = null;
|
||||
private String mButtonTitle = null;
|
||||
private boolean mConfirmHold = true;
|
||||
private int mEditTextID = 1;
|
||||
private int mButtonLayoutID = 2;
|
||||
|
||||
/***************************************************************************************
|
||||
Inner class.
|
||||
**************************************************************************************/
|
||||
class Cocos2dxEditText extends EditText {
|
||||
private final String TAG = "Cocos2dxEditBox";
|
||||
private boolean mIsMultiLine = false;
|
||||
private TextWatcher mTextWatcher = null;
|
||||
private boolean keyboardVisible = false;
|
||||
private int mScreenHeight;
|
||||
private boolean mCheckKeyboardShowNormally = false;
|
||||
|
||||
public Cocos2dxEditText(Activity context){
|
||||
super(context);
|
||||
this.setTextColor(Color.BLACK);
|
||||
mScreenHeight = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).
|
||||
getDefaultDisplay().getHeight();
|
||||
|
||||
mTextWatcher = new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
// Pass text to c++.
|
||||
CocosEditBoxActivity.this.onKeyboardInput(s.toString());
|
||||
}
|
||||
};
|
||||
registKeyboardVisible();
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
Public functions.
|
||||
**************************************************************************************/
|
||||
|
||||
public void show(String defaultValue, int maxLength, boolean isMultiline, boolean confirmHold, String confirmType, String inputType) {
|
||||
mIsMultiLine = isMultiline;
|
||||
this.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength) });
|
||||
this.setText(defaultValue);
|
||||
if (this.getText().length() >= defaultValue.length()) {
|
||||
this.setSelection(defaultValue.length());
|
||||
} else {
|
||||
this.setSelection(this.getText().length());
|
||||
}
|
||||
this.setConfirmType(confirmType);
|
||||
this.setInputType(inputType, mIsMultiLine);
|
||||
this.setVisibility(View.VISIBLE);
|
||||
|
||||
// Open soft keyboard manually. Should request focus to open soft keyboard.
|
||||
this.requestFocus();
|
||||
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
mEditText.setVisibility(View.INVISIBLE);
|
||||
this.removeListeners();
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
Private functions.
|
||||
**************************************************************************************/
|
||||
|
||||
private void setConfirmType(final String confirmType) {
|
||||
if (confirmType.contentEquals("done")) {
|
||||
this.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
mButtonTitle = getResources().getString(R.string.done);
|
||||
} else if (confirmType.contentEquals("next")) {
|
||||
this.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
mButtonTitle = getResources().getString(R.string.next);
|
||||
} else if (confirmType.contentEquals("search")) {
|
||||
this.setImeOptions(EditorInfo.IME_ACTION_SEARCH | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
mButtonTitle = getResources().getString(R.string.search);
|
||||
} else if (confirmType.contentEquals("go")) {
|
||||
this.setImeOptions(EditorInfo.IME_ACTION_GO | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
mButtonTitle = getResources().getString(R.string.go);
|
||||
} else if (confirmType.contentEquals("send")) {
|
||||
this.setImeOptions(EditorInfo.IME_ACTION_SEND | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||
mButtonTitle = getResources().getString(R.string.send);
|
||||
} else{
|
||||
mButtonTitle = null;
|
||||
Log.e(TAG, "unknown confirm type " + confirmType);
|
||||
}
|
||||
}
|
||||
|
||||
private void setInputType(final String inputType, boolean isMultiLine){
|
||||
mCheckKeyboardShowNormally = false;
|
||||
if (inputType.contentEquals("text")) {
|
||||
if (isMultiLine)
|
||||
this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
||||
else
|
||||
this.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
}
|
||||
else if (inputType.contentEquals("email"))
|
||||
this.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
|
||||
else if (inputType.contentEquals("number"))
|
||||
this.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
else if (inputType.contentEquals("phone"))
|
||||
this.setInputType(InputType.TYPE_CLASS_PHONE);
|
||||
else if (inputType.contentEquals("password")) {
|
||||
if (Build.BRAND.equalsIgnoreCase("oppo")) {
|
||||
mCheckKeyboardShowNormally = true;
|
||||
}
|
||||
this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
}
|
||||
else
|
||||
Log.e(TAG, "unknown input type " + inputType);
|
||||
}
|
||||
|
||||
private void addListeners() {
|
||||
|
||||
this.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (! mIsMultiLine) {
|
||||
CocosEditBoxActivity.this.hide();
|
||||
}
|
||||
|
||||
return false; // pass on to other listeners.
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.addTextChangedListener(mTextWatcher);
|
||||
}
|
||||
|
||||
private void removeListeners() {
|
||||
this.setOnEditorActionListener(null);
|
||||
this.removeTextChangedListener(mTextWatcher);
|
||||
}
|
||||
|
||||
private boolean isSystemAdjustUIWhenPopKeyboard(int bottom) {
|
||||
int bottomOffset = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
bottomOffset = getWindow().getDecorView().getRootWindowInsets().getSystemWindowInsetBottom();
|
||||
}
|
||||
// view will be scrolled to the target position by system,
|
||||
if (Math.abs(bottom - bottomOffset) < 10) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void registKeyboardVisible() {
|
||||
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
Rect r = new Rect();
|
||||
getWindowVisibleDisplayFrame(r);
|
||||
int heightDiff = getRootView().getHeight() - (r.bottom - r.top);
|
||||
// if more than a quarter of the screen, its probably a keyboard
|
||||
if (heightDiff > mScreenHeight/4) {
|
||||
if (!keyboardVisible) {
|
||||
keyboardVisible = true;
|
||||
}
|
||||
if (!isSystemAdjustUIWhenPopKeyboard(heightDiff)) {
|
||||
getRootView().scrollTo(0, heightDiff);
|
||||
}
|
||||
} else {
|
||||
getRootView().scrollTo(0, 0);
|
||||
if (mCheckKeyboardShowNormally && !keyboardVisible) {
|
||||
Toast.makeText(CocosEditBoxActivity.this, R.string.tip_disable_safe_input_type, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
if (keyboardVisible) {
|
||||
keyboardVisible = false;
|
||||
CocosEditBoxActivity.this.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||
CocosEditBoxActivity.sThis = this;
|
||||
|
||||
ViewGroup.LayoutParams frameLayoutParams =
|
||||
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
RelativeLayout frameLayout = new RelativeLayout(this);
|
||||
frameLayout.setLayoutParams(frameLayoutParams);
|
||||
frameLayout.setClickable(true);
|
||||
frameLayout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
CocosEditBoxActivity.this.hide();
|
||||
}
|
||||
});
|
||||
setContentView(frameLayout);
|
||||
|
||||
this.addItems(frameLayout);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle extras = null;
|
||||
if (null != intent) {
|
||||
extras = intent.getExtras();
|
||||
}
|
||||
if(extras == null){
|
||||
show("",
|
||||
10,
|
||||
false,
|
||||
false,
|
||||
"done",
|
||||
"text"
|
||||
);
|
||||
} else {
|
||||
show(extras.getString("defaultValue"),
|
||||
extras.getInt("maxLength"),
|
||||
extras.getBoolean("isMultiline"),
|
||||
extras.getBoolean("confirmHold"),
|
||||
extras.getString("confirmType"),
|
||||
extras.getString("inputType"));
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
Public functions.
|
||||
**************************************************************************************/
|
||||
|
||||
/***************************************************************************************
|
||||
Private functions.
|
||||
**************************************************************************************/
|
||||
private void addItems(RelativeLayout layout) {
|
||||
RelativeLayout myLayout = new RelativeLayout(this);
|
||||
myLayout.setBackgroundColor(Color.argb(255, 244, 244, 244));
|
||||
|
||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
|
||||
layout.addView(myLayout, layoutParams);
|
||||
|
||||
this.addEditText(myLayout);
|
||||
this.addButton(myLayout);
|
||||
}
|
||||
|
||||
private int dpToPixel(int dp) {
|
||||
final float scale = getResources().getDisplayMetrics().density;
|
||||
int px = (int) (dp * scale + 0.5f);
|
||||
return px;
|
||||
}
|
||||
private void addEditText(RelativeLayout layout) {
|
||||
mEditText = new Cocos2dxEditText(this);
|
||||
mEditText.setVisibility(View.INVISIBLE);
|
||||
mEditText.setGravity(Gravity.CENTER_VERTICAL);
|
||||
mEditText.setBackground(getRoundRectShape(18, Color.WHITE, Color.WHITE));
|
||||
mEditText.setId(mEditTextID);
|
||||
int bottomPadding = dpToPixel(4);
|
||||
int leftPadding = dpToPixel(3);
|
||||
mEditText.setPadding(leftPadding,bottomPadding,leftPadding,bottomPadding);
|
||||
|
||||
RelativeLayout.LayoutParams editParams = new RelativeLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
editParams.addRule(RelativeLayout.CENTER_VERTICAL);
|
||||
editParams.addRule(RelativeLayout.LEFT_OF, mButtonLayoutID);
|
||||
int bottomMargin = dpToPixel(5);
|
||||
int leftMargin = dpToPixel(4);
|
||||
editParams.setMargins(leftMargin, bottomMargin, bottomMargin, bottomMargin);
|
||||
layout.addView(mEditText, editParams);
|
||||
}
|
||||
|
||||
private void addButton(RelativeLayout layout) {
|
||||
mButton = new Button(this);
|
||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
mButton.setTextColor(Color.WHITE);
|
||||
mButton.setTextSize(16);
|
||||
mButton.setBackground(getRoundRectShape(18, DARK_GREEN, DARK_GREEN_PRESS));
|
||||
int paddingLeft = dpToPixel(5);
|
||||
mButton.setPadding(paddingLeft,0,paddingLeft,0);
|
||||
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
||||
layoutParams.addRule(RelativeLayout.ALIGN_TOP, mEditTextID);
|
||||
layoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mEditTextID);
|
||||
layoutParams.rightMargin = dpToPixel(4);
|
||||
layout.addView(mButton, layoutParams);
|
||||
mButton.setGravity(Gravity.CENTER);
|
||||
mButton.setId(mButtonLayoutID);
|
||||
|
||||
mButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
CocosEditBoxActivity.this.onKeyboardConfirm(mEditText.getText().toString());
|
||||
|
||||
if (!CocosEditBoxActivity.this.mConfirmHold)
|
||||
CocosEditBoxActivity.this.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Drawable getRoundRectShape(int radius, int normalColor, int pressColor) {
|
||||
float[] outerRadii = new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
|
||||
RoundRectShape roundRectShape = new RoundRectShape(outerRadii, null, null);
|
||||
ShapeDrawable shapeDrawableNormal = new ShapeDrawable();
|
||||
shapeDrawableNormal.setShape(roundRectShape);
|
||||
shapeDrawableNormal.getPaint().setStyle(Paint.Style.FILL);
|
||||
shapeDrawableNormal.getPaint().setColor(normalColor);
|
||||
ShapeDrawable shapeDrawablePress = new ShapeDrawable();
|
||||
shapeDrawablePress.setShape(roundRectShape);
|
||||
shapeDrawablePress.getPaint().setStyle(Paint.Style.FILL);
|
||||
shapeDrawablePress.getPaint().setColor(pressColor);
|
||||
StateListDrawable drawable = new StateListDrawable();
|
||||
drawable.addState(new int[]{android.R.attr.state_pressed}, shapeDrawablePress);
|
||||
drawable.addState(new int[]{}, shapeDrawableNormal);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
|
||||
private void hide() {
|
||||
Utils.hideVirtualButton();
|
||||
this.closeKeyboard();
|
||||
finish();
|
||||
}
|
||||
|
||||
public void show(String defaultValue, int maxLength, boolean isMultiline, boolean confirmHold, String confirmType, String inputType) {
|
||||
mConfirmHold = confirmHold;
|
||||
mEditText.show(defaultValue, maxLength, isMultiline, confirmHold, confirmType, inputType);
|
||||
mButton.setText(mButtonTitle);
|
||||
if (TextUtils.isEmpty(mButtonTitle)) {
|
||||
mButton.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
mButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
this.openKeyboard();
|
||||
}
|
||||
|
||||
private void closeKeyboard() {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(this.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
|
||||
|
||||
this.onKeyboardComplete(mEditText.getText().toString());
|
||||
}
|
||||
|
||||
private void openKeyboard() {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(this.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
Functions invoked by CPP.
|
||||
**************************************************************************************/
|
||||
|
||||
private static void showNative(String defaultValue, int maxLength, boolean isMultiline, boolean confirmHold, String confirmType, String inputType) {
|
||||
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Intent i = new Intent(GlobalObject.getActivity(), CocosEditBoxActivity.class);
|
||||
i.putExtra("defaultValue", defaultValue);
|
||||
i.putExtra("maxLength", maxLength);
|
||||
i.putExtra("isMultiline", isMultiline);
|
||||
i.putExtra("confirmHold", confirmHold);
|
||||
i.putExtra("confirmType", confirmType);
|
||||
i.putExtra("inputType", inputType);
|
||||
GlobalObject.getActivity().startActivity(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void hideNative() {
|
||||
if (null != CocosEditBoxActivity.sThis) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosEditBoxActivity.sThis.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
Native functions invoked by UI.
|
||||
**************************************************************************************/
|
||||
private void onKeyboardInput(String text) {
|
||||
CocosHelper.runOnGameThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosEditBoxActivity.onKeyboardInputNative(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onKeyboardComplete(String text) {
|
||||
CocosHelper.runOnGameThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosEditBoxActivity.onKeyboardCompleteNative(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onKeyboardConfirm(String text) {
|
||||
CocosHelper.runOnGameThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosEditBoxActivity.onKeyboardConfirmNative(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static native void onKeyboardInputNative(String text);
|
||||
private static native void onKeyboardCompleteNative(String text);
|
||||
private static native void onKeyboardConfirmNative(String text);
|
||||
}
|
||||
105
cocos/platform/android/java/src/com/cocos/lib/CocosHandler.java
Normal file
105
cocos/platform/android/java/src/com/cocos/lib/CocosHandler.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2013 cocos2d-x.org
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.app.Activity;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class CocosHandler extends Handler {
|
||||
// ===========================================================
|
||||
// Constants
|
||||
// ===========================================================
|
||||
public final static int HANDLER_SHOW_DIALOG = 1;
|
||||
|
||||
// ===========================================================
|
||||
// Fields
|
||||
// ===========================================================
|
||||
private WeakReference<Activity> mActivity;
|
||||
|
||||
// ===========================================================
|
||||
// Constructors
|
||||
// ===========================================================
|
||||
public CocosHandler(Activity activity) {
|
||||
this.mActivity = new WeakReference<Activity>(activity);
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Getter & Setter
|
||||
// ===========================================================
|
||||
|
||||
// ===========================================================
|
||||
// Methods for/from SuperClass/Interfaces
|
||||
// ===========================================================
|
||||
|
||||
// ===========================================================
|
||||
// Methods
|
||||
// ===========================================================
|
||||
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case CocosHandler.HANDLER_SHOW_DIALOG:
|
||||
showDialog(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void showDialog(Message msg) {
|
||||
Activity theActivity = this.mActivity.get();
|
||||
DialogMessage dialogMessage = (DialogMessage)msg.obj;
|
||||
new AlertDialog.Builder(theActivity)
|
||||
.setTitle(dialogMessage.title)
|
||||
.setMessage(dialogMessage.message)
|
||||
.setPositiveButton("Ok",
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// REFINE: Auto-generated method stub
|
||||
|
||||
}
|
||||
}).create().show();
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Inner and Anonymous Classes
|
||||
// ===========================================================
|
||||
|
||||
public static class DialogMessage {
|
||||
public String title;
|
||||
public String message;
|
||||
|
||||
public DialogMessage(String title, String message) {
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
435
cocos/platform/android/java/src/com/cocos/lib/CocosHelper.java
Normal file
435
cocos/platform/android/java/src/com/cocos/lib/CocosHelper.java
Normal file
@@ -0,0 +1,435 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2012 cocos2d-x.org
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.LocaleList;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.Surface;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.vending.expansion.zipfile.APKExpansionSupport;
|
||||
import com.android.vending.expansion.zipfile.ZipResourceFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
|
||||
public class CocosHelper {
|
||||
// ===========================================================
|
||||
// Constants
|
||||
// ===========================================================
|
||||
private static final String TAG = CocosHelper.class.getSimpleName();
|
||||
|
||||
// ===========================================================
|
||||
// Fields
|
||||
// ===========================================================
|
||||
|
||||
private static Vibrator sVibrateService;
|
||||
private static BatteryReceiver sBatteryReceiver = new BatteryReceiver();
|
||||
|
||||
public static final int NETWORK_TYPE_NONE = 0;
|
||||
public static final int NETWORK_TYPE_LAN = 1;
|
||||
public static final int NETWORK_TYPE_WWAN = 2;
|
||||
|
||||
// The absolute path to the OBB if it exists.
|
||||
private static String sObbFilePath = "";
|
||||
|
||||
// The OBB file
|
||||
private static ZipResourceFile sOBBFile = null;
|
||||
|
||||
static class LockedTaskQ {
|
||||
private final Object readMtx = new Object();
|
||||
private Queue<Runnable> sTaskQ = new LinkedList<>();
|
||||
public void addTask(Runnable runnable) {
|
||||
synchronized (readMtx) {
|
||||
sTaskQ.add(runnable);
|
||||
}
|
||||
}
|
||||
public void runTasks(){
|
||||
Queue<Runnable> tmp;
|
||||
synchronized (readMtx) {
|
||||
tmp = sTaskQ;
|
||||
sTaskQ = new LinkedList<>();
|
||||
}
|
||||
for(Runnable runnable : tmp){
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static LockedTaskQ sTaskQOnGameThread = new LockedTaskQ();
|
||||
private static LockedTaskQ sForegroundTaskQOnGameThread = new LockedTaskQ();
|
||||
/**
|
||||
* Battery receiver to getting battery level.
|
||||
*/
|
||||
static class BatteryReceiver extends BroadcastReceiver {
|
||||
public float sBatteryLevel = 0.0f;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
setBatteryLevelByIntent(intent);
|
||||
}
|
||||
|
||||
public void setBatteryLevelByIntent(Intent intent) {
|
||||
if (null != intent) {
|
||||
int current = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
|
||||
int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
|
||||
float level = current * 1.0f / total;
|
||||
// clamp to 0~1
|
||||
sBatteryLevel = Math.min(Math.max(level, 0.0f), 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void registerBatteryLevelReceiver(Context context) {
|
||||
Intent intent = context.registerReceiver(sBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||||
sBatteryReceiver.setBatteryLevelByIntent(intent);
|
||||
}
|
||||
|
||||
static void unregisterBatteryLevelReceiver(Context context) {
|
||||
context.unregisterReceiver(sBatteryReceiver);
|
||||
}
|
||||
|
||||
//Run on game thread forever, no matter foreground or background
|
||||
public static void runOnGameThread(final Runnable runnable) {
|
||||
sTaskQOnGameThread.addTask(runnable);
|
||||
}
|
||||
|
||||
static void flushTasksOnGameThread() {
|
||||
sTaskQOnGameThread.runTasks();
|
||||
}
|
||||
public static void runOnGameThreadAtForeground(final Runnable runnable) {
|
||||
sForegroundTaskQOnGameThread.addTask(runnable);
|
||||
}
|
||||
|
||||
static void flushTasksOnGameThreadAtForeground() {
|
||||
sForegroundTaskQOnGameThread.runTasks();
|
||||
}
|
||||
|
||||
public static int getNetworkType() {
|
||||
int status = NETWORK_TYPE_NONE;
|
||||
NetworkInfo networkInfo;
|
||||
try {
|
||||
ConnectivityManager connMgr = (ConnectivityManager) GlobalObject.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
networkInfo = connMgr.getActiveNetworkInfo();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return status;
|
||||
}
|
||||
if (networkInfo == null) {
|
||||
return status;
|
||||
}
|
||||
int nType = networkInfo.getType();
|
||||
if (nType == ConnectivityManager.TYPE_MOBILE) {
|
||||
status = NETWORK_TYPE_WWAN;
|
||||
} else if (nType == ConnectivityManager.TYPE_WIFI) {
|
||||
status = NETWORK_TYPE_LAN;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Constructors
|
||||
// ===========================================================
|
||||
|
||||
private static boolean sInited = false;
|
||||
|
||||
public static void init() {
|
||||
if (!sInited) {
|
||||
CocosHelper.sVibrateService = (Vibrator) GlobalObject.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
CocosHelper.initObbFilePath();
|
||||
CocosHelper.initializeOBBFile();
|
||||
|
||||
sInited = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static float getBatteryLevel() {
|
||||
return sBatteryReceiver.sBatteryLevel;
|
||||
}
|
||||
|
||||
public static String getObbFilePath() {
|
||||
return CocosHelper.sObbFilePath;
|
||||
}
|
||||
|
||||
public static String getWritablePath() {
|
||||
return GlobalObject.getContext().getFilesDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
public static String getCurrentLanguage() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
return LocaleList.getDefault().get(0).getLanguage();
|
||||
} else {
|
||||
return Locale.getDefault().getLanguage();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getCurrentLanguageCode() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
return LocaleList.getDefault().get(0).toString();
|
||||
} else {
|
||||
return Locale.getDefault().toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getDeviceModel() {
|
||||
return Build.MODEL;
|
||||
}
|
||||
|
||||
public static String getSystemVersion() {
|
||||
return Build.VERSION.RELEASE;
|
||||
}
|
||||
|
||||
public static void vibrate(float duration) {
|
||||
try {
|
||||
if (sVibrateService != null && sVibrateService.hasVibrator()) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 26) {
|
||||
Class<?> vibrationEffectClass = Class.forName("android.os.VibrationEffect");
|
||||
if (vibrationEffectClass != null) {
|
||||
final int DEFAULT_AMPLITUDE = CocosReflectionHelper.<Integer>getConstantValue(vibrationEffectClass,
|
||||
"DEFAULT_AMPLITUDE");
|
||||
//VibrationEffect.createOneShot(long milliseconds, int amplitude)
|
||||
final Method method = vibrationEffectClass.getMethod("createOneShot",
|
||||
new Class[]{Long.TYPE, Integer.TYPE});
|
||||
Class<?> type = method.getReturnType();
|
||||
|
||||
Object effect = method.invoke(vibrationEffectClass,
|
||||
new Object[]{(long) (duration * 1000), DEFAULT_AMPLITUDE});
|
||||
//sVibrateService.vibrate(VibrationEffect effect);
|
||||
CocosReflectionHelper.invokeInstanceMethod(sVibrateService, "vibrate",
|
||||
new Class[]{type}, new Object[]{(effect)});
|
||||
}
|
||||
} else {
|
||||
sVibrateService.vibrate((long) (duration * 1000));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean openURL(String url) {
|
||||
if (GlobalObject.getActivity() == null) {
|
||||
Log.e(TAG, "activity is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean ret = false;
|
||||
try {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(url));
|
||||
GlobalObject.getActivity().startActivity(i);
|
||||
ret = true;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void copyTextToClipboard(final String text) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ClipboardManager myClipboard = (ClipboardManager) GlobalObject.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData myClip = ClipData.newPlainText("text", text);
|
||||
myClipboard.setPrimaryClip(myClip);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static long[] getObbAssetFileDescriptor(final String path) {
|
||||
long[] array = new long[3];
|
||||
if (CocosHelper.sOBBFile != null) {
|
||||
AssetFileDescriptor descriptor = CocosHelper.sOBBFile.getAssetFileDescriptor(path);
|
||||
if (descriptor != null) {
|
||||
try {
|
||||
ParcelFileDescriptor parcel = descriptor.getParcelFileDescriptor();
|
||||
Method method = parcel.getClass().getMethod("getFd", new Class[]{});
|
||||
array[0] = (Integer) method.invoke(parcel);
|
||||
array[1] = descriptor.getStartOffset();
|
||||
array[2] = descriptor.getLength();
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.e(CocosHelper.TAG, "Accessing file descriptor directly from the OBB is only supported from Android 3.1 (API level 12) and above.");
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.e(CocosHelper.TAG, e.toString());
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.e(CocosHelper.TAG, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public static int getDeviceRotation() {
|
||||
try {
|
||||
Display display = ((WindowManager) GlobalObject.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||
return display.getRotation();
|
||||
} catch (NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Surface.ROTATION_0;
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Private functions.
|
||||
// ===========================================================
|
||||
|
||||
// Initialize asset path:
|
||||
// - absolute path to the OBB if it exists,
|
||||
// - else empty string.
|
||||
private static void initObbFilePath() {
|
||||
int versionCode = 1;
|
||||
final ApplicationInfo applicationInfo = GlobalObject.getContext().getApplicationInfo();
|
||||
try {
|
||||
versionCode = GlobalObject.getContext().getPackageManager().getPackageInfo(applicationInfo.packageName, 0).versionCode;
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String pathToOBB = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/obb/" + applicationInfo.packageName + "/main." + versionCode + "." + applicationInfo.packageName + ".obb";
|
||||
File obbFile = new File(pathToOBB);
|
||||
if (obbFile.exists())
|
||||
CocosHelper.sObbFilePath = pathToOBB;
|
||||
}
|
||||
|
||||
private static void initializeOBBFile() {
|
||||
int versionCode = 1;
|
||||
final ApplicationInfo applicationInfo = GlobalObject.getContext().getApplicationInfo();
|
||||
try {
|
||||
versionCode = GlobalObject.getContext().getPackageManager().getPackageInfo(applicationInfo.packageName, 0).versionCode;
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
CocosHelper.sOBBFile = APKExpansionSupport.getAPKExpansionZipFile(GlobalObject.getContext(), versionCode, 0);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static float[] getSafeArea() {
|
||||
if (GlobalObject.getActivity() == null) {
|
||||
Log.e(TAG, "activity is null");
|
||||
return new float[]{0, 0, 0, 0};
|
||||
}
|
||||
if (android.os.Build.VERSION.SDK_INT >= 28) {
|
||||
do {
|
||||
Object windowInsectObj = GlobalObject.getActivity().getWindow().getDecorView().getRootWindowInsets();
|
||||
|
||||
if (windowInsectObj == null) break;
|
||||
|
||||
Class<?> windowInsets = WindowInsets.class;
|
||||
try {
|
||||
Method wiGetDisplayCutout = windowInsets.getMethod("getDisplayCutout");
|
||||
Object cutout = wiGetDisplayCutout.invoke(windowInsectObj);
|
||||
|
||||
if (cutout == null) break;
|
||||
|
||||
Class<?> displayCutout = cutout.getClass();
|
||||
Method dcGetLeft = displayCutout.getMethod("getSafeInsetLeft");
|
||||
Method dcGetRight = displayCutout.getMethod("getSafeInsetRight");
|
||||
Method dcGetBottom = displayCutout.getMethod("getSafeInsetBottom");
|
||||
Method dcGetTop = displayCutout.getMethod("getSafeInsetTop");
|
||||
|
||||
if (dcGetLeft != null && dcGetRight != null && dcGetBottom != null && dcGetTop != null) {
|
||||
int left = (Integer) dcGetLeft.invoke(cutout);
|
||||
int right = (Integer) dcGetRight.invoke(cutout);
|
||||
int top = (Integer) dcGetTop.invoke(cutout);
|
||||
int bottom = (Integer) dcGetBottom.invoke(cutout);
|
||||
return new float[]{top, left, bottom, right};
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} while (false);
|
||||
}
|
||||
return new float[]{0, 0, 0, 0};
|
||||
}
|
||||
public static void finishActivity() {
|
||||
if (GlobalObject.getActivity() == null) {
|
||||
Log.e(TAG, "activity is null");
|
||||
return;
|
||||
}
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GlobalObject.getActivity().finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
public static void setKeepScreenOn(boolean keepScreenOn) {
|
||||
if (GlobalObject.getActivity() == null) {
|
||||
Log.e(TAG, "activity is null");
|
||||
return;
|
||||
}
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (keepScreenOn) {
|
||||
GlobalObject.getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
GlobalObject.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
public static boolean supportHPE() {
|
||||
PackageManager pm = GlobalObject.getContext().getPackageManager();
|
||||
return pm.hasSystemFeature("com.google.android.play.feature.HPE_EXPERIENCE");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2014 cocos2d-x.org
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
public class CocosHttpURLConnection
|
||||
{
|
||||
private static String TAG = "CocosHttpURLConnection";
|
||||
private static final String POST_METHOD = "POST" ;
|
||||
private static final String PUT_METHOD = "PUT" ;
|
||||
private static final String PATCH_METHOD = "PATCH" ;
|
||||
|
||||
static HttpURLConnection createHttpURLConnection(String linkURL) {
|
||||
URL url;
|
||||
HttpURLConnection urlConnection;
|
||||
try {
|
||||
url = new URL(linkURL);
|
||||
urlConnection = (HttpURLConnection) url.openConnection();
|
||||
//Accept-Encoding
|
||||
urlConnection.setRequestProperty("Accept-Encoding", "identity");
|
||||
urlConnection.setDoInput(true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "createHttpURLConnection:" + e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
return urlConnection;
|
||||
}
|
||||
|
||||
static void setReadAndConnectTimeout(HttpURLConnection urlConnection, int readMiliseconds, int connectMiliseconds) {
|
||||
urlConnection.setReadTimeout(readMiliseconds);
|
||||
urlConnection.setConnectTimeout(connectMiliseconds);
|
||||
}
|
||||
|
||||
static void setRequestMethod(HttpURLConnection urlConnection, String method){
|
||||
try {
|
||||
urlConnection.setRequestMethod(method);
|
||||
if(method.equalsIgnoreCase(POST_METHOD) || method.equalsIgnoreCase(PUT_METHOD) || method.equalsIgnoreCase(PATCH_METHOD)) {
|
||||
urlConnection.setDoOutput(true);
|
||||
}
|
||||
} catch (ProtocolException e) {
|
||||
Log.e(TAG, "setRequestMethod:" + e.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void setVerifySSL(HttpURLConnection urlConnection, String sslFilename) {
|
||||
if(!(urlConnection instanceof HttpsURLConnection))
|
||||
return;
|
||||
|
||||
|
||||
HttpsURLConnection httpsURLConnection = (HttpsURLConnection)urlConnection;
|
||||
|
||||
try {
|
||||
InputStream caInput = null;
|
||||
if (sslFilename.startsWith("/")) {
|
||||
caInput = new BufferedInputStream(new FileInputStream(sslFilename));
|
||||
}else {
|
||||
String assetString = "assets/";
|
||||
String assetsfilenameString = sslFilename.substring(assetString.length());
|
||||
caInput = new BufferedInputStream(GlobalObject.getContext().getAssets().open(assetsfilenameString));
|
||||
}
|
||||
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
Certificate ca;
|
||||
ca = cf.generateCertificate(caInput);
|
||||
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
|
||||
caInput.close();
|
||||
|
||||
// Create a KeyStore containing our trusted CAs
|
||||
String keyStoreType = KeyStore.getDefaultType();
|
||||
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
|
||||
keyStore.load(null, null);
|
||||
keyStore.setCertificateEntry("ca", ca);
|
||||
|
||||
// Create a TrustManager that trusts the CAs in our KeyStore
|
||||
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
|
||||
tmf.init(keyStore);
|
||||
|
||||
// Create an SSLContext that uses our TrustManager
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, tmf.getTrustManagers(), null);
|
||||
|
||||
httpsURLConnection.setSSLSocketFactory(context.getSocketFactory());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "setVerifySSL:" + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
//Add header
|
||||
static void addRequestHeader(HttpURLConnection urlConnection, String key, String value) {
|
||||
urlConnection.setRequestProperty(key, value);
|
||||
}
|
||||
|
||||
static int connect(HttpURLConnection http) {
|
||||
int suc = 0;
|
||||
|
||||
try {
|
||||
http.connect();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "connect" + e.toString());
|
||||
suc = 1;
|
||||
}
|
||||
|
||||
return suc;
|
||||
}
|
||||
|
||||
static void disconnect(HttpURLConnection http) {
|
||||
http.disconnect();
|
||||
}
|
||||
|
||||
static void sendRequest(HttpURLConnection http, byte[] byteArray) {
|
||||
try {
|
||||
OutputStream out = http.getOutputStream();
|
||||
if(null != byteArray) {
|
||||
out.write(byteArray);
|
||||
out.flush();
|
||||
}
|
||||
out.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "sendRequest:" + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static String getResponseHeaders(HttpURLConnection http) {
|
||||
Map<String, List<String>> headers = http.getHeaderFields();
|
||||
if (null == headers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String header = "";
|
||||
|
||||
for (Entry<String, List<String>> entry: headers.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (null == key) {
|
||||
header += listToString(entry.getValue(), ",") + "\n";
|
||||
} else {
|
||||
header += key + ":" + listToString(entry.getValue(), ",") + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
static String getResponseHeaderByIdx(HttpURLConnection http, int idx) {
|
||||
Map<String, List<String>> headers = http.getHeaderFields();
|
||||
if (null == headers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String header = null;
|
||||
|
||||
int counter = 0;
|
||||
for (Entry<String, List<String>> entry: headers.entrySet()) {
|
||||
if (counter == idx) {
|
||||
String key = entry.getKey();
|
||||
if (null == key) {
|
||||
header = listToString(entry.getValue(), ",") + "\n";
|
||||
} else {
|
||||
header = key + ":" + listToString(entry.getValue(), ",") + "\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
static String getResponseHeaderByKey(HttpURLConnection http, String key) {
|
||||
if (null == key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, List<String>> headers = http.getHeaderFields();
|
||||
if (null == headers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String header = null;
|
||||
|
||||
for (Entry<String, List<String>> entry: headers.entrySet()) {
|
||||
if (key.equalsIgnoreCase(entry.getKey())) {
|
||||
if ("set-cookie".equalsIgnoreCase(key)) {
|
||||
header = combinCookies(entry.getValue(), http.getURL().getHost());
|
||||
} else {
|
||||
header = listToString(entry.getValue(), ",");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
static int getResponseHeaderByKeyInt(HttpURLConnection http, String key) {
|
||||
String value = http.getHeaderField(key);
|
||||
|
||||
if (null == value) {
|
||||
return 0;
|
||||
} else {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] getResponseContent(HttpURLConnection http) {
|
||||
InputStream in;
|
||||
try {
|
||||
in = http.getInputStream();
|
||||
String contentEncoding = http.getContentEncoding();
|
||||
if (contentEncoding != null) {
|
||||
if(contentEncoding.equalsIgnoreCase("gzip")){
|
||||
in = new GZIPInputStream(http.getInputStream()); //reads 2 bytes to determine GZIP stream!
|
||||
}
|
||||
else if(contentEncoding.equalsIgnoreCase("deflate")){
|
||||
in = new InflaterInputStream(http.getInputStream());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
in = http.getErrorStream();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "1 getResponseContent: " + e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] buffer = new byte[1024];
|
||||
int size = 0;
|
||||
ByteArrayOutputStream bytestream = new ByteArrayOutputStream();
|
||||
while((size = in.read(buffer, 0 , 1024)) != -1)
|
||||
{
|
||||
bytestream.write(buffer, 0, size);
|
||||
}
|
||||
byte retbuffer[] = bytestream.toByteArray();
|
||||
bytestream.close();
|
||||
return retbuffer;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "2 getResponseContent:" + e.toString());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static int getResponseCode(HttpURLConnection http) {
|
||||
int code = 0;
|
||||
try {
|
||||
code = http.getResponseCode();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "getResponseCode:" + e.toString());
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
static String getResponseMessage(HttpURLConnection http) {
|
||||
String msg;
|
||||
try {
|
||||
msg = http.getResponseMessage();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
msg = e.toString();
|
||||
Log.e(TAG, "getResponseMessage: " + msg);
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static String listToString(List<String> list, String strInterVal) {
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder result = new StringBuilder();
|
||||
boolean flag = false;
|
||||
for (String str : list) {
|
||||
if (flag) {
|
||||
result.append(strInterVal);
|
||||
}
|
||||
if (null == str) {
|
||||
str = "";
|
||||
}
|
||||
result.append(str);
|
||||
flag = true;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String combinCookies(List<String> list, String hostDomain) {
|
||||
StringBuilder sbCookies = new StringBuilder();
|
||||
String domain = hostDomain;
|
||||
String tailmatch = "FALSE";
|
||||
String path = "/";
|
||||
String secure = "FALSE";
|
||||
String key = null;
|
||||
String value = null;
|
||||
String expires = null;
|
||||
for (String str : list) {
|
||||
String[] parts = str.split(";");
|
||||
for (String part : parts) {
|
||||
int firstIndex = part.indexOf("=");
|
||||
if (-1 == firstIndex)
|
||||
continue;
|
||||
|
||||
String[] item = {part.substring(0, firstIndex), part.substring(firstIndex + 1)};
|
||||
if ("expires".equalsIgnoreCase(item[0].trim())) {
|
||||
expires = str2Seconds(item[1].trim());
|
||||
} else if("path".equalsIgnoreCase(item[0].trim())) {
|
||||
path = item[1];
|
||||
} else if("secure".equalsIgnoreCase(item[0].trim())) {
|
||||
secure = item[1];
|
||||
} else if("domain".equalsIgnoreCase(item[0].trim())) {
|
||||
domain = item[1];
|
||||
} else if("version".equalsIgnoreCase(item[0].trim()) || "max-age".equalsIgnoreCase(item[0].trim())) {
|
||||
//do nothing
|
||||
} else {
|
||||
key = item[0];
|
||||
value = item[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (null == domain) {
|
||||
domain = "none";
|
||||
}
|
||||
|
||||
sbCookies.append(domain);
|
||||
sbCookies.append('\t');
|
||||
sbCookies.append(tailmatch); //access
|
||||
sbCookies.append('\t');
|
||||
sbCookies.append(path); //path
|
||||
sbCookies.append('\t');
|
||||
sbCookies.append(secure); //secure
|
||||
sbCookies.append('\t');
|
||||
sbCookies.append(expires); //expires
|
||||
sbCookies.append("\t");
|
||||
sbCookies.append(key); //key
|
||||
sbCookies.append("\t");
|
||||
sbCookies.append(value); //value
|
||||
sbCookies.append('\n');
|
||||
}
|
||||
|
||||
return sbCookies.toString();
|
||||
}
|
||||
|
||||
private static String str2Seconds(String strTime) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
long milliseconds = 0;
|
||||
|
||||
try {
|
||||
c.setTime(new SimpleDateFormat("EEE, dd-MMM-yy hh:mm:ss zzz", Locale.US).parse(strTime));
|
||||
milliseconds = c.getTimeInMillis() / 1000;
|
||||
} catch (ParseException e) {
|
||||
Log.e(TAG, "str2Seconds: " + e.toString());
|
||||
}
|
||||
|
||||
return Long.toString(milliseconds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
* Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* 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 com.cocos.lib;
|
||||
|
||||
public class CocosJavascriptJavaBridge {
|
||||
public static native int evalString(String value);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2013 cocos2d-x.org
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
public class CocosKeyCodeHandler {
|
||||
private CocosActivity mAct;
|
||||
|
||||
public native void handleKeyDown(final int keyCode);
|
||||
|
||||
public native void handleKeyUp(final int keyCode);
|
||||
|
||||
public CocosKeyCodeHandler(CocosActivity act) {
|
||||
mAct = act;
|
||||
}
|
||||
|
||||
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
// CocosVideoHelper.mVideoHandler.sendEmptyMessage(CocosVideoHelper.KeyEventBack);
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleKeyDown(keyCode);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onKeyUp(final int keyCode, KeyEvent event) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
case KeyEvent.KEYCODE_MENU:
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleKeyUp(keyCode);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
public class CocosLocalStorage {
|
||||
|
||||
private static final String TAG = "CocosLocalStorage";
|
||||
|
||||
private static String DATABASE_NAME = "jsb.sqlite";
|
||||
private static String TABLE_NAME = "data";
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
|
||||
private static DBOpenHelper mDatabaseOpenHelper = null;
|
||||
private static SQLiteDatabase mDatabase = null;
|
||||
|
||||
public static boolean init(String dbName, String tableName) {
|
||||
if (GlobalObject.getContext() != null) {
|
||||
DATABASE_NAME = dbName;
|
||||
TABLE_NAME = tableName;
|
||||
mDatabaseOpenHelper = new DBOpenHelper(GlobalObject.getContext());
|
||||
mDatabase = mDatabaseOpenHelper.getWritableDatabase();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void destroy() {
|
||||
if (mDatabase != null) {
|
||||
mDatabase.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setItem(String key, String value) {
|
||||
try {
|
||||
String sql = "replace into "+TABLE_NAME+"(key,value)values(?,?)";
|
||||
mDatabase.execSQL(sql, new Object[] { key, value });
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getItem(String key) {
|
||||
String ret = null;
|
||||
try {
|
||||
String sql = "select value from "+TABLE_NAME+" where key=?";
|
||||
Cursor c = mDatabase.rawQuery(sql, new String[]{key});
|
||||
while (c.moveToNext()) {
|
||||
// only return the first value
|
||||
if (ret != null)
|
||||
{
|
||||
Log.e(TAG, "The key contains more than one value.");
|
||||
break;
|
||||
}
|
||||
ret = c.getString(c.getColumnIndex("value"));
|
||||
}
|
||||
c.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void removeItem(String key) {
|
||||
try {
|
||||
String sql = "delete from "+TABLE_NAME+" where key=?";
|
||||
mDatabase.execSQL(sql, new Object[] {key});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
try {
|
||||
String sql = "delete from "+TABLE_NAME;
|
||||
mDatabase.execSQL(sql);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getKey(int nIndex) {
|
||||
String ret = null;
|
||||
try {
|
||||
int nCount = 0;
|
||||
String sql = "select key from "+TABLE_NAME + " order by rowid asc";
|
||||
Cursor c = mDatabase.rawQuery(sql, null);
|
||||
if(nIndex < 0 || nIndex >= c.getCount()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (c.moveToNext()) {
|
||||
if(nCount == nIndex) {
|
||||
ret = c.getString(c.getColumnIndex("key"));
|
||||
break;
|
||||
}
|
||||
nCount++;
|
||||
}
|
||||
c.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static int getLength() {
|
||||
int res = 0;
|
||||
try {
|
||||
String sql = "select count(*) as nums from "+TABLE_NAME;
|
||||
Cursor c = mDatabase.rawQuery(sql, null);
|
||||
if (c.moveToNext()){
|
||||
res = c.getInt(c.getColumnIndex("nums"));
|
||||
}
|
||||
c.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates/opens the database.
|
||||
*/
|
||||
private static class DBOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
DBOpenHelper(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS "+TABLE_NAME+"(key TEXT PRIMARY KEY,value TEXT);");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
|
||||
+ newVersion + ", which will destroy all old data");
|
||||
//db.execSQL("DROP TABLE IF EXISTS " + VIRTUAL_TABLE);
|
||||
//onCreate(db);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.OrientationEventListener;
|
||||
|
||||
public class CocosOrientationHelper extends OrientationEventListener {
|
||||
|
||||
private int mCurrentOrientation;
|
||||
|
||||
public CocosOrientationHelper(Context context) {
|
||||
super(context);
|
||||
mCurrentOrientation = CocosHelper.getDeviceRotation();
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
this.disable();
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
this.enable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOrientationChanged(int orientation) {
|
||||
int curOrientation = CocosHelper.getDeviceRotation();
|
||||
if (curOrientation != mCurrentOrientation) {
|
||||
mCurrentOrientation = CocosHelper.getDeviceRotation();
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
nativeOnOrientationChanged(mCurrentOrientation);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static native void nativeOnOrientationChanged(int rotation);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2016 cocos2d-x.org
|
||||
Copyright (c) 2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class CocosReflectionHelper {
|
||||
public static <T> T getConstantValue(final Class aClass, final String constantName) {
|
||||
try {
|
||||
return (T)aClass.getDeclaredField(constantName).get(null);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Log.e("error", "can not find " + constantName + " in " + aClass.getName());
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
Log.e("error", constantName + " is not accessable");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Log.e("error", "arguments error when get " + constantName);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.e("error", "can not get constant" + constantName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T invokeInstanceMethod(final Object instance, final String methodName,
|
||||
final Class[] parameterTypes, final Object[] parameters) {
|
||||
|
||||
final Class aClass = instance.getClass();
|
||||
try {
|
||||
final Method method = aClass.getMethod(methodName, parameterTypes);
|
||||
return (T)method.invoke(instance, parameters);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.e("error", "can not find " + methodName + " in " + aClass.getName());
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
Log.e("error", methodName + " is not accessible");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Log.e("error", "arguments are error when invoking " + methodName);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
Log.e("error", "an exception was thrown by the invoked method when invoking " + methodName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2013 cocos2d-x.org
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
|
||||
public class CocosSensorHandler implements SensorEventListener {
|
||||
// ===========================================================
|
||||
// Constants
|
||||
// ===========================================================
|
||||
|
||||
private static final String TAG = "CocosSensorHandler";
|
||||
private static CocosSensorHandler mSensorHandler;
|
||||
private static boolean mEnableSensor = false;
|
||||
|
||||
private final Context mContext;
|
||||
private SensorManager mSensorManager;
|
||||
private Sensor mAcceleration;
|
||||
private Sensor mAccelerationIncludingGravity;
|
||||
private Sensor mGyroscope;
|
||||
private int mSamplingPeriodUs = SensorManager.SENSOR_DELAY_GAME;
|
||||
|
||||
private static float[] sDeviceMotionValues = new float[9];
|
||||
|
||||
// ===========================================================
|
||||
// Constructors
|
||||
// ===========================================================
|
||||
|
||||
public CocosSensorHandler(final Context context) {
|
||||
mContext = context;
|
||||
mSensorHandler = this;
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Getter & Setter
|
||||
// ===========================================================
|
||||
public void enable() {
|
||||
if (mEnableSensor) {
|
||||
if (null == mSensorManager) {
|
||||
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
|
||||
mAcceleration = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
||||
mAccelerationIncludingGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
|
||||
mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
|
||||
}
|
||||
|
||||
mSensorManager.registerListener(this, mAcceleration, mSamplingPeriodUs);
|
||||
mSensorManager.registerListener(this, mAccelerationIncludingGravity, mSamplingPeriodUs);
|
||||
mSensorManager.registerListener(this, mGyroscope, mSamplingPeriodUs);
|
||||
}
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
if (mEnableSensor && null != mSensorManager) {
|
||||
this.mSensorManager.unregisterListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void setInterval(float interval) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 11) {
|
||||
mSamplingPeriodUs = (int) (interval * 1000000);
|
||||
}
|
||||
disable();
|
||||
enable();
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Methods for/from SuperClass/Interfaces
|
||||
// ===========================================================
|
||||
@Override
|
||||
public void onSensorChanged(final SensorEvent sensorEvent) {
|
||||
int type = sensorEvent.sensor.getType();
|
||||
if (type == Sensor.TYPE_ACCELEROMETER) {
|
||||
sDeviceMotionValues[0] = sensorEvent.values[0];
|
||||
sDeviceMotionValues[1] = sensorEvent.values[1];
|
||||
// Issue https://github.com/cocos-creator/2d-tasks/issues/2532
|
||||
// use negative event.acceleration.z to match iOS value
|
||||
sDeviceMotionValues[2] = -sensorEvent.values[2];
|
||||
} else if (type == Sensor.TYPE_LINEAR_ACCELERATION) {
|
||||
sDeviceMotionValues[3] = sensorEvent.values[0];
|
||||
sDeviceMotionValues[4] = sensorEvent.values[1];
|
||||
sDeviceMotionValues[5] = sensorEvent.values[2];
|
||||
} else if (type == Sensor.TYPE_GYROSCOPE) {
|
||||
// The unit is rad/s, need to be converted to deg/s
|
||||
sDeviceMotionValues[6] = (float) Math.toDegrees(sensorEvent.values[0]);
|
||||
sDeviceMotionValues[7] = (float) Math.toDegrees(sensorEvent.values[1]);
|
||||
sDeviceMotionValues[8] = (float) Math.toDegrees(sensorEvent.values[2]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
disable();
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
enable();
|
||||
}
|
||||
|
||||
public static void setAccelerometerInterval(float interval) {
|
||||
mSensorHandler.setInterval(interval);
|
||||
}
|
||||
|
||||
public static void setAccelerometerEnabled(boolean enabled) {
|
||||
mEnableSensor = enabled;
|
||||
if (enabled) {
|
||||
mSensorHandler.enable();
|
||||
} else {
|
||||
mSensorHandler.disable();
|
||||
}
|
||||
}
|
||||
|
||||
public static float[] getDeviceMotionValue() {
|
||||
return sDeviceMotionValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
public class CocosSurfaceView extends SurfaceView implements android.view.SurfaceHolder.Callback2 {
|
||||
private CocosTouchHandler mTouchHandler;
|
||||
private long mNativeHandle;
|
||||
private int mWindowId;
|
||||
|
||||
public CocosSurfaceView(Context context, int windowId) {
|
||||
super(context);
|
||||
mWindowId = windowId;
|
||||
mNativeHandle = constructNative(windowId);
|
||||
mTouchHandler = new CocosTouchHandler(mWindowId);
|
||||
getHolder().addCallback(this);
|
||||
}
|
||||
|
||||
private native long constructNative(int windowId);
|
||||
private native void destructNative(long handle);
|
||||
private native void onSizeChangedNative(int windowId, int width, final int height);
|
||||
private native void onSurfaceRedrawNeededNative(long handle);
|
||||
private native void onSurfaceCreatedNative(long handle, Surface surface);
|
||||
private native void onSurfaceChangedNative(long handle, Surface surface, int format, int width, int height);
|
||||
private native void onSurfaceDestroyedNative(long handle);
|
||||
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onSizeChangedNative(mWindowId, w, h);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return mTouchHandler.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceRedrawNeeded(SurfaceHolder holder) {
|
||||
onSurfaceRedrawNeededNative(mNativeHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
onSurfaceCreatedNative(mNativeHandle, holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
onSurfaceChangedNative(mNativeHandle, holder.getSurface(), format, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
onSurfaceDestroyedNative(mNativeHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
destructNative(mNativeHandle);
|
||||
mNativeHandle = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2013 cocos2d-x.org
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
public class CocosTouchHandler {
|
||||
public final static String TAG = "CocosTouchHandler";
|
||||
private boolean mStopHandleTouchAndKeyEvents = false;
|
||||
private int mWindowId;
|
||||
|
||||
public CocosTouchHandler(int windowId) {
|
||||
mWindowId = windowId;
|
||||
}
|
||||
|
||||
boolean onTouchEvent(MotionEvent pMotionEvent) {
|
||||
// these data are used in ACTION_MOVE and ACTION_CANCEL
|
||||
final int pointerNumber = pMotionEvent.getPointerCount();
|
||||
final int[] ids = new int[pointerNumber];
|
||||
final float[] xs = new float[pointerNumber];
|
||||
final float[] ys = new float[pointerNumber];
|
||||
|
||||
for (int i = 0; i < pointerNumber; i++) {
|
||||
ids[i] = pMotionEvent.getPointerId(i);
|
||||
xs[i] = pMotionEvent.getX(i);
|
||||
ys[i] = pMotionEvent.getY(i);
|
||||
}
|
||||
|
||||
switch (pMotionEvent.getAction() & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
if (mStopHandleTouchAndKeyEvents) {
|
||||
// Cocos2dxEditBox.complete();
|
||||
return true;
|
||||
}
|
||||
|
||||
final int indexPointerDown = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
final int idPointerDown = pMotionEvent.getPointerId(indexPointerDown);
|
||||
final float xPointerDown = pMotionEvent.getX(indexPointerDown);
|
||||
final float yPointerDown = pMotionEvent.getY(indexPointerDown);
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleActionDown(mWindowId, idPointerDown, xPointerDown, yPointerDown);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
if (mStopHandleTouchAndKeyEvents) {
|
||||
// Cocos2dxEditBox.complete();
|
||||
return true;
|
||||
}
|
||||
|
||||
// there are only one finger on the screen
|
||||
final int idDown = pMotionEvent.getPointerId(0);
|
||||
final float xDown = xs[0];
|
||||
final float yDown = ys[0];
|
||||
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleActionDown(mWindowId, idDown, xDown, yDown);
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleActionMove(mWindowId, ids, xs, ys);
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
final int indexPointUp = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
final int idPointerUp = pMotionEvent.getPointerId(indexPointUp);
|
||||
final float xPointerUp = pMotionEvent.getX(indexPointUp);
|
||||
final float yPointerUp = pMotionEvent.getY(indexPointUp);
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleActionUp(mWindowId, idPointerUp, xPointerUp, yPointerUp);
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
// there are only one finger on the screen
|
||||
final int idUp = pMotionEvent.getPointerId(0);
|
||||
final float xUp = xs[0];
|
||||
final float yUp = ys[0];
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleActionUp(mWindowId, idUp, xUp, yUp);
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleActionCancel(mWindowId, ids, xs, ys);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
// CocosTouchHandler.dumpMotionEvent(pMotionEvent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setStopHandleTouchAndKeyEvents(boolean value) {
|
||||
mStopHandleTouchAndKeyEvents = value;
|
||||
}
|
||||
|
||||
private static void dumpMotionEvent(final MotionEvent event) {
|
||||
final String names[] = {"DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE", "POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?"};
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final int action = event.getAction();
|
||||
final int actionCode = action & MotionEvent.ACTION_MASK;
|
||||
sb.append("event ACTION_").append(names[actionCode]);
|
||||
if (actionCode == MotionEvent.ACTION_POINTER_DOWN || actionCode == MotionEvent.ACTION_POINTER_UP) {
|
||||
sb.append("(pid ").append(action >> MotionEvent.ACTION_POINTER_INDEX_SHIFT);
|
||||
sb.append(")");
|
||||
}
|
||||
sb.append("[");
|
||||
for (int i = 0; i < event.getPointerCount(); i++) {
|
||||
sb.append("#").append(i);
|
||||
sb.append("(pid ").append(event.getPointerId(i));
|
||||
sb.append(")=").append((int) event.getX(i));
|
||||
sb.append(",").append((int) event.getY(i));
|
||||
if (i + 1 < event.getPointerCount()) {
|
||||
sb.append(";");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
Log.d(TAG, sb.toString());
|
||||
}
|
||||
|
||||
private native void handleActionDown(int windowId, final int id, final float x, final float y);
|
||||
|
||||
private native void handleActionMove(int windowId, final int[] ids, final float[] xPointerList, final float[] yPointerList);
|
||||
|
||||
private native void handleActionUp(int windowId, final int id, final float x, final float y);
|
||||
|
||||
private native void handleActionCancel(int windowId, final int[] ids, final float[] xPointerList, final float[] yPointerList);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.app.Activity;
|
||||
|
||||
import com.cocos.lib.CocosVideoView.OnVideoEventListener;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
public class CocosVideoHelper {
|
||||
|
||||
private FrameLayout mLayout = null;
|
||||
private Activity mActivity = null;
|
||||
private static SparseArray<CocosVideoView> sVideoViews = null;
|
||||
static VideoHandler mVideoHandler = null;
|
||||
private static Handler sHandler = null;
|
||||
|
||||
CocosVideoHelper(Activity activity, FrameLayout layout)
|
||||
{
|
||||
mActivity = activity;
|
||||
mLayout = layout;
|
||||
|
||||
mVideoHandler = new VideoHandler(this);
|
||||
sVideoViews = new SparseArray<CocosVideoView>();
|
||||
sHandler = new Handler(Looper.myLooper());
|
||||
}
|
||||
|
||||
private static int videoTag = 0;
|
||||
private final static int VideoTaskCreate = 0;
|
||||
private final static int VideoTaskRemove = 1;
|
||||
private final static int VideoTaskSetSource = 2;
|
||||
private final static int VideoTaskSetRect = 3;
|
||||
private final static int VideoTaskStart = 4;
|
||||
private final static int VideoTaskPause = 5;
|
||||
private final static int VideoTaskResume = 6;
|
||||
private final static int VideoTaskStop = 7;
|
||||
private final static int VideoTaskSeek = 8;
|
||||
private final static int VideoTaskSetVisible = 9;
|
||||
private final static int VideoTaskRestart = 10;
|
||||
private final static int VideoTaskKeepRatio = 11;
|
||||
private final static int VideoTaskFullScreen = 12;
|
||||
private final static int VideoTaskSetVolume = 13;
|
||||
private final static int VideoTaskSetPlaybackRate = 14;
|
||||
private final static int VideoTaskSetMute = 15;
|
||||
private final static int VideoTaskSetLoop = 16;
|
||||
|
||||
|
||||
final static int KeyEventBack = 1000;
|
||||
|
||||
static class VideoHandler extends Handler{
|
||||
WeakReference<CocosVideoHelper> mReference;
|
||||
|
||||
VideoHandler(CocosVideoHelper helper){
|
||||
mReference = new WeakReference<CocosVideoHelper>(helper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
CocosVideoHelper helper = mReference.get();
|
||||
switch (msg.what) {
|
||||
case VideoTaskCreate: {
|
||||
helper._createVideoView(msg.arg1);
|
||||
break;
|
||||
}
|
||||
case VideoTaskRemove: {
|
||||
helper._removeVideoView(msg.arg1);
|
||||
break;
|
||||
}
|
||||
case VideoTaskSetSource: {
|
||||
helper._setVideoURL(msg.arg1, msg.arg2, (String)msg.obj);
|
||||
break;
|
||||
}
|
||||
case VideoTaskStart: {
|
||||
helper._startVideo(msg.arg1);
|
||||
break;
|
||||
}
|
||||
case VideoTaskSetRect: {
|
||||
Rect rect = (Rect)msg.obj;
|
||||
helper._setVideoRect(msg.arg1, rect.left, rect.top, rect.right, rect.bottom);
|
||||
break;
|
||||
}
|
||||
case VideoTaskFullScreen:{
|
||||
if (msg.arg2 == 1) {
|
||||
helper._setFullScreenEnabled(msg.arg1, true);
|
||||
} else {
|
||||
helper._setFullScreenEnabled(msg.arg1, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VideoTaskPause: {
|
||||
helper._pauseVideo(msg.arg1);
|
||||
break;
|
||||
}
|
||||
|
||||
case VideoTaskStop: {
|
||||
helper._stopVideo(msg.arg1);
|
||||
break;
|
||||
}
|
||||
case VideoTaskSeek: {
|
||||
helper._seekVideoTo(msg.arg1, msg.arg2);
|
||||
break;
|
||||
}
|
||||
case VideoTaskSetVisible: {
|
||||
if (msg.arg2 == 1) {
|
||||
helper._setVideoVisible(msg.arg1, true);
|
||||
} else {
|
||||
helper._setVideoVisible(msg.arg1, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VideoTaskKeepRatio: {
|
||||
if (msg.arg2 == 1) {
|
||||
helper._setVideoKeepRatio(msg.arg1, true);
|
||||
} else {
|
||||
helper._setVideoKeepRatio(msg.arg1, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KeyEventBack: {
|
||||
helper.onBackKeyEvent();
|
||||
break;
|
||||
}
|
||||
case VideoTaskSetVolume: {
|
||||
float volume = (float) msg.arg2 / 10;
|
||||
helper._setVolume(msg.arg1, volume);
|
||||
break;
|
||||
}
|
||||
case VideoTaskSetPlaybackRate: {
|
||||
float rate = (float) msg.arg2 / 10;
|
||||
helper._setPlaybackRate(msg.arg1, rate);
|
||||
break;
|
||||
}
|
||||
case VideoTaskSetMute: {
|
||||
if (msg.arg2 == 1) {
|
||||
helper._setMute(msg.arg1, true);
|
||||
} else {
|
||||
helper._setMute(msg.arg1, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VideoTaskSetLoop: {
|
||||
if (msg.arg2 == 1) {
|
||||
helper._setLoop(msg.arg1, true);
|
||||
} else {
|
||||
helper._setLoop(msg.arg1, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
super.handleMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static native void nativeExecuteVideoCallback(int index,int event);
|
||||
|
||||
OnVideoEventListener videoEventListener = new OnVideoEventListener() {
|
||||
|
||||
@Override
|
||||
public void onVideoEvent(int tag,int event) {
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
nativeExecuteVideoCallback(tag, event);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public static int createVideoWidget() {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskCreate;
|
||||
msg.arg1 = videoTag;
|
||||
mVideoHandler.sendMessage(msg);
|
||||
|
||||
return videoTag++;
|
||||
}
|
||||
|
||||
private void _createVideoView(int index) {
|
||||
CocosVideoView videoView = new CocosVideoView(mActivity,index);
|
||||
sVideoViews.put(index, videoView);
|
||||
FrameLayout.LayoutParams lParams = new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT);
|
||||
mLayout.addView(videoView, lParams);
|
||||
|
||||
videoView.setZOrderOnTop(true);
|
||||
videoView.setVideoViewEventListener(videoEventListener);
|
||||
}
|
||||
|
||||
public static void removeVideoWidget(int index){
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskRemove;
|
||||
msg.arg1 = index;
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _removeVideoView(int index) {
|
||||
CocosVideoView view = sVideoViews.get(index);
|
||||
if (view != null) {
|
||||
view.stopPlayback();
|
||||
sVideoViews.remove(index);
|
||||
mLayout.removeView(view);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setVideoUrl(int index, int videoSource, String videoUrl) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskSetSource;
|
||||
msg.arg1 = index;
|
||||
msg.arg2 = videoSource;
|
||||
msg.obj = videoUrl;
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _setVideoURL(int index, int videoSource, String videoUrl) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
switch (videoSource) {
|
||||
case 0:
|
||||
videoView.setVideoFileName(videoUrl);
|
||||
break;
|
||||
case 1:
|
||||
videoView.setVideoURL(videoUrl);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setVideoRect(int index, int left, int top, int maxWidth, int maxHeight) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskSetRect;
|
||||
msg.arg1 = index;
|
||||
msg.obj = new Rect(left, top, maxWidth, maxHeight);
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _setVideoRect(int index, int left, int top, int maxWidth, int maxHeight) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.setVideoRect(left, top, maxWidth, maxHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setFullScreenEnabled(int index, boolean enabled) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskFullScreen;
|
||||
msg.arg1 = index;
|
||||
if (enabled) {
|
||||
msg.arg2 = 1;
|
||||
} else {
|
||||
msg.arg2 = 0;
|
||||
}
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _setFullScreenEnabled(int index, boolean enabled) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.setFullScreenEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
private void onBackKeyEvent() {
|
||||
int viewCount = sVideoViews.size();
|
||||
for (int i = 0; i < viewCount; i++) {
|
||||
int key = sVideoViews.keyAt(i);
|
||||
CocosVideoView videoView = sVideoViews.get(key);
|
||||
if (videoView != null) {
|
||||
videoView.setFullScreenEnabled(false);
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
nativeExecuteVideoCallback(key, KeyEventBack);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void startVideo(int index) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskStart;
|
||||
msg.arg1 = index;
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _startVideo(int index) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void pauseVideo(int index) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskPause;
|
||||
msg.arg1 = index;
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _pauseVideo(int index) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.pause();
|
||||
}
|
||||
}
|
||||
|
||||
public static void stopVideo(int index) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskStop;
|
||||
msg.arg1 = index;
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _stopVideo(int index) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public static void seekVideoTo(int index,int msec) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskSeek;
|
||||
msg.arg1 = index;
|
||||
msg.arg2 = msec;
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _seekVideoTo(int index,int msec) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.seekTo(msec);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getCurrentTime(final int index) {
|
||||
CocosVideoView video = sVideoViews.get(index);
|
||||
float currentPosition = -1;
|
||||
if (video != null) {
|
||||
currentPosition = video.getCurrentPosition() / 1000.0f;
|
||||
}
|
||||
return currentPosition;
|
||||
}
|
||||
|
||||
public static float getDuration(final int index) {
|
||||
CocosVideoView video = sVideoViews.get(index);
|
||||
float duration = -1;
|
||||
if (video != null) {
|
||||
duration = video.getDuration() / 1000.0f;
|
||||
}
|
||||
if (duration <= 0) {
|
||||
Log.w("CocosVideoHelper", "Video player's duration is not ready to get now!");
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
public static void setVideoVisible(int index, boolean visible) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskSetVisible;
|
||||
msg.arg1 = index;
|
||||
if (visible) {
|
||||
msg.arg2 = 1;
|
||||
} else {
|
||||
msg.arg2 = 0;
|
||||
}
|
||||
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _setVideoVisible(int index, boolean visible) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
if (visible) {
|
||||
videoView.fixSize();
|
||||
videoView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
videoView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setVideoKeepRatioEnabled(int index, boolean enable) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskKeepRatio;
|
||||
msg.arg1 = index;
|
||||
if (enable) {
|
||||
msg.arg2 = 1;
|
||||
} else {
|
||||
msg.arg2 = 0;
|
||||
}
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
public static void setPlaybackRate(final int index, final float value) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskSetPlaybackRate;
|
||||
msg.arg1 = index;
|
||||
msg.arg2 = (int) (value * 10);
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _setPlaybackRate(final int index, final float value) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.playbackRate(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setMute(int index, boolean enable) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskSetMute;
|
||||
msg.arg1 = index;
|
||||
if (enable) {
|
||||
msg.arg2 = 1;
|
||||
} else {
|
||||
msg.arg2 = 0;
|
||||
}
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _setMute(int index, boolean enable) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.setMute(enable);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLoop(int index, boolean enable) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskSetLoop;
|
||||
msg.arg1 = index;
|
||||
if (enable) {
|
||||
msg.arg2 = 1;
|
||||
} else {
|
||||
msg.arg2 = 0;
|
||||
}
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void _setLoop(int index, boolean enable) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.setLoop(enable);
|
||||
}
|
||||
}
|
||||
|
||||
private void _setVideoKeepRatio(int index, boolean enable) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.setKeepRatio(enable);
|
||||
}
|
||||
}
|
||||
|
||||
private void _setVolume(final int index, final float volume) {
|
||||
CocosVideoView videoView = sVideoViews.get(index);
|
||||
if (videoView != null) {
|
||||
videoView.setVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setVolume(final int index, final float volume) {
|
||||
Message msg = new Message();
|
||||
msg.what = VideoTaskSetVolume;
|
||||
msg.arg1 = index;
|
||||
msg.arg2 = (int) (volume * 10);
|
||||
mVideoHandler.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,637 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
* Copyright (c) 2014-2016 Chukong Technologies Inc.
|
||||
* Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* 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.cocos.lib;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.PlaybackParams;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
public class CocosVideoView extends SurfaceView {
|
||||
|
||||
// ===========================================================
|
||||
// Internal classes and interfaces.
|
||||
// ===========================================================
|
||||
|
||||
public interface OnVideoEventListener {
|
||||
void onVideoEvent(int tag,int event);
|
||||
}
|
||||
|
||||
private enum State {
|
||||
IDLE,
|
||||
ERROR,
|
||||
INITIALIZED,
|
||||
PREPARING,
|
||||
PREPARED,
|
||||
STARTED,
|
||||
PAUSED,
|
||||
STOPPED,
|
||||
PLAYBACK_COMPLETED,
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Constants
|
||||
// ===========================================================
|
||||
private static final String AssetResourceRoot = "@assets/";
|
||||
|
||||
// ===========================================================
|
||||
// Fields
|
||||
// ===========================================================
|
||||
|
||||
private String TAG = "CocosVideoView";
|
||||
|
||||
private Uri mVideoUri;
|
||||
private int mDuration;
|
||||
private int mPosition;
|
||||
|
||||
private State mCurrentState = State.IDLE;
|
||||
|
||||
// All the stuff we need for playing and showing a video
|
||||
private SurfaceHolder mSurfaceHolder = null;
|
||||
private MediaPlayer mMediaPlayer = null;
|
||||
private int mVideoWidth = 0;
|
||||
private int mVideoHeight = 0;
|
||||
|
||||
private OnVideoEventListener mOnVideoEventListener;
|
||||
|
||||
// recording the seek position while preparing
|
||||
private int mSeekWhenPrepared = 0;
|
||||
|
||||
protected Activity mActivity = null;
|
||||
|
||||
protected int mViewLeft = 0;
|
||||
protected int mViewTop = 0;
|
||||
protected int mViewWidth = 0;
|
||||
protected int mViewHeight = 0;
|
||||
|
||||
protected int mVisibleLeft = 0;
|
||||
protected int mVisibleTop = 0;
|
||||
protected int mVisibleWidth = 0;
|
||||
protected int mVisibleHeight = 0;
|
||||
|
||||
protected boolean mFullScreenEnabled = false;
|
||||
|
||||
private boolean mIsAssetResource = false;
|
||||
private String mVideoFilePath = null;
|
||||
|
||||
private int mViewTag = 0;
|
||||
private boolean mKeepRatio = false;
|
||||
private boolean mMetaUpdated = false;
|
||||
|
||||
// MediaPlayer will be released when surface view is destroyed, so should record the position,
|
||||
// and use it to play after MedialPlayer is created again.
|
||||
private int mPositionBeforeRelease = 0;
|
||||
// also need to record play state when surface is destroyed.
|
||||
private State mStateBeforeRelease = State.IDLE;
|
||||
|
||||
// ===========================================================
|
||||
// Constructors
|
||||
// ===========================================================
|
||||
|
||||
public CocosVideoView(Activity activity, int tag) {
|
||||
super(activity);
|
||||
|
||||
mViewTag = tag;
|
||||
mActivity = activity;
|
||||
initVideoView();
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Getter & Setter
|
||||
// ===========================================================
|
||||
|
||||
public void setVideoRect(int left, int top, int maxWidth, int maxHeight) {
|
||||
if (mViewLeft == left && mViewTop == top && mViewWidth == maxWidth && mViewHeight == maxHeight)
|
||||
return;
|
||||
|
||||
mViewLeft = left;
|
||||
mViewTop = top;
|
||||
mViewWidth = maxWidth;
|
||||
mViewHeight = maxHeight;
|
||||
|
||||
fixSize(mViewLeft, mViewTop, mViewWidth, mViewHeight);
|
||||
}
|
||||
|
||||
public void setFullScreenEnabled(boolean enabled) {
|
||||
if (mFullScreenEnabled != enabled) {
|
||||
mFullScreenEnabled = enabled;
|
||||
fixSize();
|
||||
}
|
||||
}
|
||||
|
||||
public void setVolume (float volume) {
|
||||
if (mMediaPlayer != null) {
|
||||
mMediaPlayer.setVolume(volume, volume);
|
||||
}
|
||||
}
|
||||
|
||||
public void setKeepRatio(boolean enabled) {
|
||||
mKeepRatio = enabled;
|
||||
fixSize();
|
||||
}
|
||||
|
||||
public void playbackRate(float value) {
|
||||
if (mMediaPlayer != null) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
PlaybackParams params = new PlaybackParams();
|
||||
params.setSpeed(value);
|
||||
mMediaPlayer.setPlaybackParams(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setMute(boolean enabled) {
|
||||
if (mMediaPlayer != null) {
|
||||
if (enabled) {
|
||||
mMediaPlayer.setVolume(0f, 0f);
|
||||
} else {
|
||||
mMediaPlayer.setVolume(1f, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setLoop(boolean enabled) {
|
||||
if (mMediaPlayer != null) {
|
||||
mMediaPlayer.setLooping(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setVideoURL(String url) {
|
||||
mIsAssetResource = false;
|
||||
setVideoURI(Uri.parse(url), null);
|
||||
}
|
||||
|
||||
public void setVideoFileName(String path) {
|
||||
if (path.startsWith(AssetResourceRoot)) {
|
||||
path = path.substring(AssetResourceRoot.length());
|
||||
}
|
||||
|
||||
if (path.startsWith("/")) {
|
||||
mIsAssetResource = false;
|
||||
setVideoURI(Uri.parse(path),null);
|
||||
}
|
||||
else {
|
||||
|
||||
mVideoFilePath = path;
|
||||
mIsAssetResource = true;
|
||||
setVideoURI(Uri.parse(path), null);
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentPosition() {
|
||||
if (! (mCurrentState == State.IDLE ||
|
||||
mCurrentState == State.ERROR ||
|
||||
mCurrentState == State.INITIALIZED ||
|
||||
mCurrentState == State.STOPPED ||
|
||||
mMediaPlayer == null) ) {
|
||||
mPosition = mMediaPlayer.getCurrentPosition();
|
||||
}
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
public int getDuration() {
|
||||
if (! (mCurrentState == State.IDLE ||
|
||||
mCurrentState == State.ERROR ||
|
||||
mCurrentState == State.INITIALIZED ||
|
||||
mCurrentState == State.STOPPED ||
|
||||
mMediaPlayer == null) ) {
|
||||
mDuration = mMediaPlayer.getDuration();
|
||||
}
|
||||
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when some video event triggered.
|
||||
*
|
||||
* @param l The callback that will be run
|
||||
*/
|
||||
public void setVideoViewEventListener(OnVideoEventListener l)
|
||||
{
|
||||
mOnVideoEventListener = l;
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Overrides
|
||||
// ===========================================================
|
||||
|
||||
@Override
|
||||
public void setVisibility(int visibility) {
|
||||
super.setVisibility(visibility);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
setMeasuredDimension(mVisibleWidth, mVisibleHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
|
||||
this.sendEvent(EVENT_CLICKED);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Public functions
|
||||
// ===========================================================
|
||||
|
||||
public void stop() {
|
||||
if (!(mCurrentState == State.IDLE || mCurrentState == State.INITIALIZED || mCurrentState == State.ERROR || mCurrentState == State.STOPPED)
|
||||
&& mMediaPlayer != null) {
|
||||
mCurrentState = State.STOPPED;
|
||||
mMediaPlayer.stop();
|
||||
this.sendEvent(EVENT_STOPPED);
|
||||
|
||||
// after the video is stop, it shall prepare to be playable again
|
||||
try {
|
||||
mMediaPlayer.reset(); // reset to avoid some problems on start.
|
||||
loadDataSource();
|
||||
mMediaPlayer.prepare();
|
||||
this.showFirstFrame();
|
||||
} catch (Exception ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void stopPlayback() {
|
||||
this.release();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if ((mCurrentState == State.PREPARED ||
|
||||
mCurrentState == State.PAUSED ||
|
||||
mCurrentState == State.PLAYBACK_COMPLETED) &&
|
||||
mMediaPlayer != null) {
|
||||
|
||||
mCurrentState = State.STARTED;
|
||||
mMediaPlayer.start();
|
||||
this.sendEvent(EVENT_PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
if ((mCurrentState == State.STARTED || mCurrentState == State.PLAYBACK_COMPLETED) &&
|
||||
mMediaPlayer != null) {
|
||||
mCurrentState = State.PAUSED;
|
||||
mMediaPlayer.pause();
|
||||
this.sendEvent(EVENT_PAUSED);
|
||||
}
|
||||
}
|
||||
|
||||
public void seekTo(int ms) {
|
||||
if (mCurrentState == State.IDLE || mCurrentState == State.INITIALIZED ||
|
||||
mCurrentState == State.STOPPED || mCurrentState == State.ERROR ||
|
||||
mMediaPlayer == null) {
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
Method seekTo = mMediaPlayer.getClass().getMethod("seekTo", long.class, int.class);
|
||||
// The mode argument added in API level 26, 3 = MediaPlayer.SEEK_CLOSEST
|
||||
// https://developer.android.com/reference/android/media/MediaPlayer#seekTo(int)
|
||||
seekTo.invoke(mMediaPlayer, ms, 3);
|
||||
} catch (Exception e) {
|
||||
mMediaPlayer.seekTo(ms);
|
||||
}
|
||||
}
|
||||
else {
|
||||
mMediaPlayer.seekTo(ms);
|
||||
}
|
||||
}
|
||||
|
||||
public void fixSize() {
|
||||
if (mFullScreenEnabled) {
|
||||
Point screenSize = new Point();
|
||||
mActivity.getWindowManager().getDefaultDisplay().getSize(screenSize);
|
||||
fixSize(0, 0, screenSize.x, screenSize.y);
|
||||
} else {
|
||||
fixSize(mViewLeft, mViewTop, mViewWidth, mViewHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void fixSize(int left, int top, int width, int height) {
|
||||
if (mVideoWidth == 0 || mVideoHeight == 0) {
|
||||
mVisibleLeft = left;
|
||||
mVisibleTop = top;
|
||||
mVisibleWidth = width;
|
||||
mVisibleHeight = height;
|
||||
}
|
||||
else if (width != 0 && height != 0) {
|
||||
if (mKeepRatio && !mFullScreenEnabled) {
|
||||
if ( mVideoWidth * height > width * mVideoHeight ) {
|
||||
mVisibleWidth = width;
|
||||
mVisibleHeight = width * mVideoHeight / mVideoWidth;
|
||||
} else if ( mVideoWidth * height < width * mVideoHeight ) {
|
||||
mVisibleWidth = height * mVideoWidth / mVideoHeight;
|
||||
mVisibleHeight = height;
|
||||
}
|
||||
mVisibleLeft = left + (width - mVisibleWidth) / 2;
|
||||
mVisibleTop = top + (height - mVisibleHeight) / 2;
|
||||
} else {
|
||||
mVisibleLeft = left;
|
||||
mVisibleTop = top;
|
||||
mVisibleWidth = width;
|
||||
mVisibleHeight = height;
|
||||
}
|
||||
}
|
||||
else {
|
||||
mVisibleLeft = left;
|
||||
mVisibleTop = top;
|
||||
mVisibleWidth = mVideoWidth;
|
||||
mVisibleHeight = mVideoHeight;
|
||||
}
|
||||
|
||||
getHolder().setFixedSize(mVisibleWidth, mVisibleHeight);
|
||||
|
||||
FrameLayout.LayoutParams lParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT);
|
||||
lParams.leftMargin = mVisibleLeft;
|
||||
lParams.topMargin = mVisibleTop;
|
||||
setLayoutParams(lParams);
|
||||
}
|
||||
|
||||
public int resolveAdjustedSize(int desiredSize, int measureSpec) {
|
||||
int result = desiredSize;
|
||||
int specMode = MeasureSpec.getMode(measureSpec);
|
||||
int specSize = MeasureSpec.getSize(measureSpec);
|
||||
|
||||
switch (specMode) {
|
||||
case MeasureSpec.UNSPECIFIED:
|
||||
/* Parent says we can be as big as we want. Just don't be larger
|
||||
* than max size imposed on ourselves.
|
||||
*/
|
||||
result = desiredSize;
|
||||
break;
|
||||
|
||||
case MeasureSpec.AT_MOST:
|
||||
/* Parent says we can be as big as we want, up to specSize.
|
||||
* Don't be larger than specSize, and don't be larger than
|
||||
* the max size imposed on ourselves.
|
||||
*/
|
||||
result = Math.min(desiredSize, specSize);
|
||||
break;
|
||||
|
||||
case MeasureSpec.EXACTLY:
|
||||
// No choice. Do what we are told.
|
||||
result = specSize;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ===========================================================
|
||||
// Private functions
|
||||
// ===========================================================
|
||||
|
||||
private void initVideoView() {
|
||||
mVideoWidth = 0;
|
||||
mVideoHeight = 0;
|
||||
getHolder().addCallback(mSHCallback);
|
||||
//Fix issue#11516:Can't play video on Android 2.3.x
|
||||
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
mCurrentState = State.IDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
private void setVideoURI(Uri uri, Map<String, String> headers) {
|
||||
mVideoUri = uri;
|
||||
mVideoWidth = 0;
|
||||
mVideoHeight = 0;
|
||||
}
|
||||
|
||||
private void loadDataSource() throws IOException {
|
||||
if (mIsAssetResource) {
|
||||
AssetFileDescriptor afd = mActivity.getAssets().openFd(mVideoFilePath);
|
||||
mMediaPlayer.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getLength());
|
||||
} else {
|
||||
mMediaPlayer.setDataSource(mVideoUri.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void openVideo() {
|
||||
if (mSurfaceHolder == null) {
|
||||
// not ready for playback just yet, will try again later
|
||||
return;
|
||||
}
|
||||
if (mIsAssetResource) {
|
||||
if(mVideoFilePath == null)
|
||||
return;
|
||||
} else if(mVideoUri == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pausePlaybackService();
|
||||
|
||||
try {
|
||||
mMediaPlayer = new MediaPlayer();
|
||||
mMediaPlayer.setOnPreparedListener(mPreparedListener);
|
||||
mMediaPlayer.setOnCompletionListener(mCompletionListener);
|
||||
mMediaPlayer.setOnErrorListener(mErrorListener);
|
||||
mMediaPlayer.setDisplay(mSurfaceHolder);
|
||||
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
mMediaPlayer.setScreenOnWhilePlaying(true);
|
||||
|
||||
loadDataSource();
|
||||
mCurrentState = State.INITIALIZED;
|
||||
|
||||
// Use Prepare() instead of PrepareAsync to make things easy.
|
||||
mMediaPlayer.prepare();
|
||||
this.showFirstFrame();
|
||||
|
||||
// mMediaPlayer.prepareAsync();
|
||||
} catch (IOException ex) {
|
||||
Log.w(TAG, "Unable to open content: " + mVideoUri, ex);
|
||||
mCurrentState = State.ERROR;
|
||||
mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
|
||||
return;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Log.w(TAG, "Unable to open content: " + mVideoUri, ex);
|
||||
mCurrentState = State.ERROR;
|
||||
mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
mVideoWidth = mp.getVideoWidth();
|
||||
mVideoHeight = mp.getVideoHeight();
|
||||
|
||||
if (mVideoWidth != 0 && mVideoHeight != 0) {
|
||||
fixSize();
|
||||
}
|
||||
|
||||
if(!mMetaUpdated) {
|
||||
CocosVideoView.this.sendEvent(EVENT_META_LOADED);
|
||||
CocosVideoView.this.sendEvent(EVENT_READY_TO_PLAY);
|
||||
mMetaUpdated = true;
|
||||
}
|
||||
|
||||
mCurrentState = State.PREPARED;
|
||||
|
||||
if (mStateBeforeRelease == State.STARTED) {
|
||||
CocosVideoView.this.start();
|
||||
}
|
||||
|
||||
if (mPositionBeforeRelease > 0) {
|
||||
CocosVideoView.this.seekTo(mPositionBeforeRelease);
|
||||
}
|
||||
|
||||
mStateBeforeRelease = State.IDLE;
|
||||
mPositionBeforeRelease = 0;
|
||||
}
|
||||
};
|
||||
|
||||
private MediaPlayer.OnCompletionListener mCompletionListener =
|
||||
new MediaPlayer.OnCompletionListener() {
|
||||
public void onCompletion(MediaPlayer mp) {
|
||||
mCurrentState = State.PLAYBACK_COMPLETED;
|
||||
CocosVideoView.this.sendEvent(EVENT_COMPLETED);
|
||||
//make it playable again.
|
||||
CocosVideoView.this.showFirstFrame();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private static final int EVENT_PLAYING = 0;
|
||||
private static final int EVENT_PAUSED = 1;
|
||||
private static final int EVENT_STOPPED = 2;
|
||||
private static final int EVENT_COMPLETED = 3;
|
||||
private static final int EVENT_META_LOADED = 4;
|
||||
private static final int EVENT_CLICKED = 5;
|
||||
private static final int EVENT_READY_TO_PLAY = 6;
|
||||
|
||||
private MediaPlayer.OnErrorListener mErrorListener =
|
||||
new MediaPlayer.OnErrorListener() {
|
||||
public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {
|
||||
Log.d(TAG, "Error: " + framework_err + "," + impl_err);
|
||||
mCurrentState = State.ERROR;
|
||||
|
||||
/* Otherwise, pop up an error dialog so the user knows that
|
||||
* something bad has happened. Only try and pop up the dialog
|
||||
* if we're attached to a window. When we're going away and no
|
||||
* longer have a window, don't bother showing the user an error.
|
||||
*/
|
||||
if (getWindowToken() != null) {
|
||||
Resources r = mActivity.getResources();
|
||||
int messageId;
|
||||
|
||||
if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
|
||||
// messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;
|
||||
messageId = r.getIdentifier("VideoView_error_text_invalid_progressive_playback", "string", "android");
|
||||
} else {
|
||||
// messageId = com.android.internal.R.string.VideoView_error_text_unknown;
|
||||
messageId = r.getIdentifier("VideoView_error_text_unknown", "string", "android");
|
||||
}
|
||||
|
||||
int titleId = r.getIdentifier("VideoView_error_title", "string", "android");
|
||||
int buttonStringId = r.getIdentifier("VideoView_error_button", "string", "android");
|
||||
|
||||
new AlertDialog.Builder(mActivity)
|
||||
.setTitle(r.getString(titleId))
|
||||
.setMessage(messageId)
|
||||
.setPositiveButton(r.getString(buttonStringId),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
/* If we get here, there is no onError listener, so
|
||||
* at least inform them that the video is over.
|
||||
*/
|
||||
CocosVideoView.this.sendEvent(EVENT_COMPLETED);
|
||||
}
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
|
||||
{
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
|
||||
}
|
||||
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
mSurfaceHolder = holder;
|
||||
CocosVideoView.this.openVideo();
|
||||
}
|
||||
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
// after we return from this we can't use the surface any more
|
||||
mSurfaceHolder = null;
|
||||
mPositionBeforeRelease = getCurrentPosition();
|
||||
mStateBeforeRelease = mCurrentState;
|
||||
CocosVideoView.this.release();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* release the media player in any state
|
||||
*/
|
||||
private void release() {
|
||||
if (mMediaPlayer != null) {
|
||||
mMediaPlayer.release();
|
||||
mMediaPlayer = null;
|
||||
mCurrentState = State.IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
private void showFirstFrame() {
|
||||
mMediaPlayer.seekTo(1);
|
||||
}
|
||||
|
||||
private void sendEvent(int event) {
|
||||
if (this.mOnVideoEventListener != null) {
|
||||
this.mOnVideoEventListener.onVideoEvent(this.mViewTag, event);
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the music playback service to pause
|
||||
// REFINE: these constants need to be published somewhere in the framework.
|
||||
private void pausePlaybackService() {
|
||||
Intent i = new Intent("com.android.music.musicservicecommand");
|
||||
i.putExtra("command", "pause");
|
||||
mActivity.sendBroadcast(i);
|
||||
}
|
||||
}
|
||||
173
cocos/platform/android/java/src/com/cocos/lib/CocosWebView.java
Executable file
173
cocos/platform/android/java/src/com/cocos/lib/CocosWebView.java
Executable file
@@ -0,0 +1,173 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated engine source code (the "Software"), a limited,
|
||||
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
|
||||
to use Cocos Creator solely to develop games on your target platforms. You shall
|
||||
not use Cocos Creator software for developing other software or tools that's
|
||||
used for developing games. You are not granted to publish, distribute,
|
||||
sublicense, and/or sell copies of Cocos Creator.
|
||||
|
||||
The software or tools in this License Agreement are licensed, not sold.
|
||||
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
class ShouldStartLoadingWorker implements Runnable {
|
||||
private CountDownLatch mLatch;
|
||||
private boolean[] mResult;
|
||||
private final int mViewTag;
|
||||
private final String mUrlString;
|
||||
|
||||
ShouldStartLoadingWorker(CountDownLatch latch, boolean[] result, int viewTag, String urlString) {
|
||||
this.mLatch = latch;
|
||||
this.mResult = result;
|
||||
this.mViewTag = viewTag;
|
||||
this.mUrlString = urlString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.mResult[0] = CocosWebViewHelper._shouldStartLoading(mViewTag, mUrlString);
|
||||
this.mLatch.countDown(); // notify that result is ready
|
||||
}
|
||||
}
|
||||
|
||||
public class CocosWebView extends WebView {
|
||||
private static final String TAG = CocosWebViewHelper.class.getSimpleName();
|
||||
|
||||
private int mViewTag;
|
||||
private String mJSScheme;
|
||||
|
||||
public CocosWebView(Context context) {
|
||||
this(context, -1);
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
public CocosWebView(Context context, int viewTag) {
|
||||
super(context);
|
||||
this.mViewTag = viewTag;
|
||||
this.mJSScheme = "";
|
||||
|
||||
this.setFocusable(true);
|
||||
this.setFocusableInTouchMode(true);
|
||||
|
||||
this.getSettings().setSupportZoom(false);
|
||||
|
||||
this.getSettings().setDomStorageEnabled(true);
|
||||
this.getSettings().setJavaScriptEnabled(true);
|
||||
|
||||
// `searchBoxJavaBridge_` has big security risk. http://jvn.jp/en/jp/JVN53768697
|
||||
try {
|
||||
Method method = this.getClass().getMethod("removeJavascriptInterface", new Class[]{String.class});
|
||||
method.invoke(this, "searchBoxJavaBridge_");
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "This API level do not support `removeJavascriptInterface`");
|
||||
}
|
||||
|
||||
this.setWebViewClient(new Cocos2dxWebViewClient());
|
||||
this.setWebChromeClient(new WebChromeClient());
|
||||
}
|
||||
|
||||
public void setJavascriptInterfaceScheme(String scheme) {
|
||||
this.mJSScheme = scheme != null ? scheme : "";
|
||||
}
|
||||
|
||||
public void setScalesPageToFit(boolean scalesPageToFit) {
|
||||
this.getSettings().setSupportZoom(scalesPageToFit);
|
||||
}
|
||||
|
||||
class Cocos2dxWebViewClient extends WebViewClient {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, final String urlString) {
|
||||
try {
|
||||
URI uri = URI.create(urlString);
|
||||
if (uri != null && uri.getScheme().equals(mJSScheme)) {
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebViewHelper._onJsCallback(mViewTag, urlString);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Failed to create URI from url");
|
||||
}
|
||||
|
||||
boolean[] result = new boolean[] { true };
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
// run worker on cocos thread
|
||||
GlobalObject.runOnUiThread(new ShouldStartLoadingWorker(latch, result, mViewTag, urlString));
|
||||
|
||||
// wait for result from cocos thread
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ex) {
|
||||
Log.d(TAG, "'shouldOverrideUrlLoading' failed");
|
||||
}
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, final String url) {
|
||||
super.onPageFinished(view, url);
|
||||
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebViewHelper._didFinishLoading(mViewTag, url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, final String failingUrl) {
|
||||
super.onReceivedError(view, errorCode, description, failingUrl);
|
||||
|
||||
CocosHelper.runOnGameThreadAtForeground(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebViewHelper._didFailLoading(mViewTag, failingUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setWebViewRect(int left, int top, int maxWidth, int maxHeight) {
|
||||
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT);
|
||||
layoutParams.leftMargin = left;
|
||||
layoutParams.topMargin = top;
|
||||
layoutParams.width = maxWidth;
|
||||
layoutParams.height = maxHeight;
|
||||
this.setLayoutParams(layoutParams);
|
||||
}
|
||||
}
|
||||
310
cocos/platform/android/java/src/com/cocos/lib/CocosWebViewHelper.java
Executable file
310
cocos/platform/android/java/src/com/cocos/lib/CocosWebViewHelper.java
Executable file
@@ -0,0 +1,310 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2010-2011 cocos2d-x.org
|
||||
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
||||
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.PopupWindow;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
|
||||
public class CocosWebViewHelper {
|
||||
private static final String TAG = CocosWebViewHelper.class.getSimpleName();
|
||||
private static Handler sHandler;
|
||||
private static PopupWindow sPopUp;
|
||||
private static FrameLayout sLayout;
|
||||
|
||||
private static SparseArray<CocosWebView> webViews;
|
||||
private static int viewTag = 0;
|
||||
|
||||
public CocosWebViewHelper(FrameLayout layout) {
|
||||
|
||||
CocosWebViewHelper.sLayout = layout;
|
||||
CocosWebViewHelper.sHandler = new Handler(Looper.myLooper());
|
||||
|
||||
CocosWebViewHelper.webViews = new SparseArray<CocosWebView>();
|
||||
}
|
||||
|
||||
private static native boolean shouldStartLoading(int index, String message);
|
||||
private static native void didFinishLoading(int index, String message);
|
||||
private static native void didFailLoading(int index, String message);
|
||||
private static native void onJsCallback(int index, String message);
|
||||
|
||||
public static boolean _shouldStartLoading(int index, String message) { return !shouldStartLoading(index, message); }
|
||||
public static void _didFinishLoading(int index, String message) { didFinishLoading(index, message); }
|
||||
public static void _didFailLoading(int index, String message) { didFailLoading(index, message); }
|
||||
public static void _onJsCallback(int index, String message) { onJsCallback(index, message); }
|
||||
|
||||
public static int createWebView() {
|
||||
final int index = viewTag;
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = new CocosWebView(GlobalObject.getContext(), index);
|
||||
FrameLayout.LayoutParams lParams = new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT);
|
||||
sLayout.addView(webView, lParams);
|
||||
|
||||
webViews.put(index, webView);
|
||||
}
|
||||
});
|
||||
return viewTag++;
|
||||
}
|
||||
|
||||
public static void removeWebView(final int index) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webViews.remove(index);
|
||||
sLayout.removeView(webView);
|
||||
webView.destroy();
|
||||
webView = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setVisible(final int index, final boolean visible) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setWebViewRect(final int index, final int left, final int top, final int maxWidth, final int maxHeight) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.setWebViewRect(left, top, maxWidth, maxHeight);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setBackgroundTransparent(final int index, final boolean isTransparent) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.setBackgroundColor(isTransparent ? Color.TRANSPARENT : Color.WHITE);
|
||||
webView.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setJavascriptInterfaceScheme(final int index, final String scheme) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.setJavascriptInterfaceScheme(scheme);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void loadData(final int index, final String data, final String mimeType, final String encoding, final String baseURL) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.loadDataWithBaseURL(baseURL, data, mimeType, encoding, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void loadHTMLString(final int index, final String data, final String baseUrl) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.loadDataWithBaseURL(baseUrl, data, null, null, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void loadUrl(final int index, final String url) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void loadFile(final int index, final String filePath) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.loadUrl(filePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void stopLoading(final int index) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.stopLoading();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public static void reload(final int index) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> T callInMainThread(Callable<T> call) throws ExecutionException, InterruptedException {
|
||||
FutureTask<T> task = new FutureTask<T>(call);
|
||||
sHandler.post(task);
|
||||
return task.get();
|
||||
}
|
||||
|
||||
public static boolean canGoBack(final int index) {
|
||||
Callable<Boolean> callable = new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
return webView != null && webView.canGoBack();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return callInMainThread(callable);
|
||||
} catch (ExecutionException e) {
|
||||
return false;
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean canGoForward(final int index) {
|
||||
Callable<Boolean> callable = new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
return webView != null && webView.canGoForward();
|
||||
}
|
||||
};
|
||||
try {
|
||||
return callInMainThread(callable);
|
||||
} catch (ExecutionException e) {
|
||||
return false;
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void goBack(final int index) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.goBack();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void goForward(final int index) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.goForward();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void evaluateJS(final int index, final String js) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.loadUrl("javascript:" + js);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setScalesPageToFit(final int index, final boolean scalesPageToFit) {
|
||||
GlobalObject.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CocosWebView webView = webViews.get(index);
|
||||
if (webView != null) {
|
||||
webView.setScalesPageToFit(scalesPageToFit);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
public class GlobalObject {
|
||||
private static Context sContext = null;
|
||||
private static Activity sActivity = null;
|
||||
private static Handler sHandler = null;
|
||||
private static Thread sUiThread = null;
|
||||
|
||||
// Should be invoked in UI thread. The parameter `context` and `activity` could be the same value.
|
||||
public static void init(Context context, Activity activity) {
|
||||
sContext = context;
|
||||
sActivity = activity;
|
||||
sHandler = new Handler(Looper.getMainLooper());
|
||||
sUiThread = Thread.currentThread();
|
||||
if (sUiThread != Looper.getMainLooper().getThread()) {
|
||||
throw new RuntimeException("GlobalObject.init should be invoked in UI thread");
|
||||
}
|
||||
}
|
||||
|
||||
public static void destroy() {
|
||||
sContext = null;
|
||||
sActivity = null;
|
||||
if (sHandler != null) {
|
||||
sHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
sHandler = null;
|
||||
}
|
||||
|
||||
public static Activity getActivity() {
|
||||
return sActivity;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return sContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the specified action on the UI thread. If the current thread is the UI
|
||||
* thread, then the action is executed immediately. If the current thread is
|
||||
* not the UI thread, the action is posted to the event queue of the UI thread.
|
||||
* This method keeps the same logic as which in Activity.runOnUiThread.
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-13.0.0_r37/core/java/android/app/Activity.java#7364
|
||||
* @param action the action to run on the UI thread
|
||||
*/
|
||||
public static void runOnUiThread(Runnable action) {
|
||||
if (Thread.currentThread() != sUiThread) {
|
||||
sHandler.post(action);
|
||||
} else {
|
||||
action.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
61
cocos/platform/android/java/src/com/cocos/lib/JsbBridge.java
Normal file
61
cocos/platform/android/java/src/com/cocos/lib/JsbBridge.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2018-2021 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated engine source code (the "Software"), a limited,
|
||||
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
|
||||
to use Cocos Creator solely to develop games on your target platforms. You shall
|
||||
not use Cocos Creator software for developing other software or tools that's
|
||||
used for developing games. You are not granted to publish, distribute,
|
||||
sublicense, and/or sell copies of Cocos Creator.
|
||||
|
||||
The software or tools in this License Agreement are licensed, not sold.
|
||||
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
public class JsbBridge {
|
||||
public interface ICallback{
|
||||
/**
|
||||
* Applies this callback to the given argument.
|
||||
*
|
||||
* @param arg0 as input
|
||||
* @param arg1 as input
|
||||
*/
|
||||
void onScript(String arg0, String arg1);
|
||||
}
|
||||
private static ICallback callback;
|
||||
|
||||
private static void callByScript(String arg0, String arg1){
|
||||
if(JsbBridge.callback != null)
|
||||
callback.onScript(arg0, arg1);
|
||||
}
|
||||
|
||||
/**Add a callback which you would like to apply
|
||||
* @param f ICallback, the method which will be actually applied. multiple calls will override
|
||||
* */
|
||||
public static void setCallback(ICallback f){
|
||||
JsbBridge.callback = f;
|
||||
}
|
||||
/**
|
||||
* Java dispatch Js event, use native c++ code
|
||||
* @param arg0 input values
|
||||
*/
|
||||
private static native void nativeSendToScript(String arg0, String arg1);
|
||||
public static void sendToScript(String arg0, String arg1){
|
||||
nativeSendToScript(arg0, arg1);
|
||||
}
|
||||
public static void sendToScript(String arg0){
|
||||
nativeSendToScript(arg0, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2018-2021 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated engine source code (the "Software"), a limited,
|
||||
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
|
||||
to use Cocos Creator solely to develop games on your target platforms. You shall
|
||||
not use Cocos Creator software for developing other software or tools that's
|
||||
used for developing games. You are not granted to publish, distribute,
|
||||
sublicense, and/or sell copies of Cocos Creator.
|
||||
|
||||
The software or tools in this License Agreement are licensed, not sold.
|
||||
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class JsbBridgeWrapper {
|
||||
//Interface for listener, should be implemented and dispatched
|
||||
public interface OnScriptEventListener {
|
||||
void onScriptEvent(String arg);
|
||||
}
|
||||
/**
|
||||
* Get the instance of JsbBridgetWrapper
|
||||
*/
|
||||
public static JsbBridgeWrapper getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new JsbBridgeWrapper();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
/**
|
||||
* Add a listener to specified event, if the event does not exist, the wrapper will create one. Concurrent listener will be ignored
|
||||
*/
|
||||
public void addScriptEventListener(String eventName, OnScriptEventListener listener) {
|
||||
if (eventMap.get(eventName) == null) {
|
||||
eventMap.put(eventName, new ArrayList<OnScriptEventListener>());
|
||||
}
|
||||
eventMap.get(eventName).add(listener);
|
||||
}
|
||||
/**
|
||||
* Remove listener for specified event, concurrent event will be deleted. Return false only if the event does not exist
|
||||
*/
|
||||
public boolean removeScriptEventListener(String eventName, OnScriptEventListener listener) {
|
||||
ArrayList<OnScriptEventListener> arr = eventMap.get(eventName);
|
||||
if (arr == null) {
|
||||
return false;
|
||||
}
|
||||
arr.remove(listener);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Remove all listener for event specified.
|
||||
*/
|
||||
public void removeAllListenersForEvent(String eventName) {
|
||||
this.eventMap.remove(eventName);
|
||||
}
|
||||
/**
|
||||
* Remove all event registered. Use it carefully!
|
||||
*/
|
||||
public void removeAllListeners() {
|
||||
this.eventMap.clear();
|
||||
}
|
||||
/**
|
||||
* Dispatch the event with argument, the event should be registered in javascript, or other script language in future.
|
||||
*/
|
||||
public void dispatchEventToScript(String eventName, String arg) {
|
||||
JsbBridge.sendToScript(eventName, arg);
|
||||
}
|
||||
/**
|
||||
* Dispatch the event which is registered in javascript, or other script language in future.
|
||||
*/
|
||||
public void dispatchEventToScript(String eventName) {
|
||||
JsbBridge.sendToScript(eventName);
|
||||
}
|
||||
|
||||
private JsbBridgeWrapper() {
|
||||
JsbBridge.setCallback(new JsbBridge.ICallback() {
|
||||
@Override
|
||||
public void onScript(String arg0, String arg1) {
|
||||
triggerEvents(arg0, arg1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final HashMap<String, ArrayList<OnScriptEventListener>> eventMap = new HashMap<>();
|
||||
private static JsbBridgeWrapper instance;
|
||||
|
||||
private void triggerEvents(String eventName, String arg) {
|
||||
ArrayList<OnScriptEventListener> arr = eventMap.get(eventName);
|
||||
if (arr == null)
|
||||
return;
|
||||
for (OnScriptEventListener m : arr) {
|
||||
m.onScriptEvent(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
cocos/platform/android/java/src/com/cocos/lib/Utils.java
Normal file
57
cocos/platform/android/java/src/com/cocos/lib/Utils.java
Normal file
@@ -0,0 +1,57 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2018 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos2d-x.org
|
||||
|
||||
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 com.cocos.lib;
|
||||
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static void hideVirtualButton() {
|
||||
if (Build.VERSION.SDK_INT >= 19 &&
|
||||
null != GlobalObject.getActivity()) {
|
||||
// use reflection to remove dependence of API level
|
||||
|
||||
Class viewClass = View.class;
|
||||
final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = CocosReflectionHelper.<Integer>getConstantValue(viewClass, "SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION");
|
||||
final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = CocosReflectionHelper.<Integer>getConstantValue(viewClass, "SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN");
|
||||
final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = CocosReflectionHelper.<Integer>getConstantValue(viewClass, "SYSTEM_UI_FLAG_HIDE_NAVIGATION");
|
||||
final int SYSTEM_UI_FLAG_FULLSCREEN = CocosReflectionHelper.<Integer>getConstantValue(viewClass, "SYSTEM_UI_FLAG_FULLSCREEN");
|
||||
final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = CocosReflectionHelper.<Integer>getConstantValue(viewClass, "SYSTEM_UI_FLAG_IMMERSIVE_STICKY");
|
||||
final int SYSTEM_UI_FLAG_LAYOUT_STABLE = CocosReflectionHelper.<Integer>getConstantValue(viewClass, "SYSTEM_UI_FLAG_LAYOUT_STABLE");
|
||||
|
||||
// getWindow().getDecorView().setSystemUiVisibility();
|
||||
final Object[] parameters = new Object[]{SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
|
||||
| SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
|
||||
| SYSTEM_UI_FLAG_IMMERSIVE_STICKY};
|
||||
CocosReflectionHelper.<Void>invokeInstanceMethod(GlobalObject.getActivity().getWindow().getDecorView(),
|
||||
"setSystemUiVisibility",
|
||||
new Class[]{Integer.TYPE},
|
||||
parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.cocos.lib.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* An SSL socket factory that forwards all calls to a delegate. Override {@link
|
||||
* #configureSocket} to customize a created socket before it is returned.
|
||||
*/
|
||||
class CocosDelegatingSSLSocketFactory extends SSLSocketFactory {
|
||||
protected final SSLSocketFactory delegate;
|
||||
|
||||
CocosDelegatingSSLSocketFactory(SSLSocketFactory delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return delegate.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return delegate.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket socket, String host, int port,
|
||||
boolean autoClose) throws IOException {
|
||||
return configureSocket(
|
||||
(SSLSocket)delegate.createSocket(socket, host, port, autoClose));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
return configureSocket((SSLSocket)delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost,
|
||||
int localPort) throws IOException {
|
||||
return configureSocket(
|
||||
(SSLSocket)delegate.createSocket(host, port, localHost, localPort));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return configureSocket((SSLSocket)delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port,
|
||||
InetAddress localAddress, int localPort)
|
||||
throws IOException {
|
||||
return configureSocket((SSLSocket)delegate.createSocket(
|
||||
address, port, localAddress, localPort));
|
||||
}
|
||||
|
||||
protected SSLSocket configureSocket(SSLSocket socket) throws IOException {
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.cocos.lib.websocket;
|
||||
|
||||
import org.cocos2dx.okhttp3.Interceptor;
|
||||
import org.cocos2dx.okhttp3.MediaType;
|
||||
import org.cocos2dx.okhttp3.Request;
|
||||
import org.cocos2dx.okhttp3.RequestBody;
|
||||
import org.cocos2dx.okhttp3.Response;
|
||||
import org.cocos2dx.okio.BufferedSink;
|
||||
import org.cocos2dx.okio.GzipSink;
|
||||
import org.cocos2dx.okio.Okio;
|
||||
import java.io.IOException;
|
||||
|
||||
public class CocosGzipRequestInterceptor implements Interceptor {
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request originalRequest = chain.request();
|
||||
if (originalRequest.body() == null ||
|
||||
originalRequest.header("Content-Encoding") != null) {
|
||||
return chain.proceed(originalRequest);
|
||||
}
|
||||
|
||||
Request compressedRequest =
|
||||
originalRequest.newBuilder()
|
||||
.header("Content-Encoding", "gzip")
|
||||
.method(originalRequest.method(), gzip(originalRequest.body()))
|
||||
.build();
|
||||
return chain.proceed(compressedRequest);
|
||||
}
|
||||
|
||||
private RequestBody gzip(final RequestBody body) {
|
||||
return new RequestBody() {
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return body.contentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() {
|
||||
return -1; // 无法提前知道压缩后的数据大小
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
|
||||
body.writeTo(gzipSink);
|
||||
gzipSink.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package com.cocos.lib.websocket;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.cocos.lib.GlobalObject;
|
||||
|
||||
import org.cocos2dx.okhttp3.CipherSuite;
|
||||
import org.cocos2dx.okhttp3.Dispatcher;
|
||||
import org.cocos2dx.okhttp3.OkHttpClient;
|
||||
import org.cocos2dx.okhttp3.Protocol;
|
||||
import org.cocos2dx.okhttp3.Request;
|
||||
import org.cocos2dx.okhttp3.Response;
|
||||
import org.cocos2dx.okhttp3.WebSocketListener;
|
||||
import org.cocos2dx.okio.ByteString;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class CocosWebSocket extends WebSocketListener {
|
||||
private final static String _TAG = "cocos-websocket";
|
||||
|
||||
static {
|
||||
NativeInit();
|
||||
}
|
||||
|
||||
private static class _WebSocketContext {
|
||||
long identifier;
|
||||
long handlerPtr;
|
||||
}
|
||||
private static Dispatcher dispatcher = null;
|
||||
|
||||
private final long _timeout;
|
||||
private final boolean _perMessageDeflate;
|
||||
private final boolean _tcpNoDelay;
|
||||
private final String[] _header;
|
||||
private final _WebSocketContext _wsContext =
|
||||
new _WebSocketContext();
|
||||
|
||||
private org.cocos2dx.okhttp3.WebSocket _webSocket;
|
||||
private OkHttpClient _client;
|
||||
|
||||
CocosWebSocket(long ptr, long handler, String[] header, boolean tcpNoDelay,
|
||||
boolean perMessageDeflate, long timeout) {
|
||||
_wsContext.identifier = ptr;
|
||||
_wsContext.handlerPtr = handler;
|
||||
_header = header;
|
||||
_tcpNoDelay = tcpNoDelay;
|
||||
_perMessageDeflate = perMessageDeflate;
|
||||
_timeout = timeout;
|
||||
}
|
||||
|
||||
private void _removeHandler() {
|
||||
synchronized (_wsContext) {
|
||||
_wsContext.identifier = 0;
|
||||
_wsContext.handlerPtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void _send(final byte[] msg) {
|
||||
// Log.d(_TAG, "try sending binary msg");
|
||||
if (null == _webSocket) {
|
||||
Log.e(_TAG, "WebSocket hasn't connected yet");
|
||||
return;
|
||||
}
|
||||
|
||||
ByteString byteString = ByteString.of(msg);
|
||||
_webSocket.send(byteString);
|
||||
}
|
||||
|
||||
private void _send(final String msg) {
|
||||
// Log.d(_TAG, "try sending string msg: " + msg);
|
||||
if (null == _webSocket) {
|
||||
Log.e(_TAG, "WebSocket hasn't connected yet");
|
||||
return;
|
||||
}
|
||||
|
||||
_webSocket.send(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the VM's default SSL socket factory, using {@code trustManager} for
|
||||
* trusted root certificates.
|
||||
*/
|
||||
private SSLSocketFactory
|
||||
defaultSslSocketFactory(X509TrustManager trustManager)
|
||||
throws NoSuchAlgorithmException, KeyManagementException {
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
SecureRandom random;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
random = SecureRandom.getInstanceStrong();
|
||||
} else {
|
||||
random = SecureRandom.getInstance("SHA1PRNG");
|
||||
}
|
||||
sslContext.init(null, new TrustManager[] {trustManager}, random);
|
||||
return sslContext.getSocketFactory();
|
||||
}
|
||||
|
||||
private String[] javaNames(List<CipherSuite> cipherSuites) {
|
||||
if (cipherSuites == null) {
|
||||
return new String[0];
|
||||
} else {
|
||||
String[] result = new String[cipherSuites.size()];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = cipherSuites.get(i).javaName();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private void _connect(final String url, final String protocols,
|
||||
final String caFilePath) {
|
||||
Log.d(_TAG, "connect ws url: '" + url + "' ,protocols: '" + protocols + "' ,ca_: '" + caFilePath + "'");
|
||||
Request.Builder requestBuilder = new Request.Builder().url(url);
|
||||
URI uriObj = null;
|
||||
try {
|
||||
requestBuilder = requestBuilder.url(url.trim());
|
||||
uriObj = URI.create(url);
|
||||
} catch (NullPointerException | IllegalArgumentException e) {
|
||||
synchronized (_wsContext) {
|
||||
nativeOnError("invalid url", _wsContext.identifier,
|
||||
_wsContext.handlerPtr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!protocols.isEmpty()) {
|
||||
requestBuilder.addHeader("Sec-WebSocket-Protocol", protocols);
|
||||
}
|
||||
if (_header != null) {
|
||||
for (int index = 0; index < _header.length; index += 2) {
|
||||
requestBuilder.header(_header[index], _header[index + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
String originProtocol =uriObj.getScheme().toLowerCase();
|
||||
String uriScheme = (originProtocol.equals("wss") || originProtocol.equals("https"))? "https" : "http";
|
||||
requestBuilder.addHeader("Origin", uriScheme + "://" + uriObj.getHost() + (uriObj.getPort() < 0 ? "" : ":" + uriObj.getPort()));
|
||||
|
||||
Request request = requestBuilder.build();
|
||||
|
||||
if(dispatcher == null) {
|
||||
dispatcher = new Dispatcher();
|
||||
}
|
||||
|
||||
OkHttpClient.Builder builder =
|
||||
new OkHttpClient.Builder()
|
||||
.dispatcher(dispatcher)
|
||||
.protocols(Collections.singletonList(Protocol.HTTP_1_1))
|
||||
.readTimeout(_timeout, TimeUnit.MILLISECONDS)
|
||||
.writeTimeout(_timeout, TimeUnit.MILLISECONDS)
|
||||
.connectTimeout(_timeout, TimeUnit.MILLISECONDS);
|
||||
|
||||
if (_perMessageDeflate) {
|
||||
// 开启压缩扩展, 开启 Gzip 压缩
|
||||
builder.addInterceptor(new CocosGzipRequestInterceptor());
|
||||
}
|
||||
KeyStore keyStore = null;
|
||||
if (url.toLowerCase().startsWith("wss://") && !caFilePath.isEmpty()) {
|
||||
try {
|
||||
InputStream caInput = null;
|
||||
|
||||
if (caFilePath.startsWith("assets/")) {
|
||||
caInput = GlobalObject.getContext().getResources().getAssets().open(caFilePath);
|
||||
} else {
|
||||
caInput = new FileInputStream(caFilePath);
|
||||
}
|
||||
if (caFilePath.toLowerCase().endsWith(".pem")) {
|
||||
keyStore = CocosWebSocketUtils.GetPEMKeyStore(caInput);
|
||||
} else {
|
||||
keyStore = CocosWebSocketUtils.GetCERKeyStore(caInput);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
String errMsg = e.getMessage();
|
||||
if (errMsg == null) {
|
||||
errMsg = "unknown error";
|
||||
}
|
||||
synchronized (_wsContext) {
|
||||
nativeOnError(errMsg, _wsContext.identifier, _wsContext.handlerPtr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
builder.hostnameVerifier(new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
Log.d(_TAG, "ca hostname: " + hostname);
|
||||
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
|
||||
return hv.verify(hostname, session);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (url.toLowerCase().startsWith("wss://") || _tcpNoDelay) {
|
||||
try {
|
||||
X509TrustManager trustManager =
|
||||
CocosWebSocketUtils.GetTrustManager(keyStore);
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
SecureRandom random;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
random = SecureRandom.getInstanceStrong();
|
||||
} else {
|
||||
random = SecureRandom.getInstance("SHA1PRNG");
|
||||
}
|
||||
sslContext.init(null, new TrustManager[] {trustManager}, random);
|
||||
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
||||
SSLSocketFactory customSslSocketFactory =
|
||||
new CocosDelegatingSSLSocketFactory(sslSocketFactory) {
|
||||
@Override
|
||||
protected SSLSocket configureSocket(SSLSocket socket)
|
||||
throws IOException {
|
||||
socket.setTcpNoDelay(_tcpNoDelay);
|
||||
// TLSv1.2 is disabled default below API20----
|
||||
// https://developer.android.com/reference/javax/net/ssl/SSLSocket
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH) {
|
||||
socket.setEnabledProtocols(new String[] {"TLSv1.2"});
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
};
|
||||
builder.sslSocketFactory(customSslSocketFactory, trustManager);
|
||||
} catch (GeneralSecurityException e) {
|
||||
e.printStackTrace();
|
||||
String errMsg = e.getMessage();
|
||||
if (errMsg == null) {
|
||||
errMsg = "unknown error";
|
||||
}
|
||||
synchronized (_wsContext) {
|
||||
nativeOnError(errMsg, _wsContext.identifier, _wsContext.handlerPtr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
_client = builder.build();
|
||||
_webSocket = _client.newWebSocket(request, this);
|
||||
}
|
||||
|
||||
private void _close(final int code, final String reason) {
|
||||
_webSocket.close(code, reason);
|
||||
// _client.dispatcher().executorService().shutdown();
|
||||
}
|
||||
|
||||
private long _getBufferedAmountID() {
|
||||
return _webSocket.queueSize();
|
||||
}
|
||||
|
||||
private void output(final String content) {
|
||||
Log.w(_TAG, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(org.cocos2dx.okhttp3.WebSocket _webSocket, Response response) {
|
||||
output("WebSocket onOpen _client: " + _client);
|
||||
synchronized (_wsContext) {
|
||||
nativeOnOpen(response.protocol().toString(),
|
||||
response.headers().toString(), _wsContext.identifier,
|
||||
_wsContext.handlerPtr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(org.cocos2dx.okhttp3.WebSocket _webSocket, String text) {
|
||||
// output("Receiving string msg: " + text);
|
||||
synchronized (_wsContext) {
|
||||
nativeOnStringMessage(text, _wsContext.identifier, _wsContext.handlerPtr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(org.cocos2dx.okhttp3.WebSocket _webSocket, ByteString bytes) {
|
||||
// output("Receiving binary msg");
|
||||
synchronized (_wsContext) {
|
||||
nativeOnBinaryMessage(bytes.toByteArray(), _wsContext.identifier,
|
||||
_wsContext.handlerPtr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosing(org.cocos2dx.okhttp3.WebSocket _webSocket, int code,
|
||||
String reason) {
|
||||
output("Closing : " + code + " / " + reason);
|
||||
if (_webSocket != null) {
|
||||
_webSocket.close(code, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(org.cocos2dx.okhttp3.WebSocket _webSocket, Throwable t,
|
||||
Response response) {
|
||||
String msg = "";
|
||||
if (t != null) {
|
||||
msg = t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage();
|
||||
}
|
||||
output("onFailure Error : " + msg);
|
||||
synchronized (_wsContext) {
|
||||
nativeOnError(msg, _wsContext.identifier, _wsContext.handlerPtr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(org.cocos2dx.okhttp3.WebSocket _webSocket, int code,
|
||||
String reason) {
|
||||
output("onClosed : " + code + " / " + reason);
|
||||
synchronized (_wsContext) {
|
||||
nativeOnClosed(code, reason, _wsContext.identifier,
|
||||
_wsContext.handlerPtr);
|
||||
}
|
||||
}
|
||||
|
||||
private static native void NativeInit();
|
||||
|
||||
private native void nativeOnStringMessage(final String msg, long identifier,
|
||||
long handler);
|
||||
|
||||
private native void nativeOnBinaryMessage(final byte[] msg, long identifier,
|
||||
long handler);
|
||||
|
||||
private native void nativeOnOpen(final String protocol,
|
||||
final String headerString, long identifier,
|
||||
long handler);
|
||||
|
||||
private native void nativeOnClosed(final int code, final String reason,
|
||||
long identifier, long handler);
|
||||
|
||||
private native void nativeOnError(final String msg, long identifier,
|
||||
long handler);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.cocos.lib.websocket;
|
||||
|
||||
import android.util.Base64;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.util.Arrays;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
class CocosWebSocketUtils {
|
||||
static X509TrustManager GetTrustManager(KeyStore keyStore)
|
||||
throws GeneralSecurityException {
|
||||
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
TrustManagerFactory trustManagerFactory =
|
||||
TrustManagerFactory.getInstance(algorithm);
|
||||
trustManagerFactory.init(keyStore);
|
||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
if (trustManagers.length != 1 ||
|
||||
!(trustManagers[0] instanceof X509TrustManager)) {
|
||||
String prefixErrorMessage = "Unexpected default trust managers:";
|
||||
throw new IllegalStateException(prefixErrorMessage +
|
||||
Arrays.toString(trustManagers));
|
||||
}
|
||||
return (X509TrustManager)trustManagers[0];
|
||||
}
|
||||
|
||||
static KeyStore GetCERKeyStore(InputStream inputStream)
|
||||
throws CertificateException, KeyStoreException, IOException,
|
||||
NoSuchAlgorithmException {
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||
Certificate certificate = factory.generateCertificate(inputStream);
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(null);
|
||||
keyStore.setCertificateEntry("0", certificate);
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
static KeyStore GetPEMKeyStore(InputStream inputStream) throws Exception {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(null);
|
||||
int index = 0;
|
||||
|
||||
BufferedReader bufferedReader =
|
||||
new BufferedReader(new InputStreamReader(inputStream));
|
||||
String carBegin;
|
||||
while ((carBegin = bufferedReader.readLine()) != null) {
|
||||
if (carBegin.contains("BEGIN CERTIFICATE")) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
while ((carBegin = bufferedReader.readLine()) != null) {
|
||||
if (carBegin.contains("END CERTIFICATE")) {
|
||||
String hexString = stringBuilder.toString();
|
||||
byte[] bytes = Base64.decode(hexString, Base64.DEFAULT);
|
||||
Certificate certificate = _GenerateCertificateFromDER(bytes);
|
||||
keyStore.setCertificateEntry(Integer.toString(index++),
|
||||
certificate);
|
||||
break;
|
||||
} else {
|
||||
stringBuilder.append(carBegin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bufferedReader.close();
|
||||
if (index == 0) {
|
||||
throw new IllegalArgumentException("No CERTIFICATE found");
|
||||
}
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
private static Certificate _GenerateCertificateFromDER(byte[] certBytes)
|
||||
throws CertificateException {
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||
return factory.generateCertificate(new ByteArrayInputStream(certBytes));
|
||||
}
|
||||
}
|
||||
45
cocos/platform/android/jni/JniCocosEntry.cpp
Normal file
45
cocos/platform/android/jni/JniCocosEntry.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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 <jni.h>
|
||||
|
||||
#include "game-activity/native_app_glue/android_native_app_glue.h"
|
||||
#include "platform/android/AndroidPlatform.h"
|
||||
#include "platform/java/jni/JniHelper.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void android_main(struct android_app *app) {
|
||||
auto *platform = cc::BasePlatform::getPlatform();
|
||||
auto *androidPlatform = static_cast<cc::AndroidPlatform *>(platform);
|
||||
androidPlatform->setAndroidApp(app);
|
||||
androidPlatform->init();
|
||||
androidPlatform->run(0, nullptr);
|
||||
}
|
||||
|
||||
//NOLINTNEXTLINE
|
||||
JNIEXPORT void JNICALL Java_com_cocos_lib_CocosActivity_onCreateNative(JNIEnv *env, jobject activity) {
|
||||
cc::JniHelper::init(env, activity);
|
||||
}
|
||||
}
|
||||
263
cocos/platform/android/jni/JniCocosSurfaceView.cpp
Normal file
263
cocos/platform/android/jni/JniCocosSurfaceView.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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 <android/keycodes.h>
|
||||
#include <android/log.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <jni.h>
|
||||
#include "application/ApplicationManager.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#include "engine/EngineEvents.h"
|
||||
#include "platform/android/AndroidPlatform.h"
|
||||
#include "platform/java/modules/SystemWindow.h"
|
||||
#include "platform/java/modules/SystemWindowManager.h"
|
||||
|
||||
namespace {
|
||||
struct cc::TouchEvent touchEvent;
|
||||
|
||||
class NativeWindowCache {
|
||||
public:
|
||||
explicit NativeWindowCache(int windowId) : _windowId{windowId} {
|
||||
}
|
||||
|
||||
~NativeWindowCache() {
|
||||
setSurface(nullptr);
|
||||
}
|
||||
|
||||
void setSurface(jobject surface) {
|
||||
if (_nativeWindow != nullptr) {
|
||||
ANativeWindow_release(_nativeWindow);
|
||||
}
|
||||
if (surface != nullptr) {
|
||||
_nativeWindow = ANativeWindow_fromSurface(env, surface);
|
||||
} else {
|
||||
_nativeWindow = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ANativeWindow *getNativeWindow() {
|
||||
return _nativeWindow;
|
||||
}
|
||||
|
||||
int getWindowId() const {
|
||||
return _windowId;
|
||||
}
|
||||
|
||||
JNIEnv *env;
|
||||
|
||||
private:
|
||||
int _windowId{0};
|
||||
ANativeWindow *_nativeWindow{nullptr};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
//#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "JniCocosSurfaceView JNI", __VA_ARGS__)
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT jlong Java_com_cocos_lib_CocosSurfaceView_constructNative(JNIEnv *env, jobject /*thiz*/, jint windowId) { // NOLINT JNI function name
|
||||
auto *cache = ccnew NativeWindowCache(windowId);
|
||||
cache->env = env;
|
||||
return reinterpret_cast<jlong>(cache);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_destructNative(JNIEnv * /*env*/, jobject /*thiz*/, jlong handle) { // NOLINT JNI function name
|
||||
auto *windowCache = reinterpret_cast<NativeWindowCache *>(handle);
|
||||
CC_SAFE_DELETE(windowCache);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_onSizeChangedNative(JNIEnv * /*env*/, jobject /*thiz*/, jint windowId, jint width, jint height) { // NOLINT JNI function name
|
||||
auto func = [width, height, windowId]() -> void {
|
||||
cc::events::Resize::broadcast(width, height, windowId);
|
||||
};
|
||||
CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_onSurfaceRedrawNeededNative(JNIEnv * /*env*/, jobject /*thiz*/, jlong handle) { // NOLINT JNI function name
|
||||
//
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_onSurfaceCreatedNative(JNIEnv *env, jobject /*thiz*/, jlong handle, jobject surface) { // NOLINT JNI function name
|
||||
CC_UNUSED_PARAM(env);
|
||||
auto *windowCache = reinterpret_cast<NativeWindowCache *>(handle);
|
||||
ANativeWindow *oldNativeWindow = windowCache->getNativeWindow();
|
||||
windowCache->setSurface(surface);
|
||||
ANativeWindow *nativeWindow = windowCache->getNativeWindow();
|
||||
|
||||
auto *platform = static_cast<cc::AndroidPlatform *>(cc::BasePlatform::getPlatform());
|
||||
auto *windowMgr = platform->getInterface<cc::SystemWindowManager>();
|
||||
auto *iSysWindow = windowMgr->getWindow(windowCache->getWindowId());
|
||||
auto *sysWindow = static_cast<cc::SystemWindow *>(iSysWindow);
|
||||
sysWindow->setWindowHandle(nativeWindow);
|
||||
if (oldNativeWindow) {
|
||||
auto func = [sysWindow]() -> void {
|
||||
cc::events::WindowRecreated::broadcast(sysWindow->getWindowId());
|
||||
};
|
||||
CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_onSurfaceChangedNative(JNIEnv *env, // NOLINT JNI function name
|
||||
jobject /*thiz*/,
|
||||
jlong handle,
|
||||
jobject surface,
|
||||
jint format,
|
||||
jint width,
|
||||
jint height) {
|
||||
CC_UNUSED_PARAM(env);
|
||||
CC_UNUSED_PARAM(format);
|
||||
CC_UNUSED_PARAM(width);
|
||||
CC_UNUSED_PARAM(height);
|
||||
if (handle != 0) {
|
||||
auto *windowCache = (NativeWindowCache *)handle;
|
||||
ANativeWindow *oldNativeWindow = windowCache->getNativeWindow();
|
||||
// Fix for window being destroyed behind the scenes on older Android
|
||||
// versions.
|
||||
if (oldNativeWindow != nullptr) {
|
||||
ANativeWindow_acquire(oldNativeWindow);
|
||||
}
|
||||
|
||||
windowCache->setSurface(surface);
|
||||
ANativeWindow *newNativeWindow = windowCache->getNativeWindow();
|
||||
if (oldNativeWindow != newNativeWindow) {
|
||||
auto *iSysWindow = CC_GET_PLATFORM_INTERFACE(cc::SystemWindowManager)->getWindow(windowCache->getWindowId());
|
||||
auto *sysWindow = static_cast<cc::SystemWindow *>(iSysWindow);
|
||||
sysWindow->setWindowHandle(newNativeWindow);
|
||||
|
||||
auto func = [sysWindow]() -> void {
|
||||
cc::events::WindowRecreated::broadcast(sysWindow->getWindowId());
|
||||
};
|
||||
CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func);
|
||||
}
|
||||
// Release the window we acquired earlier.
|
||||
if (oldNativeWindow != nullptr) {
|
||||
ANativeWindow_release(oldNativeWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_onSurfaceDestroyedNative(JNIEnv *env, jobject /*thiz*/, jlong handle) { // NOLINT JNI function name
|
||||
auto *windowCache = (NativeWindowCache *)handle;
|
||||
ANativeWindow *nativeWindow = windowCache->getNativeWindow();
|
||||
|
||||
// todo: destroy gfx surface
|
||||
auto func = [nativeWindow]() -> void {
|
||||
auto *platform = static_cast<cc::AndroidPlatform *>(cc::BasePlatform::getPlatform());
|
||||
auto *windowMgr = platform->getInterface<cc::SystemWindowManager>();
|
||||
cc::ISystemWindow *window = windowMgr->getWindowFromANativeWindow(nativeWindow);
|
||||
|
||||
cc::events::WindowDestroy::broadcast(window->getWindowId());
|
||||
};
|
||||
CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_cocos_lib_CocosTouchHandler_handleActionDown(JNIEnv *env, // NOLINT JNI function name
|
||||
jobject obj,
|
||||
jint windowId,
|
||||
jint id,
|
||||
jfloat x,
|
||||
jfloat y) {
|
||||
CC_UNUSED_PARAM(env);
|
||||
CC_UNUSED_PARAM(obj);
|
||||
|
||||
touchEvent.windowId = windowId;
|
||||
touchEvent.type = cc::TouchEvent::Type::BEGAN;
|
||||
touchEvent.touches.emplace_back(x, y, id);
|
||||
cc::events::Touch::broadcast(touchEvent);
|
||||
touchEvent.touches.clear();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
JNIEXPORT void JNICALL Java_com_cocos_lib_CocosTouchHandler_handleActionUp(JNIEnv *env, // NOLINT JNI function name
|
||||
jobject obj,
|
||||
jint windowId,
|
||||
jint id,
|
||||
jfloat x,
|
||||
jfloat y) {
|
||||
CC_UNUSED_PARAM(env);
|
||||
CC_UNUSED_PARAM(obj);
|
||||
|
||||
touchEvent.windowId = windowId;
|
||||
touchEvent.type = cc::TouchEvent::Type::ENDED;
|
||||
touchEvent.touches.emplace_back(x, y, id);
|
||||
cc::events::Touch::broadcast(touchEvent);
|
||||
touchEvent.touches.clear();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
JNIEXPORT void JNICALL Java_com_cocos_lib_CocosTouchHandler_handleActionMove(JNIEnv *env, // NOLINT JNI function name
|
||||
jobject obj,
|
||||
jint windowId,
|
||||
jintArray ids,
|
||||
jfloatArray xs,
|
||||
jfloatArray ys) {
|
||||
CC_UNUSED_PARAM(obj);
|
||||
|
||||
touchEvent.windowId = windowId;
|
||||
touchEvent.type = cc::TouchEvent::Type::MOVED;
|
||||
int size = env->GetArrayLength(ids);
|
||||
jint id[size];
|
||||
jfloat x[size];
|
||||
jfloat y[size];
|
||||
|
||||
env->GetIntArrayRegion(ids, 0, size, id);
|
||||
env->GetFloatArrayRegion(xs, 0, size, x);
|
||||
env->GetFloatArrayRegion(ys, 0, size, y);
|
||||
for (int i = 0; i < size; i++) {
|
||||
touchEvent.touches.emplace_back(x[i], y[i], id[i]);
|
||||
}
|
||||
|
||||
cc::events::Touch::broadcast(touchEvent);
|
||||
touchEvent.touches.clear();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
JNIEXPORT void JNICALL Java_com_cocos_lib_CocosTouchHandler_handleActionCancel(JNIEnv *env, // NOLINT JNI function name
|
||||
jobject obj,
|
||||
jint windowId,
|
||||
jintArray ids,
|
||||
jfloatArray xs,
|
||||
jfloatArray ys) {
|
||||
CC_UNUSED_PARAM(obj);
|
||||
|
||||
touchEvent.windowId = windowId;
|
||||
touchEvent.type = cc::TouchEvent::Type::CANCELLED;
|
||||
int size = env->GetArrayLength(ids);
|
||||
jint id[size];
|
||||
jfloat x[size];
|
||||
jfloat y[size];
|
||||
|
||||
env->GetIntArrayRegion(ids, 0, size, id);
|
||||
env->GetFloatArrayRegion(xs, 0, size, x);
|
||||
env->GetFloatArrayRegion(ys, 0, size, y);
|
||||
for (int i = 0; i < size; i++) {
|
||||
touchEvent.touches.emplace_back(x[i], y[i], id[i]);
|
||||
}
|
||||
cc::events::Touch::broadcast(touchEvent);
|
||||
touchEvent.touches.clear();
|
||||
}
|
||||
}
|
||||
4
cocos/platform/android/libcocos2dx/AndroidManifest.xml
Normal file
4
cocos/platform/android/libcocos2dx/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
</manifest>
|
||||
40
cocos/platform/android/libcocos2dx/build.gradle
Normal file
40
cocos/platform/android/libcocos2dx/build.gradle
Normal file
@@ -0,0 +1,40 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger()
|
||||
namespace 'com.cocos.lib'
|
||||
defaultConfig {
|
||||
minSdkVersion PROP_MIN_SDK_VERSION
|
||||
targetSdkVersion PROP_TARGET_SDK_VERSION
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
consumerProguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
aidl.srcDir "../java/src"
|
||||
java.srcDir "../java/src"
|
||||
manifest.srcFile "AndroidManifest.xml"
|
||||
}
|
||||
|
||||
buildDir = new File(rootProject.buildDir, project.name)
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api fileTree(include: ['*.jar'], dir: '../java/libs')
|
||||
}
|
||||
17
cocos/platform/android/libcocos2dx/proguard-rules.pro
vendored
Normal file
17
cocos/platform/android/libcocos2dx/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in E:\developSoftware\Android\SDK/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
-keep public class com.google.** { *; }
|
||||
-keep public class androidx.** { *; }
|
||||
-keep class com.cocos.lib.CocosActivity {
|
||||
public <methods>;
|
||||
protected <methods>;
|
||||
private void createSurface(...);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!--EditBox Confirm Button Name-->
|
||||
<string name="done" translatable="false">完成</string>
|
||||
<string name="next" translatable="false">下一个</string>
|
||||
<string name="search" translatable="false">搜索</string>
|
||||
<string name="go" translatable="false">前往</string>
|
||||
<string name="send" translatable="false">发送</string>
|
||||
<string name="tip_disable_safe_input_type" translatable="false">请到系统设置关闭安全键盘</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!--EditBox Confirm Button Title-->
|
||||
<string name="done" translatable="false">Done</string>
|
||||
<string name="next" translatable="false">Next</string>
|
||||
<string name="search" translatable="false">Search</string>
|
||||
<string name="go" translatable="false">Go</string>
|
||||
<string name="send" translatable="false">Send</string>
|
||||
<string name="tip_disable_safe_input_type" translatable="false">Please go to the system settings to turn off the security keyboard!</string>
|
||||
</resources>
|
||||
4
cocos/platform/android/libcocosxr/AndroidManifest.xml
Normal file
4
cocos/platform/android/libcocosxr/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.cocos.lib.xr">
|
||||
|
||||
</manifest>
|
||||
37
cocos/platform/android/libcocosxr/build.gradle
Normal file
37
cocos/platform/android/libcocosxr/build.gradle
Normal file
@@ -0,0 +1,37 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger()
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion PROP_MIN_SDK_VERSION
|
||||
targetSdkVersion PROP_TARGET_SDK_VERSION
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
java.srcDirs "src"
|
||||
res.srcDirs 'res'
|
||||
jniLibs.srcDirs 'libs'
|
||||
manifest.srcFile "AndroidManifest.xml"
|
||||
}
|
||||
|
||||
buildDir = new File(rootProject.buildDir, project.name)
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
|
||||
implementation project(':libcocos')
|
||||
}
|
||||
21
cocos/platform/android/libcocosxr/proguard-rules.pro
vendored
Normal file
21
cocos/platform/android/libcocosxr/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,212 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib.xr;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class CocosXRApi {
|
||||
private static final String TAG = "CocosXRApi";
|
||||
private final static String ACTION_ADB_CMD = "com.cocosxr.adb.cmd";
|
||||
private enum ActivityLifecycleType {
|
||||
UnKnown,
|
||||
Created,
|
||||
Started,
|
||||
Resumed,
|
||||
Paused,
|
||||
Stopped,
|
||||
SaveInstanceState,
|
||||
Destroyed
|
||||
}
|
||||
|
||||
private final static CocosXRApi instance = new CocosXRApi();
|
||||
|
||||
/**
|
||||
* adb shell am broadcast -a com.cocosxr.adb.cmd --es CMD_KEY LOG --ei CMD_VALUE 1
|
||||
* adb shell am broadcast -a com.cocosxr.adb.cmd --es CMD_KEY LOG --es CMD_VALUE abc
|
||||
*/
|
||||
private class CocosXRActionReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (ACTION_ADB_CMD.equals(intent.getAction())) {
|
||||
// adb cmd
|
||||
if (intent.getExtras() == null) {
|
||||
Log.w(TAG, "[CocosXRActionReceiver] intent.getExtras() == null");
|
||||
return;
|
||||
}
|
||||
Object cmdKey = intent.getExtras().get("CMD_KEY");
|
||||
String key = cmdKey == null ? "" : cmdKey.toString();
|
||||
Object cmdValue = intent.getExtras().get("CMD_VALUE");
|
||||
String valueStr = null;
|
||||
if (cmdValue instanceof Integer) {
|
||||
valueStr = String.valueOf(intent.getIntExtra("CMD_VALUE", Integer.MIN_VALUE));
|
||||
} else if (cmdValue instanceof String) {
|
||||
valueStr = intent.getStringExtra("CMD_VALUE");
|
||||
}
|
||||
|
||||
try {
|
||||
onAdbCmd(key, valueStr);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CocosXRApi() {
|
||||
}
|
||||
|
||||
public static CocosXRApi getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private Application application;
|
||||
private WeakReference<Activity> activityWeakReference;
|
||||
private Context applicationContext;
|
||||
private Application.ActivityLifecycleCallbacks activityLifecycleCallbacks;
|
||||
private CocosXRActionReceiver actionReceiver;
|
||||
private CocosXRWebViewManager webViewManager;
|
||||
|
||||
public void onCreate(Activity activity) {
|
||||
activityWeakReference = new WeakReference<>(activity);
|
||||
application = activity.getApplication();
|
||||
applicationContext = activity.getApplicationContext();
|
||||
webViewManager = new CocosXRWebViewManager();
|
||||
if (activityLifecycleCallbacks == null) {
|
||||
activityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
|
||||
@Override
|
||||
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
|
||||
try {
|
||||
onActivityLifecycleCallback(ActivityLifecycleType.Created.ordinal(), activity.getLocalClassName());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity activity) {
|
||||
webViewManager.onCreate(activity);
|
||||
try {
|
||||
onActivityLifecycleCallback(ActivityLifecycleType.Started.ordinal(), activity.getLocalClassName());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResumed(Activity activity) {
|
||||
try {
|
||||
onActivityLifecycleCallback(ActivityLifecycleType.Resumed.ordinal(), activity.getLocalClassName());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
webViewManager.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPaused(Activity activity) {
|
||||
try {
|
||||
onActivityLifecycleCallback(ActivityLifecycleType.Paused.ordinal(), activity.getLocalClassName());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
webViewManager.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {
|
||||
try {
|
||||
onActivityLifecycleCallback(ActivityLifecycleType.Stopped.ordinal(), activity.getLocalClassName());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
|
||||
try {
|
||||
onActivityLifecycleCallback(ActivityLifecycleType.SaveInstanceState.ordinal(), activity.getLocalClassName());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
try {
|
||||
onActivityLifecycleCallback(ActivityLifecycleType.Destroyed.ordinal(), activity.getLocalClassName());
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
webViewManager.onDestroy();
|
||||
}
|
||||
};
|
||||
}
|
||||
application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks);
|
||||
|
||||
if(actionReceiver != null) {
|
||||
applicationContext.unregisterReceiver(actionReceiver);
|
||||
actionReceiver =null;
|
||||
}
|
||||
|
||||
actionReceiver = new CocosXRActionReceiver();
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(ACTION_ADB_CMD);
|
||||
applicationContext.registerReceiver(actionReceiver, intentFilter);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
if (application != null && activityLifecycleCallbacks != null) {
|
||||
application.unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks);
|
||||
activityLifecycleCallbacks = null;
|
||||
}
|
||||
|
||||
if(applicationContext != null && actionReceiver != null) {
|
||||
applicationContext.unregisterReceiver(actionReceiver);
|
||||
actionReceiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
public Activity getActivity() {
|
||||
return activityWeakReference.get();
|
||||
}
|
||||
|
||||
// native
|
||||
private native void onActivityLifecycleCallback(int id, String activityClassName);
|
||||
private native void onAdbCmd(String key, String value);
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib.xr;
|
||||
|
||||
import android.opengl.GLES11Ext;
|
||||
import android.opengl.GLES30;
|
||||
import android.util.Log;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public class CocosXRGLHelper {
|
||||
private static final String TAG = "CocosXRGLHelper";
|
||||
|
||||
public static int loadShader(int shaderType, String source) {
|
||||
int shader = GLES30.glCreateShader(shaderType);
|
||||
if (shader != 0) {
|
||||
GLES30.glShaderSource(shader, source);
|
||||
GLES30.glCompileShader(shader);
|
||||
int[] compiled = new int[1];
|
||||
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0);
|
||||
if (compiled[0] == 0) {
|
||||
Log.e(TAG, "Could not compile shader " + shaderType + ":");
|
||||
Log.e(TAG, GLES30.glGetShaderInfoLog(shader));
|
||||
GLES30.glDeleteShader(shader);
|
||||
shader = 0;
|
||||
}
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
public static int createProgram(String vertexSource, String fragmentSource) {
|
||||
int vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexSource);
|
||||
if (vertexShader == 0) {
|
||||
return 0;
|
||||
}
|
||||
checkGLError("vertex shader");
|
||||
|
||||
int pixelShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentSource);
|
||||
if (pixelShader == 0) {
|
||||
return 0;
|
||||
}
|
||||
checkGLError("fragment shader");
|
||||
int program = GLES30.glCreateProgram();
|
||||
if (program != 0) {
|
||||
GLES30.glAttachShader(program, vertexShader);
|
||||
checkGLError("glAttachShader vertexShader");
|
||||
GLES30.glAttachShader(program, pixelShader);
|
||||
checkGLError("glAttachShader pixelShader");
|
||||
GLES30.glLinkProgram(program);
|
||||
GLES30.glDetachShader(program, vertexShader);
|
||||
GLES30.glDetachShader(program, pixelShader);
|
||||
int[] linkStatus = new int[1];
|
||||
GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0);
|
||||
if (linkStatus[0] != GLES30.GL_TRUE) {
|
||||
Log.e(TAG, "Could not link program: ");
|
||||
Log.e(TAG, GLES30.glGetProgramInfoLog(program));
|
||||
GLES30.glDeleteProgram(program);
|
||||
program = 0;
|
||||
}
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
private static final int SIZE_OF_FLOAT = 4;
|
||||
private static final int SIZE_OF_SHORT = 2;
|
||||
public static FloatBuffer createFloatBuffer(float[] array) {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(array.length * SIZE_OF_FLOAT);
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
FloatBuffer fb = bb.asFloatBuffer();
|
||||
fb.put(array);
|
||||
fb.position(0);
|
||||
return fb;
|
||||
}
|
||||
|
||||
public static ShortBuffer createShortBuffer(short[] array) {
|
||||
ByteBuffer bb = ByteBuffer.allocateDirect(array.length * SIZE_OF_SHORT);
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
ShortBuffer fb = bb.asShortBuffer();
|
||||
fb.put(array);
|
||||
fb.position(0);
|
||||
return fb;
|
||||
}
|
||||
|
||||
public static void checkGLError(String operation) {
|
||||
int errerCode = GLES30.glGetError();
|
||||
if (errerCode != GLES30.GL_NO_ERROR) {
|
||||
String msg = operation + ":error" + errerCode;
|
||||
Log.e(TAG, msg);
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static int createOESTexture() {
|
||||
int[] oesTex = new int[1];
|
||||
GLES30.glGenTextures(1, oesTex, 0);
|
||||
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTex[0]);
|
||||
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR);
|
||||
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
|
||||
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
|
||||
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
|
||||
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
|
||||
return oesTex[0];
|
||||
}
|
||||
|
||||
public static class GLQuadScreen {
|
||||
final String quadMeshVertexShader_EXT =
|
||||
" #version 310 es\n in vec4 vertexPosition; \n "
|
||||
+ "in vec2 vertexTexCoord; \n "
|
||||
+ "out vec2 texCoord; \n "
|
||||
+ "uniform mat4 textureMatrix;\n"
|
||||
+ "void main() \n "
|
||||
+ "{"
|
||||
+ " gl_Position = vertexPosition; \n "
|
||||
+ " vec4 temp = vec4(vertexTexCoord.x, vertexTexCoord.y, 0, 1); \n"
|
||||
+ " texCoord = (textureMatrix * temp).xy; \n "
|
||||
+ "}";
|
||||
|
||||
final String quadFragmentShader_EXT =
|
||||
"#version 310 es\n #extension GL_OES_EGL_image_external_essl3 : require \n"
|
||||
+ "precision mediump float; \n"
|
||||
+ "in vec2 texCoord; \n"
|
||||
+ "uniform samplerExternalOES texSampler2D; \n"
|
||||
+ "out vec4 frag_color;\n"
|
||||
+ "void main() \n"
|
||||
+ "{ \n"
|
||||
+ " frag_color = texture(texSampler2D, texCoord); \n"
|
||||
+ "}";
|
||||
float[] orthoQuadVertices = {
|
||||
-1.0f, -1.0f, 0.0f, 1.0f,
|
||||
1.0f, -1.0f, 0.0f, 1.0f,
|
||||
1.0f, 1.0f, 0.0f, 1.0f,
|
||||
-1.0f, 1.0f, 0.0f, 1.0f};
|
||||
|
||||
float[] orthoQuadTexCoords_EXT = {
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
1.0f, 0.0f,
|
||||
0.0f, 0.0f
|
||||
};
|
||||
|
||||
short[] orthoQuadIndices = {0, 1, 2, 2, 3, 0};
|
||||
|
||||
ShortBuffer indexBuffer;
|
||||
FloatBuffer vetexBuffer;
|
||||
FloatBuffer textureCoordBuffer;
|
||||
|
||||
int program = -1;
|
||||
int vertexHandle = 0;
|
||||
int textureCoordHandle = 0;
|
||||
int textureMatrixHandle = 0;
|
||||
|
||||
public GLQuadScreen() {
|
||||
}
|
||||
|
||||
public void initShader() {
|
||||
if(program == -1) {
|
||||
vetexBuffer = createFloatBuffer(orthoQuadVertices);
|
||||
textureCoordBuffer = createFloatBuffer(orthoQuadTexCoords_EXT);
|
||||
indexBuffer = createShortBuffer(orthoQuadIndices);
|
||||
program = createProgram(quadMeshVertexShader_EXT, quadFragmentShader_EXT);
|
||||
GLES30.glUseProgram(program);
|
||||
vertexHandle = GLES30.glGetAttribLocation(program, "vertexPosition");
|
||||
textureCoordHandle = GLES30.glGetAttribLocation(program, "vertexTexCoord");
|
||||
textureMatrixHandle = GLES30.glGetUniformLocation(program, "textureMatrix");
|
||||
GLES30.glUseProgram(0);
|
||||
}
|
||||
Log.d(TAG, "GLQuadScreen Shader Info:" + program + "," + vertexHandle + "," + textureCoordHandle);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
GLES30.glDeleteProgram(program);
|
||||
program = 0;
|
||||
}
|
||||
|
||||
public void draw(int oesTextureId, float[] videoTransformMatrix) {
|
||||
if(program == -1) {
|
||||
initShader();
|
||||
return;
|
||||
}
|
||||
|
||||
GLES30.glUseProgram(program);
|
||||
GLES30.glVertexAttribPointer(vertexHandle, 4, GLES30.GL_FLOAT, false, 0, vetexBuffer);
|
||||
GLES30.glVertexAttribPointer(textureCoordHandle, 2, GLES30.GL_FLOAT, false, 0, textureCoordBuffer);
|
||||
|
||||
GLES30.glEnableVertexAttribArray(vertexHandle);
|
||||
GLES30.glEnableVertexAttribArray(textureCoordHandle);
|
||||
|
||||
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
|
||||
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTextureId);
|
||||
|
||||
GLES30.glUniformMatrix4fv(textureMatrixHandle, 1, false, videoTransformMatrix, 0);
|
||||
|
||||
GLES30.glDrawElements(GLES30.GL_TRIANGLES, indexBuffer.capacity(), GLES30.GL_UNSIGNED_SHORT, indexBuffer);
|
||||
|
||||
GLES30.glDisableVertexAttribArray(vertexHandle);
|
||||
GLES30.glDisableVertexAttribArray(textureCoordHandle);
|
||||
GLES30.glUseProgram(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib.xr;
|
||||
|
||||
import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.EGLConfig;
|
||||
import android.opengl.EGLContext;
|
||||
import android.opengl.EGLDisplay;
|
||||
import android.opengl.EGLSurface;
|
||||
import android.opengl.GLES30;
|
||||
import android.util.Log;
|
||||
import com.cocos.lib.JsbBridgeWrapper;
|
||||
import com.cocos.lib.xr.permission.CocosXRPermissionHelper;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class CocosXRVideoManager {
|
||||
private static final String TAG = "CocosXRVideoManager";
|
||||
private final static CocosXRVideoManager instance = new CocosXRVideoManager();
|
||||
|
||||
class VideoEventData {
|
||||
public String headTag;
|
||||
public int eventId;
|
||||
public String videoPlayerHandleKey;
|
||||
public int videoSourceType;
|
||||
public String videoSourceUrl;
|
||||
public int videoWidth;
|
||||
public int videoHeight;
|
||||
public int videoTextureId;
|
||||
public boolean isLoop;
|
||||
public int seekToMsec;
|
||||
public String eventName;
|
||||
public float volume;
|
||||
public float playbackSpeed = 1.0f;
|
||||
|
||||
public VideoEventData(String data) {
|
||||
String[] dataArray = data.split("&");
|
||||
if (dataArray[0].equals(XR_VIDEO_EVENT_TAG)) {
|
||||
this.eventId = Integer.parseInt(dataArray[1]);
|
||||
this.eventName = dataArray[2];
|
||||
this.videoPlayerHandleKey = dataArray[3];
|
||||
switch (this.eventId) {
|
||||
case VIDEO_EVENT_PREPARE: {
|
||||
this.videoSourceType = Integer.parseInt(dataArray[4]);
|
||||
this.videoSourceUrl = dataArray[5];
|
||||
this.isLoop = Integer.parseInt(dataArray[6]) == 1;
|
||||
this.volume = Float.parseFloat(dataArray[7]);
|
||||
this.playbackSpeed = Float.parseFloat(dataArray[8]);
|
||||
break;
|
||||
}
|
||||
|
||||
case VIDEO_EVENT_SEEK_TO: {
|
||||
this.seekToMsec = Integer.parseInt(dataArray[4]);
|
||||
break;
|
||||
}
|
||||
|
||||
case VIDEO_EVENT_SET_LOOP: {
|
||||
this.isLoop = Integer.parseInt(dataArray[4]) == 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case VIDEO_EVENT_SET_VOLUME: {
|
||||
this.volume = Float.parseFloat(dataArray[4]);
|
||||
break;
|
||||
}
|
||||
|
||||
case VIDEO_EVENT_SET_TEXTURE_INFO: {
|
||||
this.videoWidth = Integer.parseInt(dataArray[4]);
|
||||
this.videoHeight = Integer.parseInt(dataArray[5]);
|
||||
this.videoTextureId = Integer.parseInt(dataArray[6]);
|
||||
break;
|
||||
}
|
||||
|
||||
case VIDEO_EVENT_SET_SPEED: {
|
||||
this.playbackSpeed = Float.parseFloat(dataArray[4]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CocosXRVideoManager() {
|
||||
}
|
||||
|
||||
public static CocosXRVideoManager getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
final int MAX_COUNT = 3;
|
||||
public static final int VIDEO_SOURCE_TYPE_LOCAL = 1;
|
||||
public static final int VIDEO_SOURCE_TYPE_REMOTE = 2;
|
||||
|
||||
public static final int VIDEO_EVENT_INVALID = 1;
|
||||
//xr-event&id&handle&
|
||||
public static final int VIDEO_EVENT_PREPARE = 2;
|
||||
public static final int VIDEO_EVENT_PLAY = 3;
|
||||
public static final int VIDEO_EVENT_PAUSE = 4;
|
||||
public static final int VIDEO_EVENT_STOP = 5;
|
||||
public static final int VIDEO_EVENT_RESET = 6;
|
||||
public static final int VIDEO_EVENT_DESTROY = 7;
|
||||
|
||||
public static final int VIDEO_EVENT_GET_POSITION = 30;
|
||||
public static final int VIDEO_EVENT_GET_DURATION = 31;
|
||||
public static final int VIDEO_EVENT_GET_IS_PALYING = 32;
|
||||
public static final int VIDEO_EVENT_GET_IS_LOOPING = 33;
|
||||
|
||||
public static final int VIDEO_EVENT_SET_LOOP = 50;
|
||||
public static final int VIDEO_EVENT_SEEK_TO = 51;
|
||||
public static final int VIDEO_EVENT_SET_VOLUME = 52;
|
||||
public static final int VIDEO_EVENT_SET_TEXTURE_INFO = 53;
|
||||
public static final int VIDEO_EVENT_SET_SPEED = 54;
|
||||
|
||||
public static final int VIDEO_EVENT_MEDIA_PLAYER_PREPARED = 100;
|
||||
public static final int VIDEO_EVENT_MEDIA_PLAYER_PLAY_COMPLETE = 101;
|
||||
public static final int VIDEO_EVENT_MEDIA_PLAYER_SEEK_COMPLETE = 102;
|
||||
public static final int VIDEO_EVENT_MEDIA_PLAYER_ERROR = 103;
|
||||
public static final int VIDEO_EVENT_MEDIA_PLAYER_VIDEO_SIZE = 104;
|
||||
public static final int VIDEO_EVENT_MEDIA_PLAYER_ON_INFO = 105;
|
||||
|
||||
private WeakReference<Activity> activityWeakReference;
|
||||
final String XR_VIDEO_PLAYER_EVENT_NAME = "xr-video-player:";
|
||||
//xr-event&handle&id&url&512&512&1
|
||||
final String XR_VIDEO_EVENT_TAG = "xr-event";
|
||||
|
||||
HashMap<String, CocosXRVideoPlayer> xrVideoPlayerHashMap = new HashMap<>();
|
||||
HashMap<String, ArrayList<String>> cachedScriptEventHashMap = new HashMap<>();
|
||||
CocosXRVideoGLThread videoGLThread = null;
|
||||
boolean isPaused = false;
|
||||
public void onCreate(Activity activity) {
|
||||
activityWeakReference = new WeakReference<>(activity);
|
||||
for (int i = 0; i < MAX_COUNT; i++) {
|
||||
JsbBridgeWrapper.getInstance().addScriptEventListener(XR_VIDEO_PLAYER_EVENT_NAME + i, eventData -> {
|
||||
if(isPaused) {
|
||||
return;
|
||||
}
|
||||
processVideoEvent(eventData);
|
||||
});
|
||||
}
|
||||
JsbBridgeWrapper.getInstance().addScriptEventListener(CocosXRPermissionHelper.XR_PERMISSION_EVENT_NAME, CocosXRPermissionHelper::onScriptEvent);
|
||||
|
||||
CocosXRApi.getInstance().onCreate(activity);
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
Log.d(TAG, "onResume");
|
||||
isPaused = false;
|
||||
if(videoGLThread != null) {
|
||||
videoGLThread.onResume();
|
||||
}
|
||||
Set<Map.Entry<String, ArrayList<String>>> entrySets = cachedScriptEventHashMap.entrySet();
|
||||
for (Map.Entry<String, ArrayList<String>> entrySet : entrySets) {
|
||||
if (entrySet.getKey() != null && entrySet.getValue() != null) {
|
||||
for (String data : entrySet.getValue()) {
|
||||
Log.d(TAG, "onResume.dispatchEventToScript:" + entrySet.getKey() + ":" + data);
|
||||
JsbBridgeWrapper.getInstance().dispatchEventToScript(entrySet.getKey(), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
cachedScriptEventHashMap.clear();
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
Log.d(TAG, "onPause");
|
||||
isPaused = true;
|
||||
if(videoGLThread != null) {
|
||||
videoGLThread.onPause();
|
||||
}
|
||||
|
||||
Set<Map.Entry<String, CocosXRVideoPlayer>> entrySets = xrVideoPlayerHashMap.entrySet();
|
||||
for (Map.Entry<String, CocosXRVideoPlayer> entrySet : entrySets) {
|
||||
if(entrySet.getValue() != null) {
|
||||
entrySet.getValue().pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendVideoEvent(VideoEventData videoEventData, String... eventData) {
|
||||
sendVideoEvent(videoEventData.eventId, videoEventData.eventName, videoEventData.videoPlayerHandleKey, eventData);
|
||||
}
|
||||
|
||||
public void sendVideoEvent(int eventId, String eventName, String videoPlayerHandleKey, String... eventData) {
|
||||
StringBuilder data = new StringBuilder("xr-event&" + eventId + "&" + eventName + '&' + videoPlayerHandleKey);
|
||||
if (eventData.length > 0) {
|
||||
for (String evtData : eventData) {
|
||||
data.append("&").append(evtData);
|
||||
}
|
||||
}
|
||||
|
||||
if(videoGLThread == null) return;
|
||||
if(isPaused) {
|
||||
Log.e(TAG, "sendVideoEvent failed, because is paused !!! [" + data + "]");
|
||||
if (!cachedScriptEventHashMap.containsKey(eventName)) {
|
||||
cachedScriptEventHashMap.put(eventName, new ArrayList<>());
|
||||
}
|
||||
Objects.requireNonNull(cachedScriptEventHashMap.get(eventName)).add(data.toString());
|
||||
return;
|
||||
}
|
||||
JsbBridgeWrapper.getInstance().dispatchEventToScript(eventName, data.toString());
|
||||
}
|
||||
|
||||
private void processVideoEvent(String eventData) {
|
||||
VideoEventData videoEventData = new VideoEventData(eventData);
|
||||
if (videoEventData.eventId == VIDEO_EVENT_PREPARE) {
|
||||
CocosXRVideoPlayer videoPlayer = xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey);
|
||||
if (videoPlayer == null) {
|
||||
videoPlayer = new CocosXRVideoPlayer(activityWeakReference, videoEventData.videoPlayerHandleKey, videoEventData.eventName);
|
||||
videoPlayer.prepare(videoEventData);
|
||||
xrVideoPlayerHashMap.put(videoEventData.videoPlayerHandleKey, videoPlayer);
|
||||
} else {
|
||||
videoPlayer.prepare(videoEventData);
|
||||
}
|
||||
|
||||
if (videoGLThread == null) {
|
||||
videoGLThread = new CocosXRVideoGLThread();
|
||||
videoGLThread.start();
|
||||
}
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_PLAY) {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).play();
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_PAUSE) {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).pause();
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_STOP) {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).stop();
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_RESET) {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).reset();
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_DESTROY) {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).release();
|
||||
videoGLThread.queueEvent(() -> {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).onGLDestroy();
|
||||
xrVideoPlayerHashMap.remove(videoEventData.videoPlayerHandleKey);
|
||||
});
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_GET_POSITION) {
|
||||
int position = Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).getCurrentPosition();
|
||||
sendVideoEvent(videoEventData, String.valueOf(position));
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_GET_DURATION) {
|
||||
int duration = Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).getDuration();
|
||||
sendVideoEvent(videoEventData, String.valueOf(duration));
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_GET_IS_PALYING) {
|
||||
boolean isPlaying = Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).isPlaying();
|
||||
sendVideoEvent(videoEventData, String.valueOf(isPlaying));
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_GET_IS_LOOPING) {
|
||||
boolean isLooping = Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).isLooping();
|
||||
sendVideoEvent(videoEventData, String.valueOf(isLooping));
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_SET_LOOP) {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).setLooping(videoEventData.isLoop);
|
||||
} else if (videoEventData.eventId == VIDEO_EVENT_SEEK_TO) {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).seekTo(videoEventData.seekToMsec);
|
||||
} else if(videoEventData.eventId == VIDEO_EVENT_SET_VOLUME) {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).setVolume(videoEventData.volume);
|
||||
} else if(videoEventData.eventId == VIDEO_EVENT_SET_TEXTURE_INFO) {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).setTextureInfo(videoEventData.videoWidth, videoEventData.videoHeight, videoEventData.videoTextureId);
|
||||
} else if(videoEventData.eventId == VIDEO_EVENT_SET_SPEED) {
|
||||
Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).setPlaybackSpeed(videoEventData.playbackSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "onDestroy:" + xrVideoPlayerHashMap.size());
|
||||
CocosXRApi.getInstance().onDestroy();
|
||||
if(videoGLThread != null) {
|
||||
videoGLThread.onDestroy();
|
||||
videoGLThread = null;
|
||||
}
|
||||
for (int i = 0; i < MAX_COUNT; i++) {
|
||||
JsbBridgeWrapper.getInstance().removeAllListenersForEvent(XR_VIDEO_PLAYER_EVENT_NAME + i);
|
||||
}
|
||||
JsbBridgeWrapper.getInstance().removeAllListenersForEvent(CocosXRPermissionHelper.XR_PERMISSION_EVENT_NAME);
|
||||
Set<Map.Entry<String, CocosXRVideoPlayer>> entrySets = xrVideoPlayerHashMap.entrySet();
|
||||
for (Map.Entry<String, CocosXRVideoPlayer> entrySet : entrySets) {
|
||||
entrySet.getValue().release();
|
||||
}
|
||||
xrVideoPlayerHashMap.clear();
|
||||
cachedScriptEventHashMap.clear();
|
||||
}
|
||||
|
||||
class CocosXRVideoGLThread extends Thread {
|
||||
private final ReentrantLock lockObj = new ReentrantLock(true);
|
||||
private final Condition pauseCondition = lockObj.newCondition();
|
||||
private boolean requestPaused = false;
|
||||
private boolean requestExited = false;
|
||||
long lastTickTime = System.nanoTime();
|
||||
private final ArrayList<Runnable> mEventQueue = new ArrayList<>();
|
||||
|
||||
EGLContext eglContext;
|
||||
EGLDisplay eglDisplay;
|
||||
EGLSurface pBufferSurface;
|
||||
EGLContext parentContext;
|
||||
int renderTargetFboId;
|
||||
|
||||
CocosXRVideoGLThread() {
|
||||
parentContext = EGL14.eglGetCurrentContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
init();
|
||||
while (true) {
|
||||
lockObj.lock();
|
||||
try {
|
||||
if (requestExited) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (requestPaused) {
|
||||
pauseCondition.await();
|
||||
}
|
||||
|
||||
if (requestExited) {
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
lockObj.unlock();
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (!mEventQueue.isEmpty()) {
|
||||
Runnable event = mEventQueue.remove(0);
|
||||
if (event != null) {
|
||||
event.run();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
void init() {
|
||||
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE};
|
||||
int[] attrList = new int[] {EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
|
||||
EGL14.EGL_RENDERABLE_TYPE, 0x00000040,
|
||||
EGL14.EGL_RED_SIZE, 8,
|
||||
EGL14.EGL_GREEN_SIZE, 8,
|
||||
EGL14.EGL_BLUE_SIZE, 8,
|
||||
EGL14.EGL_DEPTH_SIZE, 8,
|
||||
EGL14.EGL_SAMPLE_BUFFERS, 1,
|
||||
EGL14.EGL_SAMPLES, 1,
|
||||
EGL14.EGL_STENCIL_SIZE, 0,
|
||||
EGL14.EGL_NONE};
|
||||
EGLConfig[] configOut = new EGLConfig[1];
|
||||
int[] configNumOut = new int[1];
|
||||
EGL14.eglChooseConfig(eglDisplay, attrList, 0, configOut, 0, 1,
|
||||
configNumOut, 0);
|
||||
eglContext = EGL14.eglCreateContext(eglDisplay, configOut[0], parentContext, attrib_list, 0);
|
||||
|
||||
int[] sur_attrib_list = {EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE};
|
||||
pBufferSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configOut[0], sur_attrib_list, 0);
|
||||
|
||||
EGL14.eglMakeCurrent(eglDisplay, pBufferSurface, pBufferSurface, eglContext);
|
||||
GLES30.glDisable(GLES30.GL_DEPTH_TEST);
|
||||
GLES30.glDisable(GLES30.GL_BLEND);
|
||||
GLES30.glDisable(GLES30.GL_CULL_FACE);
|
||||
|
||||
int[] tmpFboId = new int[1];
|
||||
GLES30.glGenFramebuffers(1, tmpFboId, 0);
|
||||
renderTargetFboId = tmpFboId[0];
|
||||
CocosXRGLHelper.checkGLError("fbo");
|
||||
|
||||
lastTickTime = System.nanoTime();
|
||||
|
||||
Set<Map.Entry<String, CocosXRVideoPlayer>> entrySets = xrVideoPlayerHashMap.entrySet();
|
||||
for (Map.Entry<String, CocosXRVideoPlayer> entrySet : entrySets) {
|
||||
entrySet.getValue().onGLReady();
|
||||
}
|
||||
Log.d(TAG, "CocosXRVideoGLThread init");
|
||||
}
|
||||
|
||||
void tick() {
|
||||
// draw
|
||||
lastTickTime = System.nanoTime();
|
||||
Set<Map.Entry<String, CocosXRVideoPlayer>> entrySets = xrVideoPlayerHashMap.entrySet();
|
||||
for (Map.Entry<String, CocosXRVideoPlayer> entrySet : entrySets) {
|
||||
entrySet.getValue().onBeforeGLDrawFrame();
|
||||
if(entrySet.getValue().isStopped() || entrySet.getValue().getVideoTextureWidth() == 0 || entrySet.getValue().getVideoTextureHeight() == 0) {
|
||||
continue;
|
||||
}
|
||||
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, renderTargetFboId);
|
||||
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, entrySet.getValue().getTargetTextureId(), 0);
|
||||
int offsetX = (entrySet.getValue().getVideoTextureWidth() - entrySet.getValue().getVideoSourceWidth()) / 2;
|
||||
int offsetY = (entrySet.getValue().getVideoTextureHeight() - entrySet.getValue().getVideoSourceHeight()) / 2;
|
||||
GLES30.glViewport(offsetX, offsetY, entrySet.getValue().getVideoSourceWidth(), entrySet.getValue().getVideoSourceHeight());
|
||||
GLES30.glScissor(0, 0, entrySet.getValue().getVideoTextureWidth(), entrySet.getValue().getVideoTextureHeight());
|
||||
entrySet.getValue().onGLDrawFrame();
|
||||
}
|
||||
EGL14.eglSwapBuffers(eglDisplay, pBufferSurface);
|
||||
|
||||
if (System.nanoTime() - lastTickTime < 16666666) {
|
||||
try {
|
||||
long sleepTimeNS = 16666666 - (System.nanoTime() - lastTickTime);
|
||||
Thread.sleep(sleepTimeNS / 1000000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void exit() {
|
||||
Set<Map.Entry<String, CocosXRVideoPlayer>> entrySets = xrVideoPlayerHashMap.entrySet();
|
||||
for (Map.Entry<String, CocosXRVideoPlayer> entrySet : entrySets) {
|
||||
entrySet.getValue().onGLDestroy();
|
||||
}
|
||||
GLES30.glDeleteFramebuffers(1, new int[] {renderTargetFboId}, 0);
|
||||
EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
|
||||
EGL14.eglDestroySurface(eglDisplay, pBufferSurface);
|
||||
EGL14.eglDestroyContext(eglDisplay, eglContext);
|
||||
Log.d(TAG, "CocosXRVideoGLThread exit");
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
lockObj.lock();
|
||||
requestPaused = true;
|
||||
lockObj.unlock();
|
||||
Log.d(TAG, "CocosXRVideoGLThread onPause");
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
lockObj.lock();
|
||||
requestPaused = false;
|
||||
pauseCondition.signalAll();
|
||||
lockObj.unlock();
|
||||
Log.d(TAG, "CocosXRVideoGLThread onResume");
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
lockObj.lock();
|
||||
requestExited = true;
|
||||
pauseCondition.signalAll();
|
||||
lockObj.unlock();
|
||||
|
||||
try {
|
||||
videoGLThread.join();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, e.getLocalizedMessage());
|
||||
}
|
||||
Log.d(TAG, "CocosXRVideoGLThread onDestroy");
|
||||
}
|
||||
|
||||
public void queueEvent(Runnable r) {
|
||||
synchronized (this) {
|
||||
mEventQueue.add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib.xr;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class CocosXRVideoPlayer {
|
||||
enum MediaPlayerState {
|
||||
IDLE,
|
||||
INITIALIZED,
|
||||
READY_PREPARE,
|
||||
PREPARING,
|
||||
PREPARED,
|
||||
STARTED,
|
||||
STOPPED,
|
||||
PAUSED,
|
||||
END,
|
||||
ERROR,
|
||||
COMPLETED
|
||||
}
|
||||
|
||||
private static final String TAG = "CocosXRVideoPlayer";
|
||||
private String uniqueKey;
|
||||
private String eventName;
|
||||
private CocosXRVideoTexture videoTexture;
|
||||
private String videoSourceUrl;
|
||||
private int videoSourceType;
|
||||
private int videoTextureId = 0;
|
||||
private boolean isGLInitialized = false;
|
||||
private int videoSourceSizeWidth = 0;
|
||||
private int videoSourceSizeHeight = 0;
|
||||
private int videoTextureWidth = 0;
|
||||
private int videoTextureHeight = 0;
|
||||
private MediaPlayerState mediaPlayerState = MediaPlayerState.IDLE;
|
||||
|
||||
CocosXRGLHelper.GLQuadScreen quadScreen;
|
||||
MediaPlayer mediaPlayer;
|
||||
WeakReference<Activity> atyWeakReference;
|
||||
|
||||
public CocosXRVideoPlayer(WeakReference<Activity> activityWeakReference, String key, String eventName) {
|
||||
this.atyWeakReference = activityWeakReference;
|
||||
this.uniqueKey = key;
|
||||
this.eventName = eventName;
|
||||
this.quadScreen = new CocosXRGLHelper.GLQuadScreen();
|
||||
this.mediaPlayer = new MediaPlayer();
|
||||
this.mediaPlayer.setOnErrorListener((mp, what, extra) -> {
|
||||
mediaPlayerState = MediaPlayerState.ERROR;
|
||||
Log.e(TAG, "onError " + what + "," + extra + "." + mp.toString());
|
||||
CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_ERROR, eventName, uniqueKey);
|
||||
return false;
|
||||
});
|
||||
this.mediaPlayer.setOnInfoListener((mp, what, extra) -> {
|
||||
// Log.d(TAG, "onInfo " + what + "," + extra + "." + mp.toString());
|
||||
CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_ON_INFO, eventName, uniqueKey, String.valueOf(what));
|
||||
return false;
|
||||
});
|
||||
this.mediaPlayer.setOnPreparedListener(mp -> {
|
||||
mediaPlayerState = MediaPlayerState.PREPARED;
|
||||
CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_PREPARED, eventName, uniqueKey);
|
||||
CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_GET_DURATION, eventName, uniqueKey, String.valueOf(mp.getDuration()));
|
||||
Log.d(TAG, "onPrepared." + mp+ ", getDuration." + mp.getDuration() + "," + mp.getVideoWidth() + "X" + mp.getVideoHeight() + "," + mp.isPlaying());
|
||||
});
|
||||
this.mediaPlayer.setOnCompletionListener(mp -> {
|
||||
Log.d(TAG, "onCompletion." + mp.toString());
|
||||
mediaPlayerState = MediaPlayerState.COMPLETED;
|
||||
CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_PLAY_COMPLETE, eventName, uniqueKey);
|
||||
});
|
||||
this.mediaPlayer.setOnSeekCompleteListener(mp -> {
|
||||
// Log.d(TAG, "onSeekComplete." + mp.toString());
|
||||
CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_SEEK_COMPLETE, eventName, uniqueKey);
|
||||
});
|
||||
this.mediaPlayer.setOnVideoSizeChangedListener((mp, width, height) -> {
|
||||
Log.d(TAG, "onVideoSizeChanged " + width + "x" + height + "." + mp.toString() + ", isPlaying." + mp.isPlaying());
|
||||
if(videoSourceSizeWidth != width || videoSourceSizeHeight != height) {
|
||||
videoSourceSizeWidth = width;
|
||||
videoSourceSizeHeight = height;
|
||||
CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_GET_IS_PALYING, eventName, uniqueKey, String.valueOf(mp.isPlaying() ? 1 : 0));
|
||||
CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_VIDEO_SIZE, eventName, uniqueKey, width + "&" + height);
|
||||
}
|
||||
});
|
||||
this.mediaPlayer.setAudioAttributes(new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build());
|
||||
mediaPlayerState = MediaPlayerState.INITIALIZED;
|
||||
Log.d(TAG, "constructor() " + key + "|" + eventName + "|" + quadScreen.toString());
|
||||
}
|
||||
|
||||
public void runOnUIThread(Runnable runnable) {
|
||||
if (atyWeakReference != null && atyWeakReference.get() != null) {
|
||||
atyWeakReference.get().runOnUiThread(runnable);
|
||||
} else {
|
||||
Log.e(TAG, "runOnUIThread failed, activity not exist !!!");
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextureInfo(int textureWidth, int textureHeight, int videoTextureId) {
|
||||
this.videoTextureId = videoTextureId;
|
||||
this.videoTextureWidth = textureWidth;
|
||||
this.videoTextureHeight = textureHeight;
|
||||
Log.d(TAG, "setTextureInfo." + textureWidth + "x" + textureHeight + ":" + videoTextureId);
|
||||
}
|
||||
|
||||
public void prepare(CocosXRVideoManager.VideoEventData data) {
|
||||
if (this.videoTexture == null) {
|
||||
this.videoTexture = new CocosXRVideoTexture();
|
||||
}
|
||||
this.videoSourceType = data.videoSourceType;
|
||||
this.videoSourceUrl = data.videoSourceUrl;
|
||||
if(TextUtils.isEmpty(this.videoSourceUrl)) {
|
||||
Log.w(TAG, "prepare failed, because video source is empty !!!");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (data.videoSourceType == CocosXRVideoManager.VIDEO_SOURCE_TYPE_LOCAL) {
|
||||
AssetFileDescriptor afd = atyWeakReference.get().getResources().getAssets().openFd(data.videoSourceUrl);
|
||||
mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
|
||||
} else {
|
||||
mediaPlayer.setDataSource(data.videoSourceUrl);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
mediaPlayer.setLooping(data.isLoop);
|
||||
mediaPlayer.setVolume(data.volume, data.volume);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
try {
|
||||
mediaPlayer.setPlaybackParams(mediaPlayer.getPlaybackParams().setSpeed(data.playbackSpeed));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "prepare:" + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
mediaPlayerState = MediaPlayerState.READY_PREPARE;
|
||||
if (isGLInitialized) {
|
||||
runOnUIThread(() -> {
|
||||
if(mediaPlayerState == MediaPlayerState.READY_PREPARE) {
|
||||
mediaPlayerState = MediaPlayerState.PREPARING;
|
||||
mediaPlayer.prepareAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
Log.d(TAG, "prepare");
|
||||
}
|
||||
|
||||
public int getTargetTextureId() {
|
||||
return videoTextureId;
|
||||
}
|
||||
|
||||
public int getVideoTextureWidth() {
|
||||
return videoTextureWidth;
|
||||
}
|
||||
|
||||
public int getVideoTextureHeight() {
|
||||
return videoTextureHeight;
|
||||
}
|
||||
|
||||
public int getVideoSourceWidth() {
|
||||
return videoSourceSizeWidth;
|
||||
}
|
||||
|
||||
public int getVideoSourceHeight() {
|
||||
return videoSourceSizeHeight;
|
||||
}
|
||||
|
||||
public String getVideoSourceUrl() {
|
||||
return videoSourceUrl;
|
||||
}
|
||||
|
||||
public int getVideoSourceType() {
|
||||
return videoSourceType;
|
||||
}
|
||||
|
||||
public void onGLReady() {
|
||||
Log.d(TAG, "onGLReady." + this.hashCode());
|
||||
videoTexture.createSurfaceTexture();
|
||||
quadScreen.initShader();
|
||||
Surface surface = new Surface(videoTexture.getSurfaceTexture());
|
||||
mediaPlayer.setSurface(surface);
|
||||
surface.release();
|
||||
isGLInitialized = true;
|
||||
if(mediaPlayerState == MediaPlayerState.READY_PREPARE) {
|
||||
runOnUIThread(() -> {
|
||||
if(mediaPlayerState == MediaPlayerState.READY_PREPARE) {
|
||||
mediaPlayerState = MediaPlayerState.PREPARING;
|
||||
mediaPlayer.prepareAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onBeforeGLDrawFrame() {
|
||||
if (!isGLInitialized) {
|
||||
onGLReady();
|
||||
}
|
||||
}
|
||||
|
||||
public void onGLDrawFrame() {
|
||||
if (videoTextureId == 0 || videoTextureWidth == 0 || videoTextureHeight == 0) {
|
||||
return;
|
||||
}
|
||||
videoTexture.updateTexture();
|
||||
quadScreen.draw(videoTexture.getOESTextureId(), videoTexture.getVideoMatrix());
|
||||
}
|
||||
|
||||
public void onGLDestroy() {
|
||||
if (videoTexture != null) {
|
||||
videoTexture.release();
|
||||
videoTexture = null;
|
||||
}
|
||||
|
||||
if (quadScreen != null) {
|
||||
quadScreen.release();
|
||||
quadScreen = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void play() {
|
||||
runOnUIThread(() -> {
|
||||
Log.d(TAG, "- start");
|
||||
mediaPlayer.start();
|
||||
mediaPlayerState = MediaPlayerState.STARTED;
|
||||
CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_GET_IS_PALYING, eventName, uniqueKey, String.valueOf(mediaPlayer.isPlaying() ? 1 : 0));
|
||||
});
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
if(!mediaPlayer.isPlaying()) return;
|
||||
runOnUIThread(() -> {
|
||||
Log.d(TAG, "- pause");
|
||||
mediaPlayer.pause();
|
||||
mediaPlayerState = MediaPlayerState.PAUSED;
|
||||
CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_GET_IS_PALYING, eventName, uniqueKey, String.valueOf(mediaPlayer.isPlaying() ? 1 : 0));
|
||||
});
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
runOnUIThread(() -> {
|
||||
Log.d(TAG, "- stop");
|
||||
mediaPlayer.stop();
|
||||
mediaPlayerState = MediaPlayerState.STOPPED;
|
||||
});
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
runOnUIThread(() -> {
|
||||
Log.d(TAG, "- reset");
|
||||
mediaPlayer.reset();
|
||||
mediaPlayerState = MediaPlayerState.IDLE;
|
||||
});
|
||||
}
|
||||
|
||||
public void release() {
|
||||
runOnUIThread(() -> {
|
||||
if (mediaPlayer != null) {
|
||||
try {
|
||||
if (mediaPlayer.isPlaying()) {
|
||||
mediaPlayer.pause();
|
||||
}
|
||||
mediaPlayer.stop();
|
||||
mediaPlayer.release();
|
||||
mediaPlayer = null;
|
||||
mediaPlayerState = MediaPlayerState.END;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, e.getLocalizedMessage());
|
||||
}
|
||||
Log.d(TAG, "- release");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return mediaPlayer != null && mediaPlayer.isPlaying();
|
||||
}
|
||||
|
||||
public boolean isStopped() {
|
||||
return mediaPlayerState == MediaPlayerState.STOPPED;
|
||||
}
|
||||
|
||||
public boolean isLooping() {
|
||||
return mediaPlayer.isLooping();
|
||||
}
|
||||
|
||||
public void setLooping(boolean looping) {
|
||||
runOnUIThread(() -> {
|
||||
Log.d(TAG, "- setLooping." + looping);
|
||||
mediaPlayer.setLooping(looping);
|
||||
});
|
||||
}
|
||||
|
||||
public void setVolume(float volume) {
|
||||
runOnUIThread(() -> mediaPlayer.setVolume(volume, volume));
|
||||
}
|
||||
|
||||
public int getDuration() {
|
||||
return mediaPlayer.getDuration();
|
||||
}
|
||||
|
||||
public int getCurrentPosition() {
|
||||
return mediaPlayer.getCurrentPosition();
|
||||
}
|
||||
|
||||
public void seekTo(int mSec) {
|
||||
runOnUIThread(() -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
mediaPlayer.seekTo(mSec, MediaPlayer.SEEK_CLOSEST);
|
||||
} else {
|
||||
mediaPlayer.seekTo(mSec);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public float getPlaybackSpeed() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return mediaPlayer.getPlaybackParams().getSpeed();
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPlaybackSpeed(float speed) {
|
||||
runOnUIThread(() -> {
|
||||
Log.d(TAG, "- setPlaybackSpeed." + speed);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
try {
|
||||
mediaPlayer.setPlaybackParams(mediaPlayer.getPlaybackParams().setSpeed(Math.max(speed, 0.1f)));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib.xr;
|
||||
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.opengl.GLES30;
|
||||
import android.opengl.Matrix;
|
||||
|
||||
public class CocosXRVideoTexture implements SurfaceTexture.OnFrameAvailableListener {
|
||||
SurfaceTexture surfaceTexture;
|
||||
private boolean surfaceNeedsUpdate = false;
|
||||
private long videoTimestampNs = -1;
|
||||
private final float[] videoSTMatrix = new float[16];
|
||||
private long lastFrameAvailableTime = 0;
|
||||
|
||||
@Override
|
||||
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
|
||||
surfaceNeedsUpdate = true;
|
||||
lastFrameAvailableTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private int videoOESTextureId;
|
||||
|
||||
public CocosXRVideoTexture() {
|
||||
Matrix.setIdentityM(videoSTMatrix, 0);
|
||||
}
|
||||
|
||||
public SurfaceTexture createSurfaceTexture() {
|
||||
videoOESTextureId = CocosXRGLHelper.createOESTexture();
|
||||
surfaceTexture = new SurfaceTexture(videoOESTextureId);
|
||||
surfaceTexture.setOnFrameAvailableListener(this);
|
||||
return surfaceTexture;
|
||||
}
|
||||
|
||||
public SurfaceTexture getSurfaceTexture() {
|
||||
return surfaceTexture;
|
||||
}
|
||||
|
||||
public int getOESTextureId() {
|
||||
return videoOESTextureId;
|
||||
}
|
||||
|
||||
public float[] getVideoMatrix() {
|
||||
return videoSTMatrix;
|
||||
}
|
||||
|
||||
public long getVideoTimestampNs() {
|
||||
return videoTimestampNs;
|
||||
}
|
||||
|
||||
public synchronized boolean updateTexture() {
|
||||
if (!surfaceNeedsUpdate && System.currentTimeMillis() - lastFrameAvailableTime > 30) {
|
||||
surfaceNeedsUpdate = true;
|
||||
lastFrameAvailableTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
if (surfaceNeedsUpdate) {
|
||||
surfaceTexture.updateTexImage();
|
||||
surfaceTexture.getTransformMatrix(videoSTMatrix);
|
||||
videoTimestampNs = surfaceTexture.getTimestamp();
|
||||
surfaceNeedsUpdate = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isFrameAvailable() {
|
||||
return surfaceNeedsUpdate;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (surfaceTexture != null) {
|
||||
surfaceTexture.release();
|
||||
}
|
||||
if (videoOESTextureId != 0) {
|
||||
GLES30.glDeleteTextures(1, new int[] {videoOESTextureId}, 0);
|
||||
videoOESTextureId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib.xr;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.http.SslError;
|
||||
import android.opengl.GLES30;
|
||||
import android.os.Build;
|
||||
import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.webkit.ClientCertRequest;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.HttpAuthHandler;
|
||||
import android.webkit.PermissionRequest;
|
||||
import android.webkit.SafeBrowsingResponse;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebResourceError;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class CocosXRWebViewContainer extends FrameLayout {
|
||||
private static final String TAG = "CocosXRWebViewContainer";
|
||||
WebView webView;
|
||||
private long lastDrawTime = 0;
|
||||
private CocosXRVideoTexture videoTexture;
|
||||
private Surface webViewSurface;
|
||||
private CocosXRGLHelper.GLQuadScreen quadScreen;
|
||||
private boolean isGLInitialized = false;
|
||||
private int renderTargetFboId;
|
||||
private int videoTextureId = 0;
|
||||
private int videoTextureWidth = 0;
|
||||
private int videoTextureHeight = 0;
|
||||
|
||||
private boolean isKeyDown = false;
|
||||
private long keyDownTime = 0;
|
||||
|
||||
public CocosXRWebViewContainer(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CocosXRWebViewContainer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public CocosXRWebViewContainer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init();
|
||||
}
|
||||
|
||||
void init() {
|
||||
webView = new WebView(getContext());
|
||||
webView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT, Gravity.NO_GRAVITY));
|
||||
webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
|
||||
webView.getSettings().setSupportZoom(false);
|
||||
webView.getSettings().setBuiltInZoomControls(true);
|
||||
webView.getSettings().setTextZoom(100);
|
||||
|
||||
webView.getSettings().setDisplayZoomControls(false);
|
||||
webView.getSettings().setLoadWithOverviewMode(true);
|
||||
webView.getSettings().setUseWideViewPort(true);
|
||||
webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
|
||||
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
webView.getSettings().setDomStorageEnabled(true);
|
||||
webView.getSettings().setDefaultTextEncodingName("UTF-8");
|
||||
webView.getSettings().setAllowFileAccess(true);
|
||||
webView.getSettings().setAllowContentAccess(true);
|
||||
webView.getSettings().setAllowFileAccessFromFileURLs(true);
|
||||
webView.getSettings().setDatabaseEnabled(true);
|
||||
webView.getSettings().setAppCacheEnabled(true);
|
||||
webView.getSettings().setLoadsImagesAutomatically(true);
|
||||
webView.getSettings().setSupportMultipleWindows(false);
|
||||
webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
|
||||
webView.getSettings().setBlockNetworkImage(false);
|
||||
webView.getSettings().setGeolocationEnabled(true);
|
||||
webView.getSettings().setPluginState(WebSettings.PluginState.ON);
|
||||
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
|
||||
} else {
|
||||
webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
webView.getSettings().setSafeBrowsingEnabled(false);
|
||||
}
|
||||
CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true);
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
|
||||
@Override
|
||||
public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback) {
|
||||
Log.d(TAG, "onSafeBrowsingHit:" + threatType + "," + request.getUrl().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||
Log.d(TAG, "onReceivedSslError:" + error.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
|
||||
Log.d(TAG, "onReceivedClientCertRequest:" + request.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
|
||||
Log.d(TAG, "onReceivedHttpAuthRequest:" + host + ":" + realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedLoginRequest(WebView view, String realm, String account, String args) {
|
||||
Log.d(TAG, "onReceivedLoginRequest:" + account + ":" + realm + ":" + args);
|
||||
}
|
||||
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||
Log.e(TAG, "shouldOverrideUrlLoading failed:" + url);
|
||||
view.reload();
|
||||
return false;
|
||||
}
|
||||
|
||||
view.loadUrl(url);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Log.e(TAG, "onReceivedError:" + error.getDescription() + "," + error.getErrorCode() + "," + view.getTitle());
|
||||
}
|
||||
super.onReceivedError(view, request, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
|
||||
Log.e(TAG, "onReceivedHttpError:" + errorResponse.toString());
|
||||
super.onReceivedHttpError(view, request, errorResponse);
|
||||
}
|
||||
});
|
||||
webView.setWebChromeClient(new WebChromeClient() {
|
||||
|
||||
@Override
|
||||
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
|
||||
Log.d(TAG, "onCreateWindow:" + resultMsg.toString());
|
||||
return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseWindow(WebView window) {
|
||||
Log.d(TAG, "onCreateWindowonCloseWindow");
|
||||
super.onCloseWindow(window);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedTitle(WebView view, String title) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowCustomView(View view, CustomViewCallback callback) {
|
||||
Log.d(TAG, "onShowCustomView");
|
||||
if(callback != null) {
|
||||
view.setVisibility(View.GONE);
|
||||
callback.onCustomViewHidden();
|
||||
}
|
||||
/*ViewParent viewParent = webView.getParent();
|
||||
if (viewParent instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup)viewParent;
|
||||
if (viewGroup.getChildCount() > 1) {
|
||||
callback.onCustomViewHidden();
|
||||
return;
|
||||
}
|
||||
viewGroup.getChildAt(0).setVisibility(View.GONE);
|
||||
view.setBackgroundColor(0);
|
||||
viewGroup.addView(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
}*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
Log.d(TAG, "onHideCustomView");
|
||||
/*ViewParent viewParent = webView.getParent();
|
||||
if (viewParent instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup)viewParent;
|
||||
while (viewGroup.getChildCount() > 1)
|
||||
viewGroup.removeViewAt(1);
|
||||
viewGroup.getChildAt(0).setVisibility(View.VISIBLE);
|
||||
}*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionRequest(PermissionRequest request) {
|
||||
String[] resources = request.getResources();
|
||||
ArrayList<String> permissions = new ArrayList<>();
|
||||
for (String resource : resources) {
|
||||
Log.d(TAG, "onPermissionRequest.resource:" + resource);
|
||||
if (resource.equals("android.webkit.resource.AUDIO_CAPTURE")) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (getContext().checkSelfPermission("android.permission.RECORD_AUDIO") != PackageManager.PERMISSION_GRANTED) {
|
||||
permissions.add(resource);
|
||||
}
|
||||
}
|
||||
} else if (resource.equals("android.webkit.resource.PROTECTED_MEDIA_ID")) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (getContext().checkSelfPermission("android.permission.PROTECTED_MEDIA_ID") != PackageManager.PERMISSION_GRANTED) {
|
||||
permissions.add(resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(permissions.size() > 0) {
|
||||
String[] names = new String[permissions.size()];
|
||||
permissions.toArray(names);
|
||||
Log.d(TAG, "acquirePermissions:" + Arrays.toString(names));
|
||||
request.grant(names);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
String userAgentString = webView.getSettings().getUserAgentString();
|
||||
Log.d(TAG, "ua:" + userAgentString);
|
||||
|
||||
|
||||
this.quadScreen = new CocosXRGLHelper.GLQuadScreen();
|
||||
videoTexture = new CocosXRVideoTexture();
|
||||
webView.clearFocus();
|
||||
webView.setFocusableInTouchMode(false);
|
||||
addView(webView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
lastDrawTime = System.currentTimeMillis();
|
||||
if (webViewSurface == null && canvas != null) {
|
||||
super.draw(canvas);
|
||||
return;
|
||||
} else if(webViewSurface == null) {
|
||||
return;
|
||||
}
|
||||
//returns canvas attached to gl texture to draw on
|
||||
Canvas glAttachedCanvas;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
glAttachedCanvas = webViewSurface.lockHardwareCanvas();
|
||||
} else {
|
||||
glAttachedCanvas = webViewSurface.lockCanvas(null);
|
||||
}
|
||||
if (glAttachedCanvas != null) {
|
||||
glAttachedCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
|
||||
glAttachedCanvas.scale(1.0F, 1.0F);
|
||||
glAttachedCanvas.translate(-getScrollX(), -getScrollY());
|
||||
//draw the view to provided canvas
|
||||
super.draw(glAttachedCanvas);
|
||||
}
|
||||
webViewSurface.unlockCanvasAndPost(glAttachedCanvas);
|
||||
if (videoTexture != null) {
|
||||
videoTexture.onFrameAvailable(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void onDrawCheck() {
|
||||
if (webViewSurface != null && System.currentTimeMillis() - lastDrawTime > 16) {
|
||||
post(() -> draw(null));
|
||||
}
|
||||
}
|
||||
|
||||
CocosXRVideoTexture getVideoTexture() {
|
||||
return videoTexture;
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "destroy." + hashCode());
|
||||
if(webView != null) {
|
||||
webView.removeAllViews();
|
||||
webView.destroy();
|
||||
webView = null;
|
||||
}
|
||||
|
||||
if (webViewSurface != null) {
|
||||
webViewSurface.release();
|
||||
webViewSurface = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextureInfo(int textureWidth, int textureHeight, int videoTextureId) {
|
||||
this.videoTextureId = videoTextureId;
|
||||
this.videoTextureWidth = textureWidth;
|
||||
this.videoTextureHeight = textureHeight;
|
||||
}
|
||||
|
||||
public int getTargetTextureId() {
|
||||
return videoTextureId;
|
||||
}
|
||||
|
||||
public int getVideoTextureWidth() {
|
||||
return videoTextureWidth;
|
||||
}
|
||||
|
||||
public int getVideoTextureHeight() {
|
||||
return videoTextureHeight;
|
||||
}
|
||||
|
||||
public void onGLReady() {
|
||||
videoTexture.createSurfaceTexture();
|
||||
videoTexture.getSurfaceTexture().setDefaultBufferSize(this.videoTextureWidth, this.videoTextureHeight);
|
||||
quadScreen.initShader();
|
||||
webViewSurface = new Surface(videoTexture.getSurfaceTexture());
|
||||
isGLInitialized = true;
|
||||
|
||||
int[] tmpFboId = new int[1];
|
||||
GLES30.glGenFramebuffers(1, tmpFboId, 0);
|
||||
renderTargetFboId = tmpFboId[0];
|
||||
CocosXRGLHelper.checkGLError("fbo");
|
||||
Log.d(TAG, "onGLReady." + this.hashCode() + ",oes." + videoTexture.getOESTextureId() + ",fbo." + tmpFboId[0] +
|
||||
"," + this.videoTextureWidth + "x" + this.videoTextureHeight);
|
||||
}
|
||||
|
||||
public void onBeforeGLDrawFrame() {
|
||||
if (videoTextureId == 0 || videoTextureWidth == 0 || videoTextureHeight == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isGLInitialized) {
|
||||
onGLReady();
|
||||
}
|
||||
}
|
||||
|
||||
public void onGLDrawFrame() {
|
||||
if (videoTextureId == 0 || videoTextureWidth == 0 || videoTextureHeight == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, renderTargetFboId);
|
||||
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, videoTextureId, 0);
|
||||
GLES30.glViewport(0, 0, videoTextureWidth, videoTextureHeight);
|
||||
GLES30.glScissor(0, 0, videoTextureWidth, videoTextureHeight);
|
||||
onDrawCheck();
|
||||
videoTexture.updateTexture();
|
||||
quadScreen.draw(videoTexture.getOESTextureId(), videoTexture.getVideoMatrix());
|
||||
GLES30.glFlush();
|
||||
}
|
||||
|
||||
public void onGLDestroy() {
|
||||
Log.d(TAG, "onGLDestroy." + this.hashCode());
|
||||
if(renderTargetFboId > 0) {
|
||||
GLES30.glDeleteFramebuffers(1, new int[]{renderTargetFboId}, 0);
|
||||
renderTargetFboId = 0;
|
||||
}
|
||||
|
||||
if (videoTexture != null) {
|
||||
videoTexture.release();
|
||||
videoTexture = null;
|
||||
}
|
||||
|
||||
if (quadScreen != null) {
|
||||
quadScreen.release();
|
||||
quadScreen = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void simulateTouchDown(float ux, float uy) {
|
||||
isKeyDown = true;
|
||||
keyDownTime = SystemClock.uptimeMillis();
|
||||
float touchX = ux * getWidth();
|
||||
float touchY = getHeight() - uy * getHeight();
|
||||
post(() -> {
|
||||
MotionEvent motionEvent = MotionEvent.obtain(keyDownTime, SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_DOWN, touchX, touchY, 0);
|
||||
motionEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
|
||||
dispatchTouchEvent(motionEvent);
|
||||
});
|
||||
}
|
||||
|
||||
public void simulateTouchMove(float ux, float uy) {
|
||||
if (isKeyDown) {
|
||||
post(() -> {
|
||||
float touchX = ux * getWidth();
|
||||
float touchY = getHeight() - uy * getHeight();
|
||||
MotionEvent motionEvent = MotionEvent.obtain(keyDownTime, SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_MOVE, touchX, touchY, 0);
|
||||
motionEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
|
||||
dispatchTouchEvent(motionEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void simulateTouchUp(float ux, float uy) {
|
||||
isKeyDown = false;
|
||||
float touchX = ux * getWidth();
|
||||
float touchY = getHeight() - uy * getHeight();
|
||||
post(() -> {
|
||||
MotionEvent motionEvent = MotionEvent.obtain(keyDownTime, SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_UP, touchX, touchY, 0);
|
||||
motionEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
|
||||
dispatchTouchEvent(motionEvent);
|
||||
});
|
||||
}
|
||||
|
||||
public void loadUrl(String url) {
|
||||
if(webView != null) {
|
||||
HashMap<String, String> hashMap = new HashMap<>();
|
||||
hashMap.put("Referer", "https://www.cocos.com/");
|
||||
webView.loadUrl(url, hashMap);
|
||||
}
|
||||
}
|
||||
|
||||
public WebSettings getSettings() {
|
||||
return webView.getSettings();
|
||||
}
|
||||
|
||||
public void goForward() {
|
||||
webView.goForward();
|
||||
}
|
||||
|
||||
public void goBack() {
|
||||
webView.goBack();
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
webView.reload();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib.xr;
|
||||
|
||||
import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.EGLConfig;
|
||||
import android.opengl.EGLContext;
|
||||
import android.opengl.EGLDisplay;
|
||||
import android.opengl.EGLSurface;
|
||||
import android.opengl.GLES30;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.cocos.lib.JsbBridgeWrapper;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class CocosXRWebViewManager {
|
||||
private static final String TAG = "CocosXRWebViewManager";
|
||||
public static final String XR_WEBVIEW_EVENT_NAME = "xr-webview-";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_TO_ADD = "to-add";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_TO_REMOVE = "to-remove";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_ADDED = "added";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_REMOVED = "removed";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_TEXTUREINFO = "textureinfo";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_HOVER = "hover";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_CLICK_DOWN = "click-down";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_CLICK_UP = "click-up";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_GOFORWARD = "go-forward";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_GOBACK = "go-back";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_LOADURL = "load-url";
|
||||
public static final String XR_WEBVIEW_EVENT_TAG_RELOAD = "reload";
|
||||
final int MAX_COUNT = 3;
|
||||
|
||||
ConcurrentHashMap<String, CocosXRWebViewContainer> xrWebViewHashMap = new ConcurrentHashMap<>();
|
||||
private WeakReference<Activity> activityWeakReference;
|
||||
CocosXRWebViewGLThread webViewGLThread;
|
||||
|
||||
private final boolean isMobileUA = false;
|
||||
|
||||
public void createWebView(int webviewId, int textureWidth, int textureHeight, String url) {
|
||||
Log.d(TAG, "createWebView:" + webviewId + "," + url);
|
||||
if (activityWeakReference.get() != null) {
|
||||
activityWeakReference.get().runOnUiThread(() -> {
|
||||
CocosXRWebViewContainer xrWebViewContainer = new CocosXRWebViewContainer(activityWeakReference.get());
|
||||
View decorView = activityWeakReference.get().getWindow().getDecorView();
|
||||
if(decorView instanceof FrameLayout) {
|
||||
FrameLayout parentLayout = (FrameLayout) decorView;
|
||||
FrameLayout.LayoutParams frameLayoutParams = new FrameLayout.LayoutParams(textureWidth, textureHeight);
|
||||
parentLayout.addView(xrWebViewContainer, frameLayoutParams);
|
||||
xrWebViewContainer.setZ(-100 + webviewId);
|
||||
}
|
||||
if (url != null) {
|
||||
if (isMobileUA) {
|
||||
xrWebViewContainer.getSettings().setUserAgentString("Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.82 Mobile Safari/537.36");
|
||||
} else {
|
||||
xrWebViewContainer.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36");
|
||||
}
|
||||
xrWebViewContainer.loadUrl(url);
|
||||
}
|
||||
|
||||
xrWebViewHashMap.put(String.valueOf(webviewId), xrWebViewContainer);
|
||||
// notify
|
||||
JsbBridgeWrapper.getInstance().dispatchEventToScript(XR_WEBVIEW_EVENT_NAME.concat(String.valueOf(webviewId)), XR_WEBVIEW_EVENT_TAG_ADDED);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void removeWebView(int webviewId) {
|
||||
if (!xrWebViewHashMap.containsKey(String.valueOf(webviewId))) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "removeWebView:" + webviewId);
|
||||
if (activityWeakReference.get() != null) {
|
||||
activityWeakReference.get().runOnUiThread(() -> {
|
||||
CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId));
|
||||
if (xrWebViewContainer != null) {
|
||||
View decorView = activityWeakReference.get().getWindow().getDecorView();
|
||||
if(decorView instanceof FrameLayout) {
|
||||
FrameLayout parentLayout = (FrameLayout) decorView;
|
||||
parentLayout.removeView(xrWebViewContainer);
|
||||
}
|
||||
|
||||
xrWebViewContainer.onDestroy();
|
||||
webViewGLThread.queueEvent(() -> {
|
||||
xrWebViewContainer.onGLDestroy();
|
||||
xrWebViewHashMap.remove(String.valueOf(webviewId));
|
||||
});
|
||||
// notify
|
||||
JsbBridgeWrapper.getInstance().dispatchEventToScript(XR_WEBVIEW_EVENT_TAG_REMOVED.concat(String.valueOf(webviewId)));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void goForward(int webviewId) {
|
||||
if (activityWeakReference.get() != null) {
|
||||
activityWeakReference.get().runOnUiThread(() -> {
|
||||
CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId));
|
||||
if (xrWebViewContainer != null) {
|
||||
xrWebViewContainer.goForward();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void goBack(int webviewId) {
|
||||
if (activityWeakReference.get() != null) {
|
||||
activityWeakReference.get().runOnUiThread(() -> {
|
||||
CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId));
|
||||
if (xrWebViewContainer != null) {
|
||||
xrWebViewContainer.goBack();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void reload(int webviewId) {
|
||||
if (activityWeakReference.get() != null) {
|
||||
activityWeakReference.get().runOnUiThread(() -> {
|
||||
CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId));
|
||||
if (xrWebViewContainer != null) {
|
||||
xrWebViewContainer.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void loadUrl(int webviewId, String url) {
|
||||
if (activityWeakReference.get() != null) {
|
||||
activityWeakReference.get().runOnUiThread(() -> {
|
||||
CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId));
|
||||
if (xrWebViewContainer != null) {
|
||||
if (isMobileUA) {
|
||||
xrWebViewContainer.getSettings().setUserAgentString("Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.82 Mobile Safari/537.36");
|
||||
} else {
|
||||
xrWebViewContainer.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36");
|
||||
}
|
||||
xrWebViewContainer.loadUrl(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextureInfo(int webViewId, int textureId, int textureWidth, int textureHeight) {
|
||||
Log.d(TAG, "setTextureInfo:" + webViewId + "," + textureWidth + "x" + textureHeight + ":" + textureId);
|
||||
CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webViewId));
|
||||
if (xrWebViewContainer != null) {
|
||||
xrWebViewContainer.setTextureInfo(textureWidth, textureHeight, textureId);
|
||||
}
|
||||
|
||||
if (webViewGLThread == null) {
|
||||
webViewGLThread = new CocosXRWebViewGLThread();
|
||||
webViewGLThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void onCreate(Activity activity) {
|
||||
Log.d(TAG, "onCreate");
|
||||
activityWeakReference = new WeakReference<>(activity);
|
||||
for (int i = 0; i < MAX_COUNT; i++) {
|
||||
String eventName = XR_WEBVIEW_EVENT_NAME.concat(String.valueOf(i));
|
||||
int webviewId = i;
|
||||
JsbBridgeWrapper.getInstance().addScriptEventListener(eventName, arg -> {
|
||||
if (arg == null) {
|
||||
Log.e(TAG, "Invalid arg is null !!!");
|
||||
return;
|
||||
}
|
||||
String[] dataArray = arg.split("&");
|
||||
if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_TEXTUREINFO)) {
|
||||
// id&w&h
|
||||
setTextureInfo(webviewId, Integer.parseInt(dataArray[1]), Integer.parseInt(dataArray[2]), Integer.parseInt(dataArray[3]));
|
||||
} else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_TO_ADD)) {
|
||||
int textureWidth = Integer.parseInt(dataArray[1]);
|
||||
int textureHeight = Integer.parseInt(dataArray[2]);
|
||||
String url = dataArray.length > 3 ? dataArray[3] : null;
|
||||
createWebView(webviewId, textureWidth, textureHeight, url);
|
||||
} else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_TO_REMOVE)) {
|
||||
removeWebView(webviewId);
|
||||
} else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_HOVER)) {
|
||||
CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId));
|
||||
if (xrWebViewContainer != null) {
|
||||
xrWebViewContainer.simulateTouchMove(Float.parseFloat(dataArray[1]), Float.parseFloat(dataArray[2]));
|
||||
}
|
||||
} else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_CLICK_DOWN)) {
|
||||
CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId));
|
||||
if (xrWebViewContainer != null) {
|
||||
xrWebViewContainer.simulateTouchDown(Float.parseFloat(dataArray[1]), Float.parseFloat(dataArray[2]));
|
||||
}
|
||||
} else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_CLICK_UP)) {
|
||||
CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId));
|
||||
if (xrWebViewContainer != null) {
|
||||
xrWebViewContainer.simulateTouchUp(Float.parseFloat(dataArray[1]), Float.parseFloat(dataArray[2]));
|
||||
}
|
||||
} else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_LOADURL)) {
|
||||
loadUrl(webviewId, dataArray[1]);
|
||||
} else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_RELOAD)) {
|
||||
reload(webviewId);
|
||||
} else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_GOBACK)) {
|
||||
goBack(webviewId);
|
||||
} else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_GOFORWARD)) {
|
||||
goForward(webviewId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
Log.d(TAG, "onResume");
|
||||
if (webViewGLThread != null) {
|
||||
webViewGLThread.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
Log.d(TAG, "onPause");
|
||||
if (webViewGLThread != null) {
|
||||
webViewGLThread.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "onDestroy");
|
||||
if (webViewGLThread != null) {
|
||||
webViewGLThread.onDestroy();
|
||||
webViewGLThread = null;
|
||||
}
|
||||
for (int i = 0; i < MAX_COUNT; i++) {
|
||||
JsbBridgeWrapper.getInstance().removeAllListenersForEvent(XR_WEBVIEW_EVENT_NAME.concat(String.valueOf(i)));
|
||||
}
|
||||
Set<Map.Entry<String, CocosXRWebViewContainer>> entrySets = xrWebViewHashMap.entrySet();
|
||||
for (Map.Entry<String, CocosXRWebViewContainer> entrySet : entrySets) {
|
||||
CocosXRWebViewContainer xrWebViewContainer = entrySet.getValue();
|
||||
if (activityWeakReference.get() != null) {
|
||||
View decorView = activityWeakReference.get().getWindow().getDecorView();
|
||||
if(decorView instanceof FrameLayout) {
|
||||
FrameLayout parentLayout = (FrameLayout) decorView;
|
||||
parentLayout.removeView(xrWebViewContainer);
|
||||
}
|
||||
}
|
||||
xrWebViewContainer.onDestroy();
|
||||
}
|
||||
xrWebViewHashMap.clear();
|
||||
}
|
||||
|
||||
class CocosXRWebViewGLThread extends Thread {
|
||||
private final ReentrantLock lockObj = new ReentrantLock(true);
|
||||
private final Condition pauseCondition = lockObj.newCondition();
|
||||
private boolean running = false;
|
||||
private boolean requestPaused = false;
|
||||
private boolean requestExited = false;
|
||||
long lastTickTime = System.nanoTime();
|
||||
private final ArrayList<Runnable> mEventQueue = new ArrayList<>();
|
||||
|
||||
EGLContext eglContext;
|
||||
EGLDisplay eglDisplay;
|
||||
EGLSurface pBufferSurface;
|
||||
EGLContext parentContext;
|
||||
|
||||
CocosXRWebViewGLThread() {
|
||||
parentContext = EGL14.eglGetCurrentContext();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
init();
|
||||
while (true) {
|
||||
lockObj.lock();
|
||||
try {
|
||||
if (requestExited) {
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (requestPaused) {
|
||||
running = false;
|
||||
pauseCondition.await();
|
||||
running = true;
|
||||
}
|
||||
|
||||
if (requestExited) {
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
lockObj.unlock();
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (!mEventQueue.isEmpty()) {
|
||||
Runnable event = mEventQueue.remove(0);
|
||||
if (event != null) {
|
||||
event.run();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
void init() {
|
||||
running = true;
|
||||
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE};
|
||||
int[] attrList = new int[]{EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
|
||||
EGL14.EGL_RENDERABLE_TYPE, 0x00000040,
|
||||
EGL14.EGL_RED_SIZE, 8,
|
||||
EGL14.EGL_GREEN_SIZE, 8,
|
||||
EGL14.EGL_BLUE_SIZE, 8,
|
||||
EGL14.EGL_DEPTH_SIZE, 0,
|
||||
EGL14.EGL_SAMPLE_BUFFERS, 1,
|
||||
EGL14.EGL_SAMPLES, 1,
|
||||
EGL14.EGL_STENCIL_SIZE, 0,
|
||||
EGL14.EGL_NONE};
|
||||
EGLConfig[] configOut = new EGLConfig[1];
|
||||
int[] configNumOut = new int[1];
|
||||
EGL14.eglChooseConfig(eglDisplay, attrList, 0, configOut, 0, 1,
|
||||
configNumOut, 0);
|
||||
eglContext = EGL14.eglCreateContext(eglDisplay, configOut[0], parentContext, attrib_list, 0);
|
||||
|
||||
int[] sur_attrib_list = {EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE};
|
||||
pBufferSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configOut[0], sur_attrib_list, 0);
|
||||
|
||||
EGL14.eglMakeCurrent(eglDisplay, pBufferSurface, pBufferSurface, eglContext);
|
||||
GLES30.glDisable(GLES30.GL_DEPTH_TEST);
|
||||
GLES30.glDisable(GLES30.GL_BLEND);
|
||||
GLES30.glDisable(GLES30.GL_CULL_FACE);
|
||||
|
||||
lastTickTime = System.nanoTime();
|
||||
Log.d(TAG, "CocosXRWebViewGLThread init");
|
||||
}
|
||||
|
||||
void tick() {
|
||||
// draw
|
||||
lastTickTime = System.nanoTime();
|
||||
|
||||
Set<Map.Entry<String, CocosXRWebViewContainer>> entrySets = xrWebViewHashMap.entrySet();
|
||||
for (Map.Entry<String, CocosXRWebViewContainer> entrySet : entrySets) {
|
||||
entrySet.getValue().onBeforeGLDrawFrame();
|
||||
if(entrySet.getValue().getVideoTextureWidth() == 0 || entrySet.getValue().getVideoTextureHeight() == 0) {
|
||||
continue;
|
||||
}
|
||||
entrySet.getValue().onGLDrawFrame();
|
||||
}
|
||||
|
||||
EGL14.eglSwapBuffers(eglDisplay, pBufferSurface);
|
||||
|
||||
if (System.nanoTime() - lastTickTime < 16666666) {
|
||||
// lock 60fps
|
||||
try {
|
||||
long sleepTimeNS = 16666666 - (System.nanoTime() - lastTickTime);
|
||||
Thread.sleep(sleepTimeNS / 1000000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void exit() {
|
||||
running = false;
|
||||
Set<Map.Entry<String, CocosXRWebViewContainer>> entrySets = xrWebViewHashMap.entrySet();
|
||||
for (Map.Entry<String, CocosXRWebViewContainer> entrySet : entrySets) {
|
||||
entrySet.getValue().onGLDestroy();
|
||||
}
|
||||
EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
|
||||
EGL14.eglDestroySurface(eglDisplay, pBufferSurface);
|
||||
EGL14.eglDestroyContext(eglDisplay, eglContext);
|
||||
Log.d(TAG, "CocosXRWebViewGLThread exit");
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
lockObj.lock();
|
||||
requestPaused = true;
|
||||
lockObj.unlock();
|
||||
Log.d(TAG, "CocosXRWebViewGLThread onPause");
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
lockObj.lock();
|
||||
requestPaused = false;
|
||||
pauseCondition.signalAll();
|
||||
lockObj.unlock();
|
||||
Log.d(TAG, "CocosXRWebViewGLThread onResume");
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
lockObj.lock();
|
||||
requestExited = true;
|
||||
pauseCondition.signalAll();
|
||||
lockObj.unlock();
|
||||
|
||||
try {
|
||||
join();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, e.getLocalizedMessage());
|
||||
}
|
||||
Log.d(TAG, "CocosXRWebViewGLThread onDestroy");
|
||||
}
|
||||
|
||||
public void queueEvent(Runnable r) {
|
||||
synchronized (this) {
|
||||
mEventQueue.add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib.xr.permission;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class CocosXRPermissionFragment extends Fragment {
|
||||
private static final int PERMISSION_REQUEST_CODE = 1101;
|
||||
private static final String TAG = "CocosPermissionFragment";
|
||||
private static final String PERMISSION_TAG = "TAG_PermissionFragment";
|
||||
|
||||
public static CocosXRPermissionFragment getInstance(Activity activity) {
|
||||
FragmentManager fm = activity.getFragmentManager();
|
||||
CocosXRPermissionFragment fragment = (CocosXRPermissionFragment) fm.findFragmentByTag(PERMISSION_TAG);
|
||||
if (fragment == null) {
|
||||
try {
|
||||
Log.d(TAG, "Creating CocosXRPermissionFragment");
|
||||
fragment = new CocosXRPermissionFragment();
|
||||
FragmentTransaction trans = fm.beginTransaction();
|
||||
trans.add(fragment, PERMISSION_TAG);
|
||||
trans.commit();
|
||||
fm.executePendingTransactions();
|
||||
} catch (Throwable th) {
|
||||
Log.e(TAG, "Cannot launch PermissionFragment:" + th.getMessage(), th);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
Log.d(TAG, "onCreate");
|
||||
}
|
||||
|
||||
public void acquirePermissions(String[] permissions) {
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(() -> {
|
||||
final int[] grantResults = new int[permissions.length];
|
||||
Activity context = getActivity();
|
||||
if (context != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
String packageName = context.getPackageName();
|
||||
for (String permission : permissions) {
|
||||
if (PackageManager.PERMISSION_DENIED == packageManager.checkPermission(permission, packageName)) {
|
||||
CocosXRPermissionFragment.this.requestPermissions(permissions, PERMISSION_REQUEST_CODE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED);
|
||||
onRequestPermissionsResult(PERMISSION_REQUEST_CODE, permissions, grantResults);
|
||||
} else {
|
||||
Log.e(TAG, "acquirePermissions failed !");
|
||||
Arrays.fill(grantResults, PackageManager.PERMISSION_DENIED);
|
||||
onRequestPermissionsResult(PERMISSION_REQUEST_CODE, permissions, grantResults);
|
||||
}
|
||||
} else {
|
||||
Arrays.fill(grantResults, PackageManager.PERMISSION_DENIED);
|
||||
onRequestPermissionsResult(PERMISSION_REQUEST_CODE, permissions, grantResults);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (requestCode == PERMISSION_REQUEST_CODE && permissions.length > 0) {
|
||||
CocosXRPermissionHelper.onAcquirePermissions(permissions, grantResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd.
|
||||
*
|
||||
* http://www.cocos.com
|
||||
*
|
||||
* 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 com.cocos.lib.xr.permission;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import com.cocos.lib.CocosHelper;
|
||||
import com.cocos.lib.JsbBridgeWrapper;
|
||||
import com.cocos.lib.xr.CocosXRApi;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class CocosXRPermissionHelper {
|
||||
private static final String LOG_TAG = "CocosXRPermissionHelper";
|
||||
public static final String XR_PERMISSION_EVENT_NAME = "xr-permission";
|
||||
public static final String XR_PERMISSION_TAG_CHECK = "check";
|
||||
public static final String XR_PERMISSION_TAG_REQUEST = "request";
|
||||
public interface PermissionCallback {
|
||||
void onRequestPermissionsResult(String[] permissions, int[] grantResults);
|
||||
}
|
||||
|
||||
private static PermissionCallback permissionCallback = null;
|
||||
public static void onScriptEvent(String arg) {
|
||||
String[] array = arg.split(":");
|
||||
if (TextUtils.equals(array[0], XR_PERMISSION_TAG_CHECK)) {
|
||||
int result = checkPermission(array[1]) ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
|
||||
CocosHelper.runOnGameThread(() -> JsbBridgeWrapper.getInstance().dispatchEventToScript(XR_PERMISSION_EVENT_NAME, XR_PERMISSION_TAG_CHECK + ":" + array[1] + ":" + result));
|
||||
} else if (TextUtils.equals(array[0], XR_PERMISSION_TAG_REQUEST)) {
|
||||
String[] permissionNames = array[1].split("&");
|
||||
acquirePermissions(permissionNames, (PermissionCallback) null);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean checkPermission(String permission) {
|
||||
Context context = CocosXRApi.getInstance().getContext();
|
||||
if (context == null)
|
||||
return false;
|
||||
if (context.checkPermission(permission, android.os.Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(LOG_TAG, "checkPermission: " + permission + " has granted");
|
||||
return true;
|
||||
} else {
|
||||
Log.d(LOG_TAG, "checkPermission: " + permission + " has not granted");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void acquirePermissions(String[] permissions, PermissionCallback callback) {
|
||||
permissionCallback = callback;
|
||||
CocosXRPermissionHelper.acquirePermissions(permissions, CocosXRApi.getInstance().getActivity());
|
||||
}
|
||||
|
||||
public static void acquirePermissions(String[] permissions, Activity InActivity) {
|
||||
if (InActivity == null)
|
||||
return;
|
||||
final Activity activity = InActivity;
|
||||
activity.runOnUiThread(() -> {
|
||||
CocosXRPermissionFragment fragment = CocosXRPermissionFragment.getInstance(activity);
|
||||
if (fragment != null) {
|
||||
fragment.acquirePermissions(permissions);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void onAcquirePermissions(String[] permissions, int[] grantResults) {
|
||||
Log.d(LOG_TAG, "onAcquirePermissions:" + Arrays.toString(permissions) + "|" + Arrays.toString(grantResults));
|
||||
//
|
||||
CocosHelper.runOnGameThread(() -> {
|
||||
StringBuilder stringBuilder = new StringBuilder(XR_PERMISSION_TAG_REQUEST);
|
||||
stringBuilder.append(":");
|
||||
for (int i = 0; i < permissions.length; i++) {
|
||||
stringBuilder.append(permissions[i]).append("#").append(grantResults[i]);
|
||||
if (i != permissions.length - 1) {
|
||||
stringBuilder.append("&");
|
||||
}
|
||||
}
|
||||
JsbBridgeWrapper.getInstance().dispatchEventToScript(XR_PERMISSION_EVENT_NAME, stringBuilder.toString());
|
||||
});
|
||||
if (permissionCallback != null) {
|
||||
permissionCallback.onRequestPermissionsResult(permissions, grantResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
cocos/platform/android/modules/Screen.cpp
Normal file
46
cocos/platform/android/modules/Screen.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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 "platform/android/modules/Screen.h"
|
||||
#include <android/sensor.h>
|
||||
#include <android/window.h>
|
||||
#include <android_native_app_glue.h>
|
||||
#include "platform/java/jni/JniHelper.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
int Screen::getDPI() const {
|
||||
static int dpi = -1;
|
||||
if (dpi == -1) {
|
||||
AConfiguration *config = AConfiguration_new();
|
||||
//AConfiguration_fromAssetManager(config, cocosApp.assetManager);
|
||||
int32_t density = AConfiguration_getDensity(config);
|
||||
AConfiguration_delete(config);
|
||||
const int stdDpi = 160;
|
||||
dpi = density * stdDpi;
|
||||
}
|
||||
return dpi;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
36
cocos/platform/android/modules/Screen.h
Normal file
36
cocos/platform/android/modules/Screen.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/java/modules/CommonScreen.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class CC_DLL Screen : public CommonScreen {
|
||||
public:
|
||||
int getDPI() const override;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
36
cocos/platform/android/modules/System.cpp
Normal file
36
cocos/platform/android/modules/System.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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 "platform/android/modules/System.h"
|
||||
|
||||
namespace cc {
|
||||
System::System() = default;
|
||||
|
||||
System::~System() = default;
|
||||
|
||||
System::OSType System::getOSType() const {
|
||||
return OSType::ANDROIDOS;
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
41
cocos/platform/android/modules/System.h
Normal file
41
cocos/platform/android/modules/System.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/java/modules/CommonSystem.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class CC_DLL System : public CommonSystem {
|
||||
public:
|
||||
System();
|
||||
~System() override;
|
||||
|
||||
OSType getOSType() const override;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
Reference in New Issue
Block a user