6.6.3 - Alpha5 - webview 元素支持基于 JsBridge 的 Web 页面布局 (Ref to Auto.js Pro) (issue #281)
This commit is contained in:
@@ -11,7 +11,8 @@
|
||||
"images.compressToBytes 方法, 用于压缩图像并生成字节数组",
|
||||
"images.downsample 方法, 用于像素降采样并生成新的 ImageWrapper",
|
||||
"ui.keepScreenOn 方法, 用于 UI 页面获取焦点时保持设备屏幕常亮",
|
||||
"ui.root 属性 (getter), 用于获取 UI 页面布局的 \"窗口内容根容器\" 节点"
|
||||
"ui.root 属性 (getter), 用于获取 UI 页面布局的 \"窗口内容根容器\" 节点",
|
||||
"webview 元素支持基于 JsBridge 的 Web 页面布局 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) [参阅 示例代码 > 布局 > 可交互 HTML / Vue2 + Vant (SFC)] _[`issue #281`](http://issues.autojs6.com/281)_"
|
||||
],
|
||||
"fix": [
|
||||
"主页文档标签显示在线文档时部分内容被系统导航栏遮挡的问题",
|
||||
@@ -19,6 +20,7 @@
|
||||
"主题色设置页面调色盘对话框可能无限叠加的问题",
|
||||
"无障碍服务关闭时音量加键停止所有脚本功能失效的问题",
|
||||
"定时任务页面编辑自定义广播内容时出现的输入法遮挡问题",
|
||||
"webview 元素中的控件无法正常激活输入法软键盘的问题",
|
||||
"APK 文件类型信息对话框可能无法获取应用名称及 SDK 信息的问题",
|
||||
"文件管理器示例代码进入项目目录时可能无法自动加载子目录文件内容的问题",
|
||||
"Android 15 UI 模式顶部内容被状态栏覆盖的问题",
|
||||
@@ -45,6 +47,7 @@
|
||||
"代码编辑器格式化代码支持 `??`, `?.`, `??=` 等运算符",
|
||||
"打包单文件时自动读取并勾选已安装应用的声明权限 _[`issue #362`](http://issues.autojs6.com/362)_",
|
||||
"意图相关操作 (编辑/查看/安装/发送/播放等) 增加操作异常提示",
|
||||
"webview 元素的 url 属性支持相对路径",
|
||||
"ImageWrapper#saveTo 方法的路径参数支持相对路径",
|
||||
"images.save 方法使用 quality 参数时支持 png 格式的文件体积压缩 _[`issue #367`](http://issues.autojs6.com/367)_",
|
||||
"已忽略更新记录及客户端模式连接地址记录支持清空操作",
|
||||
|
||||
5
.idea/codeStyles/Project.xml
generated
5
.idea/codeStyles/Project.xml
generated
@@ -67,6 +67,11 @@
|
||||
<option name="BLOCK_COMMENT_ADD_SPACE" value="true" />
|
||||
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Vue">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
|
||||
93
app/src/main/assets-app/sample/布局/Vue2 + Vant (SFC)/main.js
Normal file
93
app/src/main/assets-app/sample/布局/Vue2 + Vant (SFC)/main.js
Normal file
@@ -0,0 +1,93 @@
|
||||
'ui';
|
||||
|
||||
ui.layout(
|
||||
<vertical>
|
||||
<webview id="web" url="web/index.html" w="*" h="*"/>
|
||||
</vertical>,
|
||||
);
|
||||
|
||||
ui.statusBarColor('#ffffff');
|
||||
|
||||
let FILE_REQ_CODE = 11525;
|
||||
let fileChooserResolve = null;
|
||||
let web = ui['web'];
|
||||
|
||||
// 监听 WebView 的控制台消息, 打印到控制台
|
||||
web.events.on('console_message', (event, msg) => {
|
||||
console.log(`${files.getName(msg.sourceId())}:${msg.lineNumber()}: ${msg.message()}`);
|
||||
});
|
||||
|
||||
// 处理来自 Web 的请求
|
||||
web.jsBridge
|
||||
// 处理读取本地文件的请求
|
||||
.handle('fetch', (event, args) => {
|
||||
return files.read(files.join(files.path('web'), args.path));
|
||||
})
|
||||
// 处理显示日志界面的请求
|
||||
.handle('show-log', (event) => {
|
||||
app.startActivity('console');
|
||||
})
|
||||
// 处理 toastLog 消息
|
||||
.handle('toast-log', (event, msg) => {
|
||||
toastLog(msg);
|
||||
})
|
||||
// 处理设置无障碍服务的请求
|
||||
.handle('set-accessibility-enabled', (event, enabled) => {
|
||||
enabled ? auto.enable() : auto.disable();
|
||||
})
|
||||
// 处理获取无障碍服务状态的请求
|
||||
.handle('get-accessibility-enabled', (event) => {
|
||||
return auto.isRunning();
|
||||
})
|
||||
// 处理获取应用版本名称的请求
|
||||
.handle('get-app-version-name', (event) => {
|
||||
return app.autojs.versionName;
|
||||
})
|
||||
// 处理文件选择的请求
|
||||
.handle('select-file', (event, mimeType) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (fileChooserResolve) {
|
||||
toastLog('File chooser is already open.');
|
||||
return;
|
||||
}
|
||||
fileChooserResolve = resolve;
|
||||
|
||||
let intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType(mimeType);
|
||||
intent.addCategory(android.content.Intent.CATEGORY_OPENABLE);
|
||||
activity.startActivityForResult(intent, FILE_REQ_CODE);
|
||||
});
|
||||
|
||||
})
|
||||
// 处理显示设备信息的请求
|
||||
.handle('show-device-info-dialog', (event) => {
|
||||
new MaterialDialog.Builder(activity)
|
||||
.title(R.strings.text_app_and_device_info)
|
||||
.content(DeviceUtils.getDeviceSummaryWithSimpleAppInfo(activity))
|
||||
.neutralText(R.strings.dialog_button_copy)
|
||||
.onNeutral((dialog, which) => {
|
||||
setClip(dialog.contentView.text);
|
||||
toast(activity.getString(R.strings.text_already_copied_to_clip));
|
||||
})
|
||||
.neutralColorRes(R.color.dialog_button_hint)
|
||||
.negativeText(R.strings.dialog_button_dismiss)
|
||||
.build()
|
||||
.show();
|
||||
})
|
||||
// 处理打开链接的请求. 这里用广播方式, 也可以使用 handle 的 "请求-响应" 方式
|
||||
.on('open-url', (event, url) => {
|
||||
app.openUrl(url);
|
||||
});
|
||||
|
||||
ui.emitter.on('activity_result', (requestCode, resultCode, data) => {
|
||||
if (requestCode === FILE_REQ_CODE && fileChooserResolve) {
|
||||
if (resultCode === android.app.Activity.RESULT_OK && data) {
|
||||
let uri = data.getData();
|
||||
let path = IntentUtils.getFileName(activity, uri);
|
||||
fileChooserResolve(path);
|
||||
} else {
|
||||
fileChooserResolve(null);
|
||||
}
|
||||
fileChooserResolve = null;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"assets": [],
|
||||
"useFeatures": [],
|
||||
"launchConfig": {
|
||||
"displaySplash": true,
|
||||
"hideLogs": true,
|
||||
"splashText": "Powered by AutoJs6",
|
||||
"stableMode": false
|
||||
},
|
||||
"main": "main.js",
|
||||
"name": "Vue2 + Vant (SFC)",
|
||||
"packageName": "org.example.vue2_vant",
|
||||
"scripts": {},
|
||||
"versionCode": 1,
|
||||
"versionName": "1.0.0"
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<html>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<link rel="stylesheet" href="https://unpkg.com/vant@2.12/lib/index.css"/>
|
||||
|
||||
<script src="https://unpkg.com/vue@2.6/dist/vue.min.js"></script>
|
||||
<script src="https://unpkg.com/vue3-sfc-loader@0.8.4/dist/vue2-sfc-loader.js"></script>
|
||||
<script src="https://unpkg.com/vant@2.12/lib/vant.min.js"></script>
|
||||
<script src="autojs://sdk/v1.js"></script>
|
||||
<script>
|
||||
let { loadModule, vueVersion } = window['vue2-sfc-loader'];
|
||||
let options = {
|
||||
moduleCache: {
|
||||
vue: Vue,
|
||||
myData: {
|
||||
vueVersion,
|
||||
},
|
||||
},
|
||||
async getFile(url) {
|
||||
let getContentData;
|
||||
if (typeof $autojs !== 'undefined') {
|
||||
let res = await $autojs.invoke('fetch', { path: url });
|
||||
getContentData = (asBinary) => asBinary ? str2ab(res) : res;
|
||||
} else {
|
||||
let res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
|
||||
}
|
||||
getContentData = (asBinary) => asBinary ? res.arrayBuffer() : res.text();
|
||||
}
|
||||
return {
|
||||
getContentData,
|
||||
};
|
||||
|
||||
function str2ab(str) {
|
||||
let buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
|
||||
let bufView = new Uint16Array(buf);
|
||||
for (let i = 0, strLen = str.length; i < strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
},
|
||||
addStyle() {
|
||||
/* unused here */
|
||||
},
|
||||
};
|
||||
|
||||
loadModule('/main.vue', options)
|
||||
.then(component => new Vue(component).$mount('#app'));
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
127
app/src/main/assets-app/sample/布局/Vue2 + Vant (SFC)/web/main.vue
Normal file
127
app/src/main/assets-app/sample/布局/Vue2 + Vant (SFC)/web/main.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<van-row>
|
||||
<van-nav-bar title="基于 Vue 的界面"/>
|
||||
|
||||
<van-tabs v-model="activeTab">
|
||||
<van-tab title="配置">
|
||||
<van-cell-group title="权限">
|
||||
<van-cell title="无障碍服务" label="用于脚本自动操作 (点击/长按/滑动等)">
|
||||
<van-switch
|
||||
v-model="accessibilityServiceEnabled"
|
||||
@input="onAccessibilityServiceCheckChanged"
|
||||
/>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
<van-cell-group title="配置">
|
||||
<van-cell title="开关样本">
|
||||
<van-switch
|
||||
v-model="sampleSwitchChecked"
|
||||
@change="onSampleSwitchChanged"
|
||||
/>
|
||||
</van-cell>
|
||||
<van-field v-model="greeting"
|
||||
label="问候语"
|
||||
placeholder="请输入问候语"
|
||||
maxLength="20"
|
||||
input-align="right"
|
||||
/>
|
||||
<van-field v-model.number="count"
|
||||
label="运行次数"
|
||||
placeholder="请输入运行次数"
|
||||
maxLength="4"
|
||||
inputmode="numeric"
|
||||
input-align="right"
|
||||
/>
|
||||
<van-field
|
||||
label="选择文件"
|
||||
:value="selectedFilePath"
|
||||
placeholder="选择一个文件"
|
||||
readonly
|
||||
clickable
|
||||
@click.native="selectFile"
|
||||
input-align="right"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</van-tab>
|
||||
|
||||
<van-tab title="运行">
|
||||
<van-cell title="查看日志" is-link @click="showLog"/>
|
||||
<van-row type="flex" justify="center">
|
||||
<van-button type="primary" @click="run" style="margin-top: 12px;">运行</van-button>
|
||||
</van-row>
|
||||
</van-tab>
|
||||
|
||||
<van-tab title="关于">
|
||||
<van-cell
|
||||
value="运行环境"
|
||||
:title="appVersionName ? `AutoJs6 ${appVersionName}` : `AutoJs6`"
|
||||
label="WebView + Android"
|
||||
@click="showDeviceInfoDialog"
|
||||
/>
|
||||
<van-cell
|
||||
title="Vue.js 2.6"
|
||||
label="渐进式 JavaScript 框架"
|
||||
is-link
|
||||
@click="openVueWebsite"
|
||||
/>
|
||||
<van-cell
|
||||
title="Vant 2.12"
|
||||
label="轻量, 可靠的移动端 Vue 组件库"
|
||||
is-link
|
||||
@click="openVantWebsite"
|
||||
/>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</van-row>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
accessibilityServiceEnabled: false,
|
||||
activeTab: 0,
|
||||
sampleSwitchChecked: true,
|
||||
greeting: 'Hello',
|
||||
count: 192,
|
||||
appVersionName: '',
|
||||
selectedFilePath: '',
|
||||
};
|
||||
},
|
||||
created() {
|
||||
$autojs.invoke('get-accessibility-enabled').then((value) => {
|
||||
this.accessibilityServiceEnabled = value;
|
||||
});
|
||||
$autojs.invoke('get-app-version-name').then((value) => {
|
||||
this.appVersionName = value;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onAccessibilityServiceCheckChanged(checked) {
|
||||
$autojs.invoke('set-accessibility-enabled', checked);
|
||||
},
|
||||
onSampleSwitchChanged(checked) {
|
||||
$autojs.invoke('toast-log', `样本开关已${checked ? '开启' : '关闭'}`);
|
||||
},
|
||||
showLog() {
|
||||
$autojs.invoke('show-log');
|
||||
},
|
||||
openVantWebsite() {
|
||||
$autojs.send('open-url', 'https://vant-ui.github.io/vant/v2/#/zh-CN/');
|
||||
},
|
||||
openVueWebsite() {
|
||||
$autojs.send('open-url', 'https://cn.vuejs.org/');
|
||||
},
|
||||
run() {
|
||||
$autojs.invoke('toast-log', `greeting: "${this.greeting}"\ncount: ${this.count}`);
|
||||
},
|
||||
selectFile() {
|
||||
$autojs.invoke('select-file', '*/*').then((path) => {
|
||||
this.selectedFilePath = path || '';
|
||||
});
|
||||
},
|
||||
showDeviceInfoDialog() {
|
||||
$autojs.invoke('show-device-info-dialog');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
17
app/src/main/assets-app/sample/布局/可交互 HTML/index.html
Normal file
17
app/src/main/assets-app/sample/布局/可交互 HTML/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Interactive HTML</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<link rel="stylesheet" href="style.css"/>
|
||||
<script src="autojs://sdk/v1.js"></script>
|
||||
<script src="index.js"></script>
|
||||
<button id="testButton">点击测试</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
10
app/src/main/assets-app/sample/布局/可交互 HTML/index.js
Normal file
10
app/src/main/assets-app/sample/布局/可交互 HTML/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const winOnloadBak = window.onload ? window.onload.bind(window) : null;
|
||||
|
||||
window.onload = function () {
|
||||
if (typeof winOnloadBak === 'function') {
|
||||
winOnloadBak();
|
||||
}
|
||||
document.getElementById('testButton').addEventListener('click', () => {
|
||||
$autojs.invoke('toast-log', '按钮已被点击');
|
||||
});
|
||||
};
|
||||
13
app/src/main/assets-app/sample/布局/可交互 HTML/main.js
Normal file
13
app/src/main/assets-app/sample/布局/可交互 HTML/main.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'ui';
|
||||
|
||||
ui.layout(
|
||||
<vertical>
|
||||
<webview id="web" url="index.html" w="*" h="*"/>
|
||||
</vertical>,
|
||||
);
|
||||
|
||||
let web = ui['web'];
|
||||
|
||||
web.jsBridge.handle('toast-log', (event, msg) => {
|
||||
toastLog(msg);
|
||||
});
|
||||
16
app/src/main/assets-app/sample/布局/可交互 HTML/project.json
Normal file
16
app/src/main/assets-app/sample/布局/可交互 HTML/project.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"assets": [],
|
||||
"useFeatures": [],
|
||||
"launchConfig": {
|
||||
"displaySplash": true,
|
||||
"hideLogs": true,
|
||||
"splashText": "Powered by AutoJs6",
|
||||
"stableMode": false
|
||||
},
|
||||
"main": "main.js",
|
||||
"name": "可交互 HTML",
|
||||
"packageName": "org.example.interactive_html",
|
||||
"scripts": {},
|
||||
"versionCode": 1,
|
||||
"versionName": "1.0.0"
|
||||
}
|
||||
25
app/src/main/assets-app/sample/布局/可交互 HTML/style.css
Normal file
25
app/src/main/assets-app/sample/布局/可交互 HTML/style.css
Normal file
@@ -0,0 +1,25 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
#testButton {
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
background-color: #006400;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#testButton:hover {
|
||||
background-color: #008000;
|
||||
}
|
||||
163
app/src/main/assets/modules/ui-ext.js
Normal file
163
app/src/main/assets/modules/ui-ext.js
Normal file
@@ -0,0 +1,163 @@
|
||||
( /* @ModuleIIFE */ () => {
|
||||
|
||||
// @Reference to module __ui__.js from Auto.js Pro 9.3.11 on May 24, 2025.
|
||||
|
||||
let JsBridge = (function () {
|
||||
let ResultAdapter = require('result-adapter');
|
||||
|
||||
let EVENT_REQUEST = '$autojs:internal:request';
|
||||
let EVENT_RESPONSE = '$autojs:internal:response';
|
||||
|
||||
function JavaScriptBridgeImpl(webview) {
|
||||
let self = this;
|
||||
events.__asEmitter__(self);
|
||||
self.nextId = 1;
|
||||
self.requestHandlers = new Map();
|
||||
self.webview = webview;
|
||||
webview.setJavascriptEventCallback({
|
||||
onWebJavaScriptEvent(event, args) {
|
||||
let obj = unwrapJson(args) || [];
|
||||
self.emit.apply(self, [ event, { name: event } ].concat(obj));
|
||||
},
|
||||
});
|
||||
self.on(EVENT_REQUEST, function (e, request) {
|
||||
let handler = self.requestHandlers.get(request.channel) ?? self.requestHandlers.get('');
|
||||
if (!handler) {
|
||||
self.sendResponseError(request, new Error('no handler for action: ' + request.channel));
|
||||
return;
|
||||
}
|
||||
let event = {
|
||||
channel: request.channel,
|
||||
arguments: request.args,
|
||||
};
|
||||
let result;
|
||||
try {
|
||||
result = handler.apply(void 0, [ event ].concat(event.arguments));
|
||||
} catch (e) {
|
||||
self.sendResponseError(request, e);
|
||||
return;
|
||||
}
|
||||
if (isPromise(result)) {
|
||||
result.then(function (r) {
|
||||
self.sendResponse(request, r);
|
||||
}).catch(function (err) {
|
||||
self.sendResponseError(request, err);
|
||||
});
|
||||
} else {
|
||||
self.sendResponse(request, result);
|
||||
}
|
||||
});
|
||||
return self;
|
||||
}
|
||||
|
||||
JavaScriptBridgeImpl.prototype.sendResponse = function (request, result, error) {
|
||||
this.send(EVENT_RESPONSE + ':' + request.id, {
|
||||
result: result,
|
||||
error: error,
|
||||
});
|
||||
};
|
||||
JavaScriptBridgeImpl.prototype.sendResponseError = function (request, error) {
|
||||
this.sendResponse(request, undefined, error.toString());
|
||||
};
|
||||
JavaScriptBridgeImpl.prototype.invoke = function (channel) {
|
||||
let self = this;
|
||||
let args = [];
|
||||
for (let _i = 1; _i < arguments.length; _i++) {
|
||||
args[_i - 1] = arguments[_i];
|
||||
}
|
||||
let id = this.nextId++;
|
||||
return new Promise(function (resolve, reject) {
|
||||
self.once(EVENT_RESPONSE + ':' + id, function (event, result) {
|
||||
if (result.error) {
|
||||
reject(new Error('Error occurred while handling invoke: channel = ' + channel + ', error = ' + result.error));
|
||||
} else {
|
||||
resolve(result.result);
|
||||
}
|
||||
});
|
||||
self.send(EVENT_REQUEST, {
|
||||
id: id,
|
||||
channel: channel,
|
||||
args: args,
|
||||
});
|
||||
});
|
||||
};
|
||||
JavaScriptBridgeImpl.prototype.send = function (event) {
|
||||
let args = [];
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
args[i - 1] = arguments[i];
|
||||
}
|
||||
this.webview.sendEventToWebJavaScript(event, wrapJson(args));
|
||||
};
|
||||
JavaScriptBridgeImpl.prototype.handle = function (channel, handler) {
|
||||
this.requestHandlers.set(channel !== null && channel !== void 0 ? channel : '', handler);
|
||||
return this;
|
||||
};
|
||||
JavaScriptBridgeImpl.prototype.eval = function (code) {
|
||||
let self = this;
|
||||
return new Promise(function (resolve, reject) {
|
||||
ResultAdapter.promise(self.webview.__eval(code))
|
||||
.then(result => resolve(JSON.parse(String(result))))
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
};
|
||||
|
||||
function unwrapJson(maybeJson) {
|
||||
if (!maybeJson) {
|
||||
return undefined;
|
||||
}
|
||||
return JSON.parse(maybeJson);
|
||||
}
|
||||
|
||||
function wrapJson(obj) {
|
||||
if (typeof obj === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
function isPromise(obj) {
|
||||
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
|
||||
}
|
||||
|
||||
return JavaScriptBridgeImpl;
|
||||
})();
|
||||
|
||||
/**
|
||||
* @param {org.autojs.autojs.core.ui.widget.JsWebView} webview
|
||||
*/
|
||||
function initWebView(webview) {
|
||||
webview.jsBridge = new JsBridge(webview);
|
||||
let emitter = events.emitter();
|
||||
webview.events = emitter;
|
||||
webview.setSyncWebViewEventCallback({
|
||||
onSyncWebViewEvent(event) {
|
||||
dispatchJavaEvent(event, emitter);
|
||||
},
|
||||
});
|
||||
webview.setSyncEventEnabled('', true);
|
||||
|
||||
function dispatchJavaEvent(event, emitter) {
|
||||
let eventName = event.getName();
|
||||
let args = Array.from(event.getArguments());
|
||||
let _returnValue;
|
||||
let returnValueSet = false;
|
||||
let e = {
|
||||
name: eventName,
|
||||
arguments: args,
|
||||
consumed: false,
|
||||
};
|
||||
Object.defineProperty(e, 'returnValue', {
|
||||
get: function () {
|
||||
return _returnValue;
|
||||
},
|
||||
set: function (value) {
|
||||
_returnValue = value;
|
||||
returnValueSet = true;
|
||||
},
|
||||
});
|
||||
emitter.emit.apply(emitter, [ eventName, e ].concat(args));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = initWebView;
|
||||
})();
|
||||
90
app/src/main/assets/web/dist/autojs.sdk.v1.js
vendored
Normal file
90
app/src/main/assets/web/dist/autojs.sdk.v1.js
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
|
||||
( /* @IIFE */ () => {
|
||||
|
||||
// @Reference to .../asset/web/dist/autojs.sdk.v1.js from Auto.js Pro 9.3.11 on May 24, 2025.
|
||||
|
||||
const EVENT_RESPONSE = '$autojs:internal:response';
|
||||
const EVENT_REQUEST = '$autojs:internal:request';
|
||||
|
||||
// noinspection JSValidateTypes
|
||||
/** @type {import('events')} */
|
||||
let nodejsEvents = events;
|
||||
|
||||
let nextId = 1;
|
||||
|
||||
let helper = {
|
||||
isPromise: o => o && typeof o.then === 'function',
|
||||
unwrapJson: o => o ? JSON.parse(o) : undefined,
|
||||
wrapJson: o => o === undefined ? o : JSON.stringify(o),
|
||||
};
|
||||
|
||||
Object.setPrototypeOf($autojs, new nodejsEvents.EventEmitter());
|
||||
|
||||
/** @type {Internal.AutoJsBridge} */
|
||||
let AutoJsBridge = {
|
||||
requestHandlers: {},
|
||||
onEventInternal(event, args) {
|
||||
this.emit(event, ...helper.unwrapJson(args));
|
||||
},
|
||||
send(event, ...args) {
|
||||
this.sendEventInternal(event, helper.wrapJson(args));
|
||||
},
|
||||
invoke(channel, ...args) {
|
||||
let id = nextId++;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.once(EVENT_RESPONSE + ':' + id, (result) => {
|
||||
if (result.error) {
|
||||
reject(new Error('Error occurred while handling invoke: channel = ' + channel + ', error = ' + result.error));
|
||||
} else {
|
||||
resolve(result.result);
|
||||
}
|
||||
});
|
||||
this.send(EVENT_REQUEST, {
|
||||
id, channel, args,
|
||||
});
|
||||
});
|
||||
},
|
||||
/** @this {Internal.AutoJsBridge} */
|
||||
handle(channel, handler) {
|
||||
this.requestHandlers[channel || ''] = handler;
|
||||
return this;
|
||||
},
|
||||
removeHandler(channel) {
|
||||
delete this.requestHandlers[channel];
|
||||
},
|
||||
handleRequest(request) {
|
||||
let handler = this.requestHandlers[request.channel] || this.requestHandlers[''];
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
let event = {
|
||||
channel: request.channel,
|
||||
arguments: request.args,
|
||||
};
|
||||
let result;
|
||||
try {
|
||||
result = handler(event, ...event.arguments);
|
||||
} catch (e) {
|
||||
this.sendResponse(request, undefined, e);
|
||||
return;
|
||||
}
|
||||
if (helper.isPromise(request)) {
|
||||
result.then((r) => {
|
||||
this.sendResponse(request, r);
|
||||
}).catch((err) => {
|
||||
this.sendResponse(request, undefined, err);
|
||||
});
|
||||
} else {
|
||||
this.sendResponse(request, result);
|
||||
}
|
||||
},
|
||||
sendResponse(request, result, error) {
|
||||
this.send(EVENT_RESPONSE + ':' + request.id, { result, error });
|
||||
},
|
||||
};
|
||||
|
||||
$autojs = Object.assign($autojs, AutoJsBridge);
|
||||
$autojs.on(EVENT_REQUEST, AutoJsBridge.handleRequest.bind($autojs));
|
||||
|
||||
})();
|
||||
23
app/src/main/assets/web/dist/events.min@3.3.0.js
vendored
Normal file
23
app/src/main/assets/web/dist/events.min@3.3.0.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -4,6 +4,7 @@ import android.view.View
|
||||
import android.webkit.WebView
|
||||
import org.autojs.autojs.core.ui.inflater.ResourceParser
|
||||
import org.autojs.autojs.core.ui.inflater.util.Strings
|
||||
import org.autojs.autojs.extension.StringExtensions.isUri
|
||||
import org.autojs.autojs.runtime.ScriptRuntime
|
||||
|
||||
open class WebViewAttributes(scriptRuntime: ScriptRuntime, resourceParser: ResourceParser, view: View) : ViewGroupAttributes(scriptRuntime, resourceParser, view) {
|
||||
@@ -13,7 +14,7 @@ open class WebViewAttributes(scriptRuntime: ScriptRuntime, resourceParser: Resou
|
||||
override fun onRegisterAttrs(scriptRuntime: ScriptRuntime) {
|
||||
super.onRegisterAttrs(scriptRuntime)
|
||||
|
||||
registerAttr("url") { view.loadUrl(it) }
|
||||
registerAttr("url") { view.loadUrl(if (it.isUri()) it else scriptRuntime.files.nonNullPath(it)) }
|
||||
registerAttrs(arrayOf("scale", "initialScale")) { view.setInitialScale(it.toInt()) }
|
||||
registerAttrs(arrayOf("enableNetwork", "networkAvailable")) { view.setNetworkAvailable(it.toBoolean()) }
|
||||
registerAttr("blockNetworkImage") { view.settings.blockNetworkImage = it.toBoolean() }
|
||||
|
||||
@@ -0,0 +1,599 @@
|
||||
package org.autojs.autojs.core.ui.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.net.http.SslError
|
||||
import android.os.Message
|
||||
import android.util.AttributeSet
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.webkit.ClientCertRequest
|
||||
import android.webkit.ConsoleMessage
|
||||
import android.webkit.GeolocationPermissions
|
||||
import android.webkit.HttpAuthHandler
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.JsPromptResult
|
||||
import android.webkit.JsResult
|
||||
import android.webkit.PermissionRequest
|
||||
import android.webkit.RenderProcessGoneDetail
|
||||
import android.webkit.SafeBrowsingResponse
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.ValueCallback
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebChromeClient.FileChooserParams
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.annotation.Keep
|
||||
import androidx.core.net.toUri
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import org.autojs.autojs.annotation.ScriptInterface
|
||||
import org.autojs.autojs.app.GlobalAppContext
|
||||
import org.autojs.autojs.app.OnActivityResultDelegate
|
||||
import org.autojs.autojs.event.CoroutineSyncEventHost
|
||||
import org.autojs.autojs.event.CoroutineSyncEventHost.Companion.Event
|
||||
import org.autojs.autojs.event.EventResult
|
||||
import org.autojs.autojs.event.IEventEmitter
|
||||
import org.autojs.autojs.runtime.api.Mime
|
||||
import org.autojs.autojs.runtime.api.Resolvable
|
||||
import org.autojs.autojs.util.ContextUtils.findActivity
|
||||
import org.autojs.autojs.util.RhinoUtils
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceBoolean
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by SuperMonster003 on May 23, 2025.
|
||||
*/
|
||||
// @Reference to Auto.js Pro 9.3.11 by SuperMonster003 on May 23, 2025.
|
||||
@Keep
|
||||
abstract class EventWebView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0,
|
||||
) : WebView(context, attrs, defStyleAttr, defStyleRes), IEventEmitter {
|
||||
|
||||
private val bridge = AutoJs(this)
|
||||
|
||||
private val pendingEvents = Collections.synchronizedList(mutableListOf<PendingJsEvent>())
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
|
||||
private val syncEventHost = CoroutineSyncEventHost(coroutineScope) { event ->
|
||||
when (event.sync) {
|
||||
true -> syncWebViewEventCallback?.onSyncWebViewEvent(event)
|
||||
else -> webViewEventCallback?.onWebViewEvent(event)
|
||||
} != null
|
||||
}.apply { onError = ::onError }
|
||||
|
||||
@Volatile
|
||||
var webViewEventCallback: WebViewEventCallback? = null
|
||||
|
||||
@Volatile
|
||||
var syncWebViewEventCallback: SyncViewEventCallback? = null
|
||||
|
||||
@Volatile
|
||||
var javascriptEventCallback: JavaScriptEventCallback? = null
|
||||
private set
|
||||
|
||||
init {
|
||||
initSettings()
|
||||
setWebViewClient(InternalClient())
|
||||
setWebChromeClient(InternalChromeClient())
|
||||
addJavascriptInterface(bridge, "\$autojs")
|
||||
}
|
||||
|
||||
private fun initSettings() = settings.run {
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
javaScriptEnabled = true
|
||||
|
||||
javaScriptCanOpenWindowsAutomatically = true
|
||||
domStorageEnabled = true
|
||||
builtInZoomControls = false
|
||||
allowFileAccess = true
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
allowFileAccessFromFileURLs = true
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
allowUniversalAccessFromFileURLs = true
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@ScriptInterface
|
||||
fun evalInternal(code: String): Resolvable {
|
||||
val promise = newPromise()
|
||||
RhinoUtils.dispatchToMainThread {
|
||||
evaluateJavascript(code) { promise.resolve(it) }
|
||||
}
|
||||
return promise
|
||||
}
|
||||
|
||||
override fun emitEvent(event: String, vararg args: Any?): EventResult {
|
||||
return emitInScope(event, coroutineScope, *args)
|
||||
}
|
||||
|
||||
fun emitInScope(eventName: String, scope: CoroutineScope, vararg args: Any?): EventResult {
|
||||
return syncEventHost.emitInScope(eventName, scope, *args)
|
||||
}
|
||||
|
||||
private inner class InternalClient : WebViewClient() {
|
||||
|
||||
override fun doUpdateVisitedHistory(view: WebView, url: String?, isReload: Boolean) {
|
||||
if (emitEvent("update_visited_history", view, url, isReload).callSuper) {
|
||||
super.doUpdateVisitedHistory(view, url, isReload)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFormResubmission(view: WebView, dontResend: Message?, resend: Message?) {
|
||||
if (emitEvent("form_resubmission", view, dontResend, resend).callSuper) {
|
||||
super.onFormResubmission(view, dontResend, resend)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadResource(view: WebView, url: String?) {
|
||||
if (emitEvent("load_resource", view, url).callSuper) {
|
||||
super.onLoadResource(view, url)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageCommitVisible(view: WebView, url: String?) {
|
||||
if (emitEvent("page_commit_visible", view, url).callSuper) {
|
||||
super.onPageCommitVisible(view, url)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView, url: String?) {
|
||||
if (emitEvent("page_finished", view, url).callSuper) {
|
||||
super.onPageFinished(view, url)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
|
||||
if (emitEvent("page_started", view, url, favicon).callSuper) {
|
||||
super.onPageStarted(view, url, favicon)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedClientCertRequest(view: WebView, request: ClientCertRequest?) {
|
||||
if (emitEvent("received_client_cert_request", view, request).callSuper) {
|
||||
super.onReceivedClientCertRequest(view, request)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView, request: WebResourceRequest?, error: WebResourceError?) {
|
||||
if (emitEvent("received_error", view, request, error).callSuper) {
|
||||
super.onReceivedError(view, request, error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedHttpAuthRequest(view: WebView, handler: HttpAuthHandler?, host: String?, realm: String?) {
|
||||
if (emitEvent("received_http_auth_request", view, handler, host, realm).callSuper) {
|
||||
super.onReceivedHttpAuthRequest(view, handler, host, realm)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedHttpError(view: WebView, request: WebResourceRequest?, errorResponse: WebResourceResponse?) {
|
||||
if (emitEvent("received_http_error", view, request, errorResponse).callSuper) {
|
||||
super.onReceivedHttpError(view, request, errorResponse)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedLoginRequest(view: WebView, realm: String?, account: String?, args: String?) {
|
||||
if (emitEvent("received_login_request", view, realm, account, args).callSuper) {
|
||||
super.onReceivedLoginRequest(view, realm, account, args)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler?, error: SslError?) {
|
||||
if (emitEvent("received_ssl_error", view, handler, error).callSuper) {
|
||||
super.onReceivedSslError(view, handler, error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail?): Boolean {
|
||||
val eventResult = emitEvent("render_process_gone", view, detail)
|
||||
val superResult = super.onRenderProcessGone(view, detail)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSafeBrowsingHit(view: WebView, request: WebResourceRequest?, threatType: Int, callback: SafeBrowsingResponse?) {
|
||||
if (emitEvent("safe_browsing_hit", view, request, threatType, callback).callSuper) {
|
||||
super.onSafeBrowsingHit(view, request, threatType, callback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScaleChanged(view: WebView, oldScale: Float, newScale: Float) {
|
||||
if (emitEvent("scale_changed", view, oldScale, newScale).callSuper) {
|
||||
super.onScaleChanged(view, oldScale, newScale)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
override fun onTooManyRedirects(view: WebView, cancelMsg: Message?, continueMsg: Message?) {
|
||||
if (emitEvent("too_many_redirects", view, cancelMsg, continueMsg).callSuper) {
|
||||
super.onTooManyRedirects(view, cancelMsg, continueMsg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUnhandledKeyEvent(view: WebView, event: KeyEvent?) {
|
||||
if (emitEvent("unhandled_key_event", view, event).callSuper) {
|
||||
super.onUnhandledKeyEvent(view, event)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {
|
||||
// 本地注入 AutoJs SDK
|
||||
if (url?.toUri() == AUTOJS_SDK_URI) {
|
||||
return WebResourceResponse(
|
||||
/* mimeType = */ "application/javascript",
|
||||
/* encoding = */ "UTF-8",
|
||||
/* statusCode = */ 200,
|
||||
/* reasonPhrase = */ "OK",
|
||||
/* responseHeaders = */ emptyMap(),
|
||||
/* data = */ ByteArrayInputStream(AUTOJS_SDK_JS)
|
||||
)
|
||||
}
|
||||
val eventResult = emitEvent("should_intercept_request", view, url)
|
||||
val superResult = super.shouldInterceptRequest(view, url)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> eventResult.result as? WebResourceResponse? ?: superResult
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
|
||||
// 本地注入 AutoJs SDK
|
||||
if (request.url == AUTOJS_SDK_URI) {
|
||||
return WebResourceResponse(
|
||||
/* mimeType = */ "application/javascript",
|
||||
/* encoding = */ "UTF-8",
|
||||
/* statusCode = */ 200,
|
||||
/* reasonPhrase = */ "OK",
|
||||
/* responseHeaders = */ emptyMap(),
|
||||
/* data = */ ByteArrayInputStream(AUTOJS_SDK_JS)
|
||||
)
|
||||
}
|
||||
val eventResult = emitEvent("should_intercept_request", view, request)
|
||||
val superResult = super.shouldInterceptRequest(view, request)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> eventResult.result as? WebResourceResponse? ?: superResult
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldOverrideKeyEvent(view: WebView?, event: KeyEvent?): Boolean {
|
||||
val eventResult = emitEvent("should_override_key_event", view, event)
|
||||
val superResult = super.shouldOverrideKeyEvent(view, event)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
||||
val eventResult = emitEvent("should_override_url_loading", view, request)
|
||||
val superResult = super.shouldOverrideUrlLoading(view, request)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private inner class InternalChromeClient : WebChromeClient() {
|
||||
|
||||
override fun getDefaultVideoPoster(): Bitmap? {
|
||||
val eventResult = emitEvent("get_default_video_poster")
|
||||
val superResult = super.getDefaultVideoPoster()
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> eventResult.result as? Bitmap? ?: superResult
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVideoLoadingProgressView(): View? {
|
||||
val eventResult = emitEvent("get_video_loading_progress_view")
|
||||
val superResult = super.getVideoLoadingProgressView()
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> eventResult.result as? View? ?: superResult
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVisitedHistory(callback: ValueCallback<Array<out String?>?>?) {
|
||||
if (emitEvent("get_visited_history", callback).callSuper) {
|
||||
super.getVisitedHistory(callback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCloseWindow(window: WebView?) {
|
||||
if (emitEvent("close_window", window).callSuper) {
|
||||
super.onCloseWindow(window)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
|
||||
val eventResult = emitEvent("console_message", consoleMessage)
|
||||
val superResult = super.onConsoleMessage(consoleMessage)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateWindow(view: WebView?, isDialog: Boolean, isUserGesture: Boolean, resultMsg: Message?): Boolean {
|
||||
val eventResult = emitEvent("create_window", view, isDialog, isUserGesture, resultMsg)
|
||||
val superResult = super.onCreateWindow(view, isDialog, isUserGesture, resultMsg)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGeolocationPermissionsHidePrompt() {
|
||||
if (emitEvent("geolocation_permissions_hide_prompt").callSuper) {
|
||||
super.onGeolocationPermissionsHidePrompt()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGeolocationPermissionsShowPrompt(origin: String?, callback: GeolocationPermissions.Callback?) {
|
||||
if (emitEvent("geolocation_permissions_show_prompt", origin, callback).callSuper) {
|
||||
super.onGeolocationPermissionsShowPrompt(origin, callback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHideCustomView() {
|
||||
if (emitEvent("hide_custom_view").callSuper) {
|
||||
super.onHideCustomView()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onJsAlert(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
|
||||
val eventResult = emitEvent("js_alert", view, url, message, result)
|
||||
val superResult = super.onJsAlert(view, url, message, result)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onJsBeforeUnload(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
|
||||
val eventResult = emitEvent("js_before_unload", view, url, message, result)
|
||||
val superResult = super.onJsBeforeUnload(view, url, message, result)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onJsConfirm(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
|
||||
val eventResult = emitEvent("js_confirm", view, url, message, result)
|
||||
val superResult = super.onJsConfirm(view, url, message, result)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onJsPrompt(view: WebView?, url: String?, message: String?, defaultValue: String?, result: JsPromptResult?): Boolean {
|
||||
val eventResult = emitEvent("js_prompt", view, url, message, defaultValue, result)
|
||||
val superResult = super.onJsPrompt(view, url, message, defaultValue, result)
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
override fun onJsTimeout(): Boolean {
|
||||
val eventResult = emitEvent("js_timeout")
|
||||
val superResult = super.onJsTimeout()
|
||||
return when {
|
||||
eventResult.callSuper -> superResult
|
||||
else -> coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPermissionRequest(request: PermissionRequest?) {
|
||||
if (emitEvent("permission_request", request).callSuper) {
|
||||
super.onPermissionRequest(request)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPermissionRequestCanceled(request: PermissionRequest?) {
|
||||
if (emitEvent("permission_request_canceled", request).callSuper) {
|
||||
super.onPermissionRequestCanceled(request)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProgressChanged(view: WebView?, newProgress: Int) {
|
||||
if (emitEvent("progress_changed", view, newProgress).callSuper) {
|
||||
super.onProgressChanged(view, newProgress)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedIcon(view: WebView?, icon: Bitmap?) {
|
||||
if (emitEvent("received_icon", view, icon).callSuper) {
|
||||
super.onReceivedIcon(view, icon)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedTitle(view: WebView?, title: String?) {
|
||||
if (emitEvent("received_title", view, title).callSuper) {
|
||||
super.onReceivedTitle(view, title)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedTouchIconUrl(view: WebView?, url: String?, precomposed: Boolean) {
|
||||
if (emitEvent("received_touch_icon_url", view, url, precomposed).callSuper) {
|
||||
super.onReceivedTouchIconUrl(view, url, precomposed)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestFocus(view: WebView?) {
|
||||
if (emitEvent("request_focus", view).callSuper) {
|
||||
super.onRequestFocus(view)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShowCustomView(view: View?, callback: CustomViewCallback?) {
|
||||
if (emitEvent("show_custom_view", view, callback).callSuper) {
|
||||
super.onShowCustomView(view, callback)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback<Array<out Uri?>?>?, fileChooserParams: FileChooserParams?): Boolean {
|
||||
val eventResult = emitEvent("show_file_chooser", webView, filePathCallback, fileChooserParams)
|
||||
val superResult = super.onShowFileChooser(webView, filePathCallback, fileChooserParams)
|
||||
if (!eventResult.callSuper) {
|
||||
return coerceBoolean(eventResult.result, superResult)
|
||||
}
|
||||
var handled = false
|
||||
try {
|
||||
val activity = webView?.context?.findActivity()
|
||||
if (activity is OnActivityResultDelegate.DelegateHost) {
|
||||
val mediator = activity.getOnActivityResultDelegateMediator()
|
||||
val intent = fileChooserParams?.createIntent()
|
||||
val type = intent?.type
|
||||
if (type != null && type.startsWith(".")) {
|
||||
intent.type = Mime.fromFile(type)
|
||||
}
|
||||
try {
|
||||
activity.startActivityForResult(intent, FILE_CHOOSER_REQUEST_CODE)
|
||||
mediator.addDelegate(FileChooserCallback(filePathCallback, mediator))
|
||||
handled = true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
e.printStackTrace()
|
||||
filePathCallback?.onReceiveValue(null)
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
}
|
||||
return handled || superResult
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private inner class FileChooserCallback(
|
||||
private val filePathCallback: ValueCallback<Array<out Uri?>?>?,
|
||||
private val mediator: OnActivityResultDelegate.Mediator,
|
||||
) : OnActivityResultDelegate {
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == FILE_CHOOSER_REQUEST_CODE) {
|
||||
filePathCallback?.onReceiveValue(FileChooserParams.parseResult(resultCode, data))
|
||||
emitEvent("file_chooser_result", requestCode, resultCode, data)
|
||||
mediator.removeDelegate(this)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open fun onError(t: Throwable) {
|
||||
org.autojs.autojs.AutoJs.instance.globalConsole.error(t)
|
||||
t.printStackTrace()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
interface JavaScriptEventCallback {
|
||||
fun onWebJavaScriptEvent(eventName: String, args: String?)
|
||||
}
|
||||
|
||||
interface WebViewEventCallback {
|
||||
fun onWebViewEvent(event: Event)
|
||||
}
|
||||
|
||||
interface SyncViewEventCallback {
|
||||
fun onSyncWebViewEvent(event: Event)
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
fun sendEventToWebJavaScript(event: String, jsonArgs: String?) {
|
||||
val eventName = escapeToStr(event)
|
||||
val args = escapeToStr(jsonArgs ?: "")
|
||||
val js = "\$autojs?.onEventInternal?.($eventName, $args)"
|
||||
RhinoUtils.dispatchToMainThread {
|
||||
evaluateJavascript(js, null)
|
||||
}
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
fun setJavascriptEventCallback(callback: JavaScriptEventCallback) {
|
||||
javascriptEventCallback = callback
|
||||
synchronized(pendingEvents) {
|
||||
for (pendingEvent in pendingEvents) {
|
||||
callback.onWebJavaScriptEvent(pendingEvent.event, pendingEvent.jsonArgs)
|
||||
}
|
||||
pendingEvents.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
fun setSyncEventEnabled(event: String, alwaysSync: Boolean) {
|
||||
val coroutineSyncEventHost = syncEventHost
|
||||
if (event.isEmpty()) {
|
||||
coroutineSyncEventHost.alwaysSync = alwaysSync
|
||||
} else {
|
||||
coroutineSyncEventHost.syncEventTable.put(event, alwaysSync)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun escapeToStr(src: String): String
|
||||
|
||||
abstract fun newPromise(): Resolvable
|
||||
|
||||
companion object {
|
||||
|
||||
const val FILE_CHOOSER_REQUEST_CODE = 24009
|
||||
|
||||
val AUTOJS_SDK_URI: Uri = "autojs://sdk/v1.js".toUri()
|
||||
|
||||
val AUTOJS_SDK_JS: ByteArray by lazy {
|
||||
val assets = GlobalAppContext.get().applicationContext.assets
|
||||
val eventsJs = assets.open("web/dist/events.min@3.3.0.js").bufferedReader().readText()
|
||||
val autoJsJs = assets.open("web/dist/autojs.sdk.v1.js").bufferedReader().readText()
|
||||
(eventsJs + "\n" + autoJsJs).toByteArray(StandardCharsets.UTF_8)
|
||||
}
|
||||
|
||||
data class PendingJsEvent(val event: String, val jsonArgs: String)
|
||||
|
||||
@Keep
|
||||
class AutoJs(private val webView: EventWebView) {
|
||||
|
||||
@JavascriptInterface
|
||||
fun sendEventInternal(event: String, jsonArgs: String) {
|
||||
val callback = webView.javascriptEventCallback
|
||||
if (callback == null) {
|
||||
webView.pendingEvents.add(PendingJsEvent(event, jsonArgs))
|
||||
} else {
|
||||
callback.onWebJavaScriptEvent(event, jsonArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,11 +2,11 @@ package org.autojs.autojs.core.ui.widget
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import org.autojs.autojs.util.ContextUtils.requireActivity
|
||||
import org.autojs.autojs6.R
|
||||
|
||||
class JsToolbar : Toolbar {
|
||||
@@ -19,11 +19,7 @@ class JsToolbar : Toolbar {
|
||||
|
||||
private val activity: Activity
|
||||
get() {
|
||||
var context = context
|
||||
while (context !is Activity) {
|
||||
context = (context as? ContextWrapper)?.baseContext
|
||||
}
|
||||
return context
|
||||
return context.requireActivity()
|
||||
}
|
||||
|
||||
fun setupWithDrawer(drawerLayout: DrawerLayout) {
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
package org.autojs.autojs.core.ui.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import org.autojs.autojs.AutoJs
|
||||
import org.autojs.autojs.core.eventloop.EventEmitter
|
||||
import org.autojs.autojs.runtime.api.Resolvable
|
||||
import org.autojs.autojs.runtime.api.ScriptPromiseAdapter
|
||||
import org.mozilla.javascript.ScriptRuntime
|
||||
import org.mozilla.javascript.Scriptable
|
||||
|
||||
/**
|
||||
* Created by Stardust on Nov 29, 2017.
|
||||
* Modified by SuperMonster003 as of Jan 21, 2023.
|
||||
* Transformed by SuperMonster003 on May 26, 2023.
|
||||
*/
|
||||
class JsWebView : WebView {
|
||||
class JsWebView : EventWebView {
|
||||
|
||||
@JvmField
|
||||
var events: EventEmitter? = null
|
||||
|
||||
@JvmField
|
||||
var jsBridge: Scriptable? = null
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
@@ -21,17 +31,18 @@ class JsWebView : WebView {
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
|
||||
|
||||
init {
|
||||
settings.apply {
|
||||
useWideViewPort = true
|
||||
builtInZoomControls = true
|
||||
loadWithOverviewMode = true
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
javaScriptEnabled = true
|
||||
javaScriptCanOpenWindowsAutomatically = true
|
||||
domStorageEnabled = true
|
||||
displayZoomControls = false
|
||||
}
|
||||
webViewClient = WebViewClient()
|
||||
webChromeClient = WebChromeClient()
|
||||
isFocusable = true
|
||||
isFocusableInTouchMode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape string according to Rhino's rules and wrap with single quotes.
|
||||
* zh-CN: 将字符串按 Rhino 的规则做转义, 并包裹单引号.
|
||||
*/
|
||||
override fun escapeToStr(src: String) = "'${ScriptRuntime.escapeString(src, '\'')}'"
|
||||
|
||||
override fun newPromise(): Resolvable = ScriptPromiseAdapter()
|
||||
|
||||
override fun onError(t: Throwable) = AutoJs.instance.globalConsole.error(t)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "ReplacePutWithAssignment", "unused")
|
||||
|
||||
package org.autojs.autojs.event
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Created by SuperMonster003 on May 23, 2025.
|
||||
*/
|
||||
// @Reference to com.stardust.event.CoroutineSyncEventHost from Auto.js Pro 9.3.11 by SuperMonster003 on May 23, 2025.
|
||||
class CoroutineSyncEventHost(
|
||||
val scope: CoroutineScope,
|
||||
val consumer: (Event) -> Boolean,
|
||||
) : IEventEmitter {
|
||||
|
||||
var onError: ((Throwable) -> Unit)? = null
|
||||
val syncEventTable = ConcurrentHashMap<String, Boolean>()
|
||||
var alwaysSync: Boolean = false
|
||||
|
||||
override fun emitEvent(event: String, vararg args: Any?): EventResult {
|
||||
require(event.isNotBlank()) { "event" }
|
||||
return emitInScope(event, scope, *args)
|
||||
}
|
||||
|
||||
fun emitInScope(eventName: String, scope: CoroutineScope, vararg args: Any?): EventResult {
|
||||
val sync = alwaysSync || syncEventTable[eventName] ?: false
|
||||
val evt = Event(arguments = args, name = eventName, sync = sync, consumed = false, result = null)
|
||||
return when {
|
||||
sync -> {
|
||||
val handled = runCatching {
|
||||
consumer(evt)
|
||||
}.onFailure {
|
||||
onError?.invoke(it.apply { printStackTrace() })
|
||||
}.isSuccess
|
||||
when {
|
||||
handled -> EventResult(evt.result, !evt.consumed)
|
||||
else -> IGNORE_RESULT
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
runCatching {
|
||||
consumer(evt)
|
||||
}.onFailure {
|
||||
onError?.invoke(it.apply { printStackTrace() })
|
||||
}
|
||||
}
|
||||
IGNORE_RESULT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
val IGNORE_RESULT = EventResult(null, true)
|
||||
|
||||
@Keep
|
||||
@Suppress("ArrayInDataClass")
|
||||
data class Event(val arguments: Array<out Any?>, val name: String, val sync: Boolean, var consumed: Boolean = false, var result: Any? = null)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.autojs.autojs.event
|
||||
|
||||
interface IEventEmitter {
|
||||
|
||||
fun emitEvent(event: String, vararg args: Any?): EventResult
|
||||
|
||||
}
|
||||
|
||||
data class EventResult(val result: Any?, val callSuper: Boolean)
|
||||
@@ -44,13 +44,15 @@ import org.mozilla.javascript.ContinuationPending
|
||||
* Created by Stardust on Feb 5, 2017.
|
||||
* Modified by SuperMonster003 as of Nov 15, 2023.
|
||||
*/
|
||||
class ScriptExecuteActivity : AppCompatActivity() {
|
||||
class ScriptExecuteActivity : AppCompatActivity(), OnActivityResultDelegate.DelegateHost {
|
||||
|
||||
private var mRuntime: ScriptRuntime? = null
|
||||
private var mExecutionListener: ScriptExecutionListener? = null
|
||||
private var mScriptSource: ScriptSource? = null
|
||||
private var mResult: Any? = null
|
||||
|
||||
private val mMediator = OnActivityResultDelegate.Mediator()
|
||||
|
||||
private lateinit var mScriptEngine: ScriptEngine<*>
|
||||
private lateinit var mScriptExecution: ActivityScriptExecution
|
||||
|
||||
@@ -235,6 +237,7 @@ class ScriptExecuteActivity : AppCompatActivity() {
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
emit("activity_result", requestCode, resultCode, data)
|
||||
mMediator.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
@@ -258,6 +261,10 @@ class ScriptExecuteActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOnActivityResultDelegateMediator(): OnActivityResultDelegate.Mediator {
|
||||
return mMediator
|
||||
}
|
||||
|
||||
class ActivityScriptExecution internal constructor(
|
||||
private val mScriptEngineManager: ScriptEngineManager,
|
||||
task: ScriptExecutionTask?,
|
||||
|
||||
@@ -420,6 +420,8 @@ class ScriptRuntime private constructor(builder: Builder) {
|
||||
|
||||
val js_ResultAdapter by lazy { rhinoRequire("result-adapter") as BaseFunction }
|
||||
|
||||
val js_UiExt by lazy { rhinoRequire("ui-ext") as BaseFunction }
|
||||
|
||||
private val js_object_observe_lite_min by lazy { rhinoRequire("object-observe-lite.min") as BaseFunction }
|
||||
|
||||
private val js_array_observe_min by lazy { rhinoRequire("array-observe.min") as BaseFunction }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.autojs.autojs.runtime.api
|
||||
|
||||
interface Resolvable {
|
||||
|
||||
fun resolve(result: Any?)
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.autojs.autojs.runtime.api
|
||||
|
||||
class ScriptPromiseAdapter {
|
||||
class ScriptPromiseAdapter : Resolvable {
|
||||
|
||||
interface Callback {
|
||||
fun call(arg: Any?)
|
||||
@@ -31,7 +31,7 @@ class ScriptPromiseAdapter {
|
||||
return this
|
||||
}
|
||||
|
||||
fun resolve(result: Any?) {
|
||||
override fun resolve(result: Any?) {
|
||||
mResult = result
|
||||
mResolveCallback?.call(result)
|
||||
}
|
||||
@@ -44,4 +44,5 @@ class ScriptPromiseAdapter {
|
||||
companion object {
|
||||
private val UNSET = Object()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import org.autojs.autojs.core.ui.inflater.inflaters.ViewGroupInflater
|
||||
import org.autojs.autojs.core.ui.inflater.inflaters.ViewInflater
|
||||
import org.autojs.autojs.core.ui.nativeview.NativeView
|
||||
import org.autojs.autojs.core.ui.widget.JsListView
|
||||
import org.autojs.autojs.core.ui.widget.JsWebView
|
||||
import org.autojs.autojs.execution.ScriptExecuteActivity
|
||||
import org.autojs.autojs.extension.AnyExtensions.isJsNullish
|
||||
import org.autojs.autojs.extension.AnyExtensions.jsBrief
|
||||
@@ -228,8 +229,9 @@ class UI(private val scriptRuntime: ScriptRuntime) : AugmentableProxy(scriptRunt
|
||||
}
|
||||
|
||||
override fun afterCreateView(inflateContext: InflateContext, view: View, node: Node?, viewName: String, parent: ViewGroup?): View {
|
||||
if (view is JsListView) {
|
||||
initListView(scriptRuntime, view)
|
||||
when (view) {
|
||||
is JsListView -> initListView(scriptRuntime, view)
|
||||
is JsWebView -> initWebView(scriptRuntime, view)
|
||||
}
|
||||
val widget = inflateContext.get("widget")
|
||||
if (widget is NativeObject) {
|
||||
@@ -835,6 +837,10 @@ class UI(private val scriptRuntime: ScriptRuntime) : AugmentableProxy(scriptRunt
|
||||
})
|
||||
}
|
||||
|
||||
private fun initWebView(scriptRuntime: ScriptRuntime, webView: JsWebView) {
|
||||
callFunction(scriptRuntime.js_UiExt, scriptRuntime.topLevelScope, null, arrayOf(webView))
|
||||
}
|
||||
|
||||
private fun wrapUiAction(scriptRuntime: ScriptRuntime, action: BaseFunction) = Runnable {
|
||||
when {
|
||||
!getActivity(scriptRuntime).isJsNullish() -> callFunction(scriptRuntime, action, scriptRuntime.topLevelScope, arrayOf())
|
||||
|
||||
24
app/src/main/java/org/autojs/autojs/util/ContextUtils.kt
Normal file
24
app/src/main/java/org/autojs/autojs/util/ContextUtils.kt
Normal file
@@ -0,0 +1,24 @@
|
||||
package org.autojs.autojs.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
|
||||
object ContextUtils {
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun Context.requireActivity(): Activity {
|
||||
return this.findActivity() ?: throw IllegalArgumentException("Context cannot be cast to Activity")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun Context.findActivity(): Activity? {
|
||||
var context: Context? = this
|
||||
while (context !is Activity) {
|
||||
context = (context as? ContextWrapper)?.baseContext
|
||||
}
|
||||
return context as? Activity?
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,7 @@ object DeviceUtils {
|
||||
@JvmStatic
|
||||
fun getDeviceSummary(context: Context) = DeviceInfo(context).toString()
|
||||
|
||||
@JvmStatic
|
||||
fun getDeviceSummaryWithSimpleAppInfo(context: Context) = DeviceInfo(context).toStringWithSimpleAppInfo()
|
||||
|
||||
// @Reference to com.heinrichreimersoftware.androidissuereporter.model.DeviceInfo
|
||||
|
||||
@@ -182,6 +182,9 @@ object FileUtils {
|
||||
/** Embedded JavaScript templating file, used for HTML templates. */
|
||||
EJS("ejs", TypeDataHolder.CODE),
|
||||
|
||||
/** Vue.js code file. */
|
||||
VUE("vue", TypeDataHolder.CODE),
|
||||
|
||||
/** NFEX code file. */
|
||||
NFEX("nfex", TypeDataHolder.CODE),
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import androidx.core.content.FileProvider
|
||||
@@ -225,6 +226,29 @@ object IntentUtils {
|
||||
false.also { e.printStackTrace() }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getFileName(context: Context, uri: Uri): String {
|
||||
var result: String? = null
|
||||
if (uri.scheme == "content") {
|
||||
context.contentResolver.query(uri, null, null, null, null).use { cursor ->
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (index >= 0) {
|
||||
result = cursor.getString(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = uri.path
|
||||
val cut = result!!.lastIndexOf('/')
|
||||
if (cut != -1) {
|
||||
result = result.substring(cut + 1)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun requestAppUsagePermission(context: Context) = try {
|
||||
Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package org.autojs.autojs.util
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
@@ -327,6 +328,14 @@ object RhinoUtils {
|
||||
@JvmStatic
|
||||
fun isBackgroundThread() = !isMainThread()
|
||||
|
||||
@JvmStatic
|
||||
fun dispatchToMainThread(r: Runnable) = dispatchToMainThread(Handler(Looper.getMainLooper()), r)
|
||||
|
||||
@JvmStatic
|
||||
fun dispatchToMainThread(handler: Handler, r: Runnable) {
|
||||
if (isMainThread()) r.run() else handler.post(r)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun encodeURI(str: String): String = encodeURI(null, str)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user