no message

This commit is contained in:
gem
2025-02-18 15:21:31 +08:00
commit 2d133e56d7
1980 changed files with 465595 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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>

View 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>

View 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>

View 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.

Binary file not shown.

Binary file not shown.

View 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:

View 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

View File

@@ -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;
}
}

View 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();
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View 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;
}
}
}

View 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");
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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);
}
}

View 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);
}
}
});
}
}

View File

@@ -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();
}
}
}

View 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);
}
}

View File

@@ -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);
}
}
}

View 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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
};
}
}

View File

@@ -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);
}

View File

@@ -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));
}
}

View 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);
}
}

View 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();
}
}

View 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>

View 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')
}

View 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(...);
}

View File

@@ -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>

View File

@@ -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>

View 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>

View 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')
}

View 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

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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());
}
}
});
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View 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

View 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

View 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

View 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