Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
60bd02b5a4 | ||
![]() |
d73965730c | ||
![]() |
04a804cf79 | ||
![]() |
640311163a | ||
![]() |
b22d7565c5 | ||
![]() |
c87aea801d | ||
![]() |
9157ce656c | ||
![]() |
9337b84631 | ||
![]() |
f64790f41d | ||
![]() |
658bcfde60 | ||
![]() |
4b20687b1f | ||
![]() |
cdb2eb1099 | ||
![]() |
ff00255ba4 | ||
![]() |
a4b613c299 | ||
![]() |
cdde34a2b1 | ||
![]() |
bcbae0bcef | ||
![]() |
c2709473ef | ||
![]() |
26e7b94018 | ||
![]() |
86bfe35526 | ||
![]() |
2c4c4bd72e | ||
![]() |
eb6dd4d981 | ||
![]() |
d7dd12c0d9 | ||
![]() |
39f63347c4 | ||
![]() |
9448e2ad4e | ||
![]() |
f75c7d97ee | ||
![]() |
c7052b7557 | ||
![]() |
33070c5cb4 | ||
![]() |
e981fe05d9 | ||
![]() |
79cf1450c1 | ||
![]() |
f2a3b526f3 | ||
![]() |
f36a6e4c07 | ||
![]() |
2de4391966 | ||
![]() |
8d863c01aa | ||
![]() |
bf9e2d7597 | ||
![]() |
01c63eaf1d | ||
![]() |
eab25c9c3a | ||
![]() |
ca5c11d50c | ||
![]() |
8a2573cad2 | ||
![]() |
58e3d0f9b8 | ||
![]() |
4b3294c129 | ||
![]() |
04d21c8769 | ||
![]() |
f50a15b5ac | ||
![]() |
258d19f620 | ||
![]() |
576a6296d5 | ||
![]() |
035ef84a24 | ||
![]() |
442d2efa60 | ||
![]() |
c7a74b07d0 | ||
![]() |
f98a1076c4 | ||
![]() |
039b7da4f0 | ||
![]() |
dfb689522e | ||
![]() |
288de5f8b6 | ||
![]() |
770c98d3cc | ||
![]() |
38957d9407 | ||
![]() |
1cd508af00 | ||
![]() |
2fd2850fbd | ||
![]() |
2fc94d2a56 | ||
![]() |
30b0eebc3e | ||
![]() |
46c0231b23 | ||
![]() |
ae25b0659f | ||
![]() |
8e16a02246 | ||
![]() |
234939ecab | ||
![]() |
37bb87c0ad | ||
![]() |
6bc467970b | ||
![]() |
8a64df42ea | ||
![]() |
efc91a5fd2 | ||
![]() |
033772bf93 | ||
![]() |
451d28a142 | ||
![]() |
5448ac8ae4 | ||
![]() |
761457ff2f | ||
![]() |
ba15778dfc | ||
![]() |
285b3244c5 | ||
![]() |
d038305ab1 | ||
![]() |
d000740da2 | ||
![]() |
d8ee8041d4 | ||
![]() |
406ba18744 | ||
![]() |
7f8356da93 | ||
![]() |
4cf00205d3 | ||
![]() |
94f740b51e | ||
![]() |
85a5500224 | ||
![]() |
f79ec2a94e | ||
![]() |
55d58c86e5 |
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="1.8" />
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
17
.idea/deploymentTargetDropDown.xml
generated
Normal file
17
.idea/deploymentTargetDropDown.xml
generated
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<targetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="QUICK_BOOT_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="$USER_HOME$/.android/avd/Pixel_6_API_33.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2023-09-01T11:09:07.848547Z" />
|
||||
</component>
|
||||
</project>
|
8
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
8
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,org.cocos2dx.okhttp3.Call,execute" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DesignSurface">
|
||||
<option name="filePathToZoomLevelMap">
|
||||
@ -15,7 +14,7 @@
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="adopt-1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
16
.vscode/c_cpp_properties.json
vendored
16
.vscode/c_cpp_properties.json
vendored
@ -4,31 +4,31 @@
|
||||
"name": "Mac",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"/Users/zhl/Documents/workspace/cocos/cocos2d-x/**"
|
||||
"/Users/zhl/Documents/workspace/crypto/cocos_js/**"
|
||||
],
|
||||
"defines": [],
|
||||
"macFrameworkPath": [
|
||||
"/Library/Developer/CommandLineTools/SDKs/MacOSX11.sdk/System/Library/Frameworks"
|
||||
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks"
|
||||
],
|
||||
"compilerPath": "/usr/local/opt/llvm/bin/clang",
|
||||
"compilerPath": "/opt/homebrew/opt/llvm/bin/clang",
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "c++14",
|
||||
"intelliSenseMode": "macos-clang-x64"
|
||||
"intelliSenseMode": "macos-clang-arm64"
|
||||
},
|
||||
{
|
||||
"name": "wallet",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"/Users/zhl/Documents/workspace/cocos/cocos2d-x/**"
|
||||
"/Users/zhl/Documents/workspace/crypto/cocos_js/**"
|
||||
],
|
||||
"defines": [],
|
||||
"macFrameworkPath": [
|
||||
"/Library/Developer/CommandLineTools/SDKs/MacOSX11.sdk/System/Library/Frameworks"
|
||||
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks"
|
||||
],
|
||||
"compilerPath": "/usr/local/opt/llvm/bin/clang",
|
||||
"compilerPath": "/opt/homebrew/opt/llvm/bin/clang",
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "c++14",
|
||||
"intelliSenseMode": "macos-clang-x64"
|
||||
"intelliSenseMode": "macos-clang-arm64"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -87,5 +87,6 @@
|
||||
"vector": "cpp",
|
||||
"__bits": "cpp",
|
||||
"__verbose_abort": "cpp"
|
||||
}
|
||||
},
|
||||
"cmake.configureOnOpen": false
|
||||
}
|
38
.vscode/tasks.json
vendored
38
.vscode/tasks.json
vendored
@ -16,7 +16,43 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "CleanApp",
|
||||
"label": "Clean:App",
|
||||
"type": "shell",
|
||||
"command": "${workspaceRoot}/gradlew app:clean",
|
||||
"windows": {
|
||||
"command": "${workspaceRoot}\\gradlew.bat clean "
|
||||
},
|
||||
"group": "test",
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Clean:Unity",
|
||||
"type": "shell",
|
||||
"command": "${workspaceRoot}/gradlew unityLibrary:clean",
|
||||
"windows": {
|
||||
"command": "${workspaceRoot}\\gradlew.bat clean "
|
||||
},
|
||||
"group": "test",
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Clean:Cocos",
|
||||
"type": "shell",
|
||||
"command": "${workspaceRoot}/gradlew libcocos2dx:clean",
|
||||
"windows": {
|
||||
"command": "${workspaceRoot}\\gradlew.bat clean "
|
||||
},
|
||||
"group": "test",
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "CleanAll",
|
||||
"type": "shell",
|
||||
"command": "${workspaceRoot}/gradlew clean",
|
||||
"windows": {
|
||||
|
@ -54,7 +54,6 @@ bool AppDelegate::applicationDidFinishLaunching()
|
||||
// Enable debugger here
|
||||
// jsb_enable_debugger("0.0.0.0", 6086, false);
|
||||
#endif
|
||||
|
||||
se->setExceptionCallback([](const char *location, const char *message, const char *stack) {
|
||||
// Send exception information to server like Tencent Bugly.
|
||||
cocos2d::log("\nUncaught Exception:\n - location : %s\n - msg : %s\n - detail : \n %s\n", location, message, stack);
|
||||
@ -65,6 +64,7 @@ bool AppDelegate::applicationDidFinishLaunching()
|
||||
se->start();
|
||||
|
||||
se::AutoHandleScope hs;
|
||||
jsb_run_code("window.platform='game_android';");
|
||||
jsb_run_script("Data/js/jsb-adapter/jsb-builtin.js");
|
||||
jsb_run_script("Data/js/jcwallet.js");
|
||||
jsb_run_script("Data/js/platform.js");
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "WalletEvent.h"
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include "stdarg.h"
|
||||
#include <cstdarg>
|
||||
#include <string>
|
||||
#include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
|
||||
#include "cocos/scripting/js-bindings/manual/jsb_global.h"
|
||||
@ -12,21 +12,17 @@
|
||||
#include "base/CCScheduler.h"
|
||||
#include "scrypt/native-crypto.h"
|
||||
#include <jni.h>
|
||||
|
||||
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
|
||||
#include "AppDelegate.h"
|
||||
#endif
|
||||
|
||||
#include <jni/JniImp.h>
|
||||
|
||||
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
|
||||
|
||||
#ifndef COM_JC_JCFW_CLASS_NAME
|
||||
#define COM_JC_JCFW_CLASS_NAME com_jc_jcfw_JcSDK
|
||||
#endif
|
||||
#define JNI_JCFW(FUNC) JNI_METHOD1(COM_JC_JCFW_CLASS_NAME,FUNC)
|
||||
#define JNI_JCFW(FUNC) JNI_METHOD1(COM_JC_JCFW_CLASS_NAME, FUNC)
|
||||
|
||||
#define JNI_IMP_LOG_TAG "JcWallet"
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,JNI_IMP_LOG_TAG,__VA_ARGS__)
|
||||
#define JNI_IMP_LOG_TAG "JcWallet"
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, JNI_IMP_LOG_TAG, __VA_ARGS__)
|
||||
|
||||
cocos2d::Application *cocos_android_app_init(int width, int height);
|
||||
|
||||
@ -34,221 +30,278 @@ cocos2d::Application *cocos_android_app_init(int width, int height);
|
||||
using namespace cocos2d;
|
||||
NS_CC_BEGIN
|
||||
|
||||
cocos2d::Application *g_app = nullptr;
|
||||
JcWallet *JcWallet::_instance = nullptr;
|
||||
uv_loop_t *loop = nullptr;
|
||||
bool _isStarted = false;
|
||||
uv_async_t gasync = {0};
|
||||
cocos2d::Application *g_app = nullptr;
|
||||
JcWallet *JcWallet::_instance = nullptr;
|
||||
uv_loop_t *loop = nullptr;
|
||||
bool _isStarted = false;
|
||||
uv_async_t gasync = {nullptr};
|
||||
|
||||
struct AsyncTaskData {
|
||||
std::mutex mtx;
|
||||
std::list<std::function<void()> > tasks;
|
||||
};
|
||||
struct AsyncTaskData
|
||||
{
|
||||
std::mutex mtx;
|
||||
std::list<std::function<void()>> tasks;
|
||||
};
|
||||
|
||||
// run in server thread loop
|
||||
void flush_tasks_in_server_loop_cb(uv_async_t *asyn) {
|
||||
AsyncTaskData *data = (AsyncTaskData *) asyn->data;
|
||||
// run in server thread loop
|
||||
void flush_tasks_in_server_loop_cb(uv_async_t *asyn)
|
||||
{
|
||||
auto *data = (AsyncTaskData *)asyn->data;
|
||||
std::lock_guard<std::mutex> guard(data->mtx);
|
||||
while (!data->tasks.empty())
|
||||
{
|
||||
// fetch task, run task
|
||||
data->tasks.front()();
|
||||
// drop task
|
||||
data->tasks.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void init_libuv_async_handle(uv_async_t *async)
|
||||
{
|
||||
memset(async, 0, sizeof(uv_async_t));
|
||||
// uv_loop_t *loop = uv_default_loop();
|
||||
loop = (uv_loop_t *)malloc(sizeof(uv_loop_t));
|
||||
uv_loop_init(loop);
|
||||
uv_async_init(loop, async, cocos2d::flush_tasks_in_server_loop_cb);
|
||||
async->data = new AsyncTaskData();
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
}
|
||||
|
||||
// run in game thread, dispatch runnable object into server loop
|
||||
int schedule_task_into_server_thread_task_queue(uv_async_t *asyn, const std::function<void()>& func)
|
||||
{
|
||||
|
||||
auto *data = (AsyncTaskData *)asyn->data;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(data->mtx);
|
||||
while (!data->tasks.empty()) {
|
||||
// fetch task, run task
|
||||
data->tasks.front()();
|
||||
// drop task
|
||||
data->tasks.pop_front();
|
||||
}
|
||||
|
||||
data->tasks.emplace_back(func);
|
||||
}
|
||||
// notify server thread to invoke `flush_tasks_in_server_loop_cb()`
|
||||
return uv_async_send(asyn);
|
||||
}
|
||||
#define RUN_IN_GAMETHREAD(task) \
|
||||
do \
|
||||
{ \
|
||||
JcWallet::getInstance()->performFunctionInCocosThread([=]() { task; }); \
|
||||
} while (0)
|
||||
#define RUN_IN_SERVERTHREAD(task) \
|
||||
do \
|
||||
{ \
|
||||
schedule_task_into_server_thread_task_queue(&gasync, [=]() { task; }); \
|
||||
} while (0)
|
||||
|
||||
void init_libuv_async_handle(uv_async_t *async) {
|
||||
memset(async, 0, sizeof(uv_async_t));
|
||||
// uv_loop_t *loop = uv_default_loop();
|
||||
loop = (uv_loop_t*)malloc(sizeof(uv_loop_t));
|
||||
uv_loop_init(loop);
|
||||
uv_async_init(loop, async, cocos2d::flush_tasks_in_server_loop_cb);
|
||||
async->data = new AsyncTaskData();
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
bool runGlobalMethod(const char *name, const se::ValueArray& args, se::Value *value)
|
||||
{
|
||||
se::AutoHandleScope scope;
|
||||
bool ok = false;
|
||||
auto global = se::ScriptEngine::getInstance()->getGlobalObject();
|
||||
se::Value func;
|
||||
if (global->getProperty(name, &func) && func.isObject())
|
||||
{
|
||||
ok = func.toObject()->call(args, global, value);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// run in game thread, dispatch runnable object into server loop
|
||||
int schedule_task_into_server_thread_task_queue(uv_async_t *asyn, std::function<void()> func) {
|
||||
bool addToArgArray(se::ValueArray *args, const char *valChar)
|
||||
{
|
||||
std::string strVal(valChar);
|
||||
se::Value tmpVal;
|
||||
bool ok = true;
|
||||
ok &= std_string_to_seval(strVal, &tmpVal);
|
||||
args->push_back(tmpVal);
|
||||
return ok;
|
||||
}
|
||||
|
||||
AsyncTaskData *data = (AsyncTaskData *) asyn->data;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(data->mtx);
|
||||
data->tasks.emplace_back(func);
|
||||
}
|
||||
//notify server thread to invoke `flush_tasks_in_server_loop_cb()`
|
||||
return uv_async_send(asyn);
|
||||
}
|
||||
#define RUN_IN_GAMETHREAD(task) \
|
||||
do { \
|
||||
JcWallet::getInstance()->performFunctionInCocosThread([=]() { \
|
||||
task; \
|
||||
});\
|
||||
} while(0)
|
||||
#define RUN_IN_SERVERTHREAD(task) do { \
|
||||
schedule_task_into_server_thread_task_queue(&gasync, [=](){ \
|
||||
task; \
|
||||
}); \
|
||||
} while(0)
|
||||
JcWallet::JcWallet()
|
||||
{
|
||||
JcWallet::_instance = this;
|
||||
_functionsToPerform.reserve(30);
|
||||
std::unique_lock<std::mutex> lock(_performMutex);
|
||||
}
|
||||
|
||||
bool runGlobalMethod(const char *name, se::ValueArray args, se::Value *value) {
|
||||
se::AutoHandleScope scope;
|
||||
bool ok = false;
|
||||
auto global = se::ScriptEngine::getInstance()->getGlobalObject();
|
||||
se::Value func;
|
||||
if (global->getProperty(name, &func) && func.isObject()) {
|
||||
ok = func.toObject()->call(args, global, value);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool addToArgArray(se::ValueArray *args, const char *valChar) {
|
||||
std::string strVal(valChar);
|
||||
se::Value tmpVal;
|
||||
bool ok = true;
|
||||
ok &= std_string_to_seval(strVal, &tmpVal);
|
||||
args->push_back(tmpVal);
|
||||
return ok;
|
||||
}
|
||||
|
||||
JcWallet::JcWallet() {
|
||||
JcWallet::_instance = this;
|
||||
_functionsToPerform.reserve(30);
|
||||
std::unique_lock<std::mutex> lock(_performMutex);
|
||||
}
|
||||
|
||||
void JcWallet::initEnv() {
|
||||
if (!_isStarted) {
|
||||
void JcWallet::initEnv()
|
||||
{
|
||||
if (!_isStarted)
|
||||
{
|
||||
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
|
||||
g_app = new AppDelegate(1, 1);
|
||||
g_app = new AppDelegate(1, 1);
|
||||
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
|
||||
g_app = cocos_android_app_init(1, 1);
|
||||
g_app = cocos_android_app_init(1, 1);
|
||||
#endif
|
||||
EventDispatcher::init();
|
||||
g_app->start();
|
||||
_isStarted = true;
|
||||
WalletEvent::Emit("wallet_init_event", "{}");
|
||||
// RUN_IN_GAMETHREAD(JcWallet::getInstance()->jsToUnity("wallet_init_event", "{}"));
|
||||
cocos2d::init_libuv_async_handle(&gasync);
|
||||
EventDispatcher::init();
|
||||
g_app->start();
|
||||
_isStarted = true;
|
||||
WalletEvent::Emit("wallet_init_event", "{}");
|
||||
cocos2d::init_libuv_async_handle(&gasync);
|
||||
}
|
||||
}
|
||||
|
||||
void JcWallet::initJSThread(const std::shared_ptr<JcWallet>& wallet)
|
||||
{
|
||||
JcWallet::initEnv();
|
||||
}
|
||||
|
||||
void JcWallet::tick(float dt)
|
||||
{
|
||||
g_app->getScheduler()->update(dt);
|
||||
EventDispatcher::dispatchTickEvent(0);
|
||||
}
|
||||
|
||||
JcWallet::~JcWallet()
|
||||
{
|
||||
EventDispatcher::destroy();
|
||||
se::ScriptEngine::destroyInstance();
|
||||
uv_loop_close(loop);
|
||||
JcWallet::_instance = nullptr;
|
||||
}
|
||||
|
||||
char *JcWallet::runJsMethod(const std::shared_ptr<JSMethodParam>& data)
|
||||
{
|
||||
cocos2d::log("thread: %ld call method %s params: %lu", uv_thread_self(), data->methodName.c_str(), data->args.size());
|
||||
se::Value value;
|
||||
bool ok = cocos2d::runGlobalMethod(data->methodName.c_str(), data->args, &value);
|
||||
static std::string result;
|
||||
if (value.isString())
|
||||
{
|
||||
if (ok && !value.isNullOrUndefined())
|
||||
{
|
||||
result = value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
void JcWallet::initJSThread(std::shared_ptr<JcWallet> wallet) {
|
||||
wallet->initEnv();
|
||||
}
|
||||
|
||||
void JcWallet::tick(float dt) {
|
||||
g_app->getScheduler()->update(dt);
|
||||
EventDispatcher::dispatchTickEvent(0);
|
||||
}
|
||||
|
||||
JcWallet::~JcWallet() {
|
||||
EventDispatcher::destroy();
|
||||
se::ScriptEngine::destroyInstance();
|
||||
uv_loop_close(loop);
|
||||
JcWallet::_instance = nullptr;
|
||||
}
|
||||
|
||||
char *JcWallet::runJsMethod(std::shared_ptr<JSMethodParam> data) {
|
||||
cocos2d::log("thread: %ld call method %s params: %d", uv_thread_self(), data->methodName.c_str(), data->args.size());
|
||||
se::Value value;
|
||||
bool ok = cocos2d::runGlobalMethod(data->methodName.c_str(), data->args, &value);
|
||||
static std::string result;
|
||||
if (value.isString()) {
|
||||
if (ok && !value.isNullOrUndefined()) {
|
||||
result = value.toString();
|
||||
} else {
|
||||
result = "";
|
||||
}
|
||||
cocos2d::log("method: %s ::result: %s", data->methodName.c_str(), result.c_str());
|
||||
RUN_IN_GAMETHREAD(JcWallet::getInstance()->jsToUnity(data->funId, result));
|
||||
// WalletEvent::Emit(data->funId.c_str(), result.c_str());
|
||||
else
|
||||
{
|
||||
result = "";
|
||||
}
|
||||
return const_cast<char *>(result.c_str());
|
||||
cocos2d::log("method: %s ::result: %s", data->methodName.c_str(), result.c_str());
|
||||
RUN_IN_GAMETHREAD(JcWallet::jsToUnity(data->funId, result));
|
||||
// WalletEvent::Emit(data->funId.c_str(), result.c_str());
|
||||
}
|
||||
return const_cast<char *>(result.c_str());
|
||||
}
|
||||
|
||||
void JcWallet::performFunctionInCocosThread( const std::function<void()> &function) {
|
||||
_performMutex.lock();
|
||||
_functionsToPerform.push_back(function);
|
||||
_performMutex.unlock();
|
||||
}
|
||||
void JcWallet::performFunctionInCocosThread(const std::function<void()> &function)
|
||||
{
|
||||
_performMutex.lock();
|
||||
_functionsToPerform.push_back(function);
|
||||
_performMutex.unlock();
|
||||
}
|
||||
|
||||
void JcWallet::tickRun() {
|
||||
_performMutex.lock();
|
||||
if( !_functionsToPerform.empty() ) {
|
||||
// fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if new functions are added in callback, it will cause thread deadlock.
|
||||
auto temp = _functionsToPerform;
|
||||
for( const auto &function : temp ) {
|
||||
function();
|
||||
}
|
||||
_functionsToPerform.clear();
|
||||
void JcWallet::tickRun()
|
||||
{
|
||||
_performMutex.lock();
|
||||
if (!_functionsToPerform.empty())
|
||||
{
|
||||
// fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if new functions are added in callback, it will cause thread deadlock.
|
||||
auto temp = _functionsToPerform;
|
||||
for (const auto &function : temp)
|
||||
{
|
||||
function();
|
||||
}
|
||||
_performMutex.unlock();
|
||||
_functionsToPerform.clear();
|
||||
}
|
||||
_performMutex.unlock();
|
||||
}
|
||||
|
||||
void JcWallet::jsToUnity(std::string funId, std::string msg) {
|
||||
WalletEvent::Emit(funId.c_str(), msg.c_str());
|
||||
}
|
||||
void JcWallet::jsToUnity(const std::string& funId, const std::string& msg)
|
||||
{
|
||||
WalletEvent::Emit(funId.c_str(), msg.c_str());
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
void initEnv() {
|
||||
if (!_isStarted) {
|
||||
extern "C"
|
||||
{
|
||||
void initEnv()
|
||||
{
|
||||
if (!_isStarted)
|
||||
{
|
||||
new JcWallet();
|
||||
std::shared_ptr<JcWallet> wallet(JcWallet::getInstance());
|
||||
std::thread([=]() {
|
||||
JcWallet::initJSThread(wallet);
|
||||
}).detach();
|
||||
std::thread([=]()
|
||||
{ JcWallet::initJSThread(wallet); })
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
void tick(float dt) {
|
||||
if (_isStarted) {
|
||||
schedule_task_into_server_thread_task_queue(&gasync, [=](){
|
||||
JcWallet::getInstance()->tick(dt);
|
||||
});
|
||||
JcWallet::getInstance()->tickRun();
|
||||
}
|
||||
void tick(float dt)
|
||||
{
|
||||
if (_isStarted)
|
||||
{
|
||||
schedule_task_into_server_thread_task_queue(&gasync, [=]()
|
||||
{ JcWallet::getInstance()->tick(dt); });
|
||||
JcWallet::getInstance()->tickRun();
|
||||
}
|
||||
}
|
||||
|
||||
int runWalletMethod(const char *funId, const char *methodName, int paramCount, char **paramList) {
|
||||
JSMethodParam *data = new JSMethodParam();
|
||||
void tick2(float dt)
|
||||
{
|
||||
// if (_isStarted)
|
||||
// {
|
||||
// schedule_task_into_server_thread_task_queue(&gasync, [=]()
|
||||
// { JcWallet::getInstance()->tick(dt); });
|
||||
// JcWallet::getInstance()->tickRun();
|
||||
// }
|
||||
}
|
||||
|
||||
int runWalletMethod(const char *funId, const char *methodName, int paramCount, char **paramList)
|
||||
{
|
||||
auto *data = new JSMethodParam();
|
||||
std::string methodNameStr(methodName);
|
||||
data->methodName = methodNameStr;
|
||||
data->funId = funId;
|
||||
data->paramCount = paramCount;
|
||||
addToArgArray(&data->args, funId);
|
||||
for (int i = 0; i < paramCount; i++) {
|
||||
for (int i = 0; i < paramCount; i++)
|
||||
{
|
||||
char *arg = *(paramList + i);
|
||||
addToArgArray(&data->args, arg);
|
||||
}
|
||||
std::shared_ptr<JSMethodParam> params(data);
|
||||
int result = schedule_task_into_server_thread_task_queue(&gasync, [=](){
|
||||
JcWallet::getInstance()->runJsMethod(params);
|
||||
});
|
||||
int result = schedule_task_into_server_thread_task_queue(&gasync, [=]()
|
||||
{ JcWallet::runJsMethod(params); });
|
||||
return result == 0 ? 1 : 0;
|
||||
}
|
||||
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
|
||||
JNIEXPORT jint JNICALL JNI_JCFW(runJS)(JNIEnv *env, jclass clazz, jstring jfunId, jstring jmethodName, jstring jparams) {
|
||||
std::string funId = JniHelper::jstring2string(jfunId);
|
||||
std::string methodName = JniHelper::jstring2string(jmethodName);
|
||||
JNIEXPORT jint JNICALL JNI_JCFW(runJS)(JNIEnv *env, jclass clazz, jstring j_fun_id, jstring j_method_name, jobjectArray j_params)
|
||||
{
|
||||
if (!_isStarted) {
|
||||
return 0;
|
||||
}
|
||||
std::string funId = JniHelper::jstring2string(j_fun_id);
|
||||
std::string methodName = JniHelper::jstring2string(j_method_name);
|
||||
LOGD("jni call %s | %s", methodName.c_str(), funId.c_str());
|
||||
JSMethodParam *data = new JSMethodParam();
|
||||
auto *data = new JSMethodParam();
|
||||
data->methodName = methodName;
|
||||
data->funId = funId;
|
||||
data->paramCount = 1;
|
||||
jsize count = env->GetArrayLength(j_params);
|
||||
data->paramCount = count;
|
||||
addToArgArray(&data->args, funId.c_str());
|
||||
std::string sstr = JniHelper::jstring2string(jparams);
|
||||
addToArgArray(&data->args, sstr.c_str());
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
auto j_str = (jstring)env->GetObjectArrayElement(j_params, i);
|
||||
std::string s_str = JniHelper::jstring2string(j_str);
|
||||
addToArgArray(&data->args, s_str.c_str());
|
||||
}
|
||||
std::shared_ptr<JSMethodParam> params(data);
|
||||
int result = schedule_task_into_server_thread_task_queue(&gasync, [=](){
|
||||
JcWallet::getInstance()->runJsMethod(params);
|
||||
});
|
||||
int result = schedule_task_into_server_thread_task_queue(&gasync, [=]()
|
||||
{ JcWallet::runJsMethod(params); });
|
||||
return result == 0 ? 1 : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
JNIEXPORT jstring JNICALL JNI_JCFW(decryptPass)(JNIEnv *env, jclass clazz, jstring jaccount, jstring jpass)
|
||||
{
|
||||
std::string pass_encrypted = JniHelper::jstring2string(jpass);
|
||||
std::string account = JniHelper::jstring2string(jaccount);
|
||||
std::string keyStr = account + "0x741482aE1480E552735E44Ff3A733448AcBbeD8d";
|
||||
std::string passDecrypt = decrypt_aes(pass_encrypted, keyStr);
|
||||
return env->NewStringUTF(passDecrypt.c_str());
|
||||
}
|
||||
JNIEXPORT void JNICALL JNI_JCFW(tick)(JNIEnv *env, jclass clazz, jfloat dt)
|
||||
{
|
||||
tick2(dt);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
NS_CC_END
|
||||
static bool getOrCreatePlainObject_r(const char* name, se::Object* parent, se::Object** outObj)
|
||||
static bool getOrCreatePlainObject_r(const char *name, se::Object *parent, se::Object **outObj)
|
||||
{
|
||||
assert(parent != nullptr);
|
||||
assert(outObj != nullptr);
|
||||
@ -267,10 +320,12 @@ static bool getOrCreatePlainObject_r(const char* name, se::Object* parent, se::O
|
||||
|
||||
return true;
|
||||
}
|
||||
bool jsb_wallet_callback(se::State& s) {
|
||||
const auto& args = s.args();
|
||||
bool jsb_wallet_callback(se::State &s)
|
||||
{
|
||||
const auto &args = s.args();
|
||||
size_t argc = args.size();
|
||||
if (argc >= 2) {
|
||||
if (argc >= 2)
|
||||
{
|
||||
bool ok;
|
||||
std::string funId;
|
||||
ok = seval_to_std_string(args[0], &funId);
|
||||
@ -278,18 +333,23 @@ bool jsb_wallet_callback(se::State& s) {
|
||||
std::string msg;
|
||||
ok = seval_to_std_string(args[1], &msg);
|
||||
SE_PRECONDITION2(ok, false, "Error processing arguments");
|
||||
// WalletEvent::Emit(funId.c_str(), msg.c_str());
|
||||
RUN_IN_GAMETHREAD(JcWallet::getInstance()->jsToUnity(funId, msg));
|
||||
if (funId.find("webpage_") == 0)
|
||||
{
|
||||
onProxyCBJNI(funId, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
RUN_IN_GAMETHREAD(JcWallet::jsToUnity(funId, msg));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
SE_BIND_FUNC(jsb_wallet_callback)
|
||||
|
||||
bool jsb_register_walletevent_modules(se::Object* global) {
|
||||
bool jsb_register_walletevent_modules(se::Object *global)
|
||||
{
|
||||
getOrCreatePlainObject_r("jsb", global, &__jsbObj);
|
||||
__jsbObj->defineFunction("jcCallback", _SE(jsb_wallet_callback));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ NS_CC_BEGIN
|
||||
|
||||
class CC_DLL JcWallet {
|
||||
public:
|
||||
void initEnv();
|
||||
static void initEnv();
|
||||
|
||||
JcWallet();
|
||||
|
||||
@ -28,9 +28,9 @@ NS_CC_BEGIN
|
||||
|
||||
static JcWallet *getInstance() { return _instance; }
|
||||
|
||||
char *runJsMethod(std::shared_ptr<JSMethodParam> data);
|
||||
static char *runJsMethod(const std::shared_ptr<JSMethodParam>& data);
|
||||
|
||||
static void initJSThread(std::shared_ptr<JcWallet> wallet);
|
||||
static void initJSThread(const std::shared_ptr<JcWallet>& wallet);
|
||||
|
||||
static void tick(float dt);
|
||||
|
||||
@ -38,7 +38,7 @@ NS_CC_BEGIN
|
||||
|
||||
void tickRun();
|
||||
|
||||
void jsToUnity(std::string funId, std::string msg);
|
||||
static void jsToUnity(const std::string& funId, const std::string& msg);
|
||||
|
||||
private:
|
||||
static JcWallet *_instance;
|
||||
|
File diff suppressed because one or more lines are too long
556
Data/js/main.js
556
Data/js/main.js
@ -1,4 +1,5 @@
|
||||
console.log('>> begin load wallet main file.');
|
||||
!window.jc || !jc.wallet ? new jcwallet.default() : jc.wallet;
|
||||
function promiseCb(funId, promiseFun, dataParser) {
|
||||
dataParser = dataParser || ((v) => v);
|
||||
promiseFun
|
||||
@ -12,23 +13,51 @@ function promiseCb(funId, promiseFun, dataParser) {
|
||||
}
|
||||
/**
|
||||
* oauth login before init internal wallet
|
||||
* @param {*} channel 0: google, 1: apple, 2: tiktok, 3: facebook, 4: twitter 5: tg
|
||||
* @param {string} channel:
|
||||
* 0: google,
|
||||
* 1: apple,
|
||||
* 2: tiktok,
|
||||
* 3: facebook,
|
||||
* 4: twitter
|
||||
* 5: tg,
|
||||
* 6: email,
|
||||
* 7: discord
|
||||
* 10: client
|
||||
* @param {string} env: dev release
|
||||
* @param {string} account: guest account to bind
|
||||
* @return {string} {token: string, address: string | null}
|
||||
* token: token for wallet services
|
||||
* address: address of wallet if already created (optional)
|
||||
*/
|
||||
function walletLogin(funId, channel) {
|
||||
function walletLogin(funId, channel, env, account) {
|
||||
channel = parseInt(channel);
|
||||
env = env || 'dev';
|
||||
console.log('walletLogin: ' + channel);
|
||||
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
|
||||
promiseCb(funId, wallet.preLogin(channel));
|
||||
promiseCb(funId, jc.wallet.preLogin(channel, env, account));
|
||||
}
|
||||
|
||||
function logout(funId, channel) {
|
||||
channel = parseInt(channel || '0');
|
||||
promiseCb(funId, jc.wallet.logout(channel));
|
||||
}
|
||||
|
||||
function updateGameInfo(funId, info) {
|
||||
jsb.updateGameInfo(funId, info);
|
||||
promiseCb(funId, Promise.resolve(1));
|
||||
}
|
||||
/**
|
||||
* init internal wallet with password
|
||||
* @param {number | string} chain chain id
|
||||
* @param {string} pass
|
||||
* @param {string} chain: chain id
|
||||
* @param {string} pass: password for wallet
|
||||
* @param {string} env: dev release
|
||||
* @param {string} useApi: 1: yes, 0: no
|
||||
* @return {string} address
|
||||
* @throws {Error} if password is wrong
|
||||
*/
|
||||
function initInternalWallet(funId, chain, pass, env) {
|
||||
function initInternalWallet(funId, chain, pass, env, useApi) {
|
||||
chain = parseInt(chain);
|
||||
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
|
||||
promiseCb(funId, wallet.initInternalWallet(chain, pass, env), () => {
|
||||
useApi = (useApi && useApi == '1') ? true: false
|
||||
promiseCb(funId, jc.wallet.initInternalWallet(chain, pass, env, useApi), () => {
|
||||
return jc.wallet.nativeAccount;
|
||||
});
|
||||
}
|
||||
@ -41,41 +70,58 @@ function verifyPassword(funId, pass) {
|
||||
promiseCb(funId, jc.wallet.verifyLocalPass(pass));
|
||||
}
|
||||
/**
|
||||
* @Deprecated
|
||||
* init third party wallet
|
||||
* @param {number | string} chain chain id
|
||||
*/
|
||||
function initThirdPartyWallet(funId, chain, env) {
|
||||
function initThirdPartyWallet(funId, chain, env, wallettype, provider) {
|
||||
chain = parseInt(chain);
|
||||
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 1 }) : jc.wallet;
|
||||
promiseCb(funId, wallet.initThirdPartyWallet(chain, env), () => {
|
||||
return jc.wallet.currentAccount();
|
||||
});
|
||||
wallettype = parseInt(wallettype || '1');
|
||||
promiseCb(funId, jc.wallet.initThirdPartyWallet(chain, env, wallettype, provider));
|
||||
}
|
||||
|
||||
|
||||
function initRelayWallet(funId, chain, env) {
|
||||
chain = parseInt(chain);
|
||||
promiseCb(funId, jc.wallet.initRelayWallet(chain, env));
|
||||
}
|
||||
|
||||
/**
|
||||
* all chain list we supported
|
||||
* @return {string} JSON string of
|
||||
* [{ name: string
|
||||
* type: string
|
||||
* rpc: string
|
||||
* id: number
|
||||
* network?: string
|
||||
* symbol?: string
|
||||
* explorerurl?: string
|
||||
* decimals?: number
|
||||
* }]
|
||||
*/
|
||||
function chainList(funId) {
|
||||
try {
|
||||
let data = jc.wallet.chainList;
|
||||
return JSON.stringify({ errcode: 0, data });
|
||||
} catch (err) {
|
||||
return JSON.stringify({ errcode: 1, errmsg: err.message || err });
|
||||
}
|
||||
let data = jc.wallet.chainList;
|
||||
promiseCb(funId, Promise.resolve(data));
|
||||
}
|
||||
/**
|
||||
* chain active
|
||||
* current actived chain info
|
||||
* @return {string} JSON string of
|
||||
* { name: string
|
||||
* type: string
|
||||
* rpc: string
|
||||
* id: number
|
||||
* network?: string
|
||||
* symbol?: string
|
||||
* explorerurl?: string
|
||||
* decimals?: number
|
||||
* }
|
||||
*/
|
||||
function currentChain(funId) {
|
||||
try {
|
||||
let data = jc.wallet.currentChain;
|
||||
return JSON.stringify({ errcode: 0, data });
|
||||
} catch (err) {
|
||||
return JSON.stringify({ errcode: 1, errmsg: err.message || err });
|
||||
}
|
||||
let data = jc.wallet.currentChain;
|
||||
promiseCb(funId, Promise.resolve(data));
|
||||
}
|
||||
/**
|
||||
* [BOTH]change chain
|
||||
* change current actived chain
|
||||
*/
|
||||
function changeChain(funId, chainId) {
|
||||
chainId = parseInt(chainId);
|
||||
@ -96,18 +142,18 @@ function loginSign(funId, nonce, tips) {
|
||||
* if account is null, we`ll query for current account of wallet
|
||||
*/
|
||||
function getEthBalance(funId, account) {
|
||||
promiseCb(funId, jc.wallet.getBalance(account));
|
||||
promiseCb(funId, jc.wallet.chainCommon.getBalance(account));
|
||||
}
|
||||
|
||||
/**
|
||||
* send ETH from current account
|
||||
* @param {string} to: target account
|
||||
* @param {string} amount:
|
||||
* @param {number} estimate: 1: only estimate gas price
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function sendEth(funId, to, amount, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(funId, jc.wallet.sendEth(to, amount, estimate));
|
||||
promiseCb(funId, jc.wallet.chainCommon.sendEth(to, amount, estimate));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,22 +184,30 @@ function erc20Info(funId, address) {
|
||||
* @param {string} account:
|
||||
*/
|
||||
function erc20Balance(funId, address, account) {
|
||||
promiseCb(funId, jc.wallet.erc20Balance(address, account));
|
||||
promiseCb(funId, jc.wallet.erc20Standard.getBalanceOf(address, account));
|
||||
}
|
||||
/**
|
||||
* send ERC20 token to to
|
||||
* @param {string} address: contract address of ERC20
|
||||
* @param {string} to: target account
|
||||
* @param {string} amount: amount of token to send
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function sendErc20(funId, address, to, amount, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(funId, jc.wallet.sendErc20(address, to, amount, estimate));
|
||||
promiseCb(funId, jc.wallet.erc20Standard.transfer({address, to, amount, estimate}));
|
||||
}
|
||||
|
||||
/**
|
||||
* send ERC721 NFT to to
|
||||
* @param {string} address: contract address of NFT
|
||||
* @param {string} to: target account
|
||||
* @param {string} tokenId: nft id of NFT
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function sendErc721(funId, address, to, tokenId, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(funId, jc.wallet.sendNFT(address, to, tokenId, estimate));
|
||||
promiseCb(funId, jc.wallet.erc721Standard.transfer({address, to, tokenId, estimate}));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,33 +222,42 @@ function erc721Balance(funId, address, account, chainId) {
|
||||
|
||||
/**
|
||||
* get balance of ERC1155
|
||||
* @param {string} address:
|
||||
* @param {string} account:
|
||||
* @param {string} tokenId:
|
||||
* @param {string} address: contract address of NFT
|
||||
* @param {string} account: wallet address
|
||||
* @param {string} tokenId: nft id of NFT
|
||||
*/
|
||||
function erc1155Balance(funId, address, account, tokenId) {
|
||||
promiseCb(funId, jc.wallet.erc1155Balance(address, account, tokenId));
|
||||
promiseCb(funId, jc.wallet.erc1155Standard.getBalanceOf(address, account, tokenId));
|
||||
}
|
||||
|
||||
/**
|
||||
* send ERC1155 to to
|
||||
* @param {string} address: contract address of NFT
|
||||
* @param {string} to: target account
|
||||
* @param {string} tokenIds: nft id of NFT, json string of array
|
||||
* @param {string} amounts: amount of token to send, json string of array
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function sendErc1155(funId, address, to, tokenIds, amounts, estimate) {
|
||||
tokenIds = JSON.parse(tokenIds);
|
||||
amounts = JSON.parse(amounts);
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(funId, jc.wallet.sendErc1155(address, to, tokenIds, amounts, estimate));
|
||||
promiseCb(funId, jc.wallet.erc1155Standard.transferBatch({address, to, tokenIds, amounts, estimate}));
|
||||
}
|
||||
|
||||
/**
|
||||
* show QRCode for content
|
||||
* @param {string} content: content to show
|
||||
*/
|
||||
function showQRCode(funId, content) {
|
||||
try {
|
||||
jsb.showQRCode(funId, content);
|
||||
return JSON.stringify({ errcode: 0, data: 1 });
|
||||
} catch (err) {
|
||||
return JSON.stringify({ errcode: 1, errmsg: err.message || err });
|
||||
}
|
||||
jsb.showQRCode(funId, content);
|
||||
promiseCb(funId, Promise.resolve(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* show webpage
|
||||
* don't call this when in web page
|
||||
* @param {string} url: url to show
|
||||
*/
|
||||
function showWebPage(funId, url) {
|
||||
try {
|
||||
jsb.showWebPage(funId, url);
|
||||
@ -205,23 +268,37 @@ function showWebPage(funId, url) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show QRCode scaner
|
||||
* @param {string} title: title of scaner
|
||||
*/
|
||||
function scanQRCode(funId, title) {
|
||||
console.log('scanQRCode: ' + title);
|
||||
promiseCb(funId, jc.wallet.nativeSvr.scanQRCode(title));
|
||||
}
|
||||
|
||||
/**
|
||||
* export wallet private key
|
||||
* @param {string} pass: password of wallet
|
||||
*/
|
||||
function exportWalletSecKey(funId, pass) {
|
||||
try {
|
||||
let key = jc.wallet.exportPrivateKey(pass);
|
||||
return JSON.stringify({ errcode: 0, data: key });
|
||||
} catch (err) {
|
||||
return JSON.stringify({ errcode: 1, errmsg: err.message || err });
|
||||
}
|
||||
let data = jc.wallet.exportPrivateKey(pass);
|
||||
promiseCb(funId, Promise.resolve(data));
|
||||
}
|
||||
|
||||
// ======= begin of interact with contract =======
|
||||
/**
|
||||
* mint NFT
|
||||
* @param {string} address: contract address of NFT
|
||||
* @param {string} tokenIds: token id of NFT, JSON string of string array
|
||||
* @param {string} startTime: time of signature generation
|
||||
* @param {string} saltNonce: nonce of signature
|
||||
* @param {string} signature: signature
|
||||
* @param {string} estimate: 1: only estimate gas price
|
||||
*/
|
||||
function mintNFT(funId, address, tokenIds, startTime, saltNonce, signature, estimate) {
|
||||
tokenIds = JSON.parse(tokenIds);
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.jcStandard.mintNFT({
|
||||
@ -238,8 +315,10 @@ function mintNFT(funId, address, tokenIds, startTime, saltNonce, signature, esti
|
||||
// ======= end of interact with contract =======
|
||||
// ======= begin of pay =======
|
||||
/**
|
||||
* crypto: 'CEC' or 'CEG', 'ETH'
|
||||
* address: wallet address of user
|
||||
* begin buy crypto with alchemy
|
||||
* @param {string} network: 'mainnet' or 'testnet'
|
||||
* @param {string} crypto: 'CEC' or 'CEG', 'ETH'
|
||||
* @param {string} address: wallet address of user
|
||||
* fiat: 'USD' or 'CNY'
|
||||
* fiatAmount: '100'
|
||||
* payWayCode: '10001'
|
||||
@ -247,7 +326,21 @@ function mintNFT(funId, address, tokenIds, startTime, saltNonce, signature, esti
|
||||
* accountId: account id of game user
|
||||
* orderId: from pre pay
|
||||
*/
|
||||
function beginPay(funId, crypto, address, fiat, fiatAmount, payWayCode, country, accountId, orderId) {
|
||||
function beginPay(
|
||||
funId,
|
||||
network,
|
||||
crypto,
|
||||
address,
|
||||
fiat,
|
||||
fiatAmount,
|
||||
payWayCode,
|
||||
country,
|
||||
accountId,
|
||||
orderId,
|
||||
timestamp,
|
||||
salt,
|
||||
sign
|
||||
) {
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.paySvr.alchemyPrePay({
|
||||
@ -259,23 +352,33 @@ function beginPay(funId, crypto, address, fiat, fiatAmount, payWayCode, country,
|
||||
country,
|
||||
accountId,
|
||||
orderId,
|
||||
network,
|
||||
timestamp,
|
||||
salt,
|
||||
sign,
|
||||
})
|
||||
);
|
||||
}
|
||||
// ======= end of pay =======
|
||||
// ======= begin of transaction history =======
|
||||
/**
|
||||
*
|
||||
* @param {*} funId
|
||||
* @param {*} start
|
||||
* @param {*} limit
|
||||
* @param {*} moreParam e.g. {timeBegin: 1655716867832, timeEnd: 1655716867832}
|
||||
* query eth transaction history
|
||||
* @param {string} start
|
||||
* @param {string} limit
|
||||
* @param {JSON string} moreParam e.g. {timeBegin: 1655716867832, timeEnd: 1655716867832}
|
||||
*/
|
||||
function ethHistory(funId, start, limit, moreParam) {
|
||||
moreParam = moreParam ? JSON.parse(moreParam) : {};
|
||||
promiseCb(funId, jc.wallet.historySvr.ethRecords(start, limit, moreParam));
|
||||
}
|
||||
|
||||
/**
|
||||
* query token transaction history
|
||||
* @param {string} start
|
||||
* @param {string} limit
|
||||
* @param {string} address
|
||||
* @param {string} tokenId
|
||||
* @param {JSON string} moreParam e.g. {timeBegin: 1655716867832, timeEnd: 1655716867832}
|
||||
*/
|
||||
function tokenHistory(funId, start, limit, address, tokenId, moreParam) {
|
||||
moreParam = moreParam ? JSON.parse(moreParam) : {};
|
||||
var data = { start, limit, address, tokenId };
|
||||
@ -291,17 +394,16 @@ function emailInfo(funId) {
|
||||
}
|
||||
/**
|
||||
* send code with email
|
||||
* @param {*} email
|
||||
* @param {*} type
|
||||
* @param {string} email
|
||||
* @param {string} type
|
||||
*/
|
||||
function sendEmailCode(funId, email, type) {
|
||||
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
|
||||
promiseCb(funId, wallet.emailVerifySvr.sendEmailCode(email, type));
|
||||
promiseCb(funId, jc.wallet.emailVerifySvr.sendEmailCode(email, type));
|
||||
}
|
||||
/**
|
||||
* verify email with code, and update email
|
||||
* @param {*} email
|
||||
* @param {*} code
|
||||
* @param {string} email
|
||||
* @param {string} code
|
||||
*/
|
||||
function verifyEmail(funId, email, code) {
|
||||
promiseCb(funId, jc.wallet.emailVerifySvr.updateEmailVerify(email, code));
|
||||
@ -309,11 +411,10 @@ function verifyEmail(funId, email, code) {
|
||||
|
||||
/**
|
||||
* check if email had already been registed
|
||||
* @param {*} email
|
||||
* @param {string} email
|
||||
*/
|
||||
function checkEmailExists(funId, email) {
|
||||
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
|
||||
promiseCb(funId, wallet.emailVerifySvr.isEmailRegister(email));
|
||||
promiseCb(funId, jc.wallet.emailVerifySvr.isEmailRegister(email));
|
||||
}
|
||||
/**
|
||||
* regist with email
|
||||
@ -322,8 +423,7 @@ function checkEmailExists(funId, email) {
|
||||
* @param {*} code
|
||||
*/
|
||||
function emailRegist(funId, email, password, code) {
|
||||
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
|
||||
promiseCb(funId, wallet.emailVerifySvr.registByEmail(email, password, code));
|
||||
promiseCb(funId, jc.wallet.emailVerifySvr.registByEmail(email, password, code));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -332,20 +432,15 @@ function emailRegist(funId, email, password, code) {
|
||||
* @param {*} password
|
||||
*/
|
||||
function emailLogin(funId, email, password) {
|
||||
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
|
||||
promiseCb(funId, wallet.emailLogin(email, password));
|
||||
promiseCb(funId, jc.wallet.emailLogin(email, password));
|
||||
}
|
||||
|
||||
/**
|
||||
* token list of current chain
|
||||
*/
|
||||
function tokenList(funId) {
|
||||
try {
|
||||
let data = jc.wallet.currentChainCfg.tokens;
|
||||
return JSON.stringify({ errcode: 0, data });
|
||||
} catch (err) {
|
||||
return JSON.stringify({ errcode: 1, errmsg: err.message || err });
|
||||
}
|
||||
let data = jc.wallet.currentChainCfg.tokens;
|
||||
promiseCb(funId, Promise.resolve(data));
|
||||
}
|
||||
/**
|
||||
* calc token price of USD
|
||||
@ -363,22 +458,45 @@ function tokenPrice(funId, tokenName, amount) {
|
||||
function fiatList(funId) {
|
||||
promiseCb(funId, jc.wallet.paySvr.fetchFiatList());
|
||||
}
|
||||
/**
|
||||
* query price of crypto -> usd
|
||||
* @param {string} crypto
|
||||
* @param {number} chain chain id,
|
||||
*/
|
||||
function getCryptoPriceOfUSD(funId, crypto, chain) {
|
||||
let chainData = jc.wallet.currentChain;
|
||||
if (chain) {
|
||||
chainData = jc.wallet.chainList.find((v) => v.chainId === +chain);
|
||||
}
|
||||
let network = chainData.type !== 'Testnet' ? chainData.network || chainData.symbol : 'ARBITRUM';
|
||||
network = network || 'ARBITRUM';
|
||||
promiseCb(funId, jc.wallet.paySvr.queryTokenPrice(network, crypto));
|
||||
}
|
||||
|
||||
/**
|
||||
* format price
|
||||
* @param {string} value
|
||||
* @param {string} decimal: decimal of price
|
||||
* @param {string} fixed: fixed of price
|
||||
*/
|
||||
function formatPrice(funId, value, decimal, fixed) {
|
||||
try {
|
||||
let data = jc.wallet.formatPrice(value, decimal, fixed);
|
||||
return JSON.stringify({ errcode: 0, data });
|
||||
} catch (err) {
|
||||
return JSON.stringify({ errcode: 1, errmsg: err.message || err });
|
||||
}
|
||||
let data = jc.wallet.formatPrice(value, decimal, fixed);
|
||||
promiseCb(funId, Promise.resolve(data));
|
||||
}
|
||||
|
||||
// begin of market
|
||||
// begin sell nft with market
|
||||
/**
|
||||
* sell nft with market
|
||||
* @param {string} nftToken: address of nft token to sell
|
||||
* @param {string} currency: address of currency
|
||||
* @param {string} tokenId: token id of nft to sell
|
||||
* @param {string} price: price of nft
|
||||
* @param {string} amount: amount of nft to sell, must be 1 for ERC721
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function marketSellNFT(funId, nftToken, currency, tokenId, price, amount, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.jcStandard.marketSellNFT({
|
||||
@ -392,8 +510,14 @@ function marketSellNFT(funId, nftToken, currency, tokenId, price, amount, estima
|
||||
(v) => JSON.stringify(v)
|
||||
);
|
||||
}
|
||||
// update price of order
|
||||
/**
|
||||
* update price of existed order
|
||||
* @param {string} orderId: order id
|
||||
* @param {string} price: new price
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function marketUpdatePrice(funId, orderId, price, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.jcStandard.marketUpdatePrice({
|
||||
@ -404,8 +528,13 @@ function marketUpdatePrice(funId, orderId, price, estimate) {
|
||||
(v) => JSON.stringify(v)
|
||||
);
|
||||
}
|
||||
// cancel order
|
||||
/**
|
||||
* cancel order
|
||||
* @param {string} orderId: order id
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function marketCancelOrder(funId, orderId, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.jcStandard.marketCancelOrder({
|
||||
@ -415,19 +544,44 @@ function marketCancelOrder(funId, orderId, estimate) {
|
||||
(v) => JSON.stringify(v)
|
||||
);
|
||||
}
|
||||
// buy order
|
||||
/**
|
||||
* buy order
|
||||
* @param {string} orderId: order id
|
||||
* @param {string} price: price of order
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function marketBuy(funId, orderId, price, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.jcStandard.marketBuy({
|
||||
orderId,
|
||||
price,
|
||||
estimate,
|
||||
}),
|
||||
(v) => JSON.stringify(v)
|
||||
);
|
||||
}
|
||||
// buy item of game from market
|
||||
/**
|
||||
* get order info from chain
|
||||
* @param {string} orderId: order id
|
||||
*/
|
||||
function marketOrderInfo(funId, orderId) {
|
||||
promiseCb(funId, jc.wallet.jcStandard.marketOrderInfo(orderId));
|
||||
}
|
||||
/**
|
||||
* buy item of game from market
|
||||
* @param {string} orderId: order id
|
||||
* @param {string} seller: seller address
|
||||
* @param {string} currency: address of currency
|
||||
* @param {string} price: price of order
|
||||
* @param {string} startTime: time for signature
|
||||
* @param {string} saltNonce: nonce for signature
|
||||
* @param {string} signature: signature
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function gameMarketBuy(funId, orderId, seller, currency, price, startTime, saltNonce, signature, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.jcStandard.gameMarketBuy({
|
||||
@ -443,15 +597,21 @@ function gameMarketBuy(funId, orderId, seller, currency, price, startTime, saltN
|
||||
(v) => JSON.stringify(v)
|
||||
);
|
||||
}
|
||||
// get order info from chain
|
||||
function marketOrderInfo(funId, orderId) {
|
||||
promiseCb(funId, jc.wallet.jcStandard.marketOrderInfo(orderId));
|
||||
}
|
||||
// end of market
|
||||
|
||||
// begin of mall
|
||||
// buy item of game from mall
|
||||
/**
|
||||
* buy item of game from mall
|
||||
* @param {string} orderId: order id
|
||||
* @param {string} currency: address of currency
|
||||
* @param {string} price: price of order
|
||||
* @param {string} startTime: time for signature
|
||||
* @param {string} saltNonce: nonce for signature
|
||||
* @param {string} signature: signature
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function gameMallBuy(funId, orderId, currency, price, startTime, saltNonce, signature, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.jcStandard.gameMallBuy({
|
||||
@ -466,10 +626,24 @@ function gameMallBuy(funId, orderId, currency, price, startTime, saltNonce, sign
|
||||
(v) => JSON.stringify(v)
|
||||
);
|
||||
}
|
||||
// end of mall
|
||||
|
||||
// begin of NFT mall
|
||||
/**
|
||||
* buy nft from mall
|
||||
* @param {string} currency: address of currency
|
||||
* @param {string} addresses: address of nft token, JSON string of array
|
||||
* @param {string} ids: token id of nft, JSON string of array
|
||||
* @param {string} amounts: amount of nft, JSON string of array
|
||||
* @param {string} values: JSON string, e.g. [orderId, price, startTime, saltNonce]
|
||||
* orderId: order id
|
||||
* price: price of order
|
||||
* startTime: time for signature
|
||||
* saltNonce: nonce for signature
|
||||
* @param {string} signature: signature
|
||||
* @param {string} gas: gas price
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function nftMallBuy(funId, currency, addresses, ids, amounts, values, signature, gas, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
addresses = JSON.parse(addresses);
|
||||
ids = JSON.parse(ids);
|
||||
amounts = JSON.parse(amounts);
|
||||
@ -490,4 +664,190 @@ function nftMallBuy(funId, currency, addresses, ids, amounts, values, signature,
|
||||
);
|
||||
}
|
||||
|
||||
// end of NFT mall
|
||||
/**
|
||||
* buy ceg with usdt, usdc
|
||||
* @param {string} currency: address of currency
|
||||
* @param {string} amount: amount of currency
|
||||
* @param {string} gas: gas price
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function buyTokenWithErc20(funId, currency, amount, gas, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.jcStandard.buyTokenWithErc20({
|
||||
currency,
|
||||
amount,
|
||||
estimate,
|
||||
}),
|
||||
(v) => JSON.stringify(v)
|
||||
);
|
||||
}
|
||||
// end of mall
|
||||
|
||||
// begin of in-app pay
|
||||
/**
|
||||
* query google or ios products with product ids
|
||||
* @param {string} productIds: product id for query, JSON string of array
|
||||
*/
|
||||
function queryGoogleProducts(funId, productIds) {
|
||||
let ids = JSON.parse(productIds);
|
||||
console.log('queryGoogleProducts:: ' + productIds);
|
||||
if (window.JavascriptJavaBridge) {
|
||||
promiseCb(funId, jc.wallet.paySvr.queryGoogleProducts(ids));
|
||||
} else {
|
||||
promiseCb(funId, jc.wallet.paySvr.queryIOSProducts(ids));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* query google or ios purchases unfinished
|
||||
*/
|
||||
function queryGooglePurchases(funId) {
|
||||
if (window.JavascriptJavaBridge) {
|
||||
promiseCb(funId, jc.wallet.paySvr.queryGooglePurchases());
|
||||
} else {
|
||||
promiseCb(funId, jc.wallet.paySvr.queryIOSPurchases());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* begin google or ios pay
|
||||
* @param {string} productId: product id
|
||||
* @param {string} orderId: order id
|
||||
*/
|
||||
function beginGoogleBuy(funId, productId, orderId) {
|
||||
if (window.JavascriptJavaBridge) {
|
||||
promiseCb(funId, jc.wallet.paySvr.buyGoogleProduct(productId, orderId));
|
||||
} else {
|
||||
promiseCb(funId, jc.wallet.paySvr.beginIOSPurchase(productId, orderId));
|
||||
}
|
||||
}
|
||||
// end of in-app pay
|
||||
|
||||
// begin of staking
|
||||
/**
|
||||
* stake nft
|
||||
* @param {string} nfts: address of nft token, JSON string of array
|
||||
* @param {string} tokenIds: token id of nft, JSON string of array
|
||||
* @param {string} staketimes: staking time of nft, JSON string of array
|
||||
* @param {string} gas: gas price
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function stakeNfts(funId, nfts, tokenIds, staketimes, gas, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
nfts = JSON.parse(nfts);
|
||||
tokenIds = JSON.parse(tokenIds);
|
||||
staketimes = JSON.parse(staketimes);
|
||||
promiseCb(funId, jc.wallet.jcStandard.stakeNfts({ nfts, tokenIds, staketimes, gas, estimate }), (v) =>
|
||||
JSON.stringify(v)
|
||||
);
|
||||
}
|
||||
/**
|
||||
* redeem nft
|
||||
* @param {string} nfts: address of nft token, JSON string of array
|
||||
* @param {string} tokenIds: token id of nft, JSON string of array
|
||||
* @param {string} gas: gas price
|
||||
* @param {string} estimate: 0: execute; 1: only estimate gas price; default: 0
|
||||
*/
|
||||
function redeemNfts(funId, nfts, tokenIds, gas, estimate) {
|
||||
estimate = (estimate || '0') | 0;
|
||||
nfts = JSON.parse(nfts);
|
||||
tokenIds = JSON.parse(tokenIds);
|
||||
promiseCb(funId, jc.wallet.jcStandard.redeemNfts({ nfts, tokenIds, gas, estimate }), (v) => JSON.stringify(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* query nft stake info
|
||||
* @param {string} nft: address of nft token
|
||||
* @param {string} tokenId: token id of nft
|
||||
*/
|
||||
function nftStakeInfo(funId, nft, tokenId) {
|
||||
promiseCb(funId, jc.wallet.jcStandard.nftStakeInfo({ nft, tokenId }));
|
||||
}
|
||||
// end of staking
|
||||
// begin of gold bricks
|
||||
/**
|
||||
* mint Gold Bricks
|
||||
* @param {string} address: contract address of NFT
|
||||
* @param {string} tokenIds: token id of NFT, JSON string of string array
|
||||
* @param {string} startTime: time of signature generation
|
||||
* @param {string} saltNonce: nonce of signature
|
||||
* @param {string} signature: signature
|
||||
* @param {string} estimate: 1: only estimate gas price
|
||||
*/
|
||||
function mintBricks(funId, address, tokenIds, startTime, saltNonce, signature, estimate) {
|
||||
tokenIds = JSON.parse(tokenIds);
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.jcStandard.mintBricks({
|
||||
address,
|
||||
tokenIds,
|
||||
startTime,
|
||||
saltNonce,
|
||||
signature,
|
||||
estimate,
|
||||
}),
|
||||
(v) => JSON.stringify(v)
|
||||
);
|
||||
}
|
||||
/**
|
||||
* decompose Gold Bricks
|
||||
* @param {string} address: contract address of NFT
|
||||
* @param {string} tokenIds: token id of NFT, JSON string of string array
|
||||
* @param {string} startTime: time of signature generation
|
||||
* @param {string} saltNonce: nonce of signature
|
||||
* @param {string} signature: signature
|
||||
* @param {string} estimate: 1: only estimate gas price
|
||||
*/
|
||||
function decomposeBricks(funId, address, tokenIds, startTime, saltNonce, signature, estimate) {
|
||||
tokenIds = JSON.parse(tokenIds);
|
||||
estimate = (estimate || '0') | 0;
|
||||
promiseCb(
|
||||
funId,
|
||||
jc.wallet.jcStandard.decomposeBricks({
|
||||
address,
|
||||
tokenIds,
|
||||
startTime,
|
||||
saltNonce,
|
||||
signature,
|
||||
estimate,
|
||||
}),
|
||||
(v) => JSON.stringify(v)
|
||||
);
|
||||
}
|
||||
// end of gold bricks
|
||||
|
||||
/**
|
||||
* delete account
|
||||
* delete account will delete game data for current account
|
||||
* wallet for current account will be remained
|
||||
*/
|
||||
function deleteAccount(funId) {
|
||||
promiseCb(funId, jc.wallet.deleteAccount());
|
||||
}
|
||||
/**
|
||||
* reset wallet address for current account
|
||||
*/
|
||||
function resetWalletAddress(funId) {
|
||||
promiseCb(funId, jc.wallet.resetWalletAddress());
|
||||
}
|
||||
/**
|
||||
* storage pass with google drive
|
||||
* @param {string} key: current account address
|
||||
* @param {string} val: pass for current account
|
||||
*/
|
||||
function storePassLocal(funId, key, val) {
|
||||
promiseCb(funId, jc.wallet.nativeSvr.storagePass(key, val));
|
||||
}
|
||||
/**
|
||||
* restore pass from google drive
|
||||
* @param {string} key: current account address
|
||||
*/
|
||||
function restorePassLocal(funId, key) {
|
||||
promiseCb(funId, jc.wallet.nativeSvr.authGetStoragePass(key));
|
||||
}
|
||||
|
||||
function getLocalPassState(funId, key) {
|
||||
promiseCb(funId, jc.wallet.nativeSvr.passStorageState(key));
|
||||
}
|
||||
|
||||
|
@ -1,23 +1,37 @@
|
||||
if (window.JavascriptJavaBridge) {
|
||||
console.log('register android jsb.reflection')
|
||||
jsb.reflection = new JavascriptJavaBridge()
|
||||
console.log('regist android jsb.reflection')
|
||||
jsb.reflection = new JavascriptJavaBridge();
|
||||
} else if (window.JavaScriptObjCBridge) {
|
||||
jsb.reflection = new JavaScriptObjCBridge()
|
||||
jsb.reflection = new JavaScriptObjCBridge();
|
||||
}
|
||||
window.jumpToWallet = function(url) {
|
||||
url = url || 'wc://';
|
||||
if (window.JavascriptJavaBridge) {
|
||||
url = 'metamask://wc?uri=' + url;
|
||||
} else {
|
||||
url = `https://metamask.app.link/wc?uri=${encodeURIComponent(url)}`;
|
||||
}
|
||||
console.log('open native: ' + url);
|
||||
jsb.toWallet(url);
|
||||
}
|
||||
|
||||
window.jumpToWallet = function (url) {
|
||||
url = url || 'wc://'
|
||||
console.log('jumpToWallet: ' + url)
|
||||
url = 'metamask://wc?uri=' + url
|
||||
// url = 'imtokenv2://wc?uri='+url
|
||||
jsb.toWallet(url)
|
||||
// jsb.reflection.callStaticMethod(
|
||||
// 'com/jc/jcfw/JcSDK',
|
||||
// 'toWallet',
|
||||
// '(Ljava/lang/String;)V',
|
||||
// url || 'wc://'
|
||||
// )
|
||||
window.toRelayPage = function(url) {
|
||||
// https://metamask.app.link/dapp/www.sample.com/page.html
|
||||
// okx://wallet/dapp/details?dappUrl=https://www.sample.com/page.html
|
||||
let okxUrl = `okx://wallet/dapp/details?dappUrl=${url}`;
|
||||
//let okxUrl = `https://metamask.app.link/dapp/${url.replace('https://', '')}`;
|
||||
jsb.toWallet(okxUrl);
|
||||
}
|
||||
function nativeCallback(funId, msg) {
|
||||
console.log(`native call back:: funid: ${funId} msg: ${msg}`)
|
||||
function nativeCallBack(...args) {
|
||||
console.log(`jniCallback: ${args[0]}`);
|
||||
jc.wallet.nativeSvr.handleNativeCallback(...args);
|
||||
}
|
||||
|
||||
function onGamePause() {
|
||||
console.log('game pause');
|
||||
}
|
||||
function onGameResume() {
|
||||
console.log('game resume');
|
||||
jc.wallet.relaySvr.checkResult();
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:name=".MainApplication"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
tools:replace="android:icon"
|
||||
@ -17,6 +18,9 @@
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="cocos2djs" />
|
||||
<meta-data
|
||||
android:name="firebase_crashlytics_collection_enabled"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="com.facebook.sdk.ApplicationId"
|
||||
android:value="@string/facebook_app_id" />
|
||||
@ -60,19 +64,43 @@
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data
|
||||
android:scheme="http"
|
||||
android:host="www.cebg.games"
|
||||
android:pathPrefix="/client" />
|
||||
<data android:scheme="http" />
|
||||
<data android:host="www.cebg.games"/>
|
||||
<data android:pathPrefix="/client"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="www.cebg.games"/>
|
||||
<data android:pathPrefix="/client"/>
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http" />
|
||||
<data android:host="www.counterfire.games"/>
|
||||
<data android:pathPrefix="/client"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="www.counterfire.games"/>
|
||||
<data android:pathPrefix="/client"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="www.cebg.games"
|
||||
android:pathPrefix="/client" />
|
||||
android:scheme="tebg"
|
||||
android:path="/relay_cb" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
@ -81,7 +109,15 @@
|
||||
android:theme="@style/CaptureTheme" />
|
||||
<activity
|
||||
android:name=".activity.WebPageActivity"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout"
|
||||
android:theme="@style/WebViewTheme" />
|
||||
<activity
|
||||
android:name=".activity.BiometricActivity"
|
||||
android:theme="@style/DayNightActivity"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|layoutDirection"
|
||||
/>
|
||||
<activity
|
||||
android:name=".activity.CustomCaptureActivity"
|
||||
android:theme="@style/CaptureTheme" />
|
||||
@ -109,20 +145,35 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="com.googleusercontent.apps.53206975661-ih3r0ubph3rqejdq97b029difbrk2bqj" />
|
||||
<data android:scheme="com.googleusercontent.apps.53206975661-asnf3qe4bg29p8h981pgf099osvrjbme" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http" />
|
||||
<data android:host="oauth-svr.cebggame.com"/>
|
||||
<data android:pathPrefix="/google"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="oauth-svr.cebggame.com"/>
|
||||
<data android:pathPrefix="/google"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".tiktokapi.TikTokEntryActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="com.cege.games.release"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".apple.AppleLoginActivity"
|
||||
android:theme="@style/WebViewTheme" />
|
||||
<activity
|
||||
android:name=".apple.AppleLoginCbActivity"
|
||||
|
||||
<activity
|
||||
android:name=".oauth.OAuthLoginCbActivity"
|
||||
tools:node="replace"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
@ -130,9 +181,7 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:scheme="cebg"
|
||||
android:path="/apple_login_result" />
|
||||
<data android:scheme="cfoauthcb" android:path="/login_result" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
@ -160,7 +209,8 @@
|
||||
<category android:name="android.intent.category.default" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</application>
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
@ -179,13 +229,15 @@
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen.multitouch.distinct"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission
|
||||
@ -198,7 +250,6 @@
|
||||
android:name="android.permission.CAPTURE_VIDEO_OUTPUT"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
@ -206,7 +257,9 @@
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||
<queries>
|
||||
<package android:name="com.zhiliaoapp.musically" />
|
||||
<package android:name="com.ss.android.ugc.trill" />
|
||||
@ -216,4 +269,4 @@
|
||||
<provider android:authorities="com.facebook.orca.provider.PlatformProvider" />
|
||||
<!-- allows sharing to Messenger app -->
|
||||
</queries>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
@ -29,11 +29,8 @@ android {
|
||||
applicationId "com.cege.games.release"
|
||||
minSdkVersion PROP_MIN_SDK_VERSION
|
||||
targetSdkVersion PROP_TARGET_SDK_VERSION
|
||||
versionCode 12
|
||||
versionName "1.0.10"
|
||||
ndk{
|
||||
abiFilters 'armeabi-v7a','arm64-v8a'
|
||||
}
|
||||
versionCode 46
|
||||
versionName "1.0.46"
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
@ -97,8 +94,14 @@ android {
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
ndk {
|
||||
abiFilters 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
release {
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a','arm64-v8a'
|
||||
}
|
||||
minifyEnabled false
|
||||
signingConfig signingConfigs.release
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
@ -115,6 +118,17 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
packagingOptions {
|
||||
exclude("META-INF/DEPENDENCIES")
|
||||
exclude("META-INF/LICENSE")
|
||||
exclude("META-INF/LICENSE.txt")
|
||||
exclude("META-INF/license.txt")
|
||||
exclude("META-INF/NOTICE")
|
||||
exclude("META-INF/NOTICE.txt")
|
||||
exclude("META-INF/notice.txt")
|
||||
exclude("META-INF/ASL2.0")
|
||||
exclude("META-INF/*.kotlin_module")
|
||||
}
|
||||
}
|
||||
|
||||
android.applicationVariants.all { variant ->
|
||||
@ -124,11 +138,11 @@ android.applicationVariants.all { variant ->
|
||||
variant.mergeAssetsProvider.get().doLast {
|
||||
def sourceDir = rootProject.ext.cfgs.jsFilePath
|
||||
|
||||
// copy{
|
||||
// from "${sourceDir}"
|
||||
// include "Data/js/**"
|
||||
// into outputDir
|
||||
// }
|
||||
copy{
|
||||
from "${sourceDir}"
|
||||
include "Data/js/**"
|
||||
into outputDir
|
||||
}
|
||||
copy {
|
||||
from "${sourceDir}/cert/cacert.pem"
|
||||
into outputDir
|
||||
@ -156,17 +170,30 @@ dependencies {
|
||||
implementation 'com.github.jenly1314:zxing-lite:2.1.1'
|
||||
implementation 'net.openid:appauth:0.11.1'
|
||||
implementation "com.squareup.okio:okio:2.10.0"
|
||||
implementation 'com.android.volley:volley:1.2.1'
|
||||
implementation 'org.greenrobot:eventbus:3.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'com.bytedance.ies.ugc.aweme:opensdk-oversea-external:0.2.1.0'
|
||||
implementation 'com.google.android.play:core:1.10.0' //PAD资源分发
|
||||
implementation 'com.facebook.android:facebook-core:latest.release'
|
||||
implementation 'com.facebook.android:facebook-login:latest.release'
|
||||
implementation 'com.facebook.android:facebook-share:latest.release'
|
||||
implementation "com.squareup.okhttp3:okhttp:4.10.0"
|
||||
// begin of firebase
|
||||
implementation platform('com.google.firebase:firebase-bom:32.1.1')
|
||||
implementation 'com.google.firebase:firebase-analytics'
|
||||
implementation 'com.google.firebase:firebase-crashlytics-ndk'
|
||||
implementation "org.greenrobot:eventbus:3.3.1"
|
||||
// end of firebase
|
||||
// google pay
|
||||
implementation "com.android.billingclient:billing:6.0.1"
|
||||
|
||||
// google drive
|
||||
implementation('com.google.api-client:google-api-client-android:2.2.0') {
|
||||
exclude group: 'org.apache.httpcomponents'
|
||||
exclude module: 'guava-jdk5'
|
||||
}
|
||||
implementation 'com.google.http-client:google-http-client-android:1.23.0'
|
||||
implementation('com.google.apis:google-api-services-drive:v3-rev20230815-2.0.0') {
|
||||
exclude group: 'org.apache.httpcomponents'
|
||||
exclude module: 'guava-jdk5'
|
||||
}
|
||||
}
|
@ -2,11 +2,5 @@ package com.cege.games.release;
|
||||
|
||||
public class Constants {
|
||||
public static final String PREF_NAME = "jcwallet";
|
||||
|
||||
public static final String APPLE_CLIENT_ID = "wallet.cebggame.com";
|
||||
public static final String APPLE_REDIRECT_URI = "https://wallet.cebggame.com/apple/oauth_redirect";
|
||||
public static final String APPLE_SCOPE = "name%20email";
|
||||
|
||||
public static final String APPLE_AUTH_URL = "https://appleid.apple.com/auth/authorize";
|
||||
|
||||
public static final String FUNID_PREFIX = "webpage_";
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ import com.bytedance.sdk.open.tiktok.TikTokOpenConfig;
|
||||
public class MainApplication extends Application {
|
||||
public static MainApplication application;
|
||||
public static SharedPreferences _pref;
|
||||
private String gameData;
|
||||
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
@ -17,4 +18,12 @@ public class MainApplication extends Application {
|
||||
String tiktokClientKey = "awqbuzh2qymmq8hs";
|
||||
TikTokOpenApiFactory.init(new TikTokOpenConfig(tiktokClientKey));
|
||||
}
|
||||
|
||||
public String getGameData() {
|
||||
return gameData;
|
||||
}
|
||||
|
||||
public void setGameData(String gameData) {
|
||||
this.gameData = gameData;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,96 @@
|
||||
package com.cege.games.release.activity;
|
||||
|
||||
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
||||
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
import static androidx.biometric.BiometricPrompt.ERROR_USER_CANCELED;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.security.keystore.KeyInfo;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.biometric.BiometricManager;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
|
||||
import com.cege.games.release.R;
|
||||
import com.jc.jcfw.security.BiometricHelper;
|
||||
import com.jc.jcfw.security.CryptographyManager;
|
||||
import com.jc.jcfw.security.CryptographyManagerImpl;
|
||||
import com.jc.jcfw.security.EncryptedData;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class BiometricActivity extends AppCompatActivity {
|
||||
private static final String TAG = BiometricActivity.class.getSimpleName();
|
||||
private BiometricPrompt.PromptInfo promptInfo;
|
||||
private CryptographyManager cryptographyManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_biometric);
|
||||
Log.i(TAG, "onCreate: " + getIntent().getDataString());
|
||||
promptInfo = BiometricHelper.createPromptInfo(this);
|
||||
cryptographyManager = new CryptographyManagerImpl();
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getStringExtra("action");
|
||||
String funId = intent.getStringExtra("funid");
|
||||
String account = intent.getStringExtra("account");
|
||||
// check if action is exists
|
||||
if ("encrypt".equals(action)) {
|
||||
String password = intent.getStringExtra("password");
|
||||
authenticateToEncrypt(funId, password);
|
||||
} else if ("decrypt".equals(action)) {
|
||||
String iv = intent.getStringExtra("iv");
|
||||
authenticateToDecrypt(account, iv);
|
||||
}
|
||||
}
|
||||
|
||||
public void authenticateToEncrypt(String funId, String text) {
|
||||
if (BiometricManager.from(this)
|
||||
.canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||
Cipher cipher = cryptographyManager.getInitializedCipherForEncryption("cebg_wallet_key");
|
||||
BiometricPrompt biometricPrompt = BiometricHelper.createBiometricPrompt(this, _result -> {
|
||||
if (_result.isError()) {
|
||||
if (_result.getErrcode() == ERROR_USER_CANCELED) {
|
||||
// close current activity
|
||||
finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
EncryptedData encryptedData = cryptographyManager.encryptData(text, _result.getCipher());
|
||||
String encryptedString = Base64.encodeToString(encryptedData.getCiphertext(), Base64.DEFAULT);
|
||||
String ivString = Base64.encodeToString(encryptedData.getInitializationVector(), Base64.DEFAULT);
|
||||
Log.i(TAG, "encrypted msg: " + encryptedString);
|
||||
Log.i(TAG, "encrypted iv: " + ivString);
|
||||
finish();
|
||||
});
|
||||
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
|
||||
}
|
||||
}
|
||||
|
||||
public void authenticateToDecrypt(String text, String iv) {
|
||||
if (BiometricManager.from(this)
|
||||
.canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||
byte[] ivData = Base64.decode(iv, Base64.DEFAULT);
|
||||
byte[] textData = Base64.decode(text, Base64.DEFAULT);
|
||||
Cipher cipher = cryptographyManager.getInitializedCipherForDecryption("cebg_wallet_key", ivData);
|
||||
BiometricPrompt biometricPrompt = BiometricHelper.createBiometricPrompt(this, _result -> {
|
||||
if (_result.isError()) {
|
||||
if (_result.getErrcode() == ERROR_USER_CANCELED) {
|
||||
// close current activity
|
||||
finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
String decryptedMsg = cryptographyManager.decryptData(textData, _result.getCipher());
|
||||
Log.i(TAG, "decrypted msg: " + decryptedMsg);
|
||||
finish();
|
||||
});
|
||||
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
package com.cege.games.release.activity;
|
||||
|
||||
import static com.cege.games.release.ui.UIManager.JUMP_CODE_PHOTO;
|
||||
import static com.cege.games.release.ui.UIManager.KEY_TITLE;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Button;
|
||||
@ -8,7 +11,6 @@ import android.widget.TextView;
|
||||
import com.cege.games.release.R;
|
||||
import com.king.zxing.CaptureActivity;
|
||||
|
||||
import static com.cege.games.release.MainActivity.KEY_TITLE;
|
||||
|
||||
public class CustomCaptureActivity extends CaptureActivity {
|
||||
private static final String TAG = CustomCaptureActivity.class.getSimpleName();
|
||||
@ -38,8 +40,7 @@ public class CustomCaptureActivity extends CaptureActivity {
|
||||
|
||||
protected void onClickLocalImg(){
|
||||
Intent ins=new Intent();
|
||||
ins.putExtra("localImg", true);
|
||||
setResult(1, ins);
|
||||
setResult(JUMP_CODE_PHOTO, ins);
|
||||
finish();
|
||||
}
|
||||
|
||||
|
168
app/src/com/cege/games/release/activity/UnityPlayerActivity.java
Normal file
168
app/src/com/cege/games/release/activity/UnityPlayerActivity.java
Normal file
@ -0,0 +1,168 @@
|
||||
package com.cege.games.release.activity;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Window;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.unity3d.player.IUnityPlayerLifecycleEvents;
|
||||
import com.unity3d.player.MultiWindowSupport;
|
||||
import com.unity3d.player.UnityPlayer;
|
||||
|
||||
public class UnityPlayerActivity extends AppCompatActivity implements IUnityPlayerLifecycleEvents
|
||||
{
|
||||
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
|
||||
|
||||
// Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player
|
||||
// The command line arguments are passed as a string, separated by spaces
|
||||
// UnityPlayerActivity calls this from 'onCreate'
|
||||
// Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan
|
||||
// See https://docs.unity3d.com/Manual/CommandLineArguments.html
|
||||
// @param cmdLine the current command line arguments, may be null
|
||||
// @return the modified command line string or null
|
||||
protected String updateUnityCommandLineArguments(String cmdLine)
|
||||
{
|
||||
return cmdLine;
|
||||
}
|
||||
|
||||
// Setup activity layout
|
||||
@Override protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity"));
|
||||
getIntent().putExtra("unity", cmdLine);
|
||||
|
||||
mUnityPlayer = new UnityPlayer(this, this);
|
||||
setContentView(mUnityPlayer);
|
||||
mUnityPlayer.requestFocus();
|
||||
}
|
||||
|
||||
// When Unity player unloaded move task to background
|
||||
@Override public void onUnityPlayerUnloaded() {
|
||||
moveTaskToBack(true);
|
||||
}
|
||||
|
||||
// Callback before Unity player process is killed
|
||||
@Override public void onUnityPlayerQuitted() {
|
||||
}
|
||||
|
||||
@Override protected void onNewIntent(Intent intent) {
|
||||
// To support deep linking, we need to make sure that the client can get access to
|
||||
// the last sent intent. The clients access this through a JNI api that allows them
|
||||
// to get the intent set on launch. To update that after launch we have to manually
|
||||
// replace the intent with the one caught here.
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
mUnityPlayer.newIntent(intent);
|
||||
}
|
||||
|
||||
// Quit Unity
|
||||
@Override protected void onDestroy ()
|
||||
{
|
||||
mUnityPlayer.destroy();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
// If the activity is in multi window mode or resizing the activity is allowed we will use
|
||||
// onStart/onStop (the visibility callbacks) to determine when to pause/resume.
|
||||
// Otherwise it will be done in onPause/onResume as Unity has done historically to preserve
|
||||
// existing behavior.
|
||||
@Override protected void onStop()
|
||||
{
|
||||
super.onStop();
|
||||
|
||||
if (!MultiWindowSupport.getAllowResizableWindow(this))
|
||||
return;
|
||||
|
||||
mUnityPlayer.pause();
|
||||
}
|
||||
|
||||
@Override protected void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
|
||||
if (!MultiWindowSupport.getAllowResizableWindow(this))
|
||||
return;
|
||||
|
||||
mUnityPlayer.resume();
|
||||
}
|
||||
|
||||
// Pause Unity
|
||||
@Override protected void onPause()
|
||||
{
|
||||
super.onPause();
|
||||
|
||||
MultiWindowSupport.saveMultiWindowMode(this);
|
||||
|
||||
if (MultiWindowSupport.getAllowResizableWindow(this))
|
||||
return;
|
||||
|
||||
mUnityPlayer.pause();
|
||||
}
|
||||
|
||||
// Resume Unity
|
||||
@Override protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
if (MultiWindowSupport.getAllowResizableWindow(this) && !MultiWindowSupport.isMultiWindowModeChangedToTrue(this))
|
||||
return;
|
||||
|
||||
mUnityPlayer.resume();
|
||||
}
|
||||
|
||||
// Low Memory Unity
|
||||
@Override public void onLowMemory()
|
||||
{
|
||||
super.onLowMemory();
|
||||
mUnityPlayer.lowMemory();
|
||||
}
|
||||
|
||||
// Trim Memory Unity
|
||||
@Override public void onTrimMemory(int level)
|
||||
{
|
||||
super.onTrimMemory(level);
|
||||
if (level == TRIM_MEMORY_RUNNING_CRITICAL)
|
||||
{
|
||||
mUnityPlayer.lowMemory();
|
||||
}
|
||||
}
|
||||
|
||||
// This ensures the layout will be correct.
|
||||
@Override public void onConfigurationChanged(Configuration newConfig)
|
||||
{
|
||||
super.onConfigurationChanged(newConfig);
|
||||
mUnityPlayer.configurationChanged(newConfig);
|
||||
}
|
||||
|
||||
// Notify Unity of the focus change.
|
||||
@Override public void onWindowFocusChanged(boolean hasFocus)
|
||||
{
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
mUnityPlayer.windowFocusChanged(hasFocus);
|
||||
}
|
||||
|
||||
// For some reason the multiple keyevent type is not supported by the ndk.
|
||||
// Force event injection by overriding dispatchKeyEvent().
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override public boolean dispatchKeyEvent(KeyEvent event)
|
||||
{
|
||||
if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
|
||||
return mUnityPlayer.injectEvent(event);
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
// Pass any events not handled by (unfocused) views straight to UnityPlayer
|
||||
@Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }
|
||||
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }
|
||||
@Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
|
||||
/*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
|
||||
}
|
@ -2,9 +2,8 @@ package com.cege.games.release.activity;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
import android.webkit.ConsoleMessage;
|
||||
import android.webkit.CookieManager;
|
||||
@ -16,17 +15,41 @@ import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.cege.games.release.MainApplication;
|
||||
import com.cege.games.release.R;
|
||||
import com.cege.games.release.dialog.BaseDialog;
|
||||
import com.cege.games.release.ui.UIManager;
|
||||
import com.cege.games.release.webpage.events.CallJSEvent;
|
||||
import com.cege.games.release.webpage.events.ProxyCBEvent;
|
||||
import com.cege.games.release.webpage.events.WebPageEvent;
|
||||
import com.cege.games.release.webpage.WalletInterface;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.jc.jcfw.JcSDK;
|
||||
import com.jc.jcfw.util.UIUtils;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
public class WebPageActivity extends Activity {
|
||||
private WebView mWebView;
|
||||
private final String FUNID_PREFIX = "webpage_";
|
||||
private final Map<String, Consumer<JSONObject>> actionMap = Maps.newHashMap();
|
||||
private static final String TAG = WebPageActivity.class.getSimpleName();
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@SuppressLint({ "SetJavaScriptEnabled", "DefaultLocale" })
|
||||
@Override
|
||||
protected void onCreate(android.os.Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
resistActions();
|
||||
setContentView(R.layout.activity_web_page);
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
mWebView = findViewById(R.id.web_view);
|
||||
@ -40,12 +63,15 @@ public class WebPageActivity extends Activity {
|
||||
webSettings.setUseWideViewPort(true);
|
||||
webSettings.setLoadWithOverviewMode(true);
|
||||
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView,true);
|
||||
// custom user agent
|
||||
webSettings.setUserAgentString(webSettings.getUserAgentString() + " android_game_web");
|
||||
CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, true);
|
||||
// get url from intent
|
||||
Intent intent = getIntent();
|
||||
String url = intent.getStringExtra("url");
|
||||
// show web view
|
||||
mWebView.loadUrl(url);
|
||||
mWebView.addJavascriptInterface(new WalletInterface(this), "cfwallet_JuEd8Ql5over8kneww");
|
||||
mWebView.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public void onReceivedTitle(WebView view, String title) {
|
||||
@ -55,7 +81,7 @@ public class WebPageActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
|
||||
Log.e("TAG", consoleMessage.message());
|
||||
Log.e(TAG, consoleMessage.message());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -70,11 +96,20 @@ public class WebPageActivity extends Activity {
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
super.onPageStarted(view, url, favicon);
|
||||
Log.i(TAG, "onPageStarted: " + url);
|
||||
String gameData = MainApplication.application.getGameData();
|
||||
String jsCode = String.format("window.platform='android_game_web'; window.gameData='%s';", gameData);
|
||||
callPageJS(jsCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
Log.i(TAG, "onPageFinished: " + url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadResource(WebView view, String url) {
|
||||
Log.i(TAG, "onLoadResource: " + url);
|
||||
@ -94,10 +129,116 @@ public class WebPageActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public void onReceivedSslError(WebView view, android.webkit.SslErrorHandler handler,
|
||||
android.net.http.SslError error) {
|
||||
android.net.http.SslError error) {
|
||||
Log.e(TAG, "onReceivedSslError: " + error.toString());
|
||||
}
|
||||
|
||||
});
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
UIManager.getSingleton().onStartActivity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
EventBus.getDefault().unregister(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void asyncThread(Runnable runnable) {
|
||||
new Thread(runnable).start();
|
||||
}
|
||||
|
||||
private void resistActions() {
|
||||
actionMap.put("closepage", _data -> {
|
||||
finish();
|
||||
});
|
||||
actionMap.put("proxyMethod", _data -> {
|
||||
try {
|
||||
String funId = FUNID_PREFIX + _data.getString("funid");
|
||||
String methodName = _data.getString("methodname");
|
||||
JSONArray params = _data.getJSONArray("params");
|
||||
String[] paramArr = new String[params.length()];
|
||||
for (int i = 0; i < params.length(); i++) {
|
||||
paramArr[i] = params.getString(i);
|
||||
}
|
||||
JcSDK.callNativeJS(funId, methodName, paramArr);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
actionMap.put("scanQRCode", _data -> {
|
||||
try {
|
||||
String funId = FUNID_PREFIX + _data.getString("funid");
|
||||
String title = _data.getString("title");
|
||||
UIManager.getSingleton().showQRScan(funId, title);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
});
|
||||
actionMap.put("showQRCode", _data -> {
|
||||
try {
|
||||
String funId = FUNID_PREFIX + _data.getString("funid");
|
||||
String title = _data.getString("title");
|
||||
UIUtils.showQRCode(this, title, "");
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
UIManager.getSingleton().onActivityResult(this, requestCode, resultCode, data);
|
||||
}
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
// Forward results to EasyPermissions
|
||||
UIManager.getSingleton().onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
public void callPageJS(String javascript) {
|
||||
mWebView.evaluateJavascript(javascript, s -> {
|
||||
Log.i("PAGE", s);
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onPageEvent(WebPageEvent event) {
|
||||
Log.i(TAG, "on page event: " + event.getDataStr());
|
||||
try {
|
||||
JSONObject data = new JSONObject(event.getDataStr());
|
||||
String action = data.getString("action");
|
||||
// JSONArray params = data.getJSONArray("params");
|
||||
if (actionMap.containsKey(action)) {
|
||||
actionMap.get(action).accept(data);
|
||||
} else {
|
||||
Log.w(TAG, String.format("unknown action: %s, data: %s", action, event.getDataStr()));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onCallJSEvent(CallJSEvent event) {
|
||||
String funId = event.getFunId();
|
||||
if (funId.indexOf(FUNID_PREFIX) == 0) {
|
||||
funId = funId.replace(FUNID_PREFIX, "");
|
||||
}
|
||||
callPageJS("nativeCall('" + funId + "', '" + event.getData() + "');");
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onProxyCBEvent(ProxyCBEvent event) {
|
||||
String funId = event.getFunId();
|
||||
if (funId.indexOf(FUNID_PREFIX) == 0) {
|
||||
funId = funId.replace(FUNID_PREFIX, "");
|
||||
}
|
||||
callPageJS("proxyCallback('" + funId + "', '" + event.getData() + "');");
|
||||
}
|
||||
}
|
||||
|
393
app/src/com/cege/games/release/appauth/AppAuthSvr.java
Normal file
393
app/src/com/cege/games/release/appauth/AppAuthSvr.java
Normal file
@ -0,0 +1,393 @@
|
||||
package com.cege.games.release.appauth;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
|
||||
import com.jc.jcfw.JcSDK;
|
||||
import com.jc.jcfw.appauth.AuthStateManager;
|
||||
import com.jc.jcfw.appauth.JConfiguration;
|
||||
import com.jc.jcfw.util.ThreadUtils;
|
||||
|
||||
import net.openid.appauth.AppAuthConfiguration;
|
||||
import net.openid.appauth.AuthState;
|
||||
import net.openid.appauth.AuthorizationException;
|
||||
import net.openid.appauth.AuthorizationRequest;
|
||||
import net.openid.appauth.AuthorizationResponse;
|
||||
import net.openid.appauth.AuthorizationService;
|
||||
import net.openid.appauth.AuthorizationServiceConfiguration;
|
||||
import net.openid.appauth.ClientAuthentication;
|
||||
import net.openid.appauth.ClientSecretBasic;
|
||||
import net.openid.appauth.RegistrationRequest;
|
||||
import net.openid.appauth.RegistrationResponse;
|
||||
import net.openid.appauth.ResponseTypeValues;
|
||||
import net.openid.appauth.TokenRequest;
|
||||
import net.openid.appauth.TokenResponse;
|
||||
import net.openid.appauth.browser.AnyBrowserMatcher;
|
||||
import net.openid.appauth.browser.BrowserMatcher;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class AppAuthSvr {
|
||||
private final String TAG = getClass().getSimpleName();
|
||||
private final Activity activity;
|
||||
private AuthorizationService mAuthService;
|
||||
private AuthStateManager mAuthStateManager;
|
||||
private JConfiguration mConfiguration;
|
||||
private ExecutorService mExecutor;
|
||||
private final AtomicReference<String> mClientId = new AtomicReference<>();
|
||||
private final AtomicReference<AuthorizationRequest> mAuthRequest = new AtomicReference<>();
|
||||
private final AtomicReference<CustomTabsIntent> mAuthIntent = new AtomicReference<>();
|
||||
private CountDownLatch mAuthIntentLatch = new CountDownLatch(1);
|
||||
|
||||
private String mFunID;
|
||||
|
||||
private static final int RC_AUTH = 0X04;
|
||||
|
||||
private Consumer<String> loginCbAction = null;
|
||||
|
||||
@NonNull
|
||||
private final BrowserMatcher mBrowserMatcher = AnyBrowserMatcher.INSTANCE;
|
||||
|
||||
public AppAuthSvr(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public void init(ExecutorService executorService) {
|
||||
mExecutor = executorService;
|
||||
mAuthStateManager = AuthStateManager.getInstance(activity);
|
||||
mConfiguration = JConfiguration.getInstance(activity);
|
||||
mExecutor.submit(this::initializeAppAuth);
|
||||
}
|
||||
|
||||
public void setFunID(String funID) {
|
||||
this.mFunID = funID;
|
||||
}
|
||||
|
||||
public void setLoginCbAction(Consumer<String> func) {
|
||||
this.loginCbAction = func;
|
||||
}
|
||||
|
||||
public AuthState getCurrentState() {
|
||||
return mAuthStateManager.getCurrent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the authorization request, using the browser selected in the
|
||||
* spinner,
|
||||
* and a user-provided `login_hint` if available.
|
||||
*/
|
||||
@WorkerThread
|
||||
public void doAuth(String funID, Consumer<String> func) {
|
||||
this.mFunID = funID;
|
||||
this.loginCbAction = func;
|
||||
try {
|
||||
mAuthIntentLatch.await();
|
||||
} catch (InterruptedException ex) {
|
||||
Log.w(TAG, "Interrupted while waiting for auth intent");
|
||||
}
|
||||
|
||||
Intent intent = mAuthService.getAuthorizationRequestIntent(
|
||||
mAuthRequest.get(),
|
||||
mAuthIntent.get());
|
||||
activity.startActivityForResult(intent, RC_AUTH);
|
||||
}
|
||||
|
||||
public void parseLoginResult(@NonNull Intent data) {
|
||||
AuthorizationResponse response = AuthorizationResponse.fromIntent(data);
|
||||
AuthorizationException ex = AuthorizationException.fromIntent(data);
|
||||
if (response != null || ex != null) {
|
||||
mAuthStateManager.updateAfterAuthorization(response, ex);
|
||||
}
|
||||
if (response != null && response.authorizationCode != null) {
|
||||
// authorization code exchange is required
|
||||
mAuthStateManager.updateAfterAuthorization(response, ex);
|
||||
exchangeAuthorizationCode(response);
|
||||
} else if (ex != null) {
|
||||
Log.i(TAG, "Authorization flow failed: " + ex.getMessage());
|
||||
errorCB("Authorization flow failed: " + ex.getMessage());
|
||||
} else {
|
||||
Log.i(TAG, "No authorization state retained - reauthorization required");
|
||||
errorCB("No authorization state retained - reauthorization required");
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshToken(String funID, Consumer<String> func) {
|
||||
this.mFunID = funID;
|
||||
this.loginCbAction = func;
|
||||
AuthState state = getCurrentState();
|
||||
performTokenRequest(state.createTokenRefreshRequest(), this::handleCodeExchangeResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the authorization service configuration if necessary, either from
|
||||
* the local
|
||||
* static values or by retrieving an OpenID discovery document.
|
||||
*/
|
||||
@WorkerThread
|
||||
private void initializeAppAuth() {
|
||||
Log.i(TAG, "Initializing AppAuth");
|
||||
recreateAuthorizationService();
|
||||
|
||||
if (mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration() != null) {
|
||||
// configuration is already created, skip to client initialization
|
||||
Log.i(TAG, "auth config already established");
|
||||
initializeClient();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are not using discovery, build the authorization service configuration
|
||||
// directly
|
||||
// from the static configuration values.
|
||||
if (mConfiguration.getDiscoveryUri() == null) {
|
||||
Log.i(TAG, "Creating auth config from res/raw/auth_config.json");
|
||||
AuthorizationServiceConfiguration config = new AuthorizationServiceConfiguration(
|
||||
mConfiguration.getAuthEndpointUri(),
|
||||
mConfiguration.getTokenEndpointUri(),
|
||||
mConfiguration.getRegistrationEndpointUri(),
|
||||
mConfiguration.getEndSessionEndpoint());
|
||||
|
||||
mAuthStateManager.replace(new AuthState(config));
|
||||
initializeClient();
|
||||
return;
|
||||
}
|
||||
|
||||
// WrongThread inference is incorrect for lambdas
|
||||
// noinspection WrongThread
|
||||
Log.i(TAG, "Retrieving OpenID discovery doc");
|
||||
AuthorizationServiceConfiguration.fetchFromUrl(
|
||||
mConfiguration.getDiscoveryUri(),
|
||||
this::handleConfigurationRetrievalResult,
|
||||
mConfiguration.getConnectionBuilder());
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private void handleConfigurationRetrievalResult(
|
||||
AuthorizationServiceConfiguration config,
|
||||
AuthorizationException ex) {
|
||||
if (config == null) {
|
||||
Log.i(TAG, "Failed to retrieve discovery document", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Discovery document retrieved");
|
||||
mAuthStateManager.replace(new AuthState(config));
|
||||
mExecutor.submit(this::initializeClient);
|
||||
}
|
||||
|
||||
private void recreateAuthorizationService() {
|
||||
if (mAuthService != null) {
|
||||
Log.i(TAG, "Discarding existing AuthService instance");
|
||||
mAuthService.dispose();
|
||||
}
|
||||
mAuthService = createAuthorizationService();
|
||||
mAuthRequest.set(null);
|
||||
mAuthIntent.set(null);
|
||||
}
|
||||
|
||||
private AuthorizationService createAuthorizationService() {
|
||||
Log.i(TAG, "Creating authorization service");
|
||||
AppAuthConfiguration.Builder builder = new AppAuthConfiguration.Builder();
|
||||
builder.setBrowserMatcher(mBrowserMatcher);
|
||||
builder.setConnectionBuilder(mConfiguration.getConnectionBuilder());
|
||||
|
||||
return new AuthorizationService(activity, builder.build());
|
||||
}
|
||||
|
||||
private void createAuthRequest(@Nullable String loginHint) {
|
||||
Log.i(TAG, "Creating auth request for login hint: " + loginHint);
|
||||
AuthorizationRequest.Builder authRequestBuilder = new AuthorizationRequest.Builder(
|
||||
mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(),
|
||||
mClientId.get(),
|
||||
ResponseTypeValues.CODE,
|
||||
mConfiguration.getRedirectUri())
|
||||
// apple need `form_post` when authorization_scope has `name email `
|
||||
// .setResponseMode("form_post")
|
||||
// .setResponseType("code id_token")
|
||||
.setScope(mConfiguration.getScope());
|
||||
if (!TextUtils.isEmpty(loginHint)) {
|
||||
authRequestBuilder.setLoginHint(loginHint);
|
||||
}
|
||||
mAuthRequest.set(authRequestBuilder.build());
|
||||
}
|
||||
|
||||
private void warmUpBrowser() {
|
||||
mAuthIntentLatch = new CountDownLatch(1);
|
||||
mExecutor.execute(() -> {
|
||||
Log.i(TAG, "Warming up browser instance for auth request");
|
||||
Uri uri = mAuthRequest.get().toUri();
|
||||
Log.d(TAG, "URI: " + uri);
|
||||
CustomTabsIntent.Builder intentBuilder = mAuthService.createCustomTabsIntentBuilder(uri);
|
||||
mAuthIntent.set(intentBuilder.build());
|
||||
mAuthIntentLatch.countDown();
|
||||
});
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private void initializeAuthRequest() {
|
||||
createAuthRequest("");
|
||||
warmUpBrowser();
|
||||
displayAuthOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a dynamic registration request if a client ID is not provided by
|
||||
* the static
|
||||
* configuration.
|
||||
*/
|
||||
@WorkerThread
|
||||
private void initializeClient() {
|
||||
if (mConfiguration.getClientId() != null) {
|
||||
Log.i(TAG, "Using static client ID: " + mConfiguration.getClientId());
|
||||
// use a statically configured client ID
|
||||
mClientId.set(mConfiguration.getClientId());
|
||||
ThreadUtils.runInMain(this::initializeAuthRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
RegistrationResponse lastResponse = mAuthStateManager.getCurrent().getLastRegistrationResponse();
|
||||
if (lastResponse != null) {
|
||||
Log.i(TAG, "Using dynamic client ID: " + lastResponse.clientId);
|
||||
// already dynamically registered a client ID
|
||||
mClientId.set(lastResponse.clientId);
|
||||
ThreadUtils.runInMain(this::initializeAuthRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
// WrongThread inference is incorrect for lambdas
|
||||
// noinspection WrongThread
|
||||
Log.i(TAG, "Dynamically registering client");
|
||||
|
||||
RegistrationRequest registrationRequest = new RegistrationRequest.Builder(
|
||||
mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(),
|
||||
Collections.singletonList(mConfiguration.getRedirectUri()))
|
||||
.setTokenEndpointAuthenticationMethod(ClientSecretBasic.NAME)
|
||||
.build();
|
||||
|
||||
mAuthService.performRegistrationRequest(
|
||||
registrationRequest,
|
||||
this::handleRegistrationResponse);
|
||||
}
|
||||
|
||||
private void handleRegistrationResponse(
|
||||
RegistrationResponse response,
|
||||
AuthorizationException ex) {
|
||||
mAuthStateManager.updateAfterRegistration(response, ex);
|
||||
if (response == null) {
|
||||
Log.i(TAG, "Failed to dynamically register client", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Dynamically registered client: " + response.clientId);
|
||||
mClientId.set(response.clientId);
|
||||
initializeAuthRequest();
|
||||
}
|
||||
|
||||
private void displayAuthOptions() {
|
||||
AuthState state = mAuthStateManager.getCurrent();
|
||||
AuthorizationServiceConfiguration config = state.getAuthorizationServiceConfiguration();
|
||||
|
||||
String authEndpointStr;
|
||||
if (config.discoveryDoc != null) {
|
||||
authEndpointStr = "Discovered auth endpoint: \n";
|
||||
} else {
|
||||
authEndpointStr = "Static auth endpoint: \n";
|
||||
}
|
||||
authEndpointStr += config.authorizationEndpoint;
|
||||
Log.i(TAG, authEndpointStr);
|
||||
|
||||
String clientIdStr;
|
||||
if (state.getLastRegistrationResponse() != null) {
|
||||
clientIdStr = "Dynamic client ID: \n";
|
||||
} else {
|
||||
clientIdStr = "Static client ID: \n";
|
||||
}
|
||||
clientIdStr += mClientId;
|
||||
Log.i(TAG, clientIdStr);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private void exchangeAuthorizationCode(AuthorizationResponse authorizationResponse) {
|
||||
Log.d(TAG, "Exchanging authorization code");
|
||||
performTokenRequest(
|
||||
authorizationResponse.createTokenExchangeRequest(),
|
||||
this::handleCodeExchangeResponse);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private void performTokenRequest(
|
||||
TokenRequest request,
|
||||
AuthorizationService.TokenResponseCallback callback) {
|
||||
ClientAuthentication clientAuthentication;
|
||||
try {
|
||||
clientAuthentication = mAuthStateManager.getCurrent().getClientAuthentication();
|
||||
} catch (ClientAuthentication.UnsupportedAuthenticationMethod ex) {
|
||||
Log.d(TAG, "Token request cannot be made, client authentication for the token "
|
||||
+ "endpoint could not be constructed (%s)", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
mAuthService.performTokenRequest(
|
||||
request,
|
||||
clientAuthentication,
|
||||
callback);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void handleCodeExchangeResponse(
|
||||
@Nullable TokenResponse tokenResponse,
|
||||
@Nullable AuthorizationException authException) {
|
||||
mAuthStateManager.updateAfterTokenResponse(tokenResponse, authException);
|
||||
if (!mAuthStateManager.getCurrent().isAuthorized()) {
|
||||
final String message = "Authorization Code exchange failed: "
|
||||
+ ((authException != null) ? authException.error : "");
|
||||
Log.d(TAG, message);
|
||||
errorCB(message);
|
||||
} else {
|
||||
checkAuthStateAndCB();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAuthStateAndCB() {
|
||||
AuthState state = mAuthStateManager.getCurrent();
|
||||
Log.d(TAG, "login success, auth state: " + state.isAuthorized());
|
||||
Log.d(TAG, "id token : " + state.getIdToken());
|
||||
Log.d(TAG, "access token: " + state.getAccessToken());
|
||||
Log.d(TAG, "refresh token: " + state.getRefreshToken());
|
||||
mAuthStateManager.replace(state);
|
||||
if (loginCbAction != null) {
|
||||
mExecutor.submit(() -> {
|
||||
loginCbAction.accept("success");
|
||||
clearParams();
|
||||
});
|
||||
} else {
|
||||
successCB(state.getIdToken());
|
||||
}
|
||||
}
|
||||
private void clearParams() {
|
||||
this.mFunID = null;
|
||||
this.loginCbAction = null;
|
||||
}
|
||||
public void errorCB(String msg) {
|
||||
JcSDK.nativeCb(mFunID, msg, null);
|
||||
clearParams();
|
||||
}
|
||||
|
||||
public void successCB(String dataStr) {
|
||||
JcSDK.nativeCb(mFunID, null, dataStr);
|
||||
clearParams();
|
||||
}
|
||||
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package com.cege.games.release.apple;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import com.cege.games.release.Constants;
|
||||
import com.cege.games.release.R;
|
||||
|
||||
public class AppleLoginActivity extends Activity {
|
||||
private static final String TAG = AppleLoginActivity.class.getSimpleName();
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Override
|
||||
public void onCreate(android.os.Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Intent intent = getIntent();
|
||||
setContentView(R.layout.activity_web_page);
|
||||
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
WebView mWebView = findViewById(R.id.web_view);
|
||||
WebSettings webSettings = mWebView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
webSettings.setDomStorageEnabled(true);
|
||||
webSettings.setDatabaseEnabled(true);
|
||||
webSettings.setAllowContentAccess(true);
|
||||
webSettings.setAppCacheEnabled(true);
|
||||
webSettings.setBuiltInZoomControls(true);
|
||||
webSettings.setUseWideViewPort(true);
|
||||
webSettings.setLoadWithOverviewMode(true);
|
||||
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView,true);
|
||||
// get url from intent
|
||||
String funId = intent.getStringExtra("funId");
|
||||
String url = Constants.APPLE_AUTH_URL
|
||||
+ "?response_type=code%20id_token&v=1.1.6&response_mode=form_post&client_id="
|
||||
+ Constants.APPLE_CLIENT_ID + "&scope=" + Constants.APPLE_SCOPE + "&state=" + funId + "&redirect_uri="
|
||||
+ Constants.APPLE_REDIRECT_URI;
|
||||
// show web view
|
||||
mWebView.loadUrl(url);
|
||||
mWebView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
String url = request.getUrl().toString();
|
||||
Log.i(TAG, url);
|
||||
if (url.startsWith("cebg")) {
|
||||
// Close the dialog after getting the authorization code
|
||||
Intent myapp_intent = new Intent(Intent.ACTION_VIEW);
|
||||
myapp_intent.setData(Uri.parse(url));
|
||||
startActivity(myapp_intent);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void onPageFinished(WebView view, String url) {
|
||||
// super.onPageFinished(view, url);
|
||||
// Rect displayRectangle = new Rect();
|
||||
// Window window = getWindow();
|
||||
// window.getDecorView().getWindowVisibleDisplayFrame(displayRectangle);
|
||||
// ViewGroup.LayoutParams layoutparms = view.getLayoutParams();
|
||||
// layoutparms.height = displayRectangle.height();
|
||||
// layoutparms.width = displayRectangle.width();
|
||||
// view.setLayoutParams(layoutparms);
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package com.cege.games.release.apple;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.jc.jcfw.JcSDK;
|
||||
|
||||
public class AppleLoginCbActivity extends Activity {
|
||||
private static final String TAG = AppleLoginCbActivity.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(TAG, "receive apple login callback");
|
||||
Intent intent = getIntent();
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
|
||||
Uri uri = intent.getData();
|
||||
String error = uri.getQueryParameter("error");
|
||||
String state = uri.getQueryParameter("state");
|
||||
if (null != error && !error.isEmpty()) {
|
||||
JcSDK.nativeCb(state, error, null);
|
||||
} else {
|
||||
String token = uri.getQueryParameter("token");
|
||||
JcSDK.nativeCb(state, null, token);
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +1,26 @@
|
||||
package com.cege.games.release.dialog;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Dialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.cege.games.release.MainActivity;
|
||||
import com.cege.games.release.R;
|
||||
import com.jc.jcfw.util.FileUtils;
|
||||
import com.jc.jcfw.util.ThreadUtils;
|
||||
import com.jc.jcfw.util.UIUtils;
|
||||
import com.king.zxing.util.CodeUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import pub.devrel.easypermissions.AfterPermissionGranted;
|
||||
import pub.devrel.easypermissions.EasyPermissions;
|
||||
|
||||
public class QRCodeActivity extends Dialog implements EasyPermissions.PermissionCallbacks {
|
||||
|
||||
public class QRCodeActivity extends Dialog {
|
||||
private final Context baseContent;
|
||||
private TextView titleLabel;
|
||||
private String mContent;
|
||||
private ImageView ivCode;
|
||||
private String oid;
|
||||
private Bitmap bitmap;
|
||||
private Button localBtn;
|
||||
public static final int RC_SAVE_QR = 0X111;
|
||||
|
||||
public QRCodeActivity(Context context) {
|
||||
super(context, R.style.DialogStyle);
|
||||
@ -43,77 +33,27 @@ public class QRCodeActivity extends Dialog implements EasyPermissions.Permission
|
||||
setContentView(R.layout.qrcode_view);
|
||||
ivCode = findViewById(R.id.ivCode);
|
||||
titleLabel = findViewById(R.id.qrTitleLabel);
|
||||
localBtn = findViewById(R.id.qrSaveBtn);
|
||||
if (oid != null && !"".equals(oid)) {
|
||||
localBtn.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
localBtn.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
localBtn.setOnClickListener(v -> onClickSaveImg());
|
||||
ivCode.setOnClickListener(v -> onClickImg());
|
||||
}
|
||||
|
||||
public void showQRCode(String content, String title, String _oid) {
|
||||
oid = _oid;
|
||||
if (localBtn != null && oid != null && !"".equals(oid)) {
|
||||
localBtn.setVisibility(View.VISIBLE);
|
||||
}
|
||||
public void showQRCode(String content, String title) {
|
||||
this.mContent = content;
|
||||
new Thread(() -> {
|
||||
bitmap = CodeUtils.createQRCode(content, 500, null);
|
||||
MainActivity.app.runOnUiThread(() -> {
|
||||
ThreadUtils.runInMain(() -> {
|
||||
titleLabel.setText(title);
|
||||
ivCode.setImageBitmap(bitmap);
|
||||
});
|
||||
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void saveAndClose() {
|
||||
new Thread(() -> {
|
||||
String uri = FileUtils.saveBitmap(baseContent, oid, bitmap);
|
||||
MainActivity.app.runOnUiThread(() -> {
|
||||
if (uri != null && !"".equals(uri)) {
|
||||
MainActivity.app.showToast("Wallet restore key had save to System Album");
|
||||
dismiss();
|
||||
} else {
|
||||
MainActivity.app.showToast("Wallet restore key save fail");
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
protected void onClickImg() {
|
||||
ThreadUtils.runInMain(() -> {
|
||||
ClipboardManager myClipboard = (ClipboardManager)baseContent.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData myClip = ClipData.newPlainText("text", mContent);
|
||||
myClipboard.setPrimaryClip(myClip);
|
||||
UIUtils.showToast(baseContent, "Content copied to clipboard.");
|
||||
});
|
||||
}
|
||||
|
||||
protected void onClickSaveImg() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
saveAndClose();
|
||||
} else {
|
||||
checkCameraPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
|
||||
MainActivity.app.showToast("We need Write WRITE_EXTERNAL_STORAGE for backup Wallet Restore Key");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
|
||||
}
|
||||
|
||||
@AfterPermissionGranted(RC_SAVE_QR)
|
||||
private void checkCameraPermissions() {
|
||||
String[] perms = { Manifest.permission.WRITE_EXTERNAL_STORAGE };
|
||||
if (EasyPermissions.hasPermissions(this.baseContent, perms)) {
|
||||
saveAndClose();
|
||||
} else {
|
||||
// Do not have permissions, request them now
|
||||
EasyPermissions.requestPermissions(MainActivity.app,
|
||||
"We need Write WRITE_EXTERNAL_STORAGE for save QRCode to System Album",
|
||||
RC_SAVE_QR, perms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
package com.cege.games.release.oauth;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.cege.games.release.MainActivity;
|
||||
import com.jc.jcfw.JcSDK;
|
||||
|
||||
public class OAuthLoginCbActivity extends Activity {
|
||||
private static final String TAG = OAuthLoginCbActivity.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(TAG, "receive oauth login callback");
|
||||
Intent intent = getIntent();
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
|
||||
Uri uri = intent.getData();
|
||||
String error = uri.getQueryParameter("error");
|
||||
String state = uri.getQueryParameter("state");
|
||||
if (null != error && !error.isEmpty()) {
|
||||
JcSDK.nativeCb(state, error, null);
|
||||
} else {
|
||||
String token = uri.getQueryParameter("token");
|
||||
// if token is null, get token and state from uri fragment
|
||||
if (null == token || "undefined".equals(token) || token.isEmpty()){
|
||||
String fragment = uri.getFragment();
|
||||
String[] vals = fragment.split("&");
|
||||
for (String val : vals) {
|
||||
if (val.startsWith("token=")) {
|
||||
token = val.substring(6);
|
||||
} else if (val.startsWith("id_token=")) {
|
||||
token = val.substring(9);
|
||||
} else if (val.startsWith("state=")) {
|
||||
state = val.substring(6);
|
||||
}
|
||||
}
|
||||
}
|
||||
JcSDK.nativeCb(state, null, token);
|
||||
}
|
||||
Intent intentMain = new Intent(this, MainActivity.class);
|
||||
startActivity(intentMain);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
61
app/src/com/cege/games/release/oauth/OAuthUtil.java
Normal file
61
app/src/com/cege/games/release/oauth/OAuthUtil.java
Normal file
@ -0,0 +1,61 @@
|
||||
package com.cege.games.release.oauth;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
|
||||
import net.openid.appauth.AuthorizationRequest;
|
||||
import net.openid.appauth.AuthorizationService;
|
||||
import net.openid.appauth.AuthorizationServiceConfiguration;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class OAuthUtil {
|
||||
private final Activity mActivity;
|
||||
|
||||
public OAuthUtil(Activity activity) {
|
||||
this.mActivity = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param funId
|
||||
* @param jsonData: {
|
||||
* "endpoint": "",
|
||||
* "client_id": "",
|
||||
* "redirect_uri": "",
|
||||
* "response_type": "code",
|
||||
* "response_mode": "form_post", nullable
|
||||
* "scopes": "",
|
||||
* }
|
||||
* @throws JSONException
|
||||
*/
|
||||
public void startLogin(String funId, String jsonData) throws JSONException {
|
||||
JSONObject data = new JSONObject(jsonData);
|
||||
AuthorizationServiceConfiguration config = new AuthorizationServiceConfiguration(
|
||||
Uri.parse(data.getString("endpoint")),
|
||||
Uri.parse("")
|
||||
);
|
||||
AuthorizationRequest.Builder authRequestBuilder = new AuthorizationRequest.Builder(
|
||||
config,
|
||||
data.getString("client_id"),
|
||||
data.getString("response_type"),
|
||||
Uri.parse(data.getString("redirect_uri"))
|
||||
);
|
||||
String[] scopes = data.getString("scopes").split(" +");
|
||||
if (data.has("response_mode")) {
|
||||
authRequestBuilder.setResponseMode(data.getString("response_mode"));
|
||||
}
|
||||
AuthorizationRequest authRequest = authRequestBuilder
|
||||
.setScopes(scopes)
|
||||
.setCodeVerifier(null, null, null)
|
||||
.setState(funId)
|
||||
.setNonce(funId)
|
||||
.build();
|
||||
AuthorizationService service = new AuthorizationService(this.mActivity);
|
||||
Intent authIntent = service.getAuthorizationRequestIntent(authRequest);
|
||||
this.mActivity.startActivity(authIntent);
|
||||
}
|
||||
}
|
177
app/src/com/cege/games/release/ui/UIManager.java
Normal file
177
app/src/com/cege/games/release/ui/UIManager.java
Normal file
@ -0,0 +1,177 @@
|
||||
package com.cege.games.release.ui;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
|
||||
import com.cege.games.release.R;
|
||||
import com.cege.games.release.activity.CustomCaptureActivity;
|
||||
import com.google.common.base.Strings;
|
||||
import com.jc.jcfw.JcSDK;
|
||||
import com.king.zxing.CameraScan;
|
||||
import com.king.zxing.util.CodeUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import pub.devrel.easypermissions.AfterPermissionGranted;
|
||||
import pub.devrel.easypermissions.EasyPermissions;
|
||||
|
||||
public class UIManager implements EasyPermissions.PermissionCallbacks{
|
||||
private final String TAG = this.getClass().getSimpleName();
|
||||
private volatile static UIManager singleton;
|
||||
|
||||
private WeakReference<Activity> mContext;
|
||||
|
||||
private UIManager() {
|
||||
}
|
||||
|
||||
private String mFunID;
|
||||
private String mTitle;
|
||||
|
||||
public static final int REQUEST_CODE_SCAN = 0X101;
|
||||
public static final int REQUEST_CODE_PHOTO = 0X102;
|
||||
|
||||
public static final int JUMP_CODE_PHOTO = 0X201;
|
||||
public static final int RC_CAMERA = 0X103;
|
||||
public static final int RC_READ_PHOTO = 0X104;
|
||||
|
||||
public static final String KEY_TITLE = "key_title";
|
||||
public static final String KEY_IS_CONTINUOUS = "key_continuous_scan";
|
||||
|
||||
public static UIManager getSingleton() {
|
||||
if (singleton == null) {
|
||||
synchronized (UIManager.class) {
|
||||
if (singleton == null) {
|
||||
singleton = new UIManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
public void onStartActivity(Activity context) {
|
||||
mContext = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
||||
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_SCAN:
|
||||
String result = CameraScan.parseScanResult(data);
|
||||
Log.i(TAG, "scan qrcode with funId: " + mFunID + " result: " + result);
|
||||
successCb(result);
|
||||
break;
|
||||
case REQUEST_CODE_PHOTO:
|
||||
parsePhoto(data);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (resultCode == JUMP_CODE_PHOTO && requestCode == REQUEST_CODE_SCAN) {
|
||||
startPhotoCode();
|
||||
} else if (requestCode == REQUEST_CODE_SCAN || requestCode == REQUEST_CODE_PHOTO) {
|
||||
errorCb("activity result with code: " + resultCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
// Forward results to EasyPermissions
|
||||
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
|
||||
}
|
||||
@Override
|
||||
public void onPermissionsGranted(int requestCode, @NonNull List<String> list) {
|
||||
// Some permissions have been granted
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionsDenied(int requestCode, @NonNull List<String> list) {
|
||||
// Some permissions have been denied
|
||||
if ((requestCode == RC_CAMERA || requestCode == RC_READ_PHOTO) && null != mFunID && !"".equals(mFunID)) {
|
||||
errorCb("User cancel");
|
||||
}
|
||||
}
|
||||
|
||||
public void showQRScan(String funId, String title) {
|
||||
this.mFunID = funId;
|
||||
this.mTitle = title;
|
||||
this.checkCameraPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* check if had permission for camera
|
||||
*/
|
||||
@AfterPermissionGranted(RC_CAMERA)
|
||||
private void checkCameraPermissions() {
|
||||
String[] perms = { Manifest.permission.CAMERA };
|
||||
Activity context = mContext.get();
|
||||
if (EasyPermissions.hasPermissions(context, perms)) {
|
||||
startScan(mTitle);
|
||||
} else {
|
||||
// Do not have permissions, request them now
|
||||
EasyPermissions.requestPermissions(context, context.getString(R.string.permission_camera), RC_CAMERA, perms);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* scan qrcode
|
||||
*/
|
||||
private void startScan(String title) {
|
||||
Activity context = mContext.get();
|
||||
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeCustomAnimation(context, R.anim.in, R.anim.out);
|
||||
Intent intent = new Intent(context, CustomCaptureActivity.class);
|
||||
intent.putExtra(KEY_TITLE, title);
|
||||
intent.putExtra(KEY_IS_CONTINUOUS, false);
|
||||
context.runOnUiThread(() -> context.startActivityForResult(intent, REQUEST_CODE_SCAN, optionsCompat.toBundle()));
|
||||
}
|
||||
private void parsePhoto(Intent data) {
|
||||
Activity context = mContext.get();
|
||||
try {
|
||||
Bitmap bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), data.getData());
|
||||
parsePhotoData(bitmap);
|
||||
} catch (Exception e) {
|
||||
errorCb(e.toString());
|
||||
}
|
||||
}
|
||||
private void asyncThread(Runnable runnable) {
|
||||
new Thread(runnable).start();
|
||||
}
|
||||
private void parsePhotoData(Bitmap bitmap) {
|
||||
asyncThread(() -> {
|
||||
final String result = CodeUtils.parseQRCode(bitmap);
|
||||
if (Strings.isNullOrEmpty(result)) {
|
||||
errorCb("no qrdeata");
|
||||
} else {
|
||||
successCb(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* start image scan
|
||||
*/
|
||||
public void startPhotoCode() {
|
||||
Intent pickIntent = new Intent(Intent.ACTION_PICK,
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
|
||||
mContext.get().startActivityForResult(pickIntent, REQUEST_CODE_PHOTO);
|
||||
}
|
||||
|
||||
private void successCb(String result) {
|
||||
JcSDK.nativeCb(mFunID, null, result);
|
||||
mFunID = "";
|
||||
mTitle = "";
|
||||
}
|
||||
|
||||
private void errorCb(String errMsg) {
|
||||
JcSDK.nativeCb(mFunID, errMsg, null);
|
||||
mFunID = "";
|
||||
mTitle = "";
|
||||
}
|
||||
}
|
222
app/src/com/cege/games/release/wallet/WalletUtil.java
Normal file
222
app/src/com/cege/games/release/wallet/WalletUtil.java
Normal file
@ -0,0 +1,222 @@
|
||||
package com.cege.games.release.wallet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.cege.games.release.R;
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn;
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
|
||||
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
|
||||
import com.google.api.services.drive.Drive;
|
||||
import com.google.api.services.drive.DriveScopes;
|
||||
import com.google.common.base.Strings;
|
||||
import com.jc.jcfw.JcSDK;
|
||||
import com.jc.jcfw.NativeResult;
|
||||
import com.jc.jcfw.util.DriveUtils;
|
||||
import com.jc.jcfw.util.DriverApiUtils;
|
||||
import com.jc.jcfw.util.FileUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class WalletUtil {
|
||||
private final Context mContext;
|
||||
|
||||
private String funID;
|
||||
private String account;
|
||||
public WalletUtil(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
private final String TAG = getClass().getSimpleName();
|
||||
|
||||
public void updateParams(String funID, String account) {
|
||||
this.funID = funID;
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public void clearParams() {
|
||||
this.funID = null;
|
||||
this.account = null;
|
||||
}
|
||||
|
||||
public String getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void errorCB(String msg) {
|
||||
JcSDK.nativeCb(funID, msg, null);
|
||||
clearParams();
|
||||
}
|
||||
|
||||
public void successCB(String dataStr) {
|
||||
JcSDK.nativeCb(funID, null, dataStr);
|
||||
clearParams();
|
||||
}
|
||||
|
||||
public static String generateFileName(String account) {
|
||||
return String.format("wallet_%s.json", account);
|
||||
}
|
||||
|
||||
public File getWalletCfgFile(String account) {
|
||||
String filename = generateFileName(account);
|
||||
File dic = new File(mContext.getFilesDir() + "/wallets");
|
||||
if (!dic.exists()) {
|
||||
dic.mkdir();
|
||||
}
|
||||
return new File(mContext.getFilesDir() + "/wallets", filename);
|
||||
}
|
||||
|
||||
public void savePassToLocal(String pass, Consumer<File> func) {
|
||||
File filePath = getWalletCfgFile(account);
|
||||
JSONObject content = new JSONObject();
|
||||
try {
|
||||
content.put("pass", pass);
|
||||
FileUtils.writeFile(filePath, content.toString());
|
||||
func.accept(filePath);
|
||||
} catch (JSONException | IOException e) {
|
||||
errorCB(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void getPassLocal() {
|
||||
try {
|
||||
JSONObject json = loadLocalCfg(account);
|
||||
String passEncrypted = json.getString("pass");
|
||||
String passDecrypted = JcSDK.decryptPass(account, passEncrypted);
|
||||
successCB(passDecrypted);
|
||||
} catch (JSONException | IOException e) {
|
||||
errorCB(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean localCfgExists(String account) {
|
||||
File filePath = getWalletCfgFile(account);
|
||||
return filePath.exists();
|
||||
}
|
||||
|
||||
public JSONObject loadLocalCfg(String account) throws JSONException, IOException {
|
||||
File filePath = getWalletCfgFile(account);
|
||||
if (!filePath.exists()) {
|
||||
return null;
|
||||
}
|
||||
return FileUtils.readJsonFromFile(filePath);
|
||||
}
|
||||
|
||||
public void uploadCfgWithGPS(Consumer<NativeResult> func) {
|
||||
GoogleSignInAccount ga = GoogleSignIn.getLastSignedInAccount(mContext);
|
||||
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(mContext,
|
||||
Collections.singletonList(DriveScopes.DRIVE_APPDATA));
|
||||
credential.setSelectedAccount(ga.getAccount());
|
||||
Drive service = DriveUtils.generateService(credential, mContext.getString(R.string.app_name));
|
||||
try {
|
||||
File file = getWalletCfgFile(account);
|
||||
String fileName = file.getName();
|
||||
String fileId = DriveUtils.queryOneAppFile(service, fileName);
|
||||
if (Strings.isNullOrEmpty(fileId)) {
|
||||
Log.i(TAG, String.format("%s not exists in drive, upload...", fileName));
|
||||
fileId = DriveUtils.uploadAppFile(service, file, "application/json");
|
||||
}
|
||||
Log.i(TAG, "File ID: " + fileId);
|
||||
func.accept(new NativeResult(funID, null, fileId));
|
||||
successCB(fileId);
|
||||
} catch (GoogleJsonResponseException e) {
|
||||
Log.i(TAG, "Unable to create file: " + e.getDetails());
|
||||
func.accept(new NativeResult(funID, e.getMessage(), null));
|
||||
errorCB("Unable to create file: " +e.getMessage());
|
||||
} catch (IOException e) {
|
||||
Log.i(TAG, e.getMessage());
|
||||
func.accept(new NativeResult(funID, e.getMessage(), null));
|
||||
errorCB("Unable to create file: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void downloadCfgWithGPS(Consumer<File> func) {
|
||||
GoogleSignInAccount ga = GoogleSignIn.getLastSignedInAccount(mContext);
|
||||
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(mContext,
|
||||
Collections.singletonList(DriveScopes.DRIVE_APPDATA));
|
||||
credential.setSelectedAccount(ga.getAccount());
|
||||
Drive service = DriveUtils.generateService(credential, mContext.getString(R.string.app_name));
|
||||
String fileName = generateFileName(account);
|
||||
String fileId = DriveUtils.queryOneAppFile(service, fileName);
|
||||
|
||||
if (Strings.isNullOrEmpty(fileId)) {
|
||||
Log.i(TAG, "file not found in drive");
|
||||
errorCB("file not found in drive");
|
||||
return;
|
||||
}
|
||||
boolean downloadSuccess = false;
|
||||
int count = 0;
|
||||
File fileLocal = getWalletCfgFile(account);
|
||||
while (!downloadSuccess && count++ < 10) {
|
||||
Log.i(TAG, String.format("download file: %s with GPS, try: %d", fileName, count));
|
||||
try {
|
||||
String jsonStr = DriveUtils.downloadFile(service, fileId);
|
||||
FileUtils.writeFile(fileLocal, jsonStr);
|
||||
downloadSuccess = true;
|
||||
} catch (IOException e) {
|
||||
Log.i(TAG, "error download file with GPS");
|
||||
}
|
||||
}
|
||||
if (downloadSuccess) {
|
||||
func.accept(fileLocal);
|
||||
} else {
|
||||
errorCB("error download file with GPS");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void downloadCfgWithApi(String accessToken, Consumer<File> func) {
|
||||
DriverApiUtils api = new DriverApiUtils(accessToken);
|
||||
String fileName = generateFileName(account);
|
||||
File fileLocal = getWalletCfgFile(account);
|
||||
boolean downloadSuccess = false;
|
||||
int count = 0;
|
||||
while (!downloadSuccess && count++ < 10) {
|
||||
Log.i(TAG, String.format("download file: %s with api, try: %d", fileName, count));
|
||||
try {
|
||||
String fileId = api.queryOneAppFile(fileName);
|
||||
if (Strings.isNullOrEmpty(fileId)) {
|
||||
Log.i(TAG, "file not found in drive");
|
||||
throw new IOException();
|
||||
}
|
||||
String jsonStr = api.fileInfo(fileId);
|
||||
FileUtils.writeFile(fileLocal, jsonStr);
|
||||
downloadSuccess = true;
|
||||
} catch (IOException | JSONException e) {
|
||||
Log.i(TAG, "error download file with api");
|
||||
}
|
||||
}
|
||||
if (downloadSuccess) {
|
||||
func.accept(fileLocal);
|
||||
} else {
|
||||
errorCB("error download file with api");
|
||||
}
|
||||
}
|
||||
|
||||
public void uploadCfgWithApi(String accessToken, Consumer<NativeResult> func) {
|
||||
DriverApiUtils api = new DriverApiUtils(accessToken);
|
||||
File fileLocal = getWalletCfgFile(account);
|
||||
try {
|
||||
String fileName = generateFileName(account);
|
||||
String fileId = api.queryOneAppFile(fileName);
|
||||
if (Strings.isNullOrEmpty(fileId)) {
|
||||
String rep = api.uploadFile(fileLocal, "application/json");
|
||||
JSONObject repData = new JSONObject(rep);
|
||||
fileId = repData.getString("id");
|
||||
}
|
||||
Log.i(TAG, "success upload file with api, ID: " + fileId);
|
||||
func.accept(new NativeResult(funID, null, fileId));
|
||||
successCB(fileId);
|
||||
} catch (IOException | JSONException e) {
|
||||
func.accept(new NativeResult(funID, e.getMessage(), null));
|
||||
errorCB("error upload file with api: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
24
app/src/com/cege/games/release/webpage/WalletInterface.java
Normal file
24
app/src/com/cege/games/release/webpage/WalletInterface.java
Normal file
@ -0,0 +1,24 @@
|
||||
package com.cege.games.release.webpage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.webkit.JavascriptInterface;
|
||||
|
||||
import com.cege.games.release.webpage.events.WebPageEvent;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
public class WalletInterface {
|
||||
Context mContext;
|
||||
|
||||
/** Instantiate the interface and set the context */
|
||||
public WalletInterface(Context c) {
|
||||
mContext = c;
|
||||
}
|
||||
|
||||
// sign
|
||||
@JavascriptInterface
|
||||
public void pageCall(String dataStr) {
|
||||
EventBus.getDefault().post(new WebPageEvent(dataStr));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.cege.games.release.webpage.events;
|
||||
|
||||
public class CallJSEvent {
|
||||
private String data;
|
||||
private String funId;
|
||||
|
||||
public CallJSEvent(String funId, String data) {
|
||||
this.data = data;
|
||||
this.funId = funId;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getFunId() {
|
||||
return funId;
|
||||
}
|
||||
|
||||
public void setFunId(String funId) {
|
||||
this.funId = funId;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.cege.games.release.webpage.events;
|
||||
|
||||
public class ProxyCBEvent {
|
||||
private String data;
|
||||
private String funId;
|
||||
|
||||
public ProxyCBEvent(String funId, String data) {
|
||||
this.data = data;
|
||||
this.funId = funId;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getFunId() {
|
||||
return funId;
|
||||
}
|
||||
|
||||
public void setFunId(String funId) {
|
||||
this.funId = funId;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.cege.games.release.webpage.events;
|
||||
|
||||
public class WebPageEvent {
|
||||
private String dataStr;
|
||||
|
||||
public WebPageEvent(String dataStr) {
|
||||
this.dataStr = dataStr;
|
||||
}
|
||||
|
||||
public String getDataStr() {
|
||||
return dataStr;
|
||||
}
|
||||
|
||||
public void setDataStr(String dataStr) {
|
||||
this.dataStr = dataStr;
|
||||
}
|
||||
}
|
@ -1,21 +1,46 @@
|
||||
package com.jc.jcfw;
|
||||
|
||||
import static com.cege.games.release.Constants.FUNID_PREFIX;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.cege.games.release.MainActivity;
|
||||
import com.cege.games.release.MainApplication;
|
||||
import com.cege.games.release.ui.UIManager;
|
||||
import com.cege.games.release.webpage.events.CallJSEvent;
|
||||
import com.cege.games.release.webpage.events.ProxyCBEvent;
|
||||
import com.google.common.base.Strings;
|
||||
import com.jc.jcfw.google.PayClient;
|
||||
import com.jc.jcfw.util.ThreadUtils;
|
||||
import com.jc.jcfw.util.UIUtils;
|
||||
import com.unity3d.player.UnityPlayer;
|
||||
|
||||
import org.cocos2dx.lib.CocosJSHelper;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class JcSDK {
|
||||
private static final String TAG = JcSDK.class.getSimpleName();
|
||||
private static UnityCallback commonCB;
|
||||
|
||||
private static native int runJS(final String funId, final String methodName, final String params);
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static PayClient payClient;
|
||||
|
||||
private static native int runJS(final String funId, final String methodName, final String[] params);
|
||||
|
||||
public static native String decryptPass(final String account, final String pass);
|
||||
|
||||
public static native void tick(float dt);
|
||||
|
||||
public static void initCommonCB(UnityCallback callBack) {
|
||||
Log.i(TAG, "call init common callback from unity");
|
||||
@ -35,12 +60,9 @@ public class JcSDK {
|
||||
|
||||
/**
|
||||
* 回调至c#
|
||||
*
|
||||
* @param funId
|
||||
* @param msg
|
||||
*/
|
||||
public static void csCallback(String funId, String msg) {
|
||||
if (funId != "" && funId.indexOf("js_") == 0) {
|
||||
if (!Objects.equals(funId, "") && funId.indexOf("js_") == 0) {
|
||||
commonCB.nativeCallback(funId, msg, 1);
|
||||
} else {
|
||||
commonCB.nativeCallback(funId, msg, 0);
|
||||
@ -66,6 +88,8 @@ public class JcSDK {
|
||||
String downloadUrl = "https://metamask.io/download/";
|
||||
if (url.startsWith("imtokenv2")) {
|
||||
downloadUrl = "https://token.im/download";
|
||||
} else if (url.startsWith("okx://")) {
|
||||
downloadUrl = "https://www.okx.com/download";
|
||||
}
|
||||
i.setData(Uri.parse(downloadUrl));
|
||||
MainActivity.app.startActivity(i);
|
||||
@ -73,7 +97,7 @@ public class JcSDK {
|
||||
}
|
||||
|
||||
public static void showQRCode(String funid, String content) {
|
||||
MainActivity.app.showQRCode(funid, content, "", "");
|
||||
UIUtils.showQRCode(MainActivity.app, content, "");
|
||||
}
|
||||
|
||||
public static void showWebPage(String funid, String url) {
|
||||
@ -81,7 +105,8 @@ public class JcSDK {
|
||||
}
|
||||
|
||||
public static void scanQRCode(String funid, String title) {
|
||||
MainActivity.app.showQRScan(funid, title);
|
||||
// MainActivity.app.showQRScan(funid, title);
|
||||
UIManager.getSingleton().showQRScan(funid, title);
|
||||
}
|
||||
|
||||
public static void signWithTiktok(String funid) {
|
||||
@ -105,7 +130,11 @@ public class JcSDK {
|
||||
}
|
||||
|
||||
public static void signWithApple(String funid) {
|
||||
MainActivity.app.signWithApple(funid);
|
||||
// MainActivity.app.signWithApple(funid);
|
||||
}
|
||||
|
||||
public static void signWithOAuth(String funid, String jsonData) {
|
||||
MainActivity.app.oauthLogin(funid, jsonData);
|
||||
}
|
||||
|
||||
public static void signOutGoogle(String funid) {
|
||||
@ -116,15 +145,72 @@ public class JcSDK {
|
||||
MainActivity.app.logEvent(content);
|
||||
}
|
||||
|
||||
public static void nativeCb(String funId, String error, String idToken) {
|
||||
public static void queryProducts(String funid, String skuListStr) {
|
||||
Log.i(TAG, "queryProducts with: " + skuListStr);
|
||||
if (payClient == null) {
|
||||
payClient = PayClient.getInstance();
|
||||
}
|
||||
List<String> skuList = new ArrayList<>();
|
||||
if (skuListStr.contains(",")) {
|
||||
String[] skuArr = skuListStr.split(",");
|
||||
skuList.addAll(Arrays.asList(skuArr));
|
||||
} else {
|
||||
skuList.add(skuListStr);
|
||||
}
|
||||
payClient.queryProductList(funid, skuList);
|
||||
}
|
||||
|
||||
public static void buyProduct(String funid, String productId, String orderId) {
|
||||
Log.i(TAG, "buyProduct with: " + productId);
|
||||
if (payClient == null) {
|
||||
payClient = PayClient.getInstance();
|
||||
}
|
||||
payClient.buyOne(funid, productId, orderId);
|
||||
}
|
||||
|
||||
public static void queryPurchase(String funid) {
|
||||
Log.i(TAG, "queryPurchase");
|
||||
if (payClient == null) {
|
||||
payClient = PayClient.getInstance();
|
||||
}
|
||||
payClient.queryPurchase(funid);
|
||||
}
|
||||
|
||||
public static void passStorageState(String funid, String account) {
|
||||
Log.i(TAG, "passStorageState with: " + account);
|
||||
MainActivity.app.passStorageState(funid, account);
|
||||
}
|
||||
|
||||
public static void storagePass(String funid, String account, String password) {
|
||||
MainActivity.app.storagePass(funid, account, password);
|
||||
}
|
||||
|
||||
public static void authGetStoragePass(String funid, String account) {
|
||||
MainActivity.app.authGetStoragePass(funid, account);
|
||||
}
|
||||
|
||||
public static void storageGameData(String data) {
|
||||
MainApplication.application.setGameData(data);
|
||||
}
|
||||
|
||||
public static void getClientId(String funid) {
|
||||
Log.i(TAG, "getClientId ");
|
||||
MainActivity.app.getClientId(funid);
|
||||
}
|
||||
|
||||
public static void onProxyCB(String funId, String data) {
|
||||
EventBus.getDefault().post(new ProxyCBEvent(funId, data));
|
||||
}
|
||||
|
||||
public static void nativeCb(String funId, String error, String dataStr) {
|
||||
JSONObject result = new JSONObject();
|
||||
try {
|
||||
if (error != null && !error.isEmpty()) {
|
||||
if (Strings.isNullOrEmpty(error)) {
|
||||
result.put("errcode", 0);
|
||||
result.put("data", dataStr);
|
||||
} else {
|
||||
result.put("errcode", 1);
|
||||
result.put("errmessage", error);
|
||||
} else {
|
||||
result.put("errcode", 0);
|
||||
result.put("data", idToken);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "JSONException: " + e.getMessage());
|
||||
@ -132,7 +218,37 @@ public class JcSDK {
|
||||
if (funId == null || funId.isEmpty()) {
|
||||
funId = MainActivity.app.getFunId();
|
||||
}
|
||||
Log.i(TAG, String.format("%s native cb, error: %s, data: %s", funId, error, dataStr));
|
||||
if (funId.startsWith(FUNID_PREFIX)) {
|
||||
EventBus.getDefault().post(new CallJSEvent(funId, result.toString()));
|
||||
} else {
|
||||
String finalFunId = funId;
|
||||
ThreadUtils.runInMain(() -> JcSDK.runJS(finalFunId, "jniCallback", new String[]{result.toString()}));
|
||||
}
|
||||
}
|
||||
|
||||
JcSDK.runJS(funId, "jniCallback", result.toString());
|
||||
public static void callNativeJS(String funId, String methodName, String[] params) {
|
||||
ThreadUtils.runInMain(() -> JcSDK.runJS(funId, methodName, params));
|
||||
}
|
||||
|
||||
public static void nativeCb(NativeResult result) {
|
||||
nativeCb(result.getFunid(), result.getError(), result.getDataStr());
|
||||
}
|
||||
|
||||
public static void toUnity(String funId, String error, String dataStr) {
|
||||
JSONObject result = new JSONObject();
|
||||
try {
|
||||
result.put("funid", funId);
|
||||
if (Strings.isNullOrEmpty(error)) {
|
||||
result.put("errcode", 0);
|
||||
result.put("data", dataStr);
|
||||
} else {
|
||||
result.put("errcode", 1);
|
||||
result.put("errmessage", error);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "JSONException: " + e.getMessage());
|
||||
}
|
||||
UnityPlayer.UnitySendMessage("WalletPanel1", "onNativeCallback", result.toString());
|
||||
}
|
||||
}
|
||||
|
37
app/src/com/jc/jcfw/NativeResult.java
Normal file
37
app/src/com/jc/jcfw/NativeResult.java
Normal file
@ -0,0 +1,37 @@
|
||||
package com.jc.jcfw;
|
||||
|
||||
public class NativeResult {
|
||||
private String funid;
|
||||
private String error;
|
||||
private String dataStr;
|
||||
|
||||
public NativeResult(String funid, String error, String dataStr) {
|
||||
this.funid = funid;
|
||||
this.error = error;
|
||||
this.dataStr = dataStr;
|
||||
}
|
||||
|
||||
public String getFunid() {
|
||||
return funid;
|
||||
}
|
||||
|
||||
public void setFunid(String funid) {
|
||||
this.funid = funid;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getDataStr() {
|
||||
return dataStr;
|
||||
}
|
||||
|
||||
public void setDataStr(String dataStr) {
|
||||
this.dataStr = dataStr;
|
||||
}
|
||||
}
|
56
app/src/com/jc/jcfw/accountmanager/Authenticator.java
Normal file
56
app/src/com/jc/jcfw/accountmanager/Authenticator.java
Normal file
@ -0,0 +1,56 @@
|
||||
package com.jc.jcfw.accountmanager;
|
||||
|
||||
import android.accounts.AbstractAccountAuthenticator;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorResponse;
|
||||
import android.accounts.NetworkErrorException;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Created by sebastian on 07-03-16.
|
||||
*/
|
||||
public class Authenticator extends AbstractAccountAuthenticator {
|
||||
|
||||
private Context context;
|
||||
|
||||
public Authenticator(Context context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthTokenLabel(String authTokenType) {
|
||||
return authTokenType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
|
||||
return null;
|
||||
}
|
||||
}
|
19
app/src/com/jc/jcfw/accountmanager/AuthenticatorService.java
Normal file
19
app/src/com/jc/jcfw/accountmanager/AuthenticatorService.java
Normal file
@ -0,0 +1,19 @@
|
||||
package com.jc.jcfw.accountmanager;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class AuthenticatorService extends Service {
|
||||
private Authenticator authenticator;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
authenticator = new Authenticator(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return authenticator.getIBinder();
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import org.json.JSONObject;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -176,7 +177,7 @@ public final class JConfiguration {
|
||||
Buffer configData = new Buffer();
|
||||
try {
|
||||
configSource.readAll(configData);
|
||||
mConfigJson = new JSONObject(configData.readString(Charset.forName("UTF-8")));
|
||||
mConfigJson = new JSONObject(configData.readString(StandardCharsets.UTF_8));
|
||||
} catch (IOException ex) {
|
||||
throw new InvalidConfigurationException(
|
||||
"Failed to read configuration: " + ex.getMessage());
|
||||
|
251
app/src/com/jc/jcfw/google/PayClient.java
Normal file
251
app/src/com/jc/jcfw/google/PayClient.java
Normal file
@ -0,0 +1,251 @@
|
||||
package com.jc.jcfw.google;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.BillingClientStateListener;
|
||||
import com.android.billingclient.api.BillingFlowParams;
|
||||
import com.android.billingclient.api.BillingResult;
|
||||
import com.android.billingclient.api.ProductDetails;
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.android.billingclient.api.PurchasesUpdatedListener;
|
||||
import com.android.billingclient.api.QueryProductDetailsParams;
|
||||
import com.android.billingclient.api.QueryPurchasesParams;
|
||||
import com.cege.games.release.MainActivity;
|
||||
import com.jc.jcfw.JcSDK;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class PayClient extends Activity implements PurchasesUpdatedListener {
|
||||
private static final String TAG = "GooglePayClient";
|
||||
private static volatile PayClient mInstance = null;
|
||||
private static BillingClient billingClient;
|
||||
private static ConcurrentHashMap<String, ProductDetails> skuDetailsMap;
|
||||
private Context mContext = null;
|
||||
private String mFunId;
|
||||
|
||||
public static PayClient getInstance() {
|
||||
if (null == mInstance) {
|
||||
synchronized (PayClient.class) {
|
||||
if (null == mInstance) {
|
||||
mInstance = new PayClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
public void init(Context context) {
|
||||
this.mContext = context;
|
||||
skuDetailsMap = new ConcurrentHashMap<>();
|
||||
billingClient = BillingClient.newBuilder(context).enablePendingPurchases().setListener(this).build();
|
||||
connectToPlay();
|
||||
}
|
||||
|
||||
private void connectToPlay() {
|
||||
billingClient.startConnection(new BillingClientStateListener() {
|
||||
@Override
|
||||
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
|
||||
Log.i(TAG, "onBillingSetupFinished, response code: " + billingResult.getResponseCode());
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
// The BillingClient is ready. You can query purchases here.
|
||||
Log.i(TAG, "BillingClient is ready");
|
||||
} else {
|
||||
Log.i(TAG, "error init BillingClient with error:: " +billingResult.getResponseCode());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
// Try to restart the connection on the next request to
|
||||
// Google Play by calling the startConnection() method.
|
||||
connectToPlay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
JSONArray handlePurchase(JSONArray dataArr, Purchase purchase) throws JSONException {
|
||||
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
|
||||
// || purchase.getPurchaseState() == Purchase.PurchaseState.PENDING
|
||||
) {
|
||||
// Acknowledge purchase and grant the item to the user
|
||||
// Grant entitlement to the user.
|
||||
Log.i(TAG, "handlePurchase:" + purchase.getOriginalJson());
|
||||
Log.i(TAG, "purchase sign:" + purchase.getSignature());
|
||||
if (!purchase.isAcknowledged() && purchase.getProducts().size() > 0) {
|
||||
// consumables 消耗型
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("id", purchase.getProducts().get(0));
|
||||
data.put("token", purchase.getPurchaseToken());
|
||||
dataArr.put(data);
|
||||
}
|
||||
}
|
||||
return dataArr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
|
||||
Log.i(TAG, "onPurchasesUpdated with status: " + billingResult.getResponseCode());
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
|
||||
&& purchases != null) {
|
||||
final JSONArray dataArr = new JSONArray();
|
||||
boolean hasErr = false;
|
||||
for (Purchase purchase : purchases) {
|
||||
try {
|
||||
handlePurchase(dataArr, purchase);
|
||||
} catch (JSONException e) {
|
||||
hasErr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasErr) {
|
||||
purchaseUpdateCb(null,"error parse purchase data", null);
|
||||
} else {
|
||||
purchaseUpdateCb(null,null, dataArr.toString());
|
||||
}
|
||||
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
|
||||
purchaseUpdateCb(null,"user cancel buy", null);
|
||||
} else {
|
||||
String errmsg = billingResult.getDebugMessage();
|
||||
if (errmsg.isEmpty()) {
|
||||
errmsg = "other error";
|
||||
}
|
||||
purchaseUpdateCb(null, errmsg, null);
|
||||
}
|
||||
}
|
||||
private void purchaseUpdateCb(String funId, String error, String dataStr) {
|
||||
if (funId == null || funId.isEmpty()) {
|
||||
if (mFunId != null && !mFunId.isEmpty()) {
|
||||
final String _funId = mFunId;
|
||||
runOnUiThread(() -> JcSDK.nativeCb(_funId, error, dataStr));
|
||||
mFunId = null;
|
||||
}
|
||||
} else {
|
||||
runOnUiThread(() -> JcSDK.nativeCb(funId, error, dataStr));
|
||||
}
|
||||
|
||||
}
|
||||
private boolean parseProductDetails(JSONArray dataArr, ProductDetails skuDetails) {
|
||||
JSONObject data = new JSONObject();
|
||||
try {
|
||||
data.put("name", skuDetails.getTitle());
|
||||
data.put("description", skuDetails.getDescription());
|
||||
data.put("id", skuDetails.getProductId());
|
||||
data.put("type", skuDetails.getProductType());
|
||||
if (skuDetails.getProductType().equals(BillingClient.ProductType.INAPP)) {
|
||||
data.put("currencyCode",
|
||||
skuDetails.getOneTimePurchaseOfferDetails().getPriceCurrencyCode());
|
||||
data.put("priceValue", skuDetails.getOneTimePurchaseOfferDetails().getPriceAmountMicros());
|
||||
data.put("priceShow", skuDetails.getOneTimePurchaseOfferDetails().getFormattedPrice());
|
||||
}
|
||||
dataArr.put(data);
|
||||
return true;
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public void queryProductList(String funId, List<String> productIds) {
|
||||
List<QueryProductDetailsParams.Product> productList = new ArrayList<>();
|
||||
for (String productId : productIds) {
|
||||
productList.add(
|
||||
QueryProductDetailsParams.Product.newBuilder()
|
||||
.setProductId(productId)
|
||||
.setProductType(BillingClient.ProductType.INAPP)
|
||||
.build());
|
||||
}
|
||||
QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
|
||||
.setProductList(productList)
|
||||
.build();
|
||||
billingClient.queryProductDetailsAsync(
|
||||
params,
|
||||
(billingResult, productDetailsList) -> {
|
||||
// Process the result
|
||||
Map<String, ProductDetails> pMap = new HashMap<>();
|
||||
for (ProductDetails details : productDetailsList) {
|
||||
skuDetailsMap.put(details.getProductId(), details);
|
||||
pMap.put(details.getProductId(), details);
|
||||
}
|
||||
final JSONArray dataArr = new JSONArray();
|
||||
boolean hasErr = false;
|
||||
for (Map.Entry<String, ProductDetails> entry : pMap.entrySet()) {
|
||||
ProductDetails skuDetails = entry.getValue();
|
||||
if (!parseProductDetails(dataArr, skuDetails)) {
|
||||
hasErr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasErr) {
|
||||
purchaseUpdateCb(funId, "parse product detail json error", null);
|
||||
} else {
|
||||
purchaseUpdateCb(funId, null, dataArr.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void queryPurchase(String funId) {
|
||||
billingClient.queryPurchasesAsync(
|
||||
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(),
|
||||
(billingResult, purchases) -> {
|
||||
// Process the result
|
||||
final JSONArray dataArr = new JSONArray();
|
||||
boolean hasErr = false;
|
||||
for (Purchase purchase : purchases) {
|
||||
try {
|
||||
handlePurchase(dataArr, purchase);
|
||||
} catch (JSONException e) {
|
||||
hasErr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasErr) {
|
||||
purchaseUpdateCb(funId, "error parse purchase data", null);
|
||||
} else {
|
||||
purchaseUpdateCb(funId, null, dataArr.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void buyOne(String funId, String productId, String orderId) {
|
||||
if (mFunId != null && !mFunId.isEmpty()) {
|
||||
purchaseUpdateCb(funId, "another purchase is in progress", null);
|
||||
return;
|
||||
}
|
||||
if (!skuDetailsMap.containsKey(productId)) {
|
||||
purchaseUpdateCb(funId, "product with : "+productId+ " not found", null);
|
||||
return;
|
||||
}
|
||||
ProductDetails productDetails = skuDetailsMap.get(productId);
|
||||
// Set the parameters for the offer that will be presented
|
||||
// in the billing flow creating separate productDetailsParamsList variable
|
||||
List<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = Collections
|
||||
.singletonList(BillingFlowParams.ProductDetailsParams.newBuilder()
|
||||
.setProductDetails(productDetails)
|
||||
.build());
|
||||
|
||||
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
|
||||
.setProductDetailsParamsList(productDetailsParamsList)
|
||||
.setObfuscatedAccountId(orderId)
|
||||
.setObfuscatedProfileId(orderId)
|
||||
.build();
|
||||
|
||||
// Launch the billing flow
|
||||
this.mFunId = funId;
|
||||
MainActivity.app.runOnUiThread(() -> {
|
||||
billingClient.launchBillingFlow((Activity) mContext, billingFlowParams);
|
||||
});
|
||||
}
|
||||
}
|
63
app/src/com/jc/jcfw/security/BiometricHelper.java
Normal file
63
app/src/com/jc/jcfw/security/BiometricHelper.java
Normal file
@ -0,0 +1,63 @@
|
||||
package com.jc.jcfw.security;
|
||||
|
||||
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
||||
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.cege.games.release.R;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class BiometricHelper {
|
||||
|
||||
public static final int ERROR_BIOMETRIC_FAIL = 100;
|
||||
public static final int ERROR_BIOMETRIC_NO_CIPHER = 101;
|
||||
private static final String TAG = BiometricHelper.class.getSimpleName();
|
||||
public static BiometricPrompt.PromptInfo createPromptInfo(Context context) {
|
||||
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(context.getString(R.string.prompt_info_title)) // e.g. "Sign in"
|
||||
.setSubtitle(context.getString(R.string.prompt_info_subtitle)) // e.g. "Biometric for My App"
|
||||
.setDescription(context.getString(R.string.prompt_info_description)) // e.g. "Confirm biometric to continue"
|
||||
.setConfirmationRequired(false)
|
||||
.setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
|
||||
// .setDeviceCredentialAllowed(true) // Allow PIN/pattern/password authentication.
|
||||
// Also note that setDeviceCredentialAllowed and setNegativeButtonText are
|
||||
// incompatible so that if you uncomment one you must comment out the other
|
||||
.build();
|
||||
return promptInfo;
|
||||
}
|
||||
|
||||
public static BiometricPrompt createBiometricPrompt(FragmentActivity activity, Consumer<BiometricResult> func) {
|
||||
Executor executor = ContextCompat.getMainExecutor(activity.getApplicationContext());
|
||||
return new BiometricPrompt(activity, executor, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errcode, @NonNull CharSequence errString) {
|
||||
super.onAuthenticationError(errcode, errString);
|
||||
Log.i(TAG, "Authentication error: " + errcode +" | "+ errString);
|
||||
func.accept(new BiometricResult(errcode, (String) errString));
|
||||
}
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed();
|
||||
Log.i(TAG, "Authentication failed!");
|
||||
func.accept(new BiometricResult(ERROR_BIOMETRIC_FAIL, "Authentication failed"));
|
||||
}
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
Log.i(TAG, "Authentication succeeded!");
|
||||
func.accept(new BiometricResult(result.getCryptoObject().getCipher()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
61
app/src/com/jc/jcfw/security/BiometricResult.java
Normal file
61
app/src/com/jc/jcfw/security/BiometricResult.java
Normal file
@ -0,0 +1,61 @@
|
||||
package com.jc.jcfw.security;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class BiometricResult {
|
||||
private Cipher cipher;
|
||||
private boolean error;
|
||||
private int errcode;
|
||||
private String errmsg;
|
||||
|
||||
public BiometricResult(Cipher cipher) {
|
||||
if (null != cipher) {
|
||||
this.cipher = cipher;
|
||||
this.error = false;
|
||||
} else {
|
||||
this.error = true;
|
||||
this.errcode = 101;
|
||||
this.errmsg = "cipher is null";
|
||||
}
|
||||
}
|
||||
|
||||
public BiometricResult(int errcode, String errmsg) {
|
||||
this.error = true;
|
||||
this.errcode = errcode;
|
||||
this.errmsg = errmsg;
|
||||
}
|
||||
@Nullable
|
||||
public Cipher getCipher() {
|
||||
return cipher;
|
||||
}
|
||||
|
||||
public void setCipher(Cipher cipher) {
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(boolean error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public int getErrcode() {
|
||||
return errcode;
|
||||
}
|
||||
|
||||
public void setErrcode(int errcode) {
|
||||
this.errcode = errcode;
|
||||
}
|
||||
|
||||
public String getErrmsg() {
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
public void setErrmsg(String errmsg) {
|
||||
this.errmsg = errmsg;
|
||||
}
|
||||
}
|
27
app/src/com/jc/jcfw/security/CryptographyManager.java
Normal file
27
app/src/com/jc/jcfw/security/CryptographyManager.java
Normal file
@ -0,0 +1,27 @@
|
||||
package com.jc.jcfw.security;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public interface CryptographyManager {
|
||||
/**
|
||||
* This method first gets or generates an instance of SecretKey and then initializes the Cipher
|
||||
* with the key. The secret key uses [ENCRYPT_MODE][Cipher.ENCRYPT_MODE] is used.
|
||||
*/
|
||||
Cipher getInitializedCipherForEncryption(String keyName);
|
||||
|
||||
/**
|
||||
* This method first gets or generates an instance of SecretKey and then initializes the Cipher
|
||||
* with the key. The secret key uses [DECRYPT_MODE][Cipher.DECRYPT_MODE] is used.
|
||||
*/
|
||||
Cipher getInitializedCipherForDecryption(String keyName, byte[] initializationVector);
|
||||
|
||||
/**
|
||||
* The Cipher created with [getInitializedCipherForEncryption] is used here
|
||||
*/
|
||||
EncryptedData encryptData(String plaintext, Cipher cipher);
|
||||
|
||||
/**
|
||||
* The Cipher created with [getInitializedCipherForDecryption] is used here
|
||||
*/
|
||||
String decryptData(byte[] ciphertext, Cipher cipher);
|
||||
}
|
101
app/src/com/jc/jcfw/security/CryptographyManagerImpl.java
Normal file
101
app/src/com/jc/jcfw/security/CryptographyManagerImpl.java
Normal file
@ -0,0 +1,101 @@
|
||||
package com.jc.jcfw.security;
|
||||
|
||||
import static android.security.keystore.KeyProperties.BLOCK_MODE_CBC;
|
||||
import static android.security.keystore.KeyProperties.ENCRYPTION_PADDING_PKCS7;
|
||||
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.util.Log;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class CryptographyManagerImpl implements CryptographyManager {
|
||||
|
||||
@Override
|
||||
public Cipher getInitializedCipherForEncryption(String keyName) {
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = getCipher();
|
||||
SecretKey secretKey = getOrCreateSecretKey(keyName);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize Cipher for encryption", e);
|
||||
}
|
||||
return cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cipher getInitializedCipherForDecryption(String keyName, byte[] initializationVector) {
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = getCipher();
|
||||
SecretKey secretKey = getOrCreateSecretKey(keyName);
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(initializationVector));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize Cipher for decryption", e);
|
||||
}
|
||||
return cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptedData encryptData(String plaintext, Cipher cipher) {
|
||||
try {
|
||||
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(Charset.defaultCharset()));
|
||||
return new EncryptedData(ciphertext, cipher.getIV());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to encrypt data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decryptData(byte[] ciphertext, Cipher cipher) {
|
||||
try {
|
||||
byte[] plaintext = cipher.doFinal(ciphertext);
|
||||
return new String(plaintext, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to encrypt data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Cipher getCipher() throws Exception {
|
||||
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ BLOCK_MODE_CBC + "/"
|
||||
+ ENCRYPTION_PADDING_PKCS7);
|
||||
}
|
||||
|
||||
private SecretKey getOrCreateSecretKey(String keyName) throws Exception {
|
||||
String ANDROID_KEYSTORE = "AndroidKeyStore";
|
||||
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
|
||||
keyStore.load(null); // Keystore must be loaded before it can be accessed
|
||||
SecretKey existingKey = (SecretKey) keyStore.getKey(keyName, null);
|
||||
if (existingKey != null) {
|
||||
return existingKey;
|
||||
}
|
||||
|
||||
// if you reach here, then a new SecretKey must be generated for that keyName
|
||||
KeyGenParameterSpec.Builder paramsBuilder = new KeyGenParameterSpec.Builder(keyName,
|
||||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT);
|
||||
paramsBuilder.setBlockModes(BLOCK_MODE_CBC);
|
||||
paramsBuilder.setEncryptionPaddings(ENCRYPTION_PADDING_PKCS7);
|
||||
int KEY_SIZE = 256;
|
||||
paramsBuilder.setKeySize(KEY_SIZE);
|
||||
paramsBuilder.setUserAuthenticationRequired(false);
|
||||
paramsBuilder.setUserAuthenticationValidityDurationSeconds(30);
|
||||
|
||||
KeyGenParameterSpec keyGenParams = paramsBuilder.build();
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
|
||||
ANDROID_KEYSTORE);
|
||||
keyGenerator.init(keyGenParams);
|
||||
return keyGenerator.generateKey();
|
||||
}
|
||||
}
|
19
app/src/com/jc/jcfw/security/EncryptedData.java
Normal file
19
app/src/com/jc/jcfw/security/EncryptedData.java
Normal file
@ -0,0 +1,19 @@
|
||||
package com.jc.jcfw.security;
|
||||
|
||||
public class EncryptedData {
|
||||
private final byte[] ciphertext;
|
||||
private final byte[] initializationVector;
|
||||
|
||||
public EncryptedData(byte[] ciphertext, byte[] initializationVector) {
|
||||
this.ciphertext = ciphertext;
|
||||
this.initializationVector = initializationVector;
|
||||
}
|
||||
|
||||
public byte[] getCiphertext() {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
public byte[] getInitializationVector() {
|
||||
return initializationVector;
|
||||
}
|
||||
}
|
101
app/src/com/jc/jcfw/util/DriveUtils.java
Normal file
101
app/src/com/jc/jcfw/util/DriveUtils.java
Normal file
@ -0,0 +1,101 @@
|
||||
package com.jc.jcfw.util;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.api.client.extensions.android.http.AndroidHttp;
|
||||
import com.google.api.client.extensions.android.json.AndroidJsonFactory;
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
|
||||
import com.google.api.client.http.FileContent;
|
||||
import com.google.api.services.drive.Drive;
|
||||
import com.google.api.services.drive.model.File;
|
||||
import com.google.api.services.drive.model.FileList;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class DriveUtils {
|
||||
private static final String TAG = DriveUtils.class.getSimpleName();
|
||||
|
||||
public static Drive generateService(GoogleAccountCredential credential, String appName) {
|
||||
return new Drive.Builder(
|
||||
AndroidHttp.newCompatibleTransport(),
|
||||
AndroidJsonFactory.getDefaultInstance(),
|
||||
credential)
|
||||
.setApplicationName(appName)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* query app file by filename
|
||||
*
|
||||
* @param service
|
||||
* @param fileName
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String queryOneAppFile(Drive service, String fileName) {
|
||||
boolean querySuccess = false;
|
||||
String fileId = "";
|
||||
while (!querySuccess) {
|
||||
try {
|
||||
FileList files = service.files().list()
|
||||
.setSpaces("appDataFolder")
|
||||
.setFields("nextPageToken, files(id, name)")
|
||||
.setPageSize(100)
|
||||
.execute();
|
||||
querySuccess = true;
|
||||
for (File file : files.getFiles()) {
|
||||
Log.i(TAG, String.format("Found file: %s (%s)\n", file.getName(), file.getId()));
|
||||
if (file.getName().equals(fileName)) {
|
||||
fileId = file.getId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.i(TAG, "error query file from drive");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return fileId;
|
||||
}
|
||||
|
||||
/**
|
||||
* download one file from drive
|
||||
*
|
||||
* @param service
|
||||
* @param fileId
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String downloadFile(Drive service, String fileId) throws IOException {
|
||||
OutputStream outputStream = new ByteArrayOutputStream();
|
||||
service.files().get(fileId).executeMediaAndDownloadTo(outputStream);
|
||||
// convert outputStream to JSON string
|
||||
// String json = outputStream.toString();
|
||||
return outputStream.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* upload one file to Drive
|
||||
*
|
||||
* @param service
|
||||
* @param filePath file absolute path
|
||||
* @param fileType application/json
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String uploadAppFile(Drive service, java.io.File filePath, String fileType) throws IOException {
|
||||
String fileName = filePath.getName();
|
||||
File fileMetadata = new File();
|
||||
fileMetadata.setName(fileName);
|
||||
fileMetadata.setParents(Collections.singletonList("appDataFolder"));
|
||||
// "application/json"
|
||||
FileContent mediaContent = new FileContent(fileType, filePath);
|
||||
File file = service.files().create(fileMetadata, mediaContent)
|
||||
.setFields("id")
|
||||
.execute();
|
||||
Log.i(TAG, String.format("%s upload success, File ID: %s", fileName, file.getId()));
|
||||
return file.getId();
|
||||
}
|
||||
}
|
97
app/src/com/jc/jcfw/util/DriverApiUtils.java
Normal file
97
app/src/com/jc/jcfw/util/DriverApiUtils.java
Normal file
@ -0,0 +1,97 @@
|
||||
package com.jc.jcfw.util;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class DriverApiUtils {
|
||||
private static final String driveApiBase = "https://www.googleapis.com/drive/v3/files";
|
||||
private static final String driveApiUploadBase = "https://www.googleapis.com/upload/drive/v3/files";
|
||||
private final String TAG = getClass().getSimpleName();
|
||||
|
||||
private String token = "";
|
||||
|
||||
public DriverApiUtils() {
|
||||
}
|
||||
|
||||
public DriverApiUtils(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public JSONArray fileList() throws IOException, JSONException {
|
||||
OkHttpClient client = new OkHttpClient().newBuilder()
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
.url(driveApiBase + "?spaces=appDataFolder&fields=files(id, name, modifiedTime)")
|
||||
.get()
|
||||
.addHeader("Authorization", "Bearer " + token)
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
String resStr = response.body().string();
|
||||
JSONObject resJson = new JSONObject(resStr);
|
||||
return resJson.getJSONArray("files");
|
||||
}
|
||||
}
|
||||
|
||||
public String queryOneAppFile(String fileName) throws IOException, JSONException {
|
||||
JSONArray fileList = fileList();
|
||||
for (int i = 0; i < fileList.length(); i++) {
|
||||
JSONObject file = fileList.getJSONObject(i);
|
||||
if (file.getString("name").equals(fileName)) {
|
||||
return file.getString("id");
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String fileInfo(String fileId) throws IOException {
|
||||
OkHttpClient client = new OkHttpClient().newBuilder()
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
.url(String.format("%s/%s?alt=media", driveApiBase, fileId))
|
||||
.get()
|
||||
.addHeader("Authorization", "Bearer " + token)
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
return response.body().string();
|
||||
}
|
||||
}
|
||||
|
||||
public String uploadFile(java.io.File filePath, String fileType) throws IOException {
|
||||
OkHttpClient client = new OkHttpClient().newBuilder()
|
||||
.build();
|
||||
String fileName = filePath.getName();
|
||||
String optionStr = "{\"name\":\""+fileName+"\",\"parents\":[\"appDataFolder\"]}";
|
||||
RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
|
||||
.addFormDataPart("", "upload-options.json",
|
||||
RequestBody.create(optionStr, MediaType.parse("application/json")))
|
||||
.addFormDataPart("", fileName,
|
||||
RequestBody.create(filePath, MediaType.parse("application/octet-stream")))
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
.url(driveApiUploadBase + "?uploadType=multipart")
|
||||
.post(body)
|
||||
.addHeader("Content-Type", fileType)
|
||||
.addHeader("Authorization", "Bearer " + token)
|
||||
.build();
|
||||
Response response = client.newCall(request).execute();
|
||||
return response.body().string();
|
||||
}
|
||||
}
|
@ -13,10 +13,15 @@ import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
public class FileUtils {
|
||||
private static final String TAG = FileUtils.class.getSimpleName();
|
||||
@ -26,7 +31,7 @@ public class FileUtils {
|
||||
* @param fileName path
|
||||
* @return TRUE or FALSE
|
||||
*/
|
||||
static boolean fileIsExist(String fileName) {
|
||||
public static boolean fileIsExist(String fileName) {
|
||||
File file = new File(fileName);
|
||||
if (file.exists())
|
||||
return true;
|
||||
@ -35,6 +40,20 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeFile(File filePath, String content) throws IOException {
|
||||
FileOutputStream out = new FileOutputStream(filePath);
|
||||
out.write(content.getBytes());
|
||||
out.close();
|
||||
}
|
||||
|
||||
public static JSONObject readJsonFromFile(File filePath) throws IOException, JSONException {
|
||||
RandomAccessFile f = new RandomAccessFile(filePath, "r");
|
||||
byte[] bytes = new byte[(int) f.length()];
|
||||
f.readFully(bytes);
|
||||
f.close();
|
||||
return new JSONObject(new String(bytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* get image base path of external storage
|
||||
*/
|
||||
|
72
app/src/com/jc/jcfw/util/SharedPreferencesHelper.java
Normal file
72
app/src/com/jc/jcfw/util/SharedPreferencesHelper.java
Normal file
@ -0,0 +1,72 @@
|
||||
package com.jc.jcfw.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class SharedPreferencesHelper {
|
||||
private final SharedPreferences sharedPreferences;
|
||||
private SharedPreferences.Editor editor;
|
||||
|
||||
public SharedPreferencesHelper(Context context, String FILE_NAME) {
|
||||
sharedPreferences = context.getSharedPreferences(FILE_NAME,
|
||||
Context.MODE_PRIVATE);
|
||||
editor = sharedPreferences.edit();
|
||||
}
|
||||
|
||||
public void put(String key, Object object) {
|
||||
if (object instanceof String) {
|
||||
editor.putString(key, (String) object);
|
||||
} else if (object instanceof Integer) {
|
||||
editor.putInt(key, (Integer) object);
|
||||
} else if (object instanceof Boolean) {
|
||||
editor.putBoolean(key, (Boolean) object);
|
||||
} else if (object instanceof Float) {
|
||||
editor.putFloat(key, (Float) object);
|
||||
} else if (object instanceof Long) {
|
||||
editor.putLong(key, (Long) object);
|
||||
} else {
|
||||
editor.putString(key, object.toString());
|
||||
}
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public Object getSharedPreference(String key, Object defaultObject) {
|
||||
if (defaultObject instanceof String) {
|
||||
return sharedPreferences.getString(key, (String) defaultObject);
|
||||
} else if (defaultObject instanceof Integer) {
|
||||
return sharedPreferences.getInt(key, (Integer) defaultObject);
|
||||
} else if (defaultObject instanceof Boolean) {
|
||||
return sharedPreferences.getBoolean(key, (Boolean) defaultObject);
|
||||
} else if (defaultObject instanceof Float) {
|
||||
return sharedPreferences.getFloat(key, (Float) defaultObject);
|
||||
} else if (defaultObject instanceof Long) {
|
||||
return sharedPreferences.getLong(key, (Long) defaultObject);
|
||||
} else {
|
||||
return sharedPreferences.getString(key, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void remove(String key) {
|
||||
editor.remove(key);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
|
||||
public void clear() {
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
|
||||
public Boolean contain(String key) {
|
||||
return sharedPreferences.contains(key);
|
||||
}
|
||||
|
||||
|
||||
public Map<String, ?> getAll() {
|
||||
return sharedPreferences.getAll();
|
||||
}
|
||||
}
|
24
app/src/com/jc/jcfw/util/ThreadUtils.java
Normal file
24
app/src/com/jc/jcfw/util/ThreadUtils.java
Normal file
@ -0,0 +1,24 @@
|
||||
package com.jc.jcfw.util;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
public class ThreadUtils {
|
||||
/**
|
||||
* check if current thread is main thread
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static boolean isMainThread() {
|
||||
return Looper.getMainLooper() == Looper.myLooper();
|
||||
}
|
||||
|
||||
public static void runInMain(Runnable action) {
|
||||
if (ThreadUtils.isMainThread()) {
|
||||
action.run();
|
||||
} else {
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
mainHandler.post(action);
|
||||
}
|
||||
}
|
||||
}
|
43
app/src/com/jc/jcfw/util/UIUtils.java
Normal file
43
app/src/com/jc/jcfw/util/UIUtils.java
Normal file
@ -0,0 +1,43 @@
|
||||
package com.jc.jcfw.util;
|
||||
|
||||
import static androidx.core.content.ContextCompat.getSystemService;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
|
||||
import com.cege.games.release.MainActivity;
|
||||
import com.cege.games.release.MainApplication;
|
||||
import com.cege.games.release.dialog.QRCodeActivity;
|
||||
import com.jc.jcfw.JcSDK;
|
||||
|
||||
public class UIUtils {
|
||||
private static Toast toast;
|
||||
|
||||
@MainThread
|
||||
public static void showToastReal(Context context,String text) {
|
||||
if (toast == null) {
|
||||
toast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
toast.setDuration(Toast.LENGTH_SHORT);
|
||||
toast.setText(text);
|
||||
}
|
||||
toast.show();
|
||||
}
|
||||
public static void showToast(Context context, String text) {
|
||||
ThreadUtils.runInMain(() -> showToastReal(context, text));
|
||||
}
|
||||
@MainThread
|
||||
public static void showQRCodeReal(Context context, String str, String title) {
|
||||
QRCodeActivity qrCodeActivity = new QRCodeActivity(context);
|
||||
qrCodeActivity.showQRCode(str, title);
|
||||
qrCodeActivity.show();
|
||||
}
|
||||
|
||||
public static void showQRCode(Context context, String str, String title) {
|
||||
ThreadUtils.runInMain(() -> showQRCodeReal(context, str, title));
|
||||
}
|
||||
}
|
8
app/src/main/res/layout/activity_biometric.xml
Normal file
8
app/src/main/res/layout/activity_biometric.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
||||
</LinearLayout>
|
@ -23,24 +23,23 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="sample label"
|
||||
android:text="Title Label"
|
||||
android:textColor="#0D47A1"
|
||||
app:layout_constraintBottom_toTopOf="@id/ivCode"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/qrSaveBtn"
|
||||
<TextView
|
||||
android:id="@+id/tipLabel"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/round_btn"
|
||||
android:minHeight="32dp"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingVertical="5dp"
|
||||
android:text="Save Image to Local"
|
||||
android:text="Click Image to copy content"
|
||||
android:textColor="#0D47A1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
|
12
res/raw/apple_config.json
Normal file
12
res/raw/apple_config.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"client_id": "wallet.cebggame.com",
|
||||
"redirect_uri": "https://wallet.cebggame.com/apple/oauth_redirect",
|
||||
"end_session_redirect_uri": "com.apple.apps.wallet.cebggame.com:/oauth2redirect",
|
||||
"authorization_scope": "name email",
|
||||
"discovery_uri": "https://appleid.apple.com/.well-known/openid-configuration",
|
||||
"authorization_endpoint_uri": "",
|
||||
"token_endpoint_uri": "",
|
||||
"registration_endpoint_uri": "",
|
||||
"user_info_endpoint_uri": "",
|
||||
"https_required": true
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
"client_id": "53206975661-asnf3qe4bg29p8h981pgf099osvrjbme.apps.googleusercontent.com",
|
||||
"redirect_uri": "com.googleusercontent.apps.53206975661-asnf3qe4bg29p8h981pgf099osvrjbme:/oauth2redirect",
|
||||
"end_session_redirect_uri": "com.googleusercontent.apps.53206975661-asnf3qe4bg29p8h981pgf099osvrjbme:/oauth2redirect",
|
||||
"authorization_scope": "openid email profile",
|
||||
"authorization_scope": "openid email profile https://www.googleapis.com/auth/drive.appdata",
|
||||
"discovery_uri": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"authorization_endpoint_uri": "",
|
||||
"token_endpoint_uri": "",
|
||||
|
@ -9,4 +9,10 @@
|
||||
<string name="facebook_app_id" translatable="false">1204701000119770</string>
|
||||
<string name="fb_login_protocol_scheme" translatable="false">fb1204701000119770</string>
|
||||
<string name="facebook_client_token" translatable="false">3e29f2606ae15a99bb3824d2ef1a9d0b</string>
|
||||
|
||||
<string name="prompt_info_title" translatable="false">Biometric login for Wallet</string>
|
||||
<string name="prompt_info_subtitle" translatable="false">Login using your biometric credential</string>
|
||||
<string name="prompt_info_description" translatable="false">Confirm biometric to continue</string>
|
||||
|
||||
<string name="account_manager_description" translatable="false">store you account</string>
|
||||
</resources>
|
@ -6,6 +6,26 @@
|
||||
</style>
|
||||
<style name="BaseUnityTheme" parent="android:Theme.Holo.Light.NoActionBar.Fullscreen">
|
||||
</style>
|
||||
<style name="AppCompatTranslucent" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
|
||||
</style>
|
||||
<style name="DayNightActivity" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
</style>
|
||||
<style name="CebgMainActivity" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
</style>
|
||||
<style name="WebViewTheme" parent="android:Theme.Holo.Light.NoActionBar.Fullscreen">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="colorPrimary">@android:color/black</item>
|
||||
|
5
res/xml/authenticator.xml
Normal file
5
res/xml/authenticator.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<account-authenticator
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accountType="com.cege.games.auth"
|
||||
android:label="@string/account_manager_description"/>
|
Loading…
x
Reference in New Issue
Block a user