| @@ -13,6 +13,7 @@ | |||
| <set> | |||
| <option value="$PROJECT_DIR$" /> | |||
| <option value="$PROJECT_DIR$/app" /> | |||
| <option value="$PROJECT_DIR$/dsbridge" /> | |||
| <option value="$PROJECT_DIR$/test" /> | |||
| </set> | |||
| </option> | |||
| @@ -128,12 +128,12 @@ dependencies { | |||
| implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' | |||
| implementation 'com.facebook.fresco:fresco:1.5.0' | |||
| // H5原生通信 | |||
| api 'com.github.wendux:DSBridge-Android:3.0.0' | |||
| //api 'com.github.wendux:DSBridge-Android:3.0.0' | |||
| //支持腾讯x5浏览器内核 | |||
| api 'com.github.wendux:DSBridge-Android:x5-3.0-SNAPSHOT' | |||
| //api 'com.github.wendux:DSBridge-Android:x5-3.0-SNAPSHOT' | |||
| implementation 'com.github.bumptech.glide:glide:4.16.0' | |||
| implementation 'com.tencent.tbs:tbssdk:44286' | |||
| implementation project(path: ':test') | |||
| api project(path: ':dsbridge') | |||
| implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' | |||
| implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1' | |||
| @@ -56,6 +56,26 @@ | |||
| <category android:name="android.intent.category.LAUNCHER" /> | |||
| </intent-filter> | |||
| </activity> | |||
| <activity | |||
| android:name=".JavascriptCallNativeActivity" | |||
| android:screenOrientation="portrait" | |||
| android:exported="true"> | |||
| <!-- <intent-filter>--> | |||
| <!-- <action android:name="android.intent.action.MAIN" />--> | |||
| <!-- <category android:name="android.intent.category.LAUNCHER" />--> | |||
| <!-- </intent-filter>--> | |||
| </activity> | |||
| <activity | |||
| android:name=".CallJavascriptActivity" | |||
| android:screenOrientation="portrait" | |||
| android:exported="true"> | |||
| <!-- <intent-filter>--> | |||
| <!-- <action android:name="android.intent.action.MAIN" />--> | |||
| <!-- <category android:name="android.intent.category.LAUNCHER" />--> | |||
| <!-- </intent-filter>--> | |||
| </activity> | |||
| <service android:name=".DDSService" /> | |||
| <service android:name=".PhoneCallService" /> | |||
| @@ -0,0 +1,132 @@ | |||
| var bridge = { | |||
| default:this,// for typescript | |||
| call: function (method, args, cb) { | |||
| var ret = ''; | |||
| if (typeof args == 'function') { | |||
| cb = args; | |||
| args = {}; | |||
| } | |||
| var arg={data:args===undefined?null:args} | |||
| if (typeof cb == 'function') { | |||
| var cbName = 'dscb' + window.dscb++; | |||
| window[cbName] = cb; | |||
| arg['_dscbstub'] = cbName; | |||
| } | |||
| arg = JSON.stringify(arg) | |||
| //if in webview that dsBridge provided, call! | |||
| if(window._dsbridge){ | |||
| ret= _dsbridge.call(method, arg) | |||
| }else if(window._dswk||navigator.userAgent.indexOf("_dsbridge")!=-1){ | |||
| ret = prompt("_dsbridge=" + method, arg); | |||
| } | |||
| return JSON.parse(ret||'{}').data | |||
| }, | |||
| register: function (name, fun, asyn) { | |||
| var q = asyn ? window._dsaf : window._dsf | |||
| if (!window._dsInit) { | |||
| window._dsInit = true; | |||
| //notify native that js apis register successfully on next event loop | |||
| setTimeout(function () { | |||
| bridge.call("_dsb.dsinit"); | |||
| }, 0) | |||
| } | |||
| if (typeof fun == "object") { | |||
| q._obs[name] = fun; | |||
| } else { | |||
| q[name] = fun | |||
| } | |||
| }, | |||
| registerAsyn: function (name, fun) { | |||
| this.register(name, fun, true); | |||
| }, | |||
| hasNativeMethod: function (name, type) { | |||
| return this.call("_dsb.hasNativeMethod", {name: name, type:type||"all"}); | |||
| }, | |||
| disableJavascriptDialogBlock: function (disable) { | |||
| this.call("_dsb.disableJavascriptDialogBlock", { | |||
| disable: disable !== false | |||
| }) | |||
| } | |||
| }; | |||
| !function () { | |||
| if (window._dsf) return; | |||
| var ob = { | |||
| _dsf: { | |||
| _obs: {} | |||
| }, | |||
| _dsaf: { | |||
| _obs: {} | |||
| }, | |||
| dscb: 0, | |||
| dsBridge: bridge, | |||
| close: function () { | |||
| bridge.call("_dsb.closePage") | |||
| }, | |||
| _handleMessageFromNative: function (info) { | |||
| var arg = JSON.parse(info.data); | |||
| var ret = { | |||
| id: info.callbackId, | |||
| complete: true | |||
| } | |||
| var f = this._dsf[info.method]; | |||
| var af = this._dsaf[info.method] | |||
| var callSyn = function (f, ob) { | |||
| ret.data = f.apply(ob, arg) | |||
| bridge.call("_dsb.returnValue", ret) | |||
| } | |||
| var callAsyn = function (f, ob) { | |||
| arg.push(function (data, complete) { | |||
| ret.data = data; | |||
| ret.complete = complete!==false; | |||
| bridge.call("_dsb.returnValue", ret) | |||
| }) | |||
| f.apply(ob, arg) | |||
| } | |||
| if (f) { | |||
| callSyn(f, this._dsf); | |||
| } else if (af) { | |||
| callAsyn(af, this._dsaf); | |||
| } else { | |||
| //with namespace | |||
| var name = info.method.split('.'); | |||
| if (name.length<2) return; | |||
| var method=name.pop(); | |||
| var namespace=name.join('.') | |||
| var obs = this._dsf._obs; | |||
| var ob = obs[namespace] || {}; | |||
| var m = ob[method]; | |||
| if (m && typeof m == "function") { | |||
| callSyn(m, ob); | |||
| return; | |||
| } | |||
| obs = this._dsaf._obs; | |||
| ob = obs[namespace] || {}; | |||
| m = ob[method]; | |||
| if (m && typeof m == "function") { | |||
| callAsyn(m, ob); | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| for (var attr in ob) { | |||
| window[attr] = ob[attr] | |||
| } | |||
| bridge.register("_hasJavascriptMethod", function (method, tag) { | |||
| var name = method.split('.') | |||
| if(name.length<2) { | |||
| return !!(_dsf[name]||_dsaf[name]) | |||
| }else{ | |||
| // with namespace | |||
| var method=name.pop() | |||
| var namespace=name.join('.') | |||
| var ob=_dsf._obs[namespace]||_dsaf._obs[namespace] | |||
| return ob&&!!ob[method] | |||
| } | |||
| }) | |||
| }(); | |||
| module.exports = bridge; | |||
| @@ -0,0 +1,108 @@ | |||
| <!DOCTYPE html> | |||
| <html> | |||
| <head lang="zh-cmn-Hans"> | |||
| <meta charset="UTF-8"> | |||
| <title>DSBridge Test</title> | |||
| <meta name="renderer" content="webkit"> | |||
| <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"> | |||
| <meta name="viewport" content="width=device-width,initial-scale=0.5,user-scalable=no"/> | |||
| <!--require dsbridge init js--> | |||
| <script src="https://cdn.jsdelivr.net/npm/dsbridge/dist/dsbridge.js"> </script> | |||
| </head> | |||
| <style> | |||
| .btn { | |||
| text-align: center; | |||
| background: #d8d8d8; | |||
| color: #222; | |||
| padding: 20px; | |||
| margin: 30px; | |||
| font-size: 24px; | |||
| border-radius: 4px; | |||
| box-shadow: 4px 2px 10px #999; | |||
| } | |||
| .btn:active { | |||
| opacity: .7; | |||
| box-shadow: 4px 2px 10px #555; | |||
| } | |||
| </style> | |||
| <body> | |||
| <div class="btn" onclick="callSyn()">Synchronous call</div> | |||
| <div class="btn" onclick="callAsyn()">Asynchronous call</div> | |||
| <div class="btn" onclick="callNoArgSyn()">Sync call without argument</div> | |||
| <div class="btn" onclick="callNoArgAsyn()">Async call without argument</div> | |||
| <div class="btn" onclick="echoSyn()">echo.syn</div> | |||
| <div class="btn" onclick="echoAsyn()">echo.asyn</div> | |||
| <div class="btn" onclick="callAsyn_()">Stress test,2K times consecutive asynchronous API calls</div> | |||
| <div class="btn" onclick="callNever()">Never call because without @JavascriptInterface annotation<br/>( This test is | |||
| just for Android ,should be ignored in IOS ) | |||
| </div> | |||
| <div class="btn" onclick="callProgress()">call progress <span id='progress'></span></div> | |||
| <div class="btn" onclick="hasNativeMethod('xx')">hasNativeMethod("xx")</div> | |||
| <div class="btn" onclick="hasNativeMethod('yy')">hasNativeMethod("yy")</div> | |||
| <div class="btn" onclick="hasNativeMethod('testSyn')">hasNativeMethod("testSyn")</div> | |||
| <script> | |||
| function callSyn() { | |||
| alert(dsBridge.call("testSyn", "testSyn")) | |||
| } | |||
| function callAsyn() { | |||
| dsBridge.call("testAsyn","testAsyn", function (v) { | |||
| alert(v) | |||
| }) | |||
| } | |||
| function callAsyn_() { | |||
| for (var i = 0; i < 2000; i++) { | |||
| dsBridge.call("testAsyn", "js+" + i, function (v) { | |||
| if (v == "js+1999 [ asyn call]") { | |||
| alert("All tasks completed!") | |||
| } | |||
| }) | |||
| } | |||
| } | |||
| function callNoArgSyn() { | |||
| alert(dsBridge.call("testNoArgSyn")); | |||
| } | |||
| function callNoArgAsyn() { | |||
| dsBridge.call("testNoArgAsyn", function (v) { | |||
| alert(v) | |||
| }); | |||
| } | |||
| function callNever() { | |||
| alert(dsBridge.call("testNever", {msg: "testSyn"})) | |||
| } | |||
| function echoSyn() { | |||
| // call function with namespace | |||
| var ret=dsBridge.call("echo.syn",{msg:" I am echoSyn call", tag:1}); | |||
| alert(JSON.stringify(ret)) | |||
| } | |||
| function echoAsyn() { | |||
| // call function with namespace | |||
| dsBridge.call("echo.asyn",{msg:" I am echoAsyn call",tag:2},function (ret) { | |||
| alert(JSON.stringify(ret)); | |||
| }) | |||
| } | |||
| function callProgress() { | |||
| dsBridge.call("callProgress", function (value) { | |||
| if(value==0) value=""; | |||
| document.getElementById("progress").innerText = value | |||
| }) | |||
| } | |||
| function hasNativeMethod(name) { | |||
| alert(dsBridge.hasNativeMethod(name)) | |||
| } | |||
| </script> | |||
| </body> | |||
| </html> | |||
| @@ -0,0 +1,62 @@ | |||
| <!DOCTYPE html> | |||
| <html> | |||
| <head lang="zh-cmn-Hans"> | |||
| <meta charset="UTF-8"> | |||
| <title>DSBridge Test</title> | |||
| <meta name="renderer" content="webkit"> | |||
| <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"> | |||
| <meta name="viewport" content="width=device-width,initial-scale=0.5,user-scalable=no"/> | |||
| <!--require dsbridge init js--> | |||
| <script src="https://cdn.jsdelivr.net/npm/dsbridge/dist/dsbridge.js"> </script> | |||
| </head> | |||
| <body> | |||
| <script> | |||
| dsBridge.register('addValue', function (r, l) { | |||
| return r + l; | |||
| }) | |||
| dsBridge.registerAsyn('append', function (arg1, arg2, arg3, responseCallback) { | |||
| responseCallback(arg1 + " " + arg2 + " " + arg3); | |||
| }) | |||
| dsBridge.registerAsyn('startTimer', function (responseCallback) { | |||
| var t = 0; | |||
| var timer = setInterval(function () { | |||
| if (t == 5) { | |||
| responseCallback(t) | |||
| clearInterval(timer) | |||
| } else { | |||
| // if the 2nd argument is false, the java callback handler will be not removed! | |||
| responseCallback(t++, false) | |||
| } | |||
| }, 1000) | |||
| }) | |||
| // namespace test for syn functions | |||
| dsBridge.register("syn", { | |||
| tag: "syn", | |||
| addValue:function (r,l) { | |||
| return r+l; | |||
| }, | |||
| getInfo: function () { | |||
| return {tag: this.tag, value:8} | |||
| } | |||
| }) | |||
| // namespace test for asyn functions | |||
| dsBridge.registerAsyn("asyn", { | |||
| tag: "asyn", | |||
| addValue:function (r,l, responseCallback) { | |||
| responseCallback(r+l); | |||
| }, | |||
| getInfo: function (responseCallback) { | |||
| responseCallback({tag: this.tag, value:8}) | |||
| } | |||
| }) | |||
| </script> | |||
| </body> | |||
| </html> | |||
| @@ -73,7 +73,9 @@ import com.aispeech.nativedemo.ui.InitView; | |||
| import com.aispeech.nativedemo.upload.UploadManager; | |||
| import com.aispeech.nativedemo.utils.CommandExecution; | |||
| import com.aispeech.nativedemo.utils.PermissionUtil; | |||
| import com.aispeech.nativedemo.utils.ScreenUtils; | |||
| import com.aispeech.nativedemo.utils.StatusUtils; | |||
| import com.aispeech.nativedemo.utils.Utils; | |||
| import com.aispeech.nativedemo.utils.suspension.SuspensionHelper; | |||
| import com.aispeech.nativedemo.utils.suspension.SuspensionImpl; | |||
| import com.aispeech.nativedemo.widget.AlertWindowView; | |||
| @@ -423,11 +425,12 @@ public class BaseMainActivity extends Activity implements DuiUpdateObserver.Upda | |||
| // mWebView.loadUrl(Config.DEV_H5_URL + "player/Public/player.html"); | |||
| mWebView2 = findViewById(R.id.webview2); | |||
| setWebViewAutoVideo(mWebView2); | |||
| Log.e("testtest", mWebView2.getIsX5Core() ? | |||
| "X5内核: " + QbSdk.getTbsVersion(this) : "SDK系统内核"); | |||
| mWebCore.setText(mWebView2.getIsX5Core() ? | |||
| "X5内核: " + QbSdk.getTbsVersion(this) : "SDK系统内核"); | |||
| String log = mWebView2.getIsX5Core() ? | |||
| "X5内核: " + QbSdk.getTbsVersion(this) : "SDK系统内核"; | |||
| Log.e("testtest", log); | |||
| String wh = "width " + ScreenUtils.getScreenWidth(BaseMainActivity.this) + " height " + ScreenUtils.getScreenHeight(BaseMainActivity.this); | |||
| mWebCore.setText(wh); | |||
| mWebView2.setBackgroundColor(0); | |||
| if(mWebView2.getBackground() != null){ | |||
| mWebView2.getBackground().setAlpha(0); | |||
| @@ -476,8 +479,8 @@ public class BaseMainActivity extends Activity implements DuiUpdateObserver.Upda | |||
| webSettings.setDomStorageEnabled(true); | |||
| webSettings.setMediaPlaybackRequiresUserGesture(false); | |||
| mWebView2.loadUrl("http://39.107.77.235:48088/index_config.html?devId=" + StatusUtils.getSerialNumber()); | |||
| //mWebView2.loadUrl("http://192.168.10.244:48087/index_config.html?devId=" + StatusUtils.getSerialNumber()); // 非联通测试推流 | |||
| //mWebView2.loadUrl("http://39.107.77.235:48088/index_config.html?devId=" + StatusUtils.getSerialNumber()); | |||
| mWebView2.loadUrl("http://192.168.10.244:48087/index_config.html?devId=" + StatusUtils.getSerialNumber()); // 非联通测试推流 | |||
| } | |||
| private void setWebViewAutoVideo(WebView webView) { | |||
| @@ -0,0 +1,145 @@ | |||
| package com.aispeech.nativedemo; | |||
| import android.os.Bundle; | |||
| import android.view.View; | |||
| import android.widget.Toast; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import org.json.JSONObject; | |||
| import ai.dui.sdk.log.Log; | |||
| import wendu.dsbridge.DWebView; | |||
| import wendu.dsbridge.OnReturnValue; | |||
| public class CallJavascriptActivity extends AppCompatActivity implements View.OnClickListener { | |||
| DWebView dWebView; | |||
| public <T extends View> T getView(int viewId) { | |||
| View view = findViewById(viewId); | |||
| return (T) view; | |||
| } | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_call_javascript); | |||
| getView(R.id.addValue).setOnClickListener(this); | |||
| getView(R.id.append).setOnClickListener(this); | |||
| getView(R.id.startTimer).setOnClickListener(this); | |||
| getView(R.id.synAddValue).setOnClickListener(this); | |||
| getView(R.id.synGetInfo).setOnClickListener(this); | |||
| getView(R.id.asynAddValue).setOnClickListener(this); | |||
| getView(R.id.asynGetInfo).setOnClickListener(this); | |||
| getView(R.id.hasMethodAddValue).setOnClickListener(this); | |||
| getView(R.id.hasMethodXX).setOnClickListener(this); | |||
| getView(R.id.hasMethodAsynAddValue).setOnClickListener(this); | |||
| getView(R.id.hasMethodAsynXX).setOnClickListener(this); | |||
| DWebView.setWebContentsDebuggingEnabled(true); | |||
| dWebView= getView(R.id.webview); | |||
| dWebView.loadUrl("file:///android_asset/native-call-js.html"); | |||
| } | |||
| void showToast(Object o) { | |||
| Log.e("testjs", "show toast " + o.toString()); | |||
| Toast.makeText(this, o.toString(), Toast.LENGTH_SHORT).show(); | |||
| } | |||
| @Override | |||
| public void onClick(View v) { | |||
| switch (v.getId()) { | |||
| case R.id.addValue: | |||
| dWebView.callHandler("addValue", new Object[]{3, 4}, new OnReturnValue<Integer>() { | |||
| @Override | |||
| public void onValue(Integer retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| case R.id.append: | |||
| dWebView.callHandler("append", new Object[]{"I", "love", "you"}, new OnReturnValue<String>() { | |||
| @Override | |||
| public void onValue(String retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| case R.id.startTimer: | |||
| dWebView.callHandler("startTimer", new OnReturnValue<Integer>() { | |||
| @Override | |||
| public void onValue(Integer retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| case R.id.synAddValue: | |||
| dWebView.callHandler("syn.addValue", new Object[]{5, 6}, new OnReturnValue<Integer>() { | |||
| @Override | |||
| public void onValue(Integer retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| case R.id.synGetInfo: | |||
| dWebView.callHandler("syn.getInfo", new OnReturnValue<JSONObject>() { | |||
| @Override | |||
| public void onValue(JSONObject retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| case R.id.asynAddValue: | |||
| dWebView.callHandler("asyn.addValue", new Object[]{5, 6}, new OnReturnValue<Integer>() { | |||
| @Override | |||
| public void onValue(Integer retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| case R.id.asynGetInfo: | |||
| dWebView.callHandler("asyn.getInfo", new OnReturnValue<JSONObject>() { | |||
| @Override | |||
| public void onValue(JSONObject retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| case R.id.hasMethodAddValue: | |||
| dWebView.hasJavascriptMethod("addValue", new OnReturnValue<Boolean>() { | |||
| @Override | |||
| public void onValue(Boolean retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| case R.id.hasMethodXX: | |||
| dWebView.hasJavascriptMethod("XX", new OnReturnValue<Boolean>() { | |||
| @Override | |||
| public void onValue(Boolean retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| case R.id.hasMethodAsynAddValue: | |||
| dWebView.hasJavascriptMethod("asyn.addValue", new OnReturnValue<Boolean>() { | |||
| @Override | |||
| public void onValue(Boolean retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| case R.id.hasMethodAsynXX: | |||
| dWebView.hasJavascriptMethod("asyn.XX", new OnReturnValue<Boolean>() { | |||
| @Override | |||
| public void onValue(Boolean retValue) { | |||
| showToast(retValue); | |||
| } | |||
| }); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| package com.aispeech.nativedemo; | |||
| import android.os.Bundle; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import wendu.dsbridge.DWebView; | |||
| public class JavascriptCallNativeActivity extends AppCompatActivity { | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_js_call_native); | |||
| final DWebView dwebView= (DWebView) findViewById(R.id.webview); | |||
| // set debug mode | |||
| DWebView.setWebContentsDebuggingEnabled(true); | |||
| dwebView.addJavascriptObject(new JsApi(), null); | |||
| dwebView.addJavascriptObject(new JsEchoApi(),"echo"); | |||
| dwebView.loadUrl("file:///android_asset/js-call-native.html"); | |||
| } | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| package com.aispeech.nativedemo; | |||
| import android.os.CountDownTimer; | |||
| import android.webkit.JavascriptInterface; | |||
| import org.json.JSONException; | |||
| import org.json.JSONObject; | |||
| import wendu.dsbridge.CompletionHandler; | |||
| /** | |||
| * Created by du on 16/12/31. | |||
| */ | |||
| public class JsApi{ | |||
| @JavascriptInterface | |||
| public String yy(Object msg) { | |||
| return msg + "[syn call]"; | |||
| } | |||
| @JavascriptInterface | |||
| public String testSyn(Object msg) { | |||
| return msg + "[syn call]"; | |||
| } | |||
| @JavascriptInterface | |||
| public void testAsyn(Object msg, CompletionHandler<String> handler){ | |||
| handler.complete(msg+" [ asyn call]"); | |||
| } | |||
| @JavascriptInterface | |||
| public String testNoArgSyn(Object arg) throws JSONException { | |||
| return "testNoArgSyn called [ syn call]"; | |||
| } | |||
| @JavascriptInterface | |||
| public void testNoArgAsyn(Object arg,CompletionHandler<String> handler) { | |||
| handler.complete( "testNoArgAsyn called [ asyn call]"); | |||
| } | |||
| //@JavascriptInterface | |||
| //without @JavascriptInterface annotation can't be called | |||
| public String testNever(Object arg) throws JSONException { | |||
| JSONObject jsonObject= (JSONObject) arg; | |||
| return jsonObject.getString("msg") + "[ never call]"; | |||
| } | |||
| @JavascriptInterface | |||
| public void callProgress(Object args, final CompletionHandler<Integer> handler) { | |||
| new CountDownTimer(11000, 1000) { | |||
| int i=10; | |||
| @Override | |||
| public void onTick(long millisUntilFinished) { | |||
| //setProgressData can be called many times util complete be called. | |||
| handler.setProgressData((i--)); | |||
| } | |||
| @Override | |||
| public void onFinish() { | |||
| //complete the js invocation with data; handler will be invalid when complete is called | |||
| handler.complete(0); | |||
| } | |||
| }.start(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| package com.aispeech.nativedemo; | |||
| import android.webkit.JavascriptInterface; | |||
| import org.json.JSONException; | |||
| import wendu.dsbridge.CompletionHandler; | |||
| /** | |||
| * Created by du on 16/12/31. | |||
| */ | |||
| public class JsEchoApi { | |||
| @JavascriptInterface | |||
| public Object syn(Object args) throws JSONException { | |||
| return args; | |||
| } | |||
| @JavascriptInterface | |||
| public void asyn(Object args,CompletionHandler handler){ | |||
| handler.complete(args); | |||
| } | |||
| } | |||
| @@ -7,8 +7,8 @@ public class Config { | |||
| public static final int BITMAP_WIDTH = 1920; | |||
| public static final String PROD_BASE_URL= "http://123.57.75.177:8080/"; | |||
| public static final String TEST_BASE_URL= "http://39.107.77.235:8078/"; | |||
| //public static final String TEST_BASE_URL= "http://39.107.77.235:8080/"; // 非联通测试推流 | |||
| //public static final String TEST_BASE_URL= "http://39.107.77.235:8078/"; | |||
| public static final String TEST_BASE_URL= "http://39.107.77.235:8080/"; // 非联通测试推流 | |||
| public static final String DEV_BASE_URL= "http://192.168.10.244:8080/"; | |||
| public static final String DEV_H5_URL= "http://192.168.10.244:48087/"; | |||
| public static final String CURRENT_URL= TEST_BASE_URL; | |||
| @@ -29,6 +29,7 @@ public class Skill implements Serializable { | |||
| obj.put("skillCode", skillCode); | |||
| obj.put("info", info); | |||
| obj.put("resp", resp); | |||
| obj.put("leavingResp", leavingResp + ""); | |||
| obj.put("motionId", motionId); | |||
| obj.put("motionName", motionName); | |||
| } catch (JSONException e) { | |||
| @@ -5,7 +5,6 @@ import android.content.Intent; | |||
| import android.content.pm.PackageManager; | |||
| import android.graphics.Bitmap; | |||
| import android.net.Uri; | |||
| import android.net.http.SslError; | |||
| import android.os.Build; | |||
| import android.os.Bundle; | |||
| import android.text.TextUtils; | |||
| @@ -14,15 +13,6 @@ import android.view.KeyEvent; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| import android.view.WindowManager; | |||
| import android.webkit.JavascriptInterface; | |||
| import android.webkit.SslErrorHandler; | |||
| import android.webkit.ValueCallback; | |||
| import android.webkit.WebChromeClient; | |||
| import android.webkit.WebResourceError; | |||
| import android.webkit.WebResourceRequest; | |||
| import android.webkit.WebSettings; | |||
| import android.webkit.WebView; | |||
| import android.webkit.WebViewClient; | |||
| import android.widget.FrameLayout; | |||
| import androidx.annotation.Nullable; | |||
| @@ -30,6 +20,16 @@ import androidx.annotation.RequiresApi; | |||
| import com.aispeech.nativedemo.DuiApplication; | |||
| import com.aispeech.nativedemo.databinding.ActivityCustomWebBinding; | |||
| import com.tencent.smtt.export.external.interfaces.SslError; | |||
| import com.tencent.smtt.export.external.interfaces.SslErrorHandler; | |||
| import com.tencent.smtt.export.external.interfaces.WebResourceError; | |||
| import com.tencent.smtt.export.external.interfaces.WebResourceRequest; | |||
| import com.tencent.smtt.sdk.ValueCallback; | |||
| import com.tencent.smtt.sdk.WebSettings; | |||
| import com.tencent.smtt.sdk.WebChromeClient; | |||
| import com.tencent.smtt.sdk.WebView; | |||
| import com.tencent.smtt.sdk.WebViewClient; | |||
| import android.webkit.JavascriptInterface; | |||
| import wendu.dsbridge.CompletionHandler; | |||
| @@ -43,7 +43,6 @@ public class CustomWebViewActivity extends Activity { | |||
| private ActivityCustomWebBinding mBinding; | |||
| private String mUrl; | |||
| private View mVideoView; | |||
| private WebChromeClient.CustomViewCallback mVideoViewCallback; | |||
| /** | |||
| * 分享:页面分享的URL | |||
| @@ -143,7 +142,7 @@ public class CustomWebViewActivity extends Activity { | |||
| mBinding.webView.addJavascriptObject(new JsApi(), null); | |||
| if (Build.VERSION.SDK_INT >= 21) { | |||
| mBinding.webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); | |||
| mBinding.webView.getSettings().setMixedContentMode(WebSettings.LOAD_NORMAL); // MIXED_CONTENT_ALWAYS_ALLOW | |||
| } | |||
| //错误重试 | |||
| @@ -222,17 +221,9 @@ public class CustomWebViewActivity extends Activity { | |||
| //加载进度 | |||
| mBinding.webView.setWebChromeClient(new WebChromeClient(){ | |||
| /*** 视频播放相关的方法 **/ | |||
| @Override | |||
| public void onShowCustomView(View view, CustomViewCallback callback) { | |||
| super.onShowCustomView(view, callback); | |||
| showCustomView(view, callback); | |||
| } | |||
| @Override | |||
| public void onHideCustomView() { | |||
| super.onHideCustomView(); | |||
| hideCustomView(); | |||
| } | |||
| /** | |||
| @@ -249,40 +240,6 @@ public class CustomWebViewActivity extends Activity { | |||
| }); | |||
| } | |||
| /** 视频播放全屏 **/ | |||
| private void showCustomView(View view, WebChromeClient.CustomViewCallback callback) { | |||
| if (mVideoView != null) { | |||
| callback.onCustomViewHidden(); | |||
| return; | |||
| } | |||
| mBinding.flFullVideoContainer.setVisibility(View.VISIBLE); | |||
| mBinding.webView.setVisibility(View.GONE); | |||
| mBinding.flFullVideoContainer.addView(view); | |||
| FrameLayout.LayoutParams flLayoutParams = (FrameLayout.LayoutParams)view.getLayoutParams(); | |||
| flLayoutParams.gravity = Gravity.CENTER; | |||
| view.setLayoutParams(flLayoutParams); | |||
| mVideoView = view; | |||
| mVideoViewCallback = callback; | |||
| setStatusBarVisibility(false); | |||
| } | |||
| /** 隐藏视频全屏 */ | |||
| private void hideCustomView() { | |||
| if (mVideoView == null) { | |||
| return; | |||
| } | |||
| setStatusBarVisibility(true); | |||
| mBinding.flFullVideoContainer.removeView(mVideoView); | |||
| mBinding.flFullVideoContainer.setVisibility(View.GONE); | |||
| mVideoView = null; | |||
| mVideoViewCallback.onCustomViewHidden(); | |||
| mBinding.webView.setVisibility(View.VISIBLE); | |||
| } | |||
| private void setStatusBarVisibility(boolean visible) { | |||
| int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN; | |||
| getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN); | |||
| @@ -339,9 +296,7 @@ public class CustomWebViewActivity extends Activity { | |||
| //网页后退 | |||
| if (keyCode == KeyEvent.KEYCODE_BACK) { | |||
| /** 回退键 事件处理 优先级:视频播放全屏-网页回退-关闭页面 */ | |||
| if (mVideoView != null) { | |||
| hideCustomView(); | |||
| } else if (mBinding.webView.canGoBack()) { | |||
| if (mBinding.webView.canGoBack()) { | |||
| mBinding.webView.goBack(); | |||
| } else { | |||
| finish(); | |||
| @@ -0,0 +1,106 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:id="@+id/activity_main" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:orientation="vertical"> | |||
| <ScrollView | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| > | |||
| <LinearLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:orientation="vertical" | |||
| android:padding="16dp"> | |||
| <Button | |||
| android:id="@+id/addValue" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="addValue(3,4)" | |||
| android:textAllCaps="false" /> | |||
| <Button | |||
| android:id="@+id/append" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="append('I','love','you')" | |||
| android:textAllCaps="false" /> | |||
| <Button | |||
| android:id="@+id/startTimer" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="startTimer()" | |||
| android:textAllCaps="false" /> | |||
| <Button | |||
| android:id="@+id/synAddValue" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="syn.addValue(5,6)" | |||
| android:textAllCaps="false" /> | |||
| <Button | |||
| android:id="@+id/synGetInfo" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="syn.getInfo()" | |||
| android:textAllCaps="false" /> | |||
| <Button | |||
| android:id="@+id/asynAddValue" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="asyn.addValue(5,6)" | |||
| android:textAllCaps="false" /> | |||
| <Button | |||
| android:id="@+id/asynGetInfo" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="asyn.getInfo()" | |||
| android:textAllCaps="false" /> | |||
| <Button | |||
| android:id="@+id/hasMethodAddValue" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="hasJavascriptMethod('addValue')" | |||
| android:textAllCaps="false" /> | |||
| <Button | |||
| android:id="@+id/hasMethodXX" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="hasJavascriptMethod('XX')" | |||
| android:textAllCaps="false" /> | |||
| <Button | |||
| android:id="@+id/hasMethodAsynAddValue" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="hasJavascriptMethod('asyn.addValue')" | |||
| android:textAllCaps="false" /> | |||
| <Button | |||
| android:id="@+id/hasMethodAsynXX" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:text="hasJavascriptMethod('asyn.XX')" | |||
| android:textAllCaps="false" /> | |||
| <wendu.dsbridge.DWebView | |||
| android:id="@+id/webview" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:visibility="gone" /> | |||
| </LinearLayout> | |||
| </ScrollView> | |||
| </LinearLayout> | |||
| @@ -0,0 +1,12 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:id="@+id/activity_main" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <wendu.dsbridge.DWebView | |||
| android:id="@+id/webview" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| /> | |||
| </RelativeLayout> | |||
| @@ -0,0 +1 @@ | |||
| /build | |||
| @@ -0,0 +1,30 @@ | |||
| apply plugin: 'com.android.library' | |||
| android { | |||
| compileSdkVersion 30 | |||
| buildToolsVersion '30.0.3' | |||
| defaultConfig { | |||
| minSdkVersion 26 | |||
| targetSdkVersion 30 | |||
| versionCode 1 | |||
| versionName "1.0" | |||
| testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | |||
| } | |||
| buildTypes { | |||
| release { | |||
| minifyEnabled false | |||
| proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | |||
| } | |||
| } | |||
| lintOptions { | |||
| abortOnError false | |||
| } | |||
| } | |||
| dependencies { | |||
| implementation 'androidx.appcompat:appcompat:1.3.1' | |||
| api 'com.tencent.tbs:tbssdk:44286' | |||
| } | |||
| @@ -0,0 +1,330 @@ | |||
| # Add project specific ProGuard rules here. | |||
| # By default, the flags in this file are appended to flags specified | |||
| # in /Users/du/Library/Android/sdk/tools/proguard/proguard-android.txt | |||
| # You can edit the include path and order by changing the proguardFiles | |||
| # directive in build.gradle. | |||
| # | |||
| # For more details, see | |||
| # http://developer.android.com/guide/developing/tools/proguard.html | |||
| # Add any project specific keep options here: | |||
| # If your project uses WebView with JS, uncomment the following | |||
| # and specify the fully qualified class name to the JavaScript interface | |||
| # class: | |||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | |||
| # public *; | |||
| #} | |||
| #-optimizationpasses 7 | |||
| #-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* | |||
| -keepattributes *JavascriptInterface* | |||
| -keepattributes Signature | |||
| -keepattributes *Annotation* | |||
| -dontoptimize | |||
| -dontusemixedcaseclassnames | |||
| -verbose | |||
| -dontskipnonpubliclibraryclasses | |||
| -dontskipnonpubliclibraryclassmembers | |||
| -dontwarn dalvik.** | |||
| -dontwarn com.tencent.smtt.** | |||
| #-overloadaggressively | |||
| #@proguard_debug_start | |||
| # ------------------ Keep LineNumbers and properties ---------------- # | |||
| -keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod | |||
| -renamesourcefileattribute TbsSdkJava | |||
| -keepattributes SourceFile,LineNumberTable | |||
| #@proguard_debug_end | |||
| # -------------------------------------------------------------------------- | |||
| # Addidional for x5.sdk classes for apps | |||
| -keep class com.tencent.smtt.export.external.**{ | |||
| *; | |||
| } | |||
| -keep class com.tencent.tbs.video.interfaces.IUserStateChangedListener { | |||
| *; | |||
| } | |||
| -keep class com.tencent.smtt.sdk.CacheManager { | |||
| public *; | |||
| } | |||
| -keep class com.tencent.smtt.sdk.CookieManager { | |||
| public *; | |||
| } | |||
| -keep class com.tencent.smtt.sdk.WebHistoryItem { | |||
| public *; | |||
| } | |||
| -keep class com.tencent.smtt.sdk.WebViewDatabase { | |||
| public *; | |||
| } | |||
| -keep class com.tencent.smtt.sdk.WebBackForwardList { | |||
| public *; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebView { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebView$HitTestResult { | |||
| public static final <fields>; | |||
| public java.lang.String getExtra(); | |||
| public int getType(); | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebView$WebViewTransport { | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebView$PictureListener { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keepattributes InnerClasses | |||
| -keep public enum com.tencent.smtt.sdk.WebSettings$** { | |||
| *; | |||
| } | |||
| -keep public enum com.tencent.smtt.sdk.QbSdk$** { | |||
| *; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebSettings { | |||
| public *; | |||
| } | |||
| -keepattributes Signature | |||
| -keep public class com.tencent.smtt.sdk.ValueCallback { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebViewClient { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.DownloadListener { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebChromeClient { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebChromeClient$FileChooserParams { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep class com.tencent.smtt.sdk.SystemWebChromeClient{ | |||
| public *; | |||
| } | |||
| # 1. extension interfaces should be apparent | |||
| -keep public class com.tencent.smtt.export.external.extension.interfaces.* { | |||
| public protected *; | |||
| } | |||
| # 2. interfaces should be apparent | |||
| -keep public class com.tencent.smtt.export.external.interfaces.* { | |||
| public protected *; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebViewCallbackClient { | |||
| public protected *; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebStorage$QuotaUpdater { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebIconDatabase { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.WebStorage { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.DownloadListener { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.QbSdk { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.QbSdk$PreInitCallback { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.CookieSyncManager { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.Tbs* { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.utils.LogFileUtils { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.utils.TbsLog { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.utils.TbsLogClient { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.CookieSyncManager { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| # Added for game demos | |||
| -keep public class com.tencent.smtt.sdk.TBSGamePlayer { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.TBSGamePlayerClient* { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.TBSGamePlayerClientExtension { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.TBSGamePlayerService* { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.utils.Apn { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep class com.tencent.smtt.** { | |||
| *; | |||
| } | |||
| # end | |||
| -keep public class com.tencent.smtt.export.external.extension.proxy.ProxyWebViewClientExtension { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep class MTT.ThirdAppInfoNew { | |||
| *; | |||
| } | |||
| -keep class com.tencent.mtt.MttTraceEvent { | |||
| *; | |||
| } | |||
| # Game related | |||
| -keep public class com.tencent.smtt.gamesdk.* { | |||
| public protected *; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.TBSGameBooter { | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.TBSGameBaseActivity { | |||
| public protected *; | |||
| } | |||
| -keep public class com.tencent.smtt.sdk.TBSGameBaseActivityProxy { | |||
| public protected *; | |||
| } | |||
| -keep public class com.tencent.smtt.gamesdk.internal.TBSGameServiceClient { | |||
| public *; | |||
| } | |||
| #--------------------------------------------------------------------------- | |||
| #------------------ 下方是android平台自带的排除项,这里不要动 ---------------- | |||
| -keep public class * extends android.app.Activity{ | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class * extends android.app.Application{ | |||
| public <fields>; | |||
| public <methods>; | |||
| } | |||
| -keep public class * extends android.app.Service | |||
| -keep public class * extends android.content.BroadcastReceiver | |||
| -keep public class * extends android.content.ContentProvider | |||
| -keep public class * extends android.app.backup.BackupAgentHelper | |||
| -keep public class * extends android.preference.Preference | |||
| -keepclassmembers enum * { | |||
| public static **[] values(); | |||
| public static ** valueOf(java.lang.String); | |||
| } | |||
| -keepclasseswithmembers class * { | |||
| public <init>(android.content.Context, android.util.AttributeSet); | |||
| } | |||
| -keepclasseswithmembers class * { | |||
| public <init>(android.content.Context, android.util.AttributeSet, int); | |||
| } | |||
| -keepattributes *Annotation* | |||
| -keepclasseswithmembernames class *{ | |||
| native <methods>; | |||
| } | |||
| -keep class * implements android.os.Parcelable { | |||
| public static final android.os.Parcelable$Creator *; | |||
| } | |||
| #------------------ 下方是共性的排除项目 ---------------- | |||
| # 方法名中含有“JNI”字符的,认定是Java Native Interface方法,自动排除 | |||
| # 方法名中含有“JRI”字符的,认定是Java Reflection Interface方法,自动排除 | |||
| -keepclasseswithmembers class * { | |||
| ... *JNI*(...); | |||
| } | |||
| -keepclasseswithmembernames class * { | |||
| ... *JRI*(...); | |||
| } | |||
| -keep class **JNI* {*;} | |||
| @@ -0,0 +1,12 @@ | |||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="wendu.dsbridge"> | |||
| <uses-permission android:name="android.permission.INTERNET"/> | |||
| <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | |||
| <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | |||
| <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> | |||
| <uses-permission android:name="android.permission.READ_PHONE_STATE" /> | |||
| <application android:allowBackup="true" android:label="@string/app_name" | |||
| android:supportsRtl="true"> | |||
| </application> | |||
| </manifest> | |||
| @@ -0,0 +1,11 @@ | |||
| package wendu.dsbridge; | |||
| /** | |||
| * Created by du on 16/12/31. | |||
| */ | |||
| public interface CompletionHandler<T> { | |||
| void complete(T retValue); | |||
| void complete(); | |||
| void setProgressData(T value); | |||
| } | |||
| @@ -0,0 +1,964 @@ | |||
| package wendu.dsbridge; | |||
| import android.annotation.TargetApi; | |||
| import android.app.Activity; | |||
| import android.app.Dialog; | |||
| import android.content.Context; | |||
| import android.content.DialogInterface; | |||
| import android.graphics.Bitmap; | |||
| import android.net.Uri; | |||
| import android.os.Build; | |||
| import android.os.Handler; | |||
| import android.os.Looper; | |||
| import android.os.Message; | |||
| import android.util.AttributeSet; | |||
| import android.util.Log; | |||
| import android.view.Gravity; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| import android.webkit.JavascriptInterface; | |||
| import android.widget.EditText; | |||
| import android.widget.FrameLayout; | |||
| import com.tencent.smtt.export.external.interfaces.ConsoleMessage; | |||
| import com.tencent.smtt.export.external.interfaces.GeolocationPermissionsCallback; | |||
| import com.tencent.smtt.export.external.interfaces.IX5WebChromeClient; | |||
| import com.tencent.smtt.export.external.interfaces.JsPromptResult; | |||
| import com.tencent.smtt.export.external.interfaces.JsResult; | |||
| import com.tencent.smtt.sdk.CookieManager; | |||
| import com.tencent.smtt.sdk.ValueCallback; | |||
| import com.tencent.smtt.sdk.WebChromeClient; | |||
| import com.tencent.smtt.sdk.WebSettings; | |||
| import com.tencent.smtt.sdk.WebStorage; | |||
| import com.tencent.smtt.sdk.WebView; | |||
| import org.json.JSONArray; | |||
| import org.json.JSONException; | |||
| import org.json.JSONObject; | |||
| import java.io.File; | |||
| import java.lang.ref.WeakReference; | |||
| import java.lang.reflect.Method; | |||
| import java.util.ArrayList; | |||
| import java.util.Arrays; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| import static android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW; | |||
| import androidx.annotation.Keep; | |||
| import androidx.appcompat.app.AlertDialog; | |||
| /** | |||
| * Created by du on 16/12/29. | |||
| */ | |||
| public class DWebView extends WebView { | |||
| private static final String BRIDGE_NAME = "_dsbridge"; | |||
| private static final String LOG_TAG = "dsBridge"; | |||
| private static boolean isDebug = false; | |||
| private Map<String, Object> javaScriptNamespaceInterfaces = new HashMap(); | |||
| private String APP_CACHE_DIRNAME; | |||
| int callID = 0; | |||
| private WebChromeClient webChromeClient; | |||
| private volatile boolean alertBoxBlock = true; | |||
| private JavascriptCloseWindowListener javascriptCloseWindowListener = null; | |||
| private ArrayList<CallInfo> callInfoList; | |||
| private InnerJavascriptInterface innerJavascriptInterface = new InnerJavascriptInterface(); | |||
| private Handler mainHandler = new Handler(Looper.getMainLooper()); | |||
| class InnerJavascriptInterface { | |||
| private void PrintDebugInfo(String error) { | |||
| Log.d(LOG_TAG, error); | |||
| if (isDebug) { | |||
| evaluateJavascript(String.format("alert('%s')", "DEBUG ERR MSG:\\n" + error.replaceAll("\\'", "\\\\'"))); | |||
| } | |||
| } | |||
| @Keep | |||
| @JavascriptInterface | |||
| public String call(String methodName, String argStr) { | |||
| String error = "Js bridge called, but can't find a corresponded " + | |||
| "JavascriptInterface object , please check your code!"; | |||
| String[] nameStr = parseNamespace(methodName.trim()); | |||
| methodName = nameStr[1]; | |||
| Object jsb = javaScriptNamespaceInterfaces.get(nameStr[0]); | |||
| JSONObject ret = new JSONObject(); | |||
| try { | |||
| ret.put("code", -1); | |||
| } catch (JSONException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| if (jsb == null) { | |||
| PrintDebugInfo(error); | |||
| return ret.toString(); | |||
| } | |||
| Object arg = null; | |||
| Method method = null; | |||
| String callback = null; | |||
| try { | |||
| JSONObject args = new JSONObject(argStr); | |||
| if (args.has("_dscbstub")) { | |||
| callback = args.getString("_dscbstub"); | |||
| } | |||
| if (args.has("data")) { | |||
| arg = args.get("data"); | |||
| } | |||
| } catch (JSONException e) { | |||
| error = String.format("The argument of \"%s\" must be a JSON object string!", methodName); | |||
| PrintDebugInfo(error); | |||
| e.printStackTrace(); | |||
| return ret.toString(); | |||
| } | |||
| Class<?> cls = jsb.getClass(); | |||
| boolean asyn = false; | |||
| try { | |||
| method = cls.getMethod(methodName, | |||
| new Class[]{Object.class, CompletionHandler.class}); | |||
| asyn = true; | |||
| } catch (Exception e) { | |||
| try { | |||
| method = cls.getMethod(methodName, new Class[]{Object.class}); | |||
| } catch (Exception ex) { | |||
| } | |||
| } | |||
| if (method == null) { | |||
| error = "Not find method \"" + methodName + "\" implementation! please check if the signature or namespace of the method is right "; | |||
| PrintDebugInfo(error); | |||
| return ret.toString(); | |||
| } | |||
| JavascriptInterface annotation = method.getAnnotation(JavascriptInterface.class); | |||
| if (annotation == null) { | |||
| error = "Method " + methodName + " is not invoked, since " + | |||
| "it is not declared with JavascriptInterface annotation! "; | |||
| PrintDebugInfo(error); | |||
| return ret.toString(); | |||
| } | |||
| Object retData; | |||
| method.setAccessible(true); | |||
| try { | |||
| if (asyn) { | |||
| final String cb = callback; | |||
| method.invoke(jsb, arg, new CompletionHandler() { | |||
| @Override | |||
| public void complete(Object retValue) { | |||
| complete(retValue, true); | |||
| } | |||
| @Override | |||
| public void complete() { | |||
| complete(null, true); | |||
| } | |||
| @Override | |||
| public void setProgressData(Object value) { | |||
| complete(value, false); | |||
| } | |||
| private void complete(Object retValue, boolean complete) { | |||
| try { | |||
| JSONObject ret = new JSONObject(); | |||
| ret.put("code", 0); | |||
| ret.put("data", retValue); | |||
| //retValue = URLEncoder.encode(ret.toString(), "UTF-8").replaceAll("\\+", "%20"); | |||
| if (cb != null) { | |||
| //String script = String.format("%s(JSON.parse(decodeURIComponent(\"%s\")).data);", cb, retValue); | |||
| String script = String.format("%s(%s.data);", cb, ret.toString()); | |||
| if (complete) { | |||
| script += "delete window." + cb; | |||
| } | |||
| evaluateJavascript(script); | |||
| } | |||
| } catch (Exception e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| }); | |||
| } else { | |||
| retData = method.invoke(jsb, arg); | |||
| ret.put("code", 0); | |||
| ret.put("data", retData); | |||
| return ret.toString(); | |||
| } | |||
| } catch (Exception e) { | |||
| e.printStackTrace(); | |||
| error = String.format("Call failed:The parameter of \"%s\" in Java is invalid.", methodName); | |||
| PrintDebugInfo(error); | |||
| return ret.toString(); | |||
| } | |||
| return ret.toString(); | |||
| } | |||
| } | |||
| Map<Integer, OnReturnValue> handlerMap = new HashMap<>(); | |||
| public interface JavascriptCloseWindowListener { | |||
| /** | |||
| * @return If true, close the current activity, otherwise, do nothing. | |||
| */ | |||
| boolean onClose(); | |||
| } | |||
| @Deprecated | |||
| public interface FileChooser { | |||
| @TargetApi(Build.VERSION_CODES.HONEYCOMB) | |||
| void openFileChooser(ValueCallback valueCallback, String acceptType); | |||
| @TargetApi(Build.VERSION_CODES.JELLY_BEAN) | |||
| void openFileChooser(ValueCallback<Uri> valueCallback, | |||
| String acceptType, String capture); | |||
| } | |||
| public DWebView(Context context, AttributeSet attrs) { | |||
| super(context, attrs); | |||
| init(); | |||
| } | |||
| public DWebView(Context context) { | |||
| super(context); | |||
| init(); | |||
| } | |||
| /** | |||
| * Set debug mode. if in debug mode, some errors will be prompted by a dialog | |||
| * and the exception caused by the native handlers will not be captured. | |||
| * | |||
| * @param enabled | |||
| */ | |||
| public static void setWebContentsDebuggingEnabled(boolean enabled) { | |||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | |||
| WebView.setWebContentsDebuggingEnabled(enabled); | |||
| } | |||
| isDebug = enabled; | |||
| } | |||
| @Keep | |||
| private void init() { | |||
| APP_CACHE_DIRNAME = getContext().getFilesDir().getAbsolutePath() + "/webcache"; | |||
| WebSettings settings = getSettings(); | |||
| settings.setDomStorageEnabled(true); | |||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | |||
| CookieManager.getInstance().setAcceptThirdPartyCookies(this, true); | |||
| settings.setMixedContentMode(MIXED_CONTENT_ALWAYS_ALLOW); | |||
| } | |||
| settings.setAllowFileAccess(false); | |||
| settings.setAppCacheEnabled(false); | |||
| settings.setCacheMode(WebSettings.LOAD_NO_CACHE); | |||
| settings.setJavaScriptEnabled(true); | |||
| settings.setLoadWithOverviewMode(true); | |||
| settings.setAppCachePath(APP_CACHE_DIRNAME); | |||
| settings.setUseWideViewPort(true); | |||
| super.setWebChromeClient(mWebChromeClient); | |||
| addInternalJavascriptObject(); | |||
| if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) { | |||
| super.addJavascriptInterface(innerJavascriptInterface, BRIDGE_NAME); | |||
| } else { | |||
| // add bridge tag in lower android version | |||
| settings.setUserAgentString(settings.getUserAgentString() + " _dsbridge"); | |||
| } | |||
| } | |||
| private String[] parseNamespace(String method) { | |||
| int pos = method.lastIndexOf('.'); | |||
| String namespace = ""; | |||
| if (pos != -1) { | |||
| namespace = method.substring(0, pos); | |||
| method = method.substring(pos + 1); | |||
| } | |||
| return new String[]{namespace, method}; | |||
| } | |||
| @Keep | |||
| private void addInternalJavascriptObject() { | |||
| addJavascriptObject(new Object() { | |||
| @Keep | |||
| @JavascriptInterface | |||
| public boolean hasNativeMethod(Object args) throws JSONException { | |||
| JSONObject jsonObject = (JSONObject) args; | |||
| String methodName = jsonObject.getString("name").trim(); | |||
| String type = jsonObject.getString("type").trim(); | |||
| String[] nameStr = parseNamespace(methodName); | |||
| Log.e("testjs", " method name " + methodName); | |||
| Object jsb = javaScriptNamespaceInterfaces.get(nameStr[0]); | |||
| if (jsb != null) { | |||
| Class<?> cls = jsb.getClass(); | |||
| boolean asyn = false; | |||
| Method method = null; | |||
| try { | |||
| method = cls.getMethod(nameStr[1], | |||
| new Class[]{Object.class, CompletionHandler.class}); | |||
| asyn = true; | |||
| } catch (Exception e) { | |||
| try { | |||
| method = cls.getMethod(nameStr[1], new Class[]{Object.class}); | |||
| } catch (Exception ex) { | |||
| } | |||
| } | |||
| if (method != null) { | |||
| JavascriptInterface annotation = method.getAnnotation(JavascriptInterface.class); | |||
| if (annotation != null) { | |||
| if ("all".equals(type) || (asyn && "asyn".equals(type) || (!asyn && "syn".equals(type)))) { | |||
| return true; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| @Keep | |||
| @JavascriptInterface | |||
| public String closePage(Object object) throws JSONException { | |||
| runOnMainThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| if (javascriptCloseWindowListener == null | |||
| || javascriptCloseWindowListener.onClose()) { | |||
| Context context = getContext(); | |||
| if (context instanceof Activity) { | |||
| ((Activity) getContext()).onBackPressed(); | |||
| } | |||
| } | |||
| } | |||
| }); | |||
| return null; | |||
| } | |||
| @Keep | |||
| @JavascriptInterface | |||
| public void disableJavascriptDialogBlock(Object object) throws JSONException { | |||
| JSONObject jsonObject = (JSONObject) object; | |||
| alertBoxBlock = !jsonObject.getBoolean("disable"); | |||
| } | |||
| @Keep | |||
| @JavascriptInterface | |||
| public void dsinit(Object jsonObject) { | |||
| DWebView.this.dispatchStartupQueue(); | |||
| } | |||
| @Keep | |||
| @JavascriptInterface | |||
| public void returnValue(final Object obj) throws JSONException { | |||
| runOnMainThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| JSONObject jsonObject = (JSONObject) obj; | |||
| Object data = null; | |||
| try { | |||
| int id = jsonObject.getInt("id"); | |||
| boolean isCompleted = jsonObject.getBoolean("complete"); | |||
| OnReturnValue handler = handlerMap.get(id); | |||
| if (jsonObject.has("data")) { | |||
| data = jsonObject.get("data"); | |||
| } | |||
| if (handler != null) { | |||
| handler.onValue(data); | |||
| if (isCompleted) { | |||
| handlerMap.remove(id); | |||
| } | |||
| } | |||
| } catch (JSONException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| }); | |||
| } | |||
| }, "_dsb"); | |||
| } | |||
| private void _evaluateJavascript(String script) { | |||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | |||
| Log.e("testjs", "_evaluateJavascript " + script); | |||
| DWebView.super.evaluateJavascript(script, null); | |||
| } else { | |||
| Log.e("testjs", "_evaluateJavascript loadUrl " + script); | |||
| super.loadUrl("javascript:" + script); | |||
| } | |||
| } | |||
| /** | |||
| * This method can be called in any thread, and if it is not called in the main thread, | |||
| * it will be automatically distributed to the main thread. | |||
| * | |||
| * @param script | |||
| */ | |||
| public void evaluateJavascript(final String script) { | |||
| runOnMainThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| _evaluateJavascript(script); | |||
| } | |||
| }); | |||
| } | |||
| /** | |||
| * This method can be called in any thread, and if it is not called in the main thread, | |||
| * it will be automatically distributed to the main thread. | |||
| * | |||
| * @param url | |||
| */ | |||
| @Override | |||
| public void loadUrl(final String url) { | |||
| runOnMainThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| callInfoList = new ArrayList<>(); | |||
| DWebView.super.loadUrl(url); | |||
| } | |||
| }); | |||
| } | |||
| /** | |||
| * This method can be called in any thread, and if it is not called in the main thread, | |||
| * it will be automatically distributed to the main thread. | |||
| * | |||
| * @param url | |||
| * @param additionalHttpHeaders | |||
| */ | |||
| @Override | |||
| public void loadUrl(final String url, final Map<String, String> additionalHttpHeaders) { | |||
| runOnMainThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| callInfoList = new ArrayList<>(); | |||
| DWebView.super.loadUrl(url, additionalHttpHeaders); | |||
| } | |||
| }); | |||
| } | |||
| @Override | |||
| public void reload() { | |||
| runOnMainThread(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| callInfoList = new ArrayList<>(); | |||
| DWebView.super.reload(); | |||
| } | |||
| }); | |||
| } | |||
| /** | |||
| * set a listener for javascript closing the current activity. | |||
| */ | |||
| public void setJavascriptCloseWindowListener(JavascriptCloseWindowListener listener) { | |||
| javascriptCloseWindowListener = listener; | |||
| } | |||
| private class CallInfo { | |||
| public CallInfo(String handlerName, int id, Object[] args) { | |||
| if (args == null) args = new Object[0]; | |||
| data = new JSONArray(Arrays.asList(args)).toString(); | |||
| callbackId = id; | |||
| method = handlerName; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| JSONObject jo = new JSONObject(); | |||
| try { | |||
| jo.put("method", method); | |||
| jo.put("callbackId", callbackId); | |||
| jo.put("data", data); | |||
| } catch (JSONException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| return jo.toString(); | |||
| } | |||
| public String data = null; | |||
| public int callbackId; | |||
| public String method; | |||
| } | |||
| private synchronized void dispatchStartupQueue() { | |||
| if (callInfoList != null) { | |||
| for (CallInfo info : callInfoList) { | |||
| dispatchJavascriptCall(info); | |||
| } | |||
| callInfoList = null; | |||
| } | |||
| } | |||
| private void dispatchJavascriptCall(CallInfo info) { | |||
| evaluateJavascript(String.format("window._handleMessageFromNative(%s)", info.toString())); | |||
| } | |||
| public synchronized <T> void callHandler(String method, Object[] args, final OnReturnValue<T> handler) { | |||
| CallInfo callInfo = new CallInfo(method, callID++, args); | |||
| if (handler != null) { | |||
| handlerMap.put(callInfo.callbackId, handler); | |||
| } | |||
| if (callInfoList != null) { | |||
| callInfoList.add(callInfo); | |||
| } else { | |||
| dispatchJavascriptCall(callInfo); | |||
| } | |||
| } | |||
| public void callHandler(String method, Object[] args) { | |||
| callHandler(method, args, null); | |||
| } | |||
| public <T> void callHandler(String method, OnReturnValue<T> handler) { | |||
| callHandler(method, null, handler); | |||
| } | |||
| /** | |||
| * Test whether the handler exist in javascript | |||
| * | |||
| * @param handlerName | |||
| * @param existCallback | |||
| */ | |||
| public void hasJavascriptMethod(String handlerName, OnReturnValue<Boolean> existCallback) { | |||
| callHandler("_hasJavascriptMethod", new Object[]{handlerName}, existCallback); | |||
| } | |||
| /** | |||
| * Add a java object which implemented the javascript interfaces to dsBridge with namespace. | |||
| * Remove the object using {@link #removeJavascriptObject(String) removeJavascriptObject(String)} | |||
| * | |||
| * @param object | |||
| * @param namespace if empty, the object have no namespace. | |||
| */ | |||
| public void addJavascriptObject(Object object, String namespace) { | |||
| if (namespace == null) { | |||
| namespace = ""; | |||
| } | |||
| if (object != null) { | |||
| javaScriptNamespaceInterfaces.put(namespace, object); | |||
| } | |||
| } | |||
| /** | |||
| * remove the javascript object with supplied namespace. | |||
| * | |||
| * @param namespace | |||
| */ | |||
| public void removeJavascriptObject(String namespace) { | |||
| if (namespace == null) { | |||
| namespace = ""; | |||
| } | |||
| javaScriptNamespaceInterfaces.remove(namespace); | |||
| } | |||
| public void disableJavascriptDialogBlock(boolean disable) { | |||
| alertBoxBlock = !disable; | |||
| } | |||
| @Override | |||
| public void setWebChromeClient(WebChromeClient client) { | |||
| webChromeClient = client; | |||
| } | |||
| private WebChromeClient mWebChromeClient = new WebChromeClient() { | |||
| @Override | |||
| public void onProgressChanged(WebView view, int newProgress) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onProgressChanged(view, newProgress); | |||
| } else { | |||
| super.onProgressChanged(view, newProgress); | |||
| } | |||
| } | |||
| @Override | |||
| public void onReceivedTitle(WebView view, String title) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onReceivedTitle(view, title); | |||
| } else { | |||
| super.onReceivedTitle(view, title); | |||
| } | |||
| } | |||
| @Override | |||
| public void onReceivedIcon(WebView view, Bitmap icon) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onReceivedIcon(view, icon); | |||
| } else { | |||
| super.onReceivedIcon(view, icon); | |||
| } | |||
| } | |||
| @Override | |||
| public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onReceivedTouchIconUrl(view, url, precomposed); | |||
| } else { | |||
| super.onReceivedTouchIconUrl(view, url, precomposed); | |||
| } | |||
| } | |||
| @Override | |||
| public void onShowCustomView(View view, IX5WebChromeClient.CustomViewCallback callback) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onShowCustomView(view, callback); | |||
| } else { | |||
| super.onShowCustomView(view, callback); | |||
| } | |||
| } | |||
| @Override | |||
| public void onShowCustomView(View view, int requestedOrientation, | |||
| IX5WebChromeClient.CustomViewCallback callback) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onShowCustomView(view, requestedOrientation, callback); | |||
| } else { | |||
| super.onShowCustomView(view, requestedOrientation, callback); | |||
| } | |||
| } | |||
| @Override | |||
| public void onHideCustomView() { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onHideCustomView(); | |||
| } else { | |||
| super.onHideCustomView(); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean onCreateWindow(WebView view, boolean isDialog, | |||
| boolean isUserGesture, Message resultMsg) { | |||
| if (webChromeClient != null) { | |||
| return webChromeClient.onCreateWindow(view, isDialog, | |||
| isUserGesture, resultMsg); | |||
| } | |||
| return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg); | |||
| } | |||
| @Override | |||
| public void onRequestFocus(WebView view) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onRequestFocus(view); | |||
| } else { | |||
| super.onRequestFocus(view); | |||
| } | |||
| } | |||
| @Override | |||
| public void onCloseWindow(WebView window) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onCloseWindow(window); | |||
| } else { | |||
| super.onCloseWindow(window); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean onJsAlert(WebView view, String url, final String message, final JsResult result) { | |||
| if (!alertBoxBlock) { | |||
| result.confirm(); | |||
| } | |||
| if (webChromeClient != null) { | |||
| if (webChromeClient.onJsAlert(view, url, message, result)) { | |||
| return true; | |||
| } | |||
| } | |||
| Dialog alertDialog = new AlertDialog.Builder(getContext()). | |||
| setMessage(message). | |||
| setCancelable(false). | |||
| setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| dialog.dismiss(); | |||
| if (alertBoxBlock) { | |||
| result.confirm(); | |||
| } | |||
| } | |||
| }) | |||
| .create(); | |||
| alertDialog.show(); | |||
| return true; | |||
| } | |||
| @Override | |||
| public boolean onJsConfirm(WebView view, String url, String message, | |||
| final JsResult result) { | |||
| if (!alertBoxBlock) { | |||
| result.confirm(); | |||
| } | |||
| if (webChromeClient != null && webChromeClient.onJsConfirm(view, url, message, result)) { | |||
| return true; | |||
| } else { | |||
| DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| if (alertBoxBlock) { | |||
| if (which == Dialog.BUTTON_POSITIVE) { | |||
| result.confirm(); | |||
| } else { | |||
| result.cancel(); | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| new AlertDialog.Builder(getContext()) | |||
| .setMessage(message) | |||
| .setCancelable(false) | |||
| .setPositiveButton(android.R.string.ok, listener) | |||
| .setNegativeButton(android.R.string.cancel, listener).show(); | |||
| return true; | |||
| } | |||
| } | |||
| @Override | |||
| public boolean onJsPrompt(WebView view, String url, final String message, | |||
| String defaultValue, final JsPromptResult result) { | |||
| if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) { | |||
| String prefix = "_dsbridge="; | |||
| if (message.startsWith(prefix)) { | |||
| result.confirm(innerJavascriptInterface.call(message.substring(prefix.length()), defaultValue)); | |||
| return true; | |||
| } | |||
| } | |||
| if (!alertBoxBlock) { | |||
| result.confirm(); | |||
| } | |||
| if (webChromeClient != null && webChromeClient.onJsPrompt(view, url, message, defaultValue, result)) { | |||
| return true; | |||
| } else { | |||
| final EditText editText = new EditText(getContext()); | |||
| editText.setText(defaultValue); | |||
| if (defaultValue != null) { | |||
| editText.setSelection(defaultValue.length()); | |||
| } | |||
| float dpi = getContext().getResources().getDisplayMetrics().density; | |||
| DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| if (alertBoxBlock) { | |||
| if (which == Dialog.BUTTON_POSITIVE) { | |||
| result.confirm(editText.getText().toString()); | |||
| } else { | |||
| result.cancel(); | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| new AlertDialog.Builder(getContext()) | |||
| .setTitle(message) | |||
| .setView(editText) | |||
| .setCancelable(false) | |||
| .setPositiveButton(android.R.string.ok, listener) | |||
| .setNegativeButton(android.R.string.cancel, listener) | |||
| .show(); | |||
| FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( | |||
| ViewGroup.LayoutParams.MATCH_PARENT, | |||
| ViewGroup.LayoutParams.WRAP_CONTENT); | |||
| int t = (int) (dpi * 16); | |||
| layoutParams.setMargins(t, 0, t, 0); | |||
| layoutParams.gravity = Gravity.CENTER_HORIZONTAL; | |||
| editText.setLayoutParams(layoutParams); | |||
| int padding = (int) (15 * dpi); | |||
| editText.setPadding(padding - (int) (5 * dpi), padding, padding, padding); | |||
| return true; | |||
| } | |||
| } | |||
| @Override | |||
| public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) { | |||
| if (webChromeClient != null) { | |||
| return webChromeClient.onJsBeforeUnload(view, url, message, result); | |||
| } | |||
| return super.onJsBeforeUnload(view, url, message, result); | |||
| } | |||
| @Override | |||
| public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, | |||
| long estimatedDatabaseSize, | |||
| long totalQuota, | |||
| WebStorage.QuotaUpdater quotaUpdater) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quota, | |||
| estimatedDatabaseSize, totalQuota, quotaUpdater); | |||
| } else { | |||
| super.onExceededDatabaseQuota(url, databaseIdentifier, quota, | |||
| estimatedDatabaseSize, totalQuota, quotaUpdater); | |||
| } | |||
| } | |||
| @Override | |||
| public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater); | |||
| } | |||
| super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater); | |||
| } | |||
| @Override | |||
| public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissionsCallback callback) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onGeolocationPermissionsShowPrompt(origin, callback); | |||
| } else { | |||
| super.onGeolocationPermissionsShowPrompt(origin, callback); | |||
| } | |||
| } | |||
| @Override | |||
| public void onGeolocationPermissionsHidePrompt() { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.onGeolocationPermissionsHidePrompt(); | |||
| } else { | |||
| super.onGeolocationPermissionsHidePrompt(); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean onJsTimeout() { | |||
| if (webChromeClient != null) { | |||
| return webChromeClient.onJsTimeout(); | |||
| } | |||
| return super.onJsTimeout(); | |||
| } | |||
| @Override | |||
| public boolean onConsoleMessage(ConsoleMessage consoleMessage) { | |||
| if (webChromeClient != null) { | |||
| return webChromeClient.onConsoleMessage(consoleMessage); | |||
| } | |||
| return super.onConsoleMessage(consoleMessage); | |||
| } | |||
| @Override | |||
| public Bitmap getDefaultVideoPoster() { | |||
| if (webChromeClient != null) { | |||
| return webChromeClient.getDefaultVideoPoster(); | |||
| } | |||
| return super.getDefaultVideoPoster(); | |||
| } | |||
| @Override | |||
| public View getVideoLoadingProgressView() { | |||
| if (webChromeClient != null) { | |||
| return webChromeClient.getVideoLoadingProgressView(); | |||
| } | |||
| return super.getVideoLoadingProgressView(); | |||
| } | |||
| @Override | |||
| public void getVisitedHistory(ValueCallback<String[]> callback) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.getVisitedHistory(callback); | |||
| } else { | |||
| super.getVisitedHistory(callback); | |||
| } | |||
| } | |||
| @Override | |||
| public void openFileChooser(ValueCallback<Uri> valueCallback, String s, String s1) { | |||
| if (webChromeClient != null) { | |||
| webChromeClient.openFileChooser(valueCallback, s, s1); | |||
| return; | |||
| } | |||
| super.openFileChooser(valueCallback, s, s1); | |||
| } | |||
| @Override | |||
| public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, | |||
| FileChooserParams fileChooserParams) { | |||
| if (webChromeClient != null) { | |||
| return webChromeClient.onShowFileChooser(webView, filePathCallback, fileChooserParams); | |||
| } | |||
| return super.onShowFileChooser(webView, filePathCallback, fileChooserParams); | |||
| } | |||
| @Keep | |||
| @TargetApi(Build.VERSION_CODES.HONEYCOMB) | |||
| public void openFileChooser(ValueCallback valueCallback, String acceptType) { | |||
| if (webChromeClient instanceof FileChooser) { | |||
| ((FileChooser) webChromeClient).openFileChooser(valueCallback, acceptType); | |||
| } | |||
| } | |||
| }; | |||
| @Override | |||
| public void clearCache(boolean includeDiskFiles) { | |||
| super.clearCache(includeDiskFiles); | |||
| CookieManager.getInstance().removeAllCookie(); | |||
| Context context = getContext(); | |||
| try { | |||
| context.deleteDatabase("webview.db"); | |||
| context.deleteDatabase("webviewCache.db"); | |||
| } catch (Exception e) { | |||
| e.printStackTrace(); | |||
| } | |||
| File appCacheDir = new File(APP_CACHE_DIRNAME); | |||
| File webviewCacheDir = new File(context.getCacheDir() | |||
| .getAbsolutePath() + "/webviewCache"); | |||
| if (webviewCacheDir.exists()) { | |||
| deleteFile(webviewCacheDir); | |||
| } | |||
| if (appCacheDir.exists()) { | |||
| deleteFile(appCacheDir); | |||
| } | |||
| } | |||
| public void deleteFile(File file) { | |||
| if (file.exists()) { | |||
| if (file.isFile()) { | |||
| file.delete(); | |||
| } else if (file.isDirectory()) { | |||
| File files[] = file.listFiles(); | |||
| for (int i = 0; i < files.length; i++) { | |||
| deleteFile(files[i]); | |||
| } | |||
| } | |||
| file.delete(); | |||
| } else { | |||
| Log.e("Webview", "delete file no exists " + file.getAbsolutePath()); | |||
| } | |||
| } | |||
| private void runOnMainThread(Runnable runnable) { | |||
| if (Looper.getMainLooper() == Looper.myLooper()) { | |||
| runnable.run(); | |||
| return; | |||
| } | |||
| mainHandler.post(runnable); | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| package wendu.dsbridge; | |||
| /** | |||
| * Created by du on 16/12/31. | |||
| */ | |||
| public interface OnReturnValue<T> { | |||
| void onValue( T retValue); | |||
| } | |||
| @@ -0,0 +1,3 @@ | |||
| <resources> | |||
| <string name="app_name">JsBridge</string> | |||
| </resources> | |||
| @@ -15,5 +15,6 @@ dependencyResolutionManagement { | |||
| } | |||
| include ':test' | |||
| include ':dsbridge' | |||
| include ':app' | |||
| rootProject.name = "nativedemo" | |||