Repository: Justson/AgentWeb Branch: androidx Commit: 95d48cd5a032 Files: 202 Total size: 630.7 KB Directory structure: gitextract_lyaxu09n/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── agentweb-core/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ ├── providedLibs/ │ │ └── alipaySdk-20180601.jar │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── just/ │ │ └── agentweb/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── just/ │ │ │ └── agentweb/ │ │ │ ├── AbsAgentWebSettings.java │ │ │ ├── AbsAgentWebUIController.java │ │ │ ├── Action.java │ │ │ ├── AgentActionFragment.java │ │ │ ├── AgentWeb.java │ │ │ ├── AgentWebCompat.java │ │ │ ├── AgentWebConfig.java │ │ │ ├── AgentWebFileProvider.java │ │ │ ├── AgentWebJsInterfaceCompat.java │ │ │ ├── AgentWebPermissions.java │ │ │ ├── AgentWebSettingsImpl.java │ │ │ ├── AgentWebUIControllerImplBase.java │ │ │ ├── AgentWebUtils.java │ │ │ ├── AgentWebView.java │ │ │ ├── BaseIndicatorSpec.java │ │ │ ├── BaseIndicatorView.java │ │ │ ├── BaseJsAccessEntrace.java │ │ │ ├── DefaultChromeClient.java │ │ │ ├── DefaultDesignUIController.java │ │ │ ├── DefaultDownloadImpl.java │ │ │ ├── DefaultUIController.java │ │ │ ├── DefaultWebClient.java │ │ │ ├── DefaultWebCreator.java │ │ │ ├── DefaultWebLifeCycleImpl.java │ │ │ ├── EventHandlerImpl.java │ │ │ ├── EventInterceptor.java │ │ │ ├── HookManager.java │ │ │ ├── HttpHeaders.java │ │ │ ├── IAgentWebSettings.java │ │ │ ├── IEventHandler.java │ │ │ ├── IUrlLoader.java │ │ │ ├── IVideo.java │ │ │ ├── IWebIndicator.java │ │ │ ├── IWebLayout.java │ │ │ ├── IndicatorController.java │ │ │ ├── IndicatorHandler.java │ │ │ ├── JsAccessEntrace.java │ │ │ ├── JsAccessEntraceImpl.java │ │ │ ├── JsBaseInterfaceHolder.java │ │ │ ├── JsCallJava.java │ │ │ ├── JsCallback.java │ │ │ ├── JsInterfaceHolder.java │ │ │ ├── JsInterfaceHolderImpl.java │ │ │ ├── JsInterfaceObjectException.java │ │ │ ├── LayoutParamsOffer.java │ │ │ ├── LogUtils.java │ │ │ ├── LollipopFixedWebView.java │ │ │ ├── MiddlewareWebChromeBase.java │ │ │ ├── MiddlewareWebClientBase.java │ │ │ ├── NestedScrollAgentWebView.java │ │ │ ├── PermissionInterceptor.java │ │ │ ├── ProcessUtils.java │ │ │ ├── Provider.java │ │ │ ├── QuickCallJs.java │ │ │ ├── RomUtils.java │ │ │ ├── UrlCommonException.java │ │ │ ├── UrlLoaderImpl.java │ │ │ ├── VideoImpl.java │ │ │ ├── WebChromeClient.java │ │ │ ├── WebChromeClientDelegate.java │ │ │ ├── WebCreator.java │ │ │ ├── WebIndicator.java │ │ │ ├── WebLifeCycle.java │ │ │ ├── WebListenerManager.java │ │ │ ├── WebParentLayout.java │ │ │ ├── WebSecurityCheckLogic.java │ │ │ ├── WebSecurityController.java │ │ │ ├── WebSecurityControllerImpl.java │ │ │ ├── WebSecurityLogicImpl.java │ │ │ ├── WebViewClient.java │ │ │ └── WebViewClientDelegate.java │ │ └── res/ │ │ ├── layout/ │ │ │ └── agentweb_error_page.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── ids.xml │ │ │ ├── strings.xml │ │ │ └── style.xml │ │ ├── values-zh/ │ │ │ └── strings.xml │ │ └── xml/ │ │ └── web_files_public.xml │ └── test/ │ └── java/ │ └── com/ │ └── just/ │ └── agentweb/ │ └── ExampleUnitTest.java ├── agentweb-filechooser/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── just/ │ │ └── agentweb/ │ │ └── filechooser/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── just/ │ │ │ └── agentweb/ │ │ │ └── filechooser/ │ │ │ ├── FileChooser.java │ │ │ ├── FileCompressor.java │ │ │ └── FileParcel.java │ │ └── res/ │ │ └── values/ │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── just/ │ └── agentweb/ │ └── filechooser/ │ └── ExampleUnitTest.java ├── agentweb.apk ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── releasenote.md ├── sample/ │ ├── .gitignore │ ├── build.gradle │ ├── keystore/ │ │ └── keystore.jks │ ├── keystore.jks │ ├── libs/ │ │ └── alipaySdk-20180601.jar │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── just/ │ │ └── agentweb/ │ │ └── sample/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── assets/ │ │ │ ├── js_interaction/ │ │ │ │ ├── button.css │ │ │ │ └── hello.html │ │ │ ├── jsbridge/ │ │ │ │ └── demo.html │ │ │ ├── sms/ │ │ │ │ └── sms.html │ │ │ └── upload_file/ │ │ │ ├── event.js │ │ │ ├── jsuploadfile.html │ │ │ ├── upload.css │ │ │ └── uploadfile.html │ │ ├── java/ │ │ │ └── com/ │ │ │ └── just/ │ │ │ └── agentweb/ │ │ │ └── sample/ │ │ │ ├── activity/ │ │ │ │ ├── AutoHidenToolbarActivity.java │ │ │ │ ├── BaseWebActivity.java │ │ │ │ ├── CommonActivity.java │ │ │ │ ├── ContainerActivity.java │ │ │ │ ├── EasyWebActivity.java │ │ │ │ ├── ExternalActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── NativeDownloadActivity.java │ │ │ │ ├── RemoteWebViewlActivity.java │ │ │ │ └── WebActivity.java │ │ │ ├── api/ │ │ │ │ └── Api.java │ │ │ ├── app/ │ │ │ │ └── App.java │ │ │ ├── base/ │ │ │ │ ├── BaseAgentWebActivity.java │ │ │ │ ├── BaseAgentWebFragment.java │ │ │ │ └── FragmentKeyDown.java │ │ │ ├── behavior/ │ │ │ │ └── BottomNavigationViewBehavior.java │ │ │ ├── client/ │ │ │ │ ├── MiddlewareChromeClient.java │ │ │ │ ├── MiddlewareWebViewClient.java │ │ │ │ └── SonicWebViewClient.java │ │ │ ├── common/ │ │ │ │ ├── AndroidInterface.java │ │ │ │ ├── CommonWebChromeClient.java │ │ │ │ ├── CustomSettings.java │ │ │ │ ├── FragmentKeyDown.java │ │ │ │ ├── GuideItemEntity.java │ │ │ │ └── UIController.java │ │ │ ├── fragment/ │ │ │ │ ├── AgentWebFragment.java │ │ │ │ ├── BounceWebFragment.java │ │ │ │ ├── CustomIndicatorFragment.java │ │ │ │ ├── CustomSettingsFragment.java │ │ │ │ ├── CustomWebViewFragment.java │ │ │ │ ├── EasyWebFragment.java │ │ │ │ ├── JsAgentWebFragment.java │ │ │ │ ├── JsbridgeWebFragment.java │ │ │ │ ├── SmartRefreshWebFragment.java │ │ │ │ └── VasSonicFragment.java │ │ │ ├── provider/ │ │ │ │ ├── ServiceProvider.java │ │ │ │ └── WebServiceProvider.java │ │ │ ├── service/ │ │ │ │ └── WebService.java │ │ │ ├── sonic/ │ │ │ │ ├── DefaultSonicRuntimeImpl.java │ │ │ │ ├── SonicImpl.java │ │ │ │ ├── SonicJavaScriptInterface.java │ │ │ │ ├── SonicSessionClientImpl.java │ │ │ │ └── SonicWebViewClient.java │ │ │ ├── utils/ │ │ │ │ ├── FileUtils.java │ │ │ │ ├── ProcessUtils.java │ │ │ │ └── WebCompat.java │ │ │ └── widget/ │ │ │ ├── CommonIndicator.java │ │ │ ├── CoolIndicatorLayout.java │ │ │ ├── SmartRefreshWebLayout.java │ │ │ └── WebLayout.java │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── btn_shape.xml │ │ │ ├── btn_shape_s.xml │ │ │ ├── ic_baseline_search_24.xml │ │ │ ├── indicator_shape.xml │ │ │ ├── iv_back_selector.xml │ │ │ └── selector_drawable_for_btn.xml │ │ ├── drawable-v21/ │ │ │ ├── ripple_for_btn.xml │ │ │ └── selector_drawable_for_btn.xml │ │ ├── layout/ │ │ │ ├── activity_auto_hiden_toolbar.xml │ │ │ ├── activity_common.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_native_download.xml │ │ │ ├── activity_web.xml │ │ │ ├── fragment_agentweb.xml │ │ │ ├── fragment_js.xml │ │ │ ├── fragment_srl_web.xml │ │ │ ├── fragment_twk_web.xml │ │ │ ├── listview_main.xml │ │ │ ├── markdown_view.xml │ │ │ ├── recyclerview_item_download.xml │ │ │ └── toorbar_main.xml │ │ ├── menu/ │ │ │ └── toolbar_menu.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── xml/ │ │ └── network_security_config.xml │ └── test/ │ └── java/ │ └── com/ │ └── just/ │ └── agentweb/ │ └── sample/ │ └── ExampleUnitTest.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel custom: # Replace with a single custom sponsorship URL ================================================ FILE: .gitignore ================================================ *.iml .gradle .idea /local.properties .DS_Store /build /captures .externalNativeBuild # Built application files # *.apk *.ap_ # Files for the ART/Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ out/ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log # Android Studio Navigation editor temp files .navigation/ # Android Studio captures folder captures/ # Intellij *.iml .idea/workspace.xml local.properties # Keystore files #*.jks app/keystore/ #sample/keystore/ library/src/main/res/mipmap-xxhdpi/ sample/sampledata/ node_modules package-lock.json package.json ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a onResult of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================
## AgentWeb 介绍 AgentWeb 是一个基于的 Android WebView ,极度容易使用以及功能强大的库,提供了 Android WebView 一系列的问题解决方案 ,并且轻量和极度灵活,详细使用请参照上面的 Sample 。 ## Gradle 引入 ```groovy allprojects { repositories { mavenCentral() maven { url 'https://jitpack.io' } } } ``` * Androidx ```groovy implementation 'io.github.justson:agentweb-core:v5.1.1-androidx' implementation 'io.github.justson:agentweb-filechooser:v5.1.1-androidx' // (可选) implementation 'com.github.Justson:Downloader:v5.0.4-androidx' // (可选) ``` ## 相关 * [flying-pigeon跨进程IPC组件](https://github.com/Justson/flying-pigeon) * [AgentWebX5](https://github.com/Justson/AgentWebX5) * [WebView 进度条](https://github.com/Justson/CoolIndicator) * [Downloader 一个轻量的文件下载器](https://github.com/Justson/Downloader) ## 注意事项 * 支付宝使用需要引入支付宝SDK ,并在项目中依赖 , 微信支付不需要做任何操作。 * AgentWeb 内部使用了 `AlertDialog` 需要依赖 `AppCompat` 主题 。 * `setAgentWebParent` 不支持 `ConstraintLayout` 。 * `mAgentWeb.getWebLifeCycle().onPause();`会暂停应用内所有`WebView` 。 * `minSdkVersion` 低于等于16以下自定义`WebView`请注意与 `JS` 之间通信安全。 ## 文档帮助 * [Wiki](https://github.com/Justson/AgentWeb/wiki)(不全) * `Sample`(推荐,详细) * [更新日志](./releasenote.md) ## 有问题或者有更好的建议 * [![QQ0Group][qq0groupsvg]][qq0group] * 欢迎提 [Issues](https://github.com/Justson/AgentWeb/issues) ## 赞赏 开源不易,你的支持是我更新的动力。 [licensesvg]: https://img.shields.io/badge/License-Apache--2.0-brightgreen.svg [license]: https://github.com/Justson/AgentWeb/blob/master/LICENSE [qq0groupsvg]: https://img.shields.io/badge/QQ群-599471474-fba7f9.svg [qq0group]: http://qm.qq.com/cgi-bin/qm/qr?k=KpyfInzI2nr-Lh4StG0oh68GpbcD0vMG [![License][licensesvg]][license] ## License ``` Copyright (C) Justson(https://github.com/Justson/AgentWeb) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ​ ​ ================================================ FILE: agentweb-core/.gitignore ================================================ /build ================================================ FILE: agentweb-core/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'maven-publish' android { compileSdk COMPILE_SDK_VERSION.toInteger() defaultConfig { minSdkVersion 14 targetSdkVersion TARGET_SDK_VERSION.toInteger() namespace 'com.just.agentweb' versionCode 3 versionName VERSION_NAME testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { abortOnError false } repositories { flatDir { dirs 'libs', 'providedLibs' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // defaultPublishConfig "debug" } dependencies { compileOnly fileTree(include: ['*.jar'], dir: 'libs') androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' }) testImplementation 'junit:junit:4.12' compileOnly 'com.github.Justson:Downloader:v5.0.4-androidx' compileOnly 'com.google.android.material:material:1.0.0' compileOnly 'androidx.legacy:legacy-support-v4:1.0.0' compileOnly fileTree(include: ['*.jar'], dir: 'providedLibs') } publishing { publications { // Creates a Maven publication called "release". release(MavenPublication) { groupId = 'com.github.Justson.AgentWeb' artifactId = 'agentweb-core' version = 'v5.0.7-androidx' } } } ================================================ FILE: agentweb-core/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/cenxiaozhong/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.create.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keep class com.just.agentweb.** { *; } -dontwarn com.just.agentweb.** ================================================ FILE: agentweb-core/src/androidTest/java/com/just/agentweb/ExampleInstrumentedTest.java ================================================ package com.just.agentweb; import android.content.Context; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.just.library.test", appContext.getPackageName()); } } ================================================ FILE: agentweb-core/src/main/AndroidManifest.xml ================================================ ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AbsAgentWebSettings.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.os.Build; import android.view.View; import android.webkit.DownloadListener; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; /** * @author cenxiaozhong * @update 4.0.0 ,WebDefaultSettingsManager rename to AbsAgentWebSettings * @since 1.0.0 */ public abstract class AbsAgentWebSettings implements IAgentWebSettings, WebListenerManager { private WebSettings mWebSettings; private static final String TAG = AbsAgentWebSettings.class.getSimpleName(); public static final String USERAGENT_UC = " UCBrowser/11.6.4.950 "; public static final String USERAGENT_QQ_BROWSER = " MQQBrowser/8.0 "; public static final String USERAGENT_AGENTWEB = " " + AgentWebConfig.AGENTWEB_VERSION + " "; protected AgentWeb mAgentWeb; public static AbsAgentWebSettings getInstance() { return new AgentWebSettingsImpl(); } public AbsAgentWebSettings() { } final void bindAgentWeb(AgentWeb agentWeb) { this.mAgentWeb = agentWeb; this.bindAgentWebSupport(agentWeb); } protected abstract void bindAgentWebSupport(AgentWeb agentWeb); @Override public IAgentWebSettings toSetting(WebView webView) { settings(webView); return this; } private void settings(WebView webView) { mWebSettings = webView.getSettings(); mWebSettings.setJavaScriptEnabled(true); mWebSettings.setSupportZoom(true); mWebSettings.setBuiltInZoomControls(false); mWebSettings.setSavePassword(false); if (AgentWebUtils.checkNetwork(webView.getContext().getApplicationContext())) { //根据cache-control获取数据。 mWebSettings.setCacheMode(WebSettings.LOAD_DEFAULT); } else { //没网,则从本地获取,即离线加载 mWebSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //适配5.0不允许http和https混合使用情况 mWebSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { webView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } mWebSettings.setTextZoom(100); mWebSettings.setDatabaseEnabled(true); mWebSettings.setLoadsImagesAutomatically(true); mWebSettings.setSupportMultipleWindows(false); // 是否阻塞加载网络图片 协议http or https mWebSettings.setBlockNetworkImage(false); // 允许加载本地文件html file协议 mWebSettings.setAllowFileAccess(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { // 通过 file url 加载的 Javascript 读取其他的本地文件 .建议关闭 mWebSettings.setAllowFileAccessFromFileURLs(false); // 允许通过 file url 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源 mWebSettings.setAllowUniversalAccessFromFileURLs(false); } mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); } else { mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); } mWebSettings.setLoadWithOverviewMode(false); mWebSettings.setUseWideViewPort(false); mWebSettings.setDomStorageEnabled(true); mWebSettings.setNeedInitialFocus(true); mWebSettings.setDefaultTextEncodingName("utf-8");//设置编码格式 mWebSettings.setDefaultFontSize(16); mWebSettings.setMinimumFontSize(12);//设置 WebView 支持的最小字体大小,默认为 8 mWebSettings.setGeolocationEnabled(true); String dir = AgentWebConfig.getCachePath(webView.getContext()); LogUtils.i(TAG, "dir:" + dir + " appcache:" + AgentWebConfig.getCachePath(webView.getContext())); //设置数据库路径 api19 已经废弃,这里只针对 webkit 起作用 mWebSettings.setGeolocationDatabasePath(dir); mWebSettings.setDatabasePath(dir); mWebSettings.setUserAgentString(getWebSettings() .getUserAgentString() .concat(USERAGENT_AGENTWEB) .concat(USERAGENT_UC) ); LogUtils.i(TAG, "UserAgentString : " + mWebSettings.getUserAgentString()); } @Override public WebSettings getWebSettings() { return mWebSettings; } @Override public WebListenerManager setWebChromeClient(WebView webview, WebChromeClient webChromeClient) { webview.setWebChromeClient(webChromeClient); return this; } @Override public WebListenerManager setWebViewClient(WebView webView, WebViewClient webViewClient) { webView.setWebViewClient(webViewClient); return this; } @Override public WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener) { webView.setDownloadListener(downloadListener); return this; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AbsAgentWebUIController.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.app.Activity; import android.app.Dialog; import android.net.http.SslError; import android.os.Handler; import android.webkit.JsPromptResult; import android.webkit.JsResult; import android.webkit.PermissionRequest; import android.webkit.SslErrorHandler; import android.webkit.WebView; /** * 该类统一控制了与用户交互的界面 * * @author cenxiaozhong * @since 3.0.0 */ public abstract class AbsAgentWebUIController { public static boolean HAS_DESIGN_LIB = false; private Activity mActivity; private WebParentLayout mWebParentLayout; private volatile boolean mIsBindWebParent = false; protected AbsAgentWebUIController mAgentWebUIControllerDelegate; protected String TAG = this.getClass().getSimpleName(); static { try { Class.forName("com.google.android.material.snackbar.Snackbar"); Class.forName("com.google.android.material.bottomsheet.BottomSheetDialog"); HAS_DESIGN_LIB = true; } catch (Throwable ignore) { HAS_DESIGN_LIB = false; if (LogUtils.isDebug()) { ignore.printStackTrace(); } } } protected AbsAgentWebUIController create() { return HAS_DESIGN_LIB ? new DefaultDesignUIController() : new DefaultUIController(); } protected AbsAgentWebUIController getDelegate() { AbsAgentWebUIController mAgentWebUIController = this.mAgentWebUIControllerDelegate; if (mAgentWebUIController == null) { this.mAgentWebUIControllerDelegate = mAgentWebUIController = create(); } return mAgentWebUIController; } final synchronized void bindWebParent(WebParentLayout webParentLayout, Activity activity) { if (!mIsBindWebParent) { mIsBindWebParent = true; this.mWebParentLayout = webParentLayout; this.mActivity = activity; bindSupportWebParent(webParentLayout, activity); } } protected void toDismissDialog(Dialog dialog) { if (dialog != null && dialog.isShowing()) { dialog.dismiss(); } } protected void toShowDialog(Dialog dialog) { if (dialog != null && !dialog.isShowing()) { dialog.show(); } } protected abstract void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity); /** * WebChromeClient#onJsAlert * * @param view * @param url * @param message */ public abstract void onJsAlert(WebView view, String url, String message); /** * 咨询用户是否前往其他页面 * * @param view * @param url * @param callback */ public abstract void onOpenPagePrompt(WebView view, String url, Handler.Callback callback); /** * WebChromeClient#onJsConfirm * * @param view * @param url * @param message * @param jsResult */ public abstract void onJsConfirm(WebView view, String url, String message, JsResult jsResult); public abstract void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback); /** * 强制下载弹窗 * * @param url 当前下载地址。 * @param callback 用户操作回调回调 */ public abstract void onForceDownloadAlert(String url, Handler.Callback callback); /** * WebChromeClient#onJsPrompt * * @param view * @param url * @param message * @param defaultValue * @param jsPromptResult */ public abstract void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult); /** * 显示错误页 * * @param view * @param errorCode * @param description * @param failingUrl */ public abstract void onMainFrameError(WebView view, int errorCode, String description, String failingUrl); /** * 隐藏错误页 */ public abstract void onShowMainFrame(); /** * 正在加载... * * @param msg */ public abstract void onLoading(String msg); /** * 取消正在加载... */ public abstract void onCancelLoading(); /** * @param message 消息 * @param intent 说明message的来源,意图 */ public abstract void onShowMessage(String message, String intent); /** * 当权限被拒回调该方法 * * @param permissions * @param permissionType * @param action */ public abstract void onPermissionsDeny(String[] permissions, String permissionType, String action); /** * * @param view * @param handler * @param error */ public abstract void onShowSslCertificateErrorDialog(WebView view, SslErrorHandler handler, SslError error); /** * 权限请求 * @param request */ public abstract void onPermissionRequest(PermissionRequest request); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/Action.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.content.Intent; import android.net.Uri; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @author cenxiaozhong * @since 2.0.0 */ public final class Action { public transient static final int ACTION_PERMISSION = 1; public transient static final int ACTION_FILE = 2; public transient static final int ACTION_CAMERA = 3; public transient static final int ACTION_VIDEO = 4; private ArrayList mPermissions = new ArrayList<>(); private int mAction; private int mFromIntention; private Intent mIntent; private Uri mUri; private AgentActionFragment.RationaleListener mRationaleListener; private AgentActionFragment.PermissionListener mPermissionListener; private AgentActionFragment.ChooserListener mChooserListener; public Action() { } public ArrayList getPermissions() { return mPermissions; } public void setPermissions(ArrayList permissions) { this.mPermissions = permissions; } public void setPermissions(String[] permissions) { this.mPermissions = new ArrayList<>(Arrays.asList(permissions)); } public int getAction() { return mAction; } public void setAction(int action) { this.mAction = action; } public int getFromIntention() { return mFromIntention; } public static Action createPermissionsAction(String[] permissions) { Action mAction = new Action(); mAction.setAction(Action.ACTION_PERMISSION); List mList = Arrays.asList(permissions); mAction.setPermissions(new ArrayList(mList)); return mAction; } public Action setFromIntention(int fromIntention) { this.mFromIntention = fromIntention; return this; } public AgentActionFragment.RationaleListener getRationaleListener() { return mRationaleListener; } public void setRationaleListener(AgentActionFragment.RationaleListener rationaleListener) { mRationaleListener = rationaleListener; } public AgentActionFragment.PermissionListener getPermissionListener() { return mPermissionListener; } public void setPermissionListener(AgentActionFragment.PermissionListener permissionListener) { mPermissionListener = permissionListener; } public AgentActionFragment.ChooserListener getChooserListener() { return mChooserListener; } public void setChooserListener(AgentActionFragment.ChooserListener chooserListener) { mChooserListener = chooserListener; } public Intent getIntent() { return mIntent; } public Uri getUri() { return mUri; } public void setUri(Uri uri) { mUri = uri; } public void setIntent(Intent intent) { mIntent = intent; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentActionFragment.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import static android.provider.MediaStore.EXTRA_OUTPUT; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import java.io.File; import java.util.List; /** * @author cenxiaozhong * @since 2.0.0 */ public final class AgentActionFragment extends Fragment { public static final String KEY_URI = "KEY_URI"; public static final String KEY_FROM_INTENTION = "KEY_FROM_INTENTION"; private static final String TAG = AgentActionFragment.class.getSimpleName(); private Action mAction; public static final int REQUEST_CODE = 0x254; public static final String FRAGMENT_TAG = "AgentWebActionFragment"; public static void start(Activity activity, Action action) { FragmentActivity fragmentActivity = (FragmentActivity) activity; FragmentManager fragmentManager = fragmentActivity.getSupportFragmentManager(); AgentActionFragment fragment = (AgentActionFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG); if (fragment == null) { fragment = new AgentActionFragment(); fragmentManager.beginTransaction().add(fragment, FRAGMENT_TAG).commitAllowingStateLoss(); } fragment.mAction = action; if (fragment.isViewCreated) { fragment.runAction(); } } public AgentActionFragment() { } private void resetAction() { // mAction = null; } private boolean isViewCreated = false; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { LogUtils.i(TAG, "savedInstanceState:" + savedInstanceState); return; } isViewCreated = true; runAction(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); } private void runAction() { if (mAction == null) { resetAction(); return; } if (mAction.getAction() == Action.ACTION_PERMISSION) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermission(mAction); } else { resetAction(); } } else if (mAction.getAction() == Action.ACTION_CAMERA) { captureCamera(); } else if (mAction.getAction() == Action.ACTION_VIDEO) { recordVideo(); } else { choose(); } } private void choose() { try { if (mAction.getChooserListener() == null) { return; } Intent mIntent = mAction.getIntent(); if (mIntent == null) { resetAction(); return; } this.startActivityForResult(mIntent, REQUEST_CODE); } catch (Throwable throwable) { LogUtils.i(TAG, "找不到文件选择器"); chooserActionCallback(-1, null); if (LogUtils.isDebug()) { throwable.printStackTrace(); } } } private void chooserActionCallback(int resultCode, Intent data) { if (mAction.getChooserListener() != null) { mAction.getChooserListener().onChoiceResult(REQUEST_CODE, resultCode, data); } resetAction(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mAction == null) { return; } if (requestCode == REQUEST_CODE) { if (mAction.getUri() != null) { chooserActionCallback(resultCode, new Intent().putExtra(KEY_URI, mAction.getUri())); } else { chooserActionCallback(resultCode, data); } } resetAction(); } @RequiresApi(api = Build.VERSION_CODES.M) private void requestPermission(Action action) { List permissions = action.getPermissions(); if (AgentWebUtils.isEmptyCollection(permissions)) { resetAction(); return; } if (mAction.getRationaleListener() != null) { boolean rationale = false; for (String permission : permissions) { rationale = shouldShowRequestPermissionRationale(permission); if (rationale) { break; } } mAction.getRationaleListener().onRationaleResult(rationale, new Bundle()); resetAction(); return; } if (mAction.getPermissionListener() != null) { requestPermissions(permissions.toArray(new String[]{}), 1); } } private void captureCamera() { try { if (mAction.getChooserListener() == null) { resetAction(); return; } File mFile = AgentWebUtils.createImageFile(this.getActivity()); if (mFile == null) { mAction.getChooserListener().onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null); } Intent intent = AgentWebUtils.getIntentCaptureCompat(getActivity(), mFile); // 指定开启系统相机的Action mAction.setUri((Uri) intent.getParcelableExtra(EXTRA_OUTPUT)); this.startActivityForResult(intent, REQUEST_CODE); } catch (Throwable ignore) { LogUtils.e(TAG, "找不到系统相机"); if (mAction.getChooserListener() != null) { mAction.getChooserListener().onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null); } resetAction(); if (LogUtils.isDebug()) { ignore.printStackTrace(); } } } private void recordVideo() { try { if (mAction.getChooserListener() == null) { resetAction(); return; } File mFile = AgentWebUtils.createVideoFile(this.getActivity()); if (mFile == null) { mAction.getChooserListener().onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null); resetAction(); return; } Intent intent = AgentWebUtils.getIntentVideoCompat(getActivity(), mFile); // 指定开启系统相机的Action mAction.setUri((Uri) intent.getParcelableExtra(EXTRA_OUTPUT)); this.startActivityForResult(intent, REQUEST_CODE); } catch (Throwable ignore) { LogUtils.e(TAG, "找不到系统相机"); if (mAction.getChooserListener() != null) { mAction.getChooserListener().onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null); } resetAction(); if (LogUtils.isDebug()) { ignore.printStackTrace(); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (mAction.getPermissionListener() != null) { Bundle mBundle = new Bundle(); mBundle.putInt(KEY_FROM_INTENTION, mAction.getFromIntention()); mAction.getPermissionListener().onRequestPermissionsResult(permissions, grantResults, mBundle); } resetAction(); } public interface RationaleListener { void onRationaleResult(boolean showRationale, Bundle extras); } public interface PermissionListener { void onRequestPermissionsResult(@NonNull String[] permissions, @NonNull int[] grantResults, Bundle extras); } public interface ChooserListener { void onChoiceResult(int requestCode, int resultCode, Intent data); } @Override public void onDestroy() { super.onDestroy(); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWeb.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.app.Activity; import androidx.annotation.ColorInt; import androidx.annotation.IdRes; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.collection.ArrayMap; import android.text.TextUtils; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; import java.lang.ref.WeakReference; import java.util.Map; /** * @author cenxiaozhong * @update 4.0.0 * @since 1.0.0 */ public final class AgentWeb { /** * AgentWeb 's TAG */ private static final String TAG = AgentWeb.class.getSimpleName(); /** * Activity */ private Activity mActivity; /** * 承载 WebParentLayout 的 ViewGroup */ private ViewGroup mViewGroup; /** * 负责创建布局 WebView ,WebParentLayout Indicator等。 */ private WebCreator mWebCreator; /** * 管理 WebSettings */ private IAgentWebSettings mAgentWebSettings; /** * AgentWeb */ private AgentWeb mAgentWeb = null; /** * IndicatorController 控制Indicator */ private IndicatorController mIndicatorController; /** * WebChromeClient */ private com.just.agentweb.WebChromeClient mWebChromeClient; /** * WebViewClient */ private com.just.agentweb.WebViewClient mWebViewClient; /** * is show indicator */ private boolean mEnableIndicator; /** * IEventHandler 处理WebView相关返回事件 */ private IEventHandler mIEventHandler; /** * WebView 注入对象 */ private ArrayMap mJavaObjects = new ArrayMap<>(); /** * flag */ private int mTagTarget = 0; /** * WebListenerManager */ private WebListenerManager mWebListenerManager; /** * 安全 Controller */ private WebSecurityController mWebSecurityController = null; /** * WebSecurityCheckLogic */ private WebSecurityCheckLogic mWebSecurityCheckLogic = null; /** * WebChromeClient */ private WebChromeClient mTargetChromeClient; /** * flag security 's mode */ private SecurityType mSecurityType = SecurityType.DEFAULT_CHECK; /** * Activity */ private static final int ACTIVITY_TAG = 0; /** * Fragment */ private static final int FRAGMENT_TAG = 1; /** * AgentWeb 默认注入对像 */ private AgentWebJsInterfaceCompat mAgentWebJsInterfaceCompat = null; /** * JsAccessEntrace 提供快速JS方法调用 */ private JsAccessEntrace mJsAccessEntrace = null; /** * URL Loader , 提供了 WebView#loadUrl(url) reload() stopLoading() postUrl()等方法 */ private IUrlLoader mIUrlLoader = null; /** * WebView 生命周期 , 跟随生命周期释放CPU */ private WebLifeCycle mWebLifeCycle; /** * Video 视屏播放管理类 */ private IVideo mIVideo = null; /** * WebViewClient 辅助控制开关 */ private boolean mWebClientHelper = true; /** * PermissionInterceptor 权限拦截 */ private PermissionInterceptor mPermissionInterceptor; /** * 是否拦截未知的Url, @link{DefaultWebClient} */ private boolean mIsInterceptUnkownUrl = true; /** * Url处理方式,是直接跳转还是弹窗让用户去选择 */ private int mUrlHandleWays = -1; /** * MiddlewareWebClientBase WebViewClient 中间件 */ private MiddlewareWebClientBase mMiddleWrareWebClientBaseHeader; /** * MiddlewareWebChromeBase WebChromeClient 中间件 */ private MiddlewareWebChromeBase mMiddlewareWebChromeBaseHeader; /** * 事件拦截 */ private EventInterceptor mEventInterceptor; /** * 注入对象管理类 */ private JsInterfaceHolder mJsInterfaceHolder = null; private AgentWeb(AgentBuilder agentBuilder) { mTagTarget = agentBuilder.mTag; this.mActivity = agentBuilder.mActivity; this.mViewGroup = agentBuilder.mViewGroup; this.mIEventHandler = agentBuilder.mIEventHandler; this.mEnableIndicator = agentBuilder.mEnableIndicator; mWebCreator = agentBuilder.mWebCreator == null ? configWebCreator(agentBuilder.mBaseIndicatorView, agentBuilder.mIndex, agentBuilder.mLayoutParams, agentBuilder.mIndicatorColor, agentBuilder.mHeight, agentBuilder.mWebView, agentBuilder.mWebLayout) : agentBuilder.mWebCreator; mIndicatorController = agentBuilder.mIndicatorController; this.mWebChromeClient = agentBuilder.mWebChromeClient; this.mWebViewClient = agentBuilder.mWebViewClient; mAgentWeb = this; this.mAgentWebSettings = agentBuilder.mAgentWebSettings; if (agentBuilder.mJavaObject != null && !agentBuilder.mJavaObject.isEmpty()) { this.mJavaObjects.putAll((Map) agentBuilder.mJavaObject); LogUtils.i(TAG, "mJavaObject size:" + this.mJavaObjects.size()); } this.mPermissionInterceptor = agentBuilder.mPermissionInterceptor == null ? null : new PermissionInterceptorWrapper(agentBuilder.mPermissionInterceptor); this.mSecurityType = agentBuilder.mSecurityType; this.mIUrlLoader = new UrlLoaderImpl(mWebCreator.create().getWebView(), agentBuilder.mHttpHeaders); if (this.mWebCreator.getWebParentLayout() instanceof WebParentLayout) { WebParentLayout mWebParentLayout = (WebParentLayout) this.mWebCreator.getWebParentLayout(); mWebParentLayout.bindController(agentBuilder.mAgentWebUIController == null ? AgentWebUIControllerImplBase.build() : agentBuilder.mAgentWebUIController); mWebParentLayout.setErrorLayoutRes(agentBuilder.mErrorLayout, agentBuilder.mReloadId); mWebParentLayout.setErrorView(agentBuilder.mErrorView); } this.mWebLifeCycle = new DefaultWebLifeCycleImpl(mWebCreator.getWebView()); mWebSecurityController = new WebSecurityControllerImpl(mWebCreator.getWebView(), this.mAgentWeb.mJavaObjects, this.mSecurityType); this.mWebClientHelper = agentBuilder.mWebClientHelper; this.mIsInterceptUnkownUrl = agentBuilder.mIsInterceptUnkownUrl; if (agentBuilder.mOpenOtherPage != null) { this.mUrlHandleWays = agentBuilder.mOpenOtherPage.code; } this.mMiddleWrareWebClientBaseHeader = agentBuilder.mMiddlewareWebClientBaseHeader; this.mMiddlewareWebChromeBaseHeader = agentBuilder.mChromeMiddleWareHeader; init(); } /** * @return PermissionInterceptor 权限控制者 */ public PermissionInterceptor getPermissionInterceptor() { return this.mPermissionInterceptor; } public WebLifeCycle getWebLifeCycle() { return this.mWebLifeCycle; } public JsAccessEntrace getJsAccessEntrace() { JsAccessEntrace mJsAccessEntrace = this.mJsAccessEntrace; if (mJsAccessEntrace == null) { this.mJsAccessEntrace = mJsAccessEntrace = JsAccessEntraceImpl.getInstance(mWebCreator.getWebView()); } return mJsAccessEntrace; } public AgentWeb clearWebCache() { if (this.getWebCreator().getWebView() != null) { AgentWebUtils.clearWebViewAllCache(mActivity, this.getWebCreator().getWebView()); } else { AgentWebUtils.clearWebViewAllCache(mActivity); } return this; } public static AgentBuilder with(@NonNull Activity activity) { if (activity == null) { throw new NullPointerException("activity can not be null ."); } return new AgentBuilder(activity); } public static AgentBuilder with(@NonNull Fragment fragment) { Activity mActivity = null; if ((mActivity = fragment.getActivity()) == null) { throw new NullPointerException("activity can not be null ."); } return new AgentBuilder(mActivity, fragment); } public boolean handleKeyEvent(int keyCode, KeyEvent keyEvent) { if (mIEventHandler == null) { mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor()); } return mIEventHandler.onKeyDown(keyCode, keyEvent); } public boolean back() { if (mIEventHandler == null) { mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor()); } return mIEventHandler.back(); } public WebCreator getWebCreator() { return this.mWebCreator; } public IEventHandler getIEventHandler() { return this.mIEventHandler == null ? (this.mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor())) : this.mIEventHandler; } public IAgentWebSettings getAgentWebSettings() { return this.mAgentWebSettings; } public IndicatorController getIndicatorController() { return this.mIndicatorController; } public JsInterfaceHolder getJsInterfaceHolder() { return this.mJsInterfaceHolder; } public IUrlLoader getUrlLoader() { return this.mIUrlLoader; } public void destroy() { this.mWebLifeCycle.onDestroy(); } public static class PreAgentWeb { private AgentWeb mAgentWeb; private boolean isReady = false; PreAgentWeb(AgentWeb agentWeb) { this.mAgentWeb = agentWeb; } public PreAgentWeb ready() { if (!isReady) { mAgentWeb.ready(); isReady = true; } return this; } public AgentWeb get() { ready(); return mAgentWeb; } public AgentWeb go(@Nullable String url) { if (!isReady) { ready(); } return mAgentWeb.go(url); } } private void doSafeCheck() { WebSecurityCheckLogic mWebSecurityCheckLogic = this.mWebSecurityCheckLogic; if (mWebSecurityCheckLogic == null) { this.mWebSecurityCheckLogic = mWebSecurityCheckLogic = WebSecurityLogicImpl.getInstance(mWebCreator.getWebViewType()); } mWebSecurityController.check(mWebSecurityCheckLogic); } private void doCompat() { mJavaObjects.put("agentWeb", mAgentWebJsInterfaceCompat = new AgentWebJsInterfaceCompat(this, mActivity)); } private WebCreator configWebCreator(BaseIndicatorView progressView, int index, ViewGroup.LayoutParams lp, int indicatorColor, int height_dp, WebView webView, IWebLayout webLayout) { if (progressView != null && mEnableIndicator) { return new DefaultWebCreator(mActivity, mViewGroup, lp, index, progressView, webView, webLayout); } else { return mEnableIndicator ? new DefaultWebCreator(mActivity, mViewGroup, lp, index, indicatorColor, height_dp, webView, webLayout) : new DefaultWebCreator(mActivity, mViewGroup, lp, index, webView, webLayout); } } private AgentWeb go(String url) { this.getUrlLoader().loadUrl(url); IndicatorController mIndicatorController = null; if (!TextUtils.isEmpty(url) && (mIndicatorController = getIndicatorController()) != null && mIndicatorController.offerIndicator() != null) { getIndicatorController().offerIndicator().show(); } return this; } private EventInterceptor getInterceptor() { if (this.mEventInterceptor != null) { return this.mEventInterceptor; } if (mIVideo instanceof VideoImpl) { return this.mEventInterceptor = (EventInterceptor) this.mIVideo; } return null; } private void init() { doCompat(); doSafeCheck(); } private IVideo getIVideo() { return mIVideo == null ? new VideoImpl(mActivity, mWebCreator.getWebView()) : mIVideo; } private WebViewClient getWebViewClient() { LogUtils.i(TAG, "getDelegate:" + this.mMiddleWrareWebClientBaseHeader); DefaultWebClient mDefaultWebClient = DefaultWebClient .createBuilder() .setActivity(this.mActivity) .setWebClientHelper(this.mWebClientHelper) .setPermissionInterceptor(this.mPermissionInterceptor) .setWebView(this.mWebCreator.getWebView()) .setInterceptUnkownUrl(this.mIsInterceptUnkownUrl) .setUrlHandleWays(this.mUrlHandleWays) .build(); MiddlewareWebClientBase header = this.mMiddleWrareWebClientBaseHeader; if (this.mWebViewClient != null) { this.mWebViewClient.enq(this.mMiddleWrareWebClientBaseHeader); header = this.mWebViewClient; } if (header != null) { MiddlewareWebClientBase tail = header; int count = 1; MiddlewareWebClientBase tmp = header; while (tmp.next() != null) { tail = tmp = tmp.next(); count++; } LogUtils.i(TAG, "MiddlewareWebClientBase middleware count:" + count); tail.setDelegate(mDefaultWebClient); return header; } else { return mDefaultWebClient; } } private AgentWeb ready() { AgentWebConfig.initCookiesManager(mActivity.getApplicationContext()); IAgentWebSettings mAgentWebSettings = this.mAgentWebSettings; if (mAgentWebSettings == null) { this.mAgentWebSettings = mAgentWebSettings = AgentWebSettingsImpl.getInstance(); } if (mAgentWebSettings instanceof AbsAgentWebSettings) { ((AbsAgentWebSettings) mAgentWebSettings).bindAgentWeb(this); } if (mWebListenerManager == null && mAgentWebSettings instanceof AbsAgentWebSettings) { mWebListenerManager = (WebListenerManager) mAgentWebSettings; } mAgentWebSettings.toSetting(mWebCreator.getWebView()); if (mJsInterfaceHolder == null) { mJsInterfaceHolder = JsInterfaceHolderImpl.getJsInterfaceHolder(mWebCreator, this.mSecurityType); } LogUtils.i(TAG, "mJavaObjects:" + mJavaObjects.size()); if (mJavaObjects != null && !mJavaObjects.isEmpty()) { mJsInterfaceHolder.addJavaObjects(mJavaObjects); } if (mWebListenerManager != null) { mWebListenerManager.setDownloader(mWebCreator.getWebView(), null); mWebListenerManager.setWebChromeClient(mWebCreator.getWebView(), getChromeClient()); mWebListenerManager.setWebViewClient(mWebCreator.getWebView(), getWebViewClient()); } return this; } private WebChromeClient getChromeClient() { IndicatorController mIndicatorController = (this.mIndicatorController == null) ? IndicatorHandler.getInstance().inJectIndicator(mWebCreator.offer()) : this.mIndicatorController; DefaultChromeClient mDefaultChromeClient = new DefaultChromeClient(this.mActivity, this.mIndicatorController = mIndicatorController, null, this.mIVideo = getIVideo(), this.mPermissionInterceptor, mWebCreator.getWebView()); LogUtils.i(TAG, "WebChromeClient:" + this.mWebChromeClient); MiddlewareWebChromeBase header = this.mMiddlewareWebChromeBaseHeader; if (this.mWebChromeClient != null) { this.mWebChromeClient.enq(header); header = this.mWebChromeClient; } if (header != null) { MiddlewareWebChromeBase tail = header; int count = 1; MiddlewareWebChromeBase tmp = header; for (; tmp.next() != null; ) { tail = tmp = tmp.next(); count++; } LogUtils.i(TAG, "MiddlewareWebClientBase middleware count:" + count); tail.setDelegate(mDefaultChromeClient); return this.mTargetChromeClient = header; } else { return this.mTargetChromeClient = mDefaultChromeClient; } } public enum SecurityType { DEFAULT_CHECK, STRICT_CHECK; } public static final class AgentBuilder { private Activity mActivity; private Fragment mFragment; private ViewGroup mViewGroup; private boolean mIsNeedDefaultProgress; private int mIndex = -1; private BaseIndicatorView mBaseIndicatorView; private IndicatorController mIndicatorController = null; /*默认进度条是显示的*/ private boolean mEnableIndicator = true; private ViewGroup.LayoutParams mLayoutParams = null; private com.just.agentweb.WebViewClient mWebViewClient; private com.just.agentweb.WebChromeClient mWebChromeClient; private int mIndicatorColor = -1; private IAgentWebSettings mAgentWebSettings; private WebCreator mWebCreator; private HttpHeaders mHttpHeaders = null; private IEventHandler mIEventHandler; private int mHeight = -1; private ArrayMap mJavaObject; private SecurityType mSecurityType = SecurityType.DEFAULT_CHECK; private WebView mWebView; private boolean mWebClientHelper = true; private IWebLayout mWebLayout = null; private PermissionInterceptor mPermissionInterceptor = null; private AbsAgentWebUIController mAgentWebUIController; private DefaultWebClient.OpenOtherPageWays mOpenOtherPage = null; private boolean mIsInterceptUnkownUrl = true; private MiddlewareWebClientBase mMiddlewareWebClientBaseHeader; private MiddlewareWebClientBase mMiddlewareWebClientBaseTail; private MiddlewareWebChromeBase mChromeMiddleWareHeader = null; private MiddlewareWebChromeBase mChromeMiddleWareTail = null; private View mErrorView; private int mErrorLayout; private int mReloadId; private int mTag = -1; public AgentBuilder(@NonNull Activity activity, @NonNull Fragment fragment) { mActivity = activity; mFragment = fragment; mTag = AgentWeb.FRAGMENT_TAG; } public AgentBuilder(@NonNull Activity activity) { mActivity = activity; mTag = AgentWeb.ACTIVITY_TAG; } public IndicatorBuilder setAgentWebParent(@NonNull ViewGroup v, @NonNull ViewGroup.LayoutParams lp) { this.mViewGroup = v; this.mLayoutParams = lp; return new IndicatorBuilder(this); } public IndicatorBuilder setAgentWebParent(@NonNull ViewGroup v, int index, @NonNull ViewGroup.LayoutParams lp) { this.mViewGroup = v; this.mLayoutParams = lp; this.mIndex = index; return new IndicatorBuilder(this); } private PreAgentWeb buildAgentWeb() { if (mTag == AgentWeb.FRAGMENT_TAG && this.mViewGroup == null) { throw new NullPointerException("ViewGroup is null,Please check your parameters ."); } return new PreAgentWeb(HookManager.hookAgentWeb(new AgentWeb(this), this)); } private void addJavaObject(String key, Object o) { if (mJavaObject == null) { mJavaObject = new ArrayMap<>(); } mJavaObject.put(key, o); } private void addHeader(String baseUrl, String k, String v) { if (mHttpHeaders == null) { mHttpHeaders = HttpHeaders.create(); } mHttpHeaders.additionalHttpHeader(baseUrl, k, v); } private void addHeader(String baseUrl, Map headers) { if (mHttpHeaders == null) { mHttpHeaders = HttpHeaders.create(); } mHttpHeaders.additionalHttpHeaders(baseUrl, headers); } } public static class IndicatorBuilder { private AgentBuilder mAgentBuilder = null; public IndicatorBuilder(AgentBuilder agentBuilder) { this.mAgentBuilder = agentBuilder; } public CommonBuilder useDefaultIndicator(int color) { this.mAgentBuilder.mEnableIndicator = true; this.mAgentBuilder.mIndicatorColor = color; return new CommonBuilder(mAgentBuilder); } public CommonBuilder useDefaultIndicator() { this.mAgentBuilder.mEnableIndicator = true; return new CommonBuilder(mAgentBuilder); } public CommonBuilder closeIndicator() { this.mAgentBuilder.mEnableIndicator = false; this.mAgentBuilder.mIndicatorColor = -1; this.mAgentBuilder.mHeight = -1; return new CommonBuilder(mAgentBuilder); } public CommonBuilder setCustomIndicator(@NonNull BaseIndicatorView v) { if (v != null) { this.mAgentBuilder.mEnableIndicator = true; this.mAgentBuilder.mBaseIndicatorView = v; this.mAgentBuilder.mIsNeedDefaultProgress = false; } else { this.mAgentBuilder.mEnableIndicator = true; this.mAgentBuilder.mIsNeedDefaultProgress = true; } return new CommonBuilder(mAgentBuilder); } public CommonBuilder useDefaultIndicator(@ColorInt int color, int height_dp) { this.mAgentBuilder.mIndicatorColor = color; this.mAgentBuilder.mHeight = height_dp; return new CommonBuilder(this.mAgentBuilder); } } public static class CommonBuilder { private AgentBuilder mAgentBuilder; public CommonBuilder(AgentBuilder agentBuilder) { this.mAgentBuilder = agentBuilder; } public CommonBuilder setEventHanadler(@Nullable IEventHandler iEventHandler) { mAgentBuilder.mIEventHandler = iEventHandler; return this; } public CommonBuilder closeWebViewClientHelper() { mAgentBuilder.mWebClientHelper = false; return this; } public CommonBuilder setWebChromeClient(@Nullable com.just.agentweb.WebChromeClient webChromeClient) { this.mAgentBuilder.mWebChromeClient = webChromeClient; return this; } public CommonBuilder setWebViewClient(@Nullable com.just.agentweb.WebViewClient webChromeClient) { this.mAgentBuilder.mWebViewClient = webChromeClient; return this; } public CommonBuilder useMiddlewareWebClient(@NonNull MiddlewareWebClientBase middleWrareWebClientBase) { if (middleWrareWebClientBase == null) { return this; } if (this.mAgentBuilder.mMiddlewareWebClientBaseHeader == null) { this.mAgentBuilder.mMiddlewareWebClientBaseHeader = this.mAgentBuilder.mMiddlewareWebClientBaseTail = middleWrareWebClientBase; } else { this.mAgentBuilder.mMiddlewareWebClientBaseTail.enq(middleWrareWebClientBase); this.mAgentBuilder.mMiddlewareWebClientBaseTail = middleWrareWebClientBase; } return this; } public CommonBuilder useMiddlewareWebChrome(@NonNull MiddlewareWebChromeBase middlewareWebChromeBase) { if (middlewareWebChromeBase == null) { return this; } if (this.mAgentBuilder.mChromeMiddleWareHeader == null) { this.mAgentBuilder.mChromeMiddleWareHeader = this.mAgentBuilder.mChromeMiddleWareTail = middlewareWebChromeBase; } else { this.mAgentBuilder.mChromeMiddleWareTail.enq(middlewareWebChromeBase); this.mAgentBuilder.mChromeMiddleWareTail = middlewareWebChromeBase; } return this; } public CommonBuilder setMainFrameErrorView(@NonNull View view) { this.mAgentBuilder.mErrorView = view; return this; } public CommonBuilder setMainFrameErrorView(@LayoutRes int errorLayout, @IdRes int clickViewId) { this.mAgentBuilder.mErrorLayout = errorLayout; this.mAgentBuilder.mReloadId = clickViewId; return this; } public CommonBuilder setAgentWebWebSettings(@Nullable IAgentWebSettings agentWebSettings) { this.mAgentBuilder.mAgentWebSettings = agentWebSettings; return this; } public PreAgentWeb createAgentWeb() { return this.mAgentBuilder.buildAgentWeb(); } public CommonBuilder addJavascriptInterface(@NonNull String name, @NonNull Object o) { this.mAgentBuilder.addJavaObject(name, o); return this; } public CommonBuilder setSecurityType(@NonNull SecurityType type) { this.mAgentBuilder.mSecurityType = type; return this; } public CommonBuilder setWebView(@Nullable WebView webView) { this.mAgentBuilder.mWebView = webView; return this; } public CommonBuilder setWebLayout(@Nullable IWebLayout iWebLayout) { this.mAgentBuilder.mWebLayout = iWebLayout; return this; } public CommonBuilder additionalHttpHeader(String baseUrl, String k, String v) { this.mAgentBuilder.addHeader(baseUrl, k, v); return this; } public CommonBuilder additionalHttpHeader(String baseUrl, Map headers) { this.mAgentBuilder.addHeader(baseUrl, headers); return this; } public CommonBuilder setPermissionInterceptor(@Nullable PermissionInterceptor permissionInterceptor) { this.mAgentBuilder.mPermissionInterceptor = permissionInterceptor; return this; } public CommonBuilder setAgentWebUIController(@Nullable AgentWebUIControllerImplBase agentWebUIController) { this.mAgentBuilder.mAgentWebUIController = agentWebUIController; return this; } public CommonBuilder setOpenOtherPageWays(@Nullable DefaultWebClient.OpenOtherPageWays openOtherPageWays) { this.mAgentBuilder.mOpenOtherPage = openOtherPageWays; return this; } public CommonBuilder interceptUnkownUrl() { this.mAgentBuilder.mIsInterceptUnkownUrl = true; return this; } public CommonBuilder isInterceptUnkownUrl(boolean isInterceptUnkownUrl) { this.mAgentBuilder.mIsInterceptUnkownUrl = isInterceptUnkownUrl; return this; } } Activity getActivity() { return this.mActivity; } private static final class PermissionInterceptorWrapper implements PermissionInterceptor { private WeakReference mWeakReference; private PermissionInterceptorWrapper(PermissionInterceptor permissionInterceptor) { this.mWeakReference = new WeakReference(permissionInterceptor); } @Override public boolean intercept(String url, String[] permissions, String a) { if (this.mWeakReference.get() == null) { return false; } return mWeakReference.get().intercept(url, permissions, a); } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebCompat.java ================================================ package com.just.agentweb; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.text.TextUtils; import android.webkit.WebView; import java.io.File; import java.io.RandomAccessFile; import java.nio.channels.FileLock; import java.util.HashSet; import java.util.Set; /** * @author cenxiaozhong * @date 2021/11/24 * @since 1.0.0 */ public class AgentWebCompat { /** * 来之 https://github.com/Justson/AgentWeb/issues/934 建议 * https://juejin.cn/post/6950091477192015902 * fix Using WebView from more than one process * @param context */ public static void setDataDirectorySuffix(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { return; } try { Set pathSet = new HashSet<>(); String suffix = ""; String dataPath = context.getDataDir().getAbsolutePath(); String webViewDir = "/app_webview"; String huaweiWebViewDir = "/app_hws_webview"; String lockFile = "/webview_data.lock"; String processName = ProcessUtils.getCurrentProcessName(context); if (!TextUtils.equals(context.getPackageName(), processName)) {//判断不等于默认进程名称 suffix = TextUtils.isEmpty(processName) ? context.getPackageName() : processName; WebView.setDataDirectorySuffix(suffix); suffix = "_" + suffix; pathSet.add(dataPath + webViewDir + suffix + lockFile); if (RomUtils.isHuawei()) { pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile); } }else{ //主进程 suffix = "_" + processName; pathSet.add(dataPath + webViewDir + lockFile);//默认未添加进程名后缀 pathSet.add(dataPath + webViewDir + suffix + lockFile);//系统自动添加了进程名后缀 if (RomUtils.isHuawei()) {//部分华为手机更改了webview目录名 pathSet.add(dataPath + huaweiWebViewDir + lockFile); pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile); } } for (String path : pathSet) { File file = new File(path); if (file.exists()) { tryLockOrRecreateFile(file); break; } } } catch (Exception e) { e.printStackTrace(); } } @TargetApi(Build.VERSION_CODES.P) private static void tryLockOrRecreateFile(File file) { try { FileLock tryLock = new RandomAccessFile(file, "rw").getChannel().tryLock(); if (tryLock != null) { tryLock.close(); } else { createFile(file, file.delete()); } } catch (Exception e) { e.printStackTrace(); boolean deleted = false; if (file.exists()) { deleted = file.delete(); } createFile(file, deleted); } } private static void createFile(File file, boolean deleted){ try { if (deleted && !file.exists()) { file.createNewFile(); } } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebConfig.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.content.Context; import android.os.AsyncTask; import android.os.Build; import androidx.annotation.Nullable; import android.text.TextUtils; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.ValueCallback; import android.webkit.WebView; import java.io.File; import static com.just.agentweb.AgentWebUtils.getAgentWebFilePath; /** * @author cenxiaozhong * @since 1.0.0 */ public class AgentWebConfig { static final String FILE_CACHE_PATH = "agentweb-cache"; static final String AGENTWEB_CACHE_PATCH = File.separator + "agentweb-cache"; /** * 缓存路径 */ static String AGENTWEB_FILE_PATH; /** * DEBUG 模式 , 如果需要查看日志请设置为 true */ public static boolean DEBUG = false; /** * 当前操作系统是否低于 KITKAT */ static final boolean IS_KITKAT_OR_BELOW_KITKAT = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT; /** * 默认 WebView 类型 。 */ public static final int WEBVIEW_DEFAULT_TYPE = 1; /** * 使用 AgentWebView */ public static final int WEBVIEW_AGENTWEB_SAFE_TYPE = 2; /** * 自定义 WebView */ public static final int WEBVIEW_CUSTOM_TYPE = 3; private static volatile boolean IS_INITIALIZED = false; private static final String TAG = AgentWebConfig.class.getSimpleName(); public static final String AGENTWEB_NAME = "AgentWeb"; /** * AgentWeb 的版本 */ public static final String AGENTWEB_VERSION = AGENTWEB_NAME + "/" + "5.0.8"; /** * 通过JS获取的文件大小, 这里限制最大为5MB ,太大会抛出 OutOfMemoryError */ public static int MAX_FILE_LENGTH = 1024 * 1024 * 5; //获取Cookie public static String getCookiesByUrl(String url) { return CookieManager.getInstance() == null ? null : CookieManager.getInstance().getCookie(url); } public static void debug() { DEBUG = true; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { WebView.setWebContentsDebuggingEnabled(true); } } /** * 删除所有已经过期的 Cookies */ public static void removeExpiredCookies() { CookieManager mCookieManager = null; if ((mCookieManager = CookieManager.getInstance()) != null) { //同步清除 mCookieManager.removeExpiredCookie(); toSyncCookies(); } } /** * 删除所有 Cookies */ public static void removeAllCookies() { removeAllCookies(null); } // 解决兼容 Android 4.4 java.lang.NoSuchMethodError: android.webkit.CookieManager.removeSessionCookies public static void removeSessionCookies() { removeSessionCookies(null); } /** * 同步cookie * * @param url * @param cookies */ public static void syncCookie(String url, String cookies) { CookieManager mCookieManager = CookieManager.getInstance(); if (mCookieManager != null) { mCookieManager.setCookie(url, cookies); toSyncCookies(); } } public static void removeSessionCookies(ValueCallback callback) { if (callback == null) { callback = getDefaultIgnoreCallback(); } if (CookieManager.getInstance() == null) { callback.onReceiveValue(new Boolean(false)); return; } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { CookieManager.getInstance().removeSessionCookie(); toSyncCookies(); callback.onReceiveValue(new Boolean(true)); return; } CookieManager.getInstance().removeSessionCookies(callback); toSyncCookies(); } /** * @param context * @return WebView 的缓存路径 */ public static String getCachePath(Context context) { return context.getCacheDir().getAbsolutePath() + AGENTWEB_CACHE_PATCH; } /** * @param context * @return AgentWeb 缓存路径 */ public static String getExternalCachePath(Context context) { return getAgentWebFilePath(context); } //Android 4.4 NoSuchMethodError: android.webkit.CookieManager.removeAllCookies public static void removeAllCookies(@Nullable ValueCallback callback) { if (callback == null) { callback = getDefaultIgnoreCallback(); } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { CookieManager.getInstance().removeAllCookie(); toSyncCookies(); callback.onReceiveValue(!CookieManager.getInstance().hasCookies()); return; } CookieManager.getInstance().removeAllCookies(callback); toSyncCookies(); } /** * 清空缓存 * * @param context */ public static synchronized void clearDiskCache(Context context) { try { AgentWebUtils.clearCacheFolder(new File(getCachePath(context)), 0); String path = getExternalCachePath(context); if (!TextUtils.isEmpty(path)) { File mFile = new File(path); AgentWebUtils.clearCacheFolder(mFile, 0); } } catch (Throwable throwable) { if (LogUtils.isDebug()) { throwable.printStackTrace(); } } } static synchronized void initCookiesManager(Context context) { if (!IS_INITIALIZED) { createCookiesSyncInstance(context); IS_INITIALIZED = true; } } private static void createCookiesSyncInstance(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { CookieSyncManager.createInstance(context); } } private static void toSyncCookies() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { CookieSyncManager.getInstance().sync(); return; } AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { @Override public void run() { CookieManager.getInstance().flush(); } }); } static String getDatabasesCachePath(Context context) { return context.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath(); } private static ValueCallback getDefaultIgnoreCallback() { return new ValueCallback() { @Override public void onReceiveValue(Boolean ignore) { LogUtils.i(TAG, "removeExpiredCookies:" + ignore); } }; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebFileProvider.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.content.Context; import android.content.pm.ProviderInfo; import androidx.annotation.NonNull; import androidx.core.content.FileProvider; /** * @since 2.0.0 * @author cenxiaozhong */ public class AgentWebFileProvider extends FileProvider { @Override public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) { super.attachInfo(context, info); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebJsInterfaceCompat.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.app.Activity; import android.os.Handler; import android.os.Message; import android.webkit.JavascriptInterface; import java.lang.ref.WeakReference; /** * @author cenxiaozhong * @since 1.0.0 */ public class AgentWebJsInterfaceCompat { private WeakReference mReference = null; private WeakReference mActivityWeakReference = null; private String TAG = this.getClass().getSimpleName(); AgentWebJsInterfaceCompat(AgentWeb agentWeb, Activity activity) { mReference = new WeakReference(agentWeb); mActivityWeakReference = new WeakReference(activity); } @JavascriptInterface public void uploadFile() { uploadFile("*/*"); } @JavascriptInterface public void uploadFile(String acceptType) { LogUtils.i(TAG, acceptType + " " + mActivityWeakReference.get() + " " + mReference.get()); if (mActivityWeakReference.get() != null && mReference.get() != null) { AgentWebUtils.showFileChooserCompat(mActivityWeakReference.get(), mReference.get().getWebCreator().getWebView(), null, null, mReference.get().getPermissionInterceptor(), null, acceptType, new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (mReference.get() != null) { mReference.get().getJsAccessEntrace() .quickCallJs("uploadFileResult", msg.obj instanceof String ? (String) msg.obj : null); } return true; } } ); } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebPermissions.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.Manifest; import android.os.Build; /** * @author cenxiaozhong * @since 1.0.0 */ public class AgentWebPermissions { public static String[] CAMERA; public static String[] LOCATION; public static String[] MEDIA; public static final String ACTION_CAMERA = "Camera"; public static final String ACTION_LOCATION = "Location"; public static final String ACTION_MEDIA = "Media"; static { CAMERA = new String[]{ Manifest.permission.CAMERA}; LOCATION = new String[]{ Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}; if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { MEDIA = new String[]{ Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_IMAGES, }; } else { MEDIA = new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; } } private static void emptyMediaPermission() { MEDIA = new String[]{}; } private static void emptyCameraPermission() { CAMERA = new String[]{}; } public static void dontAskUnnecessaryPermissions() { if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { emptyMediaPermission(); emptyCameraPermission(); } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebSettingsImpl.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; import android.webkit.DownloadListener; import android.webkit.WebView; /** * @author cenxiaozhong * @since 1.0.0 */ public class AgentWebSettingsImpl extends AbsAgentWebSettings { private AgentWeb mAgentWeb; @Override protected void bindAgentWebSupport(AgentWeb agentWeb) { this.mAgentWeb = agentWeb; } @Override public WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener) { if (downloadListener == null) { downloadListener = DefaultDownloadImpl.create(mAgentWeb.getActivity(), webView, mAgentWeb.getPermissionInterceptor()); } return super.setDownloader(webView, downloadListener); } /** * Copy from com.blankj.utilcode.util.ActivityUtils#getActivityByView */ private Activity getActivityByContext(Context context) { if (context instanceof Activity) return (Activity) context; while (context instanceof ContextWrapper) { if (context instanceof Activity) { return (Activity) context; } context = ((ContextWrapper) context).getBaseContext(); } return null; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebUIControllerImplBase.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.app.Activity; import android.net.http.SslError; import android.os.Handler; import android.webkit.JsPromptResult; import android.webkit.JsResult; import android.webkit.PermissionRequest; import android.webkit.SslErrorHandler; import android.webkit.WebView; /** * @author cenxiaozhong * @date 2017/12/6 * @since 3.0.0 */ public class AgentWebUIControllerImplBase extends AbsAgentWebUIController { public static AbsAgentWebUIController build() { return new AgentWebUIControllerImplBase(); } @Override public void onJsAlert(WebView view, String url, String message) { getDelegate().onJsAlert(view, url, message); } @Override public void onOpenPagePrompt(WebView view, String url, Handler.Callback callback) { getDelegate().onOpenPagePrompt(view, url, callback); } @Override public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) { getDelegate().onJsConfirm(view, url, message, jsResult); } @Override public void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback) { getDelegate().onSelectItemsPrompt(view, url, ways, callback); } @Override public void onForceDownloadAlert(String url, Handler.Callback callback) { getDelegate().onForceDownloadAlert(url, callback); } @Override public void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult) { getDelegate().onJsPrompt(view, url, message, defaultValue, jsPromptResult); } @Override public void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) { getDelegate().onMainFrameError(view, errorCode, description, failingUrl); } @Override public void onShowMainFrame() { getDelegate().onShowMainFrame(); } @Override public void onLoading(String msg) { getDelegate().onLoading(msg); } @Override public void onCancelLoading() { getDelegate().onCancelLoading(); } @Override public void onShowMessage(String message, String from) { getDelegate().onShowMessage(message, from); } @Override public void onPermissionsDeny(String[] permissions, String permissionType, String action) { getDelegate().onPermissionsDeny(permissions, permissionType, action); } @Override public void onShowSslCertificateErrorDialog(WebView view, SslErrorHandler handler, SslError error) { getDelegate().onShowSslCertificateErrorDialog(view, handler, error); } @Override public void onPermissionRequest(PermissionRequest request) { getDelegate().onPermissionRequest(request); } @Override protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) { getDelegate().bindSupportWebParent(webParentLayout, activity); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebUtils.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.StatFs; import android.provider.DocumentsContract; import android.provider.MediaStore; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import com.google.android.material.snackbar.Snackbar; import androidx.core.app.AppOpsManagerCompat; import androidx.core.content.ContextCompat; import androidx.loader.content.CursorLoader; import androidx.core.content.FileProvider; import androidx.core.os.EnvironmentCompat; import android.telephony.TelephonyManager; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.ForegroundColorSpan; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.widget.Toast; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.math.BigInteger; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import static com.just.agentweb.AgentWebConfig.AGENTWEB_FILE_PATH; import static com.just.agentweb.AgentWebConfig.FILE_CACHE_PATH; /** * @author cenxiaozhong * @since 1.0.0 */ public class AgentWebUtils { private static final String TAG = AgentWebUtils.class.getSimpleName(); private static Handler mHandler = null; private AgentWebUtils() { throw new UnsupportedOperationException("u can't init me"); } public static int dp2px(Context context, float dipValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } static final void clearWebView(WebView m) { if (m == null) { return; } if (Looper.myLooper() != Looper.getMainLooper()) { return; } m.loadUrl("about:blank"); m.stopLoading(); if (m.getHandler() != null) { m.getHandler().removeCallbacksAndMessages(null); } m.removeAllViews(); ViewGroup mViewGroup = null; if ((mViewGroup = ((ViewGroup) m.getParent())) != null) { mViewGroup.removeView(m); } m.setWebChromeClient(null); m.setWebViewClient(null); m.setTag(null); m.clearHistory(); m.destroy(); m = null; } public static String getAgentWebFilePath(Context context) { if (!TextUtils.isEmpty(AGENTWEB_FILE_PATH)) { return AGENTWEB_FILE_PATH; } String dir = getDiskExternalCacheDir(context); File mFile = new File(dir, FILE_CACHE_PATH); try { if (!mFile.exists()) { mFile.mkdirs(); } } catch (Throwable throwable) { LogUtils.i(TAG, "create dir exception"); } LogUtils.i(TAG, "path:" + mFile.getAbsolutePath() + " path:" + mFile.getPath()); return AGENTWEB_FILE_PATH = mFile.getAbsolutePath(); } public static File createFileByName(Context context, String name, boolean cover) throws IOException { String path = getAgentWebFilePath(context); if (TextUtils.isEmpty(path)) { return null; } File mFile = new File(path, name); if (mFile.exists()) { if (cover) { mFile.delete(); mFile.createNewFile(); } } else { mFile.createNewFile(); } return mFile; } public static int checkNetworkType(Context context) { int netType = 0; //连接管理对象 ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); //获取NetworkInfo对象 @SuppressLint("MissingPermission") NetworkInfo networkInfo = manager.getActiveNetworkInfo(); if (networkInfo == null) { return netType; } switch (networkInfo.getType()) { case ConnectivityManager.TYPE_WIFI: case ConnectivityManager.TYPE_WIMAX: case ConnectivityManager.TYPE_ETHERNET: return 1; case ConnectivityManager.TYPE_MOBILE: switch (networkInfo.getSubtype()) { case TelephonyManager.NETWORK_TYPE_LTE: // 4G case TelephonyManager.NETWORK_TYPE_HSPAP: case TelephonyManager.NETWORK_TYPE_EHRPD: return 2; case TelephonyManager.NETWORK_TYPE_UMTS: // 3G case TelephonyManager.NETWORK_TYPE_CDMA: case TelephonyManager.NETWORK_TYPE_EVDO_0: case TelephonyManager.NETWORK_TYPE_EVDO_A: case TelephonyManager.NETWORK_TYPE_EVDO_B: return 3; case TelephonyManager.NETWORK_TYPE_GPRS: // 2G case TelephonyManager.NETWORK_TYPE_EDGE: return 4; default: return netType; } default: return netType; } } public static long getAvailableStorage() { try { StatFs stat = new StatFs(Environment.getExternalStorageDirectory().toString()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { return stat.getAvailableBlocksLong() * stat.getBlockSizeLong(); } else { return (long) stat.getAvailableBlocks() * (long) stat.getBlockSize(); } } catch (RuntimeException ex) { return 0; } } public static Uri getUriFromFile(Context context, File file) { Uri uri = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { uri = getUriFromFileForN(context, file); } else { uri = Uri.fromFile(file); } return uri; } static Uri getUriFromFileForN(Context context, File file) { Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".AgentWebFileProvider", file); return fileUri; } static void setIntentDataAndType(Context context, Intent intent, String type, File file, boolean writeAble) { if (Build.VERSION.SDK_INT >= 24) { intent.setDataAndType(getUriFromFile(context, file), type); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (writeAble) { intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } } else { intent.setDataAndType(Uri.fromFile(file), type); } } static void setIntentData(Context context, Intent intent, File file, boolean writeAble) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setData(getUriFromFile(context, file)); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (writeAble) { intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } } else { intent.setData(Uri.fromFile(file)); } } static String getDiskExternalCacheDir(Context context) { File mFile = context.getExternalCacheDir(); if (Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(mFile))) { return mFile.getAbsolutePath(); } return null; } static void grantPermissions(Context context, Intent intent, Uri uri, boolean writeAble) { int flag = Intent.FLAG_GRANT_READ_URI_PERMISSION; if (writeAble) { flag |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; } intent.addFlags(flag); List resInfoList = context.getPackageManager() .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; context.grantUriPermission(packageName, uri, flag); } } private static String getMIMEType(File f) { String type = ""; String fName = f.getName(); /* 取得扩展名 */ String end = fName.substring(fName.lastIndexOf(".") + 1, fName.length()).toLowerCase(); /* 依扩展名的类型决定MimeType */ if (end.equals("pdf")) { type = "application/pdf";// } else if (end.equals("m4a") || end.equals("mp3") || end.equals("mid") || end.equals("xmf") || end.equals("ogg") || end.equals("wav")) { type = "audio/*"; } else if (end.equals("3gp") || end.equals("mp4")) { type = "video/*"; } else if (end.equals("jpg") || end.equals("gif") || end.equals("png") || end.equals("jpeg") || end.equals("bmp")) { type = "image/*"; } else if (end.equals("apk")) { type = "application/vnd.android.package-archive"; } else if (end.equals("pptx") || end.equals("ppt")) { type = "application/vnd.ms-powerpoint"; } else if (end.equals("docx") || end.equals("doc")) { type = "application/vnd.ms-word"; } else if (end.equals("xlsx") || end.equals("xls")) { type = "application/vnd.ms-excel"; } else { type = "*/*"; } return type; } private static WeakReference snackbarWeakReference; static void show(View parent, CharSequence text, int duration, @ColorInt int textColor, @ColorInt int bgColor, CharSequence actionText, @ColorInt int actionTextColor, View.OnClickListener listener) { SpannableString spannableString = new SpannableString(text); ForegroundColorSpan colorSpan = new ForegroundColorSpan(textColor); spannableString.setSpan(colorSpan, 0, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); snackbarWeakReference = new WeakReference<>(Snackbar.make(parent, spannableString, duration)); Snackbar snackbar = snackbarWeakReference.get(); View view = snackbar.getView(); view.setBackgroundColor(bgColor); if (actionText != null && actionText.length() > 0 && listener != null) { snackbar.setActionTextColor(actionTextColor); snackbar.setAction(actionText, listener); } snackbar.show(); } static void dismiss() { if (snackbarWeakReference != null && snackbarWeakReference.get() != null) { snackbarWeakReference.get().dismiss(); snackbarWeakReference = null; } } public static boolean checkWifi(Context context) { ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivity == null) { return false; } @SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo(); return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI; } public static boolean checkNetwork(Context context) { ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivity == null) { return false; } @SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo(); return info != null && info.isConnected(); } static boolean isOverriedMethod(Object currentObject, String methodName, String method, Class... clazzs) { LogUtils.i(TAG, " methodName:" + methodName + " method:" + method); boolean tag = false; if (currentObject == null) { return tag; } try { Class clazz = currentObject.getClass(); Method mMethod = clazz.getMethod(methodName, clazzs); String gStr = mMethod.toGenericString(); tag = !gStr.contains(method); } catch (Exception igonre) { if (LogUtils.isDebug()) { igonre.printStackTrace(); } } LogUtils.i(TAG, "isOverriedMethod:" + tag); return tag; } static Method isExistMethod(Object o, String methodName, Class... clazzs) { if (null == o) { return null; } try { Class clazz = o.getClass(); Method mMethod = clazz.getDeclaredMethod(methodName, clazzs); mMethod.setAccessible(true); return mMethod; } catch (Throwable ignore) { // if (LogUtils.isDebug()) { // ignore.printStackTrace(); // } } return null; } static void clearAgentWebCache(Context context) { try { clearCacheFolder(new File(getAgentWebFilePath(context)), 0); } catch (Throwable throwable) { if (LogUtils.isDebug()) { throwable.printStackTrace(); } } } static void clearWebViewAllCache(Context context, WebView webView) { try { AgentWebConfig.removeAllCookies(null); webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); context.deleteDatabase("webviewCache.db"); context.deleteDatabase("webview.db"); webView.clearCache(true); webView.clearHistory(); webView.clearFormData(); clearCacheFolder(new File(AgentWebConfig.getCachePath(context)), 0); } catch (Exception ignore) { //ignore.printStackTrace(); if (AgentWebConfig.DEBUG) { ignore.printStackTrace(); } } } static void clearWebViewAllCache(Context context) { try { clearWebViewAllCache(context, new LollipopFixedWebView(context.getApplicationContext())); } catch (Exception e) { e.printStackTrace(); } } static int clearCacheFolder(final File dir, final int numDays) { int deletedFiles = 0; if (dir != null) { Log.i("Info", "dir:" + dir.getAbsolutePath()); } if (dir != null && dir.isDirectory()) { try { for (File child : dir.listFiles()) { //first delete subdirectories recursively if (child.isDirectory()) { deletedFiles += clearCacheFolder(child, numDays); } //then delete the files and subdirectories in this dir //only empty directories can be deleted, so subdirs have been done first if (child.lastModified() < new Date().getTime() - numDays * DateUtils.DAY_IN_MILLIS) { Log.i(TAG, "file name:" + child.getName()); if (child.delete()) { deletedFiles++; } } } } catch (Exception e) { Log.e("Info", String.format("Failed to clean the cache, result %s", e.getMessage())); } } return deletedFiles; } static void clearCache(final Context context, final int numDays) { Log.i("Info", String.format("Starting cache prune, deleting files older than %d days", numDays)); int numDeletedFiles = clearCacheFolder(context.getCacheDir(), numDays); Log.i("Info", String.format("Cache pruning completed, %d files deleted", numDeletedFiles)); } public static String[] uriToPath(Activity activity, Uri[] uris) { if (activity == null || uris == null || uris.length == 0) { return null; } try { String[] paths = new String[uris.length]; int i = 0; for (Uri mUri : uris) { paths[i++] = Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 ? getFileAbsolutePath(activity, mUri) : getRealPathBelowVersion(activity, mUri); } return paths; } catch (Throwable throwable) { if (LogUtils.isDebug()) { throwable.printStackTrace(); } } return null; } private static String getRealPathBelowVersion(Context context, Uri uri) { String filePath = null; LogUtils.i(TAG, "method -> getRealPathBelowVersion " + uri + " path:" + uri.getPath() + " getAuthority:" + uri.getAuthority()); String[] projection = {MediaStore.Images.Media.DATA}; CursorLoader loader = new CursorLoader(context, uri, projection, null, null, null); Cursor cursor = loader.loadInBackground(); if (cursor != null) { cursor.moveToFirst(); filePath = cursor.getString(cursor.getColumnIndex(projection[0])); cursor.close(); } if (filePath == null) { filePath = uri.getPath(); } return filePath; } public static File createImageFile(Context context) { File mFile = null; try { String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date()); String imageName = String.format("aw_%s.jpg", timeStamp); mFile = createFileByName(context, imageName, true); } catch (Throwable e) { e.printStackTrace(); } return mFile; } static File createVideoFile(Context context){ File mFile = null; try { String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date()); String imageName = String.format("aw_%s.mp4", timeStamp); //默认生成mp4 mFile = createFileByName(context, imageName, true); } catch (Throwable e) { e.printStackTrace(); } return mFile; } public static void closeIO(Closeable closeable) { try { if (closeable != null) { closeable.close(); } } catch (Exception e) { e.printStackTrace(); } } @TargetApi(19) static String getFileAbsolutePath(Activity context, Uri fileUri) { if (context == null || fileUri == null) { return null; } // LogUtils.i(TAG, "getAuthority:" + fileUri.getAuthority() + " getHost:" + fileUri.getHost() + " getPath:" + fileUri.getPath() + " getScheme:" + fileUri.getScheme() + " query:" + fileUri.getQuery()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, fileUri)) { if (isExternalStorageDocument(fileUri)) { String docId = DocumentsContract.getDocumentId(fileUri); String[] split = docId.split(":"); String type = split[0]; if ("primary".equalsIgnoreCase(type)) { return Environment.getExternalStorageDirectory() + "/" + split[1]; } } else if (isDownloadsDocument(fileUri)) { String id = DocumentsContract.getDocumentId(fileUri); Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); return getDataColumn(context, contentUri, null, null); } else if (isMediaDocument(fileUri)) { String docId = DocumentsContract.getDocumentId(fileUri); String[] split = docId.split(":"); String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } String selection = MediaStore.Images.Media._ID + "=?"; String[] selectionArgs = new String[]{split[1]}; return getDataColumn(context, contentUri, selection, selectionArgs); } else { } } // MediaStore (and general) else if (fileUri.getAuthority().equalsIgnoreCase(context.getPackageName() + ".AgentWebFileProvider")) { String path = fileUri.getPath(); int index = path.lastIndexOf("/"); return getAgentWebFilePath(context) + File.separator + path.substring(index + 1, path.length()); } else if ("content".equalsIgnoreCase(fileUri.getScheme())) { // Return the remote address if (isGooglePhotosUri(fileUri)) { return fileUri.getLastPathSegment(); } return getDataColumn(context, fileUri, null, null); } // File else if ("file".equalsIgnoreCase(fileUri.getScheme())) { return fileUri.getPath(); } return null; } static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; String[] projection = {MediaStore.Images.Media.DATA}; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); return cursor.getString(index); } } finally { if (cursor != null) { cursor.close(); } } return null; } /** * @param uri The Uri to check. * @return Whether the Uri authority is ExternalStorageProvider. */ static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is DownloadsProvider. */ static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is MediaProvider. */ static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is Google Photos. */ static boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); } static Intent getInstallApkIntentCompat(Context context, File file) { Intent mIntent = new Intent().setAction(Intent.ACTION_VIEW); setIntentDataAndType(context, mIntent, "application/vnd.android.package-archive", file, false); return mIntent; } public static Intent getCommonFileIntentCompat(Context context, File file) { Intent mIntent = new Intent().setAction(Intent.ACTION_VIEW); setIntentDataAndType(context, mIntent, getMIMEType(file), file, false); return mIntent; } static Intent getIntentCaptureCompat(Context context, File file) { Intent mIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Uri mUri = getUriFromFile(context, file); mIntent.addCategory(Intent.CATEGORY_DEFAULT); mIntent.putExtra(MediaStore.EXTRA_OUTPUT, mUri); return mIntent; } static Intent getIntentVideoCompat(Context context, File file){ Intent mIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); Uri mUri = getUriFromFile(context, file); mIntent.addCategory(Intent.CATEGORY_DEFAULT); mIntent.putExtra(MediaStore.EXTRA_OUTPUT, mUri); return mIntent; } static boolean isJson(String target) { if (TextUtils.isEmpty(target)) { return false; } boolean tag = false; try { if (target.startsWith("[")) { new JSONArray(target); } else { new JSONObject(target); } tag = true; } catch (JSONException ignore) { // ignore.printStackTrace(); tag = false; } return tag; } public static boolean isUIThread() { return Looper.myLooper() == Looper.getMainLooper(); } static boolean isEmptyCollection(Collection collection) { return collection == null || collection.isEmpty(); } static boolean isEmptyMap(Map map) { return map == null || map.isEmpty(); } private static Toast mToast = null; static void toastShowShort(Context context, String msg) { if (mToast == null) { mToast = Toast.makeText(context.getApplicationContext(), msg, Toast.LENGTH_SHORT); } else { mToast.setText(msg); } mToast.show(); } @Deprecated static void getUIControllerAndShowMessage(Activity activity, String message, String from) { if (activity == null || activity.isFinishing()) { return; } WebParentLayout mWebParentLayout = (WebParentLayout) activity.findViewById(R.id.web_parent_layout_id); AbsAgentWebUIController mAgentWebUIController = mWebParentLayout.provide(); if (mAgentWebUIController != null) { mAgentWebUIController.onShowMessage(message, from); } } public static boolean hasPermission(@NonNull Context context, @NonNull String... permissions) { return hasPermission(context, Arrays.asList(permissions)); } public static boolean hasPermission(@NonNull Context context, @NonNull List permissions) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } for (String permission : permissions) { int result = ContextCompat.checkSelfPermission(context, permission); if (result == PackageManager.PERMISSION_DENIED) { return false; } String op = AppOpsManagerCompat.permissionToOp(permission); if (TextUtils.isEmpty(op)) { continue; } result = AppOpsManagerCompat.noteProxyOp(context, op, context.getPackageName()); if (result != AppOpsManagerCompat.MODE_ALLOWED) { return false; } } return true; } public static List getDeniedPermissions(Activity activity, String[] permissions) { if (permissions == null || permissions.length == 0) { return Collections.EMPTY_LIST; } List deniedPermissions = new ArrayList<>(permissions.length); for (int i = 0; i < permissions.length; i++) { if (!hasPermission(activity, permissions[i])) { deniedPermissions.add(permissions[i]); } } return deniedPermissions; } public static AbsAgentWebUIController getAgentWebUIControllerByWebView(WebView webView) { WebParentLayout mWebParentLayout = getWebParentLayoutByWebView(webView); return mWebParentLayout.provide(); } //获取应用的名称 public static String getApplicationName(Context context) { PackageManager packageManager = null; ApplicationInfo applicationInfo = null; try { packageManager = context.getApplicationContext().getPackageManager(); applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0); } catch (PackageManager.NameNotFoundException e) { applicationInfo = null; } String applicationName = (String) packageManager.getApplicationLabel(applicationInfo); return applicationName; } static WebParentLayout getWebParentLayoutByWebView(WebView webView) { ViewGroup mViewGroup = null; if (!(webView.getParent() instanceof ViewGroup)) { throw new IllegalStateException("please check webcreator's create method was be called ?"); } mViewGroup = (ViewGroup) webView.getParent(); AbsAgentWebUIController mAgentWebUIController; while (mViewGroup != null) { LogUtils.i(TAG, "ViewGroup:" + mViewGroup); if (mViewGroup.getId() == R.id.web_parent_layout_id) { WebParentLayout mWebParentLayout = (WebParentLayout) mViewGroup; LogUtils.i(TAG, "found WebParentLayout"); return mWebParentLayout; } else { ViewParent mViewParent = mViewGroup.getParent(); if (mViewParent instanceof ViewGroup) { mViewGroup = (ViewGroup) mViewParent; } else { mViewGroup = null; } } } throw new IllegalStateException("please check webcreator's create method was be called ?"); } public static void runInUiThread(Runnable runnable) { if (mHandler == null) { mHandler = new Handler(Looper.getMainLooper()); } mHandler.post(runnable); } public static boolean showFileChooserCompat(Activity activity, WebView webView, ValueCallback valueCallbacks, WebChromeClient.FileChooserParams fileChooserParams, PermissionInterceptor permissionInterceptor, ValueCallback valueCallback, String mimeType, Handler.Callback jsChannelCallback ) { try { Class clz = Class.forName("com.just.agentweb.filechooser.FileChooser"); Object mFileChooser$Builder = clz.getDeclaredMethod("newBuilder", Activity.class, WebView.class) .invoke(null, activity, webView); clz = mFileChooser$Builder.getClass(); Method mMethod = null; if (valueCallbacks != null) { mMethod = clz.getDeclaredMethod("setUriValueCallbacks", ValueCallback.class); mMethod.setAccessible(true); mMethod.invoke(mFileChooser$Builder, valueCallbacks); } if (fileChooserParams != null) { mMethod = clz.getDeclaredMethod("setFileChooserParams", WebChromeClient.FileChooserParams.class); mMethod.setAccessible(true); mMethod.invoke(mFileChooser$Builder, fileChooserParams); } if (valueCallback != null) { mMethod = clz.getDeclaredMethod("setUriValueCallback", ValueCallback.class); mMethod.setAccessible(true); mMethod.invoke(mFileChooser$Builder, valueCallback); } if (!TextUtils.isEmpty(mimeType)) { // LogUtils.i(TAG, Arrays.toString(clz.getDeclaredMethods())); mMethod = clz.getDeclaredMethod("setAcceptType", String.class); mMethod.setAccessible(true); mMethod.invoke(mFileChooser$Builder, mimeType); } if (jsChannelCallback != null) { mMethod = clz.getDeclaredMethod("setJsChannelCallback", Handler.Callback.class); mMethod.setAccessible(true); mMethod.invoke(mFileChooser$Builder, jsChannelCallback); } mMethod = clz.getDeclaredMethod("setPermissionInterceptor", PermissionInterceptor.class); mMethod.setAccessible(true); mMethod.invoke(mFileChooser$Builder, permissionInterceptor); mMethod = clz.getDeclaredMethod("build"); mMethod.setAccessible(true); Object mFileChooser = mMethod.invoke(mFileChooser$Builder); mMethod = mFileChooser.getClass().getDeclaredMethod("openFileChooser"); mMethod.setAccessible(true); mMethod.invoke(mFileChooser); } catch (Throwable throwable) { if (LogUtils.isDebug()) { throwable.printStackTrace(); } if (throwable instanceof ClassNotFoundException) { LogUtils.e(TAG, "Please check whether compile'com.just.agentweb:filechooser:x.x.x' dependency was added."); } if (valueCallbacks != null) { LogUtils.i(TAG, "onReceiveValue empty"); return false; } if (valueCallback != null) { valueCallback.onReceiveValue(null); } } return true; } public static String md5(String str) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(str.getBytes()); return new BigInteger(1, md.digest()).toString(16); } catch (Exception e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } return ""; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebView.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; import android.view.ActionMode; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.accessibility.AccessibilityManager; import android.webkit.JsPromptResult; import android.webkit.WebBackForwardList; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; import org.json.JSONObject; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URI; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; /** * @author cenxiaozhong * @since 1.0.0 */ public class AgentWebView extends LollipopFixedWebView { private static final String TAG = AgentWebView.class.getSimpleName(); private Map mJsCallJavas; private Map mInjectJavaScripts; private FixedOnReceivedTitle mFixedOnReceivedTitle; private boolean mIsInited; private Boolean mIsAccessibilityEnabledOriginal; public AgentWebView(Context context) { this(context, null); } public AgentWebView(Context context, AttributeSet attrs) { super(context, attrs); removeSearchBoxJavaBridge(); mIsInited = true; mFixedOnReceivedTitle = new FixedOnReceivedTitle(); } /** * 经过大量的测试,按照以下方式才能保证JS脚本100%注入成功: * 1、在第一次loadUrl之前注入JS(在addJavascriptInterface里面注入即可,setWebViewClient和setWebChromeClient要在addJavascriptInterface之前执行); * 2、在webViewClient.onPageStarted中都注入JS; * 3、在webChromeClient.onProgressChanged中都注入JS,并且不能通过自检查(onJsPrompt里面判断)JS是否注入成功来减少注入JS的次数,因为网页中的JS可以同时打开多个url导致无法控制检查的准确性; * * @deprecated Android 4.2.2及以上版本的 addJavascriptInterface 方法已经解决了安全问题,如果不使用“网页能将JS函数传到Java层”功能,不建议使用该类,毕竟系统的JS注入效率才是最高的; */ @Override @Deprecated public final void addJavascriptInterface(Object interfaceObj, String interfaceName) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { super.addJavascriptInterface(interfaceObj, interfaceName); Log.i(TAG, "注入"); return; } else { Log.i(TAG, "use mJsCallJavas:" + interfaceName); } LogUtils.i(TAG, "addJavascriptInterface:" + interfaceObj + " interfaceName:" + interfaceName); if (mJsCallJavas == null) { mJsCallJavas = new HashMap(); } mJsCallJavas.put(interfaceName, new JsCallJava(interfaceObj, interfaceName)); injectJavaScript(); if (LogUtils.isDebug()) { Log.d(TAG, "injectJavaScript, addJavascriptInterface.interfaceObj = " + interfaceObj + ", interfaceName = " + interfaceName); } addJavascriptInterfaceSupport(interfaceObj, interfaceName); } protected void addJavascriptInterfaceSupport(Object interfaceObj, String interfaceName) { } @Override public final void setWebChromeClient(WebChromeClient client) { AgentWebChrome mAgentWebChrome = new AgentWebChrome(this); mAgentWebChrome.setDelegate(client); mFixedOnReceivedTitle.setWebChromeClient(client); super.setWebChromeClient(mAgentWebChrome); setWebChromeClientSupport(mAgentWebChrome); } protected final void setWebChromeClientSupport(WebChromeClient client) { } @Override public final void setWebViewClient(WebViewClient client) { AgentWebClient mAgentWebClient = new AgentWebClient(this); mAgentWebClient.setDelegate(client); super.setWebViewClient(mAgentWebClient); setWebViewClientSupport(mAgentWebClient); } public final void setWebViewClientSupport(WebViewClient client) { } @Override public ActionMode startActionMode(ActionMode.Callback callback) { return super.startActionMode(callback); } @Override public void destroy() { setVisibility(View.GONE); if (mJsCallJavas != null) { mJsCallJavas.clear(); } if (mInjectJavaScripts != null) { mInjectJavaScripts.clear(); } removeAllViewsInLayout(); fixedStillAttached(); releaseConfigCallback(); if (mIsInited) { resetAccessibilityEnabled(); LogUtils.i(TAG, "destroy web"); super.destroy(); } } @Override public void clearHistory() { if (mIsInited) { super.clearHistory(); } } public static Pair isWebViewPackageException(Throwable e) { String messageCause = e.getCause() == null ? e.toString() : e.getCause().toString(); String trace = Log.getStackTraceString(e); if (trace.contains("android.content.pm.PackageManager$NameNotFoundException") || trace.contains("java.lang.RuntimeException: Cannot load WebView") || trace.contains("android.webkit.WebViewFactory$MissingWebViewPackageException: Failed to load WebView provider: No WebView installed")) { LogUtils.safeCheckCrash(TAG, "isWebViewPackageException", e); return new Pair(true, "WebView load failed, " + messageCause); } return new Pair(false, messageCause); } @Override public void setOverScrollMode(int mode) { try { super.setOverScrollMode(mode); } catch (Throwable e) { Pair pair = isWebViewPackageException(e); if (pair.first) { Toast.makeText(getContext(), pair.second, Toast.LENGTH_SHORT).show(); destroy(); } else { throw e; } } } @Override public boolean isPrivateBrowsingEnabled() { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 && getSettings() == null) { return false; // getSettings().isPrivateBrowsingEnabled() } else { return super.isPrivateBrowsingEnabled(); } } /** * 添加并注入JavaScript脚本(和“addJavascriptInterface”注入对象的注入时机一致,100%能注入成功); * 注意:为了做到能100%注入,需要在注入的js中自行判断对象是否已经存在(如:if (typeof(window.Android) = 'undefined')); * * @param javaScript */ public void addInjectJavaScript(String javaScript) { if (mInjectJavaScripts == null) { mInjectJavaScripts = new HashMap(); } mInjectJavaScripts.put(String.valueOf(javaScript.hashCode()), javaScript); injectExtraJavaScript(); } private void injectJavaScript() { for (Map.Entry entry : mJsCallJavas.entrySet()) { this.loadUrl(buildNotRepeatInjectJS(entry.getKey(), entry.getValue().getPreloadInterfaceJs())); } } private void injectExtraJavaScript() { for (Map.Entry entry : mInjectJavaScripts.entrySet()) { this.loadUrl(buildNotRepeatInjectJS(entry.getKey(), entry.getValue())); } } /** * 构建一个“不会重复注入”的js脚本; * * @param key * @param js * @return */ public String buildNotRepeatInjectJS(String key, String js) { String obj = String.format("__injectFlag_%1$s__", key); StringBuilder sb = new StringBuilder(); sb.append("javascript:try{(function(){if(window."); sb.append(obj); sb.append("){console.log('"); sb.append(obj); sb.append(" has been injected');return;}window."); sb.append(obj); sb.append("=true;"); sb.append(js); sb.append("}())}catch(e){console.warn(e)}"); return sb.toString(); } /** * 构建一个“带try catch”的js脚本; * * @param js * @return */ public String buildTryCatchInjectJS(String js) { StringBuilder sb = new StringBuilder(); sb.append("javascript:try{"); sb.append(js); sb.append("}catch(e){console.warn(e)}"); return sb.toString(); } public static class AgentWebClient extends MiddlewareWebClientBase { private AgentWebView mAgentWebView; private AgentWebClient(AgentWebView agentWebView) { this.mAgentWebView = agentWebView; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); if (mAgentWebView.mJsCallJavas != null) { mAgentWebView.injectJavaScript(); if (LogUtils.isDebug()) { Log.d(TAG, "injectJavaScript, onPageStarted.url = " + view.getUrl()); } } if (mAgentWebView.mInjectJavaScripts != null) { mAgentWebView.injectExtraJavaScript(); } mAgentWebView.mFixedOnReceivedTitle.onPageStarted(); mAgentWebView.fixedAccessibilityInjectorExceptionForOnPageFinished(url); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); mAgentWebView.mFixedOnReceivedTitle.onPageFinished(view); if (LogUtils.isDebug()) { Log.d(TAG, "onPageFinished.url = " + view.getUrl()); } } } public static class AgentWebChrome extends MiddlewareWebChromeBase { private AgentWebView mAgentWebView; private AgentWebChrome(AgentWebView agentWebView) { this.mAgentWebView = agentWebView; } @Override public void onReceivedTitle(WebView view, String title) { this.mAgentWebView.mFixedOnReceivedTitle.onReceivedTitle(); super.onReceivedTitle(view, title); } @Override public void onProgressChanged(WebView view, int newProgress) { if (this.mAgentWebView.mJsCallJavas != null) { this.mAgentWebView.injectJavaScript(); if (LogUtils.isDebug()) { Log.d(TAG, "injectJavaScript, onProgressChanged.newProgress = " + newProgress + ", url = " + view.getUrl()); } } if (this.mAgentWebView.mInjectJavaScripts != null) { this.mAgentWebView.injectExtraJavaScript(); } super.onProgressChanged(view, newProgress); } @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { Log.i(TAG, "onJsPrompt:" + url + " message:" + message + " d:" + defaultValue + " "); if (this.mAgentWebView.mJsCallJavas != null && JsCallJava.isSafeWebViewCallMsg(message)) { JSONObject jsonObject = JsCallJava.getMsgJSONObject(message); String interfacedName = JsCallJava.getInterfacedName(jsonObject); if (interfacedName != null) { JsCallJava mJsCallJava = this.mAgentWebView.mJsCallJavas.get(interfacedName); if (mJsCallJava != null) { result.confirm(mJsCallJava.call(view, jsonObject)); } } return true; } else { return super.onJsPrompt(view, url, message, defaultValue, result); } } } /** * 解决部分手机webView返回时不触发onReceivedTitle的问题(如:三星SM-G9008V 4.4.2); */ private static class FixedOnReceivedTitle { private WebChromeClient mWebChromeClient; private boolean mIsOnReceivedTitle; public void setWebChromeClient(WebChromeClient webChromeClient) { mWebChromeClient = webChromeClient; } public void onPageStarted() { mIsOnReceivedTitle = false; } public void onPageFinished(WebView view) { if (!mIsOnReceivedTitle && mWebChromeClient != null) { WebBackForwardList list = null; try { list = view.copyBackForwardList(); } catch (NullPointerException e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } if (list != null && list.getSize() > 0 && list.getCurrentIndex() >= 0 && list.getItemAtIndex(list.getCurrentIndex()) != null) { String previousTitle = list.getItemAtIndex(list.getCurrentIndex()).getTitle(); mWebChromeClient.onReceivedTitle(view, previousTitle); } } } public void onReceivedTitle() { mIsOnReceivedTitle = true; } } // Activity在onDestory时调用webView的destroy,可以停止播放页面中的音频 private void fixedStillAttached() { // java.lang.Throwable: Error: WebView.destroy() called while still attached! // at android.webkit.WebViewClassic.destroy(WebViewClassic.java:4142) // at android.webkit.WebView.destroy(WebView.java:707) ViewParent parent = getParent(); if (parent instanceof ViewGroup) { // 由于自定义webView构建时传入了该Activity的context对象,因此需要先从父容器中移除webView,然后再销毁webView; ViewGroup mWebViewContainer = (ViewGroup) getParent(); mWebViewContainer.removeAllViewsInLayout(); } } // 解决WebView内存泄漏问题; private void releaseConfigCallback() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { // JELLY_BEAN try { Field field = WebView.class.getDeclaredField("mWebViewCore"); field = field.getType().getDeclaredField("mBrowserFrame"); field = field.getType().getDeclaredField("sConfigCallback"); field.setAccessible(true); field.set(null, null); } catch (NoSuchFieldException e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } catch (IllegalAccessException e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { // KITKAT try { Field sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback"); if (sConfigCallback != null) { sConfigCallback.setAccessible(true); sConfigCallback.set(null, null); } } catch (NoSuchFieldException e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } catch (ClassNotFoundException e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } catch (IllegalAccessException e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } } } /** * Android 4.4 KitKat 使用Chrome DevTools 远程调试WebView * WebView.setWebContentsDebuggingEnabled(true); * http://blog.csdn.net/t12x3456/article/details/14225235 */ @TargetApi(19) protected void trySetWebDebuggEnabled() { if (LogUtils.isDebug() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { try { Class clazz = WebView.class; Method method = clazz.getMethod("setWebContentsDebuggingEnabled", boolean.class); method.invoke(null, true); } catch (Throwable e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } } } @TargetApi(11) protected boolean removeSearchBoxJavaBridge() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { Method method = this.getClass().getMethod("removeJavascriptInterface", String.class); method.invoke(this, "searchBoxJavaBridge_"); return true; } } catch (Exception e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } return false; } protected void fixedAccessibilityInjectorException() { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1 && mIsAccessibilityEnabledOriginal == null && isAccessibilityEnabled()) { mIsAccessibilityEnabledOriginal = true; setAccessibilityEnabled(false); } } protected void fixedAccessibilityInjectorExceptionForOnPageFinished(String url) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN && getSettings().getJavaScriptEnabled() && mIsAccessibilityEnabledOriginal == null && isAccessibilityEnabled()) { try { try { URLEncoder.encode(String.valueOf(new URI(url)), "utf-8"); // URLEncodedUtils.parse(new URI(url), null); // AccessibilityInjector.getAxsUrlParameterValue } catch (IllegalArgumentException e) { if ("bad parameter".equals(e.getMessage())) { mIsAccessibilityEnabledOriginal = true; setAccessibilityEnabled(false); LogUtils.safeCheckCrash(TAG, "fixedAccessibilityInjectorExceptionForOnPageFinished.url = " + url, e); } } } catch (Throwable e) { if (LogUtils.isDebug()) { LogUtils.e(TAG, "fixedAccessibilityInjectorExceptionForOnPageFinished", e); } } } } private boolean isAccessibilityEnabled() { AccessibilityManager am = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); return am.isEnabled(); } private void setAccessibilityEnabled(boolean enabled) { AccessibilityManager am = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); try { Method setAccessibilityState = am.getClass().getDeclaredMethod("setAccessibilityState", boolean.class); setAccessibilityState.setAccessible(true); setAccessibilityState.invoke(am, enabled); setAccessibilityState.setAccessible(false); } catch (Throwable e) { if (LogUtils.isDebug()) { LogUtils.e(TAG, "setAccessibilityEnabled", e); } } } private void resetAccessibilityEnabled() { if (mIsAccessibilityEnabledOriginal != null) { setAccessibilityEnabled(mIsAccessibilityEnabledOriginal); } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorSpec.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; /** * @author cenxiaozhong * @since 1.0.0 */ public interface BaseIndicatorSpec { void show(); void hide(); void reset(); void setProgress(int newProgress); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorView.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.content.Context; import androidx.annotation.Nullable; import android.util.AttributeSet; import android.widget.FrameLayout; /** * @author cenxiaozhong * @date 2017/5/12 * @since 1.0.0 */ public abstract class BaseIndicatorView extends FrameLayout implements BaseIndicatorSpec,LayoutParamsOffer{ public BaseIndicatorView(Context context) { super(context); } public BaseIndicatorView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public BaseIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public void reset() { } @Override public void setProgress(int newProgress) { } @Override public void show() { } @Override public void hide() { } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/BaseJsAccessEntrace.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.os.Build; import android.webkit.ValueCallback; import android.webkit.WebView; /** * @author cenxiaozhong * @date 2017/5/26 * @since 1.0.0 */ public abstract class BaseJsAccessEntrace implements JsAccessEntrace { private WebView mWebView; public static final String TAG=BaseJsAccessEntrace.class.getSimpleName(); BaseJsAccessEntrace(WebView webView){ this.mWebView=webView; } @Override public void callJs(String js, final ValueCallback callback) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { this.evaluateJs(js, callback); } else { this.loadJs(js); } } @Override public void callJs(String js) { this.callJs(js, null); } private void loadJs(String js) { mWebView.loadUrl(js); } private void evaluateJs(String js, final ValueCallbackcallback){ mWebView.evaluateJavascript(js, new ValueCallback() { @Override public void onReceiveValue(String value) { if (callback != null){ callback.onReceiveValue(value); } } }); } @Override public void quickCallJs(String method, ValueCallback callback, String... params) { StringBuilder sb=new StringBuilder(); sb.append("javascript:"+method); if(params==null||params.length==0){ sb.append("()"); }else{ sb.append("(").append(concat(params)).append(")"); } callJs(sb.toString(),callback); } private String concat(String...params){ StringBuilder mStringBuilder=new StringBuilder(); for(int i=0;i mActivityWeakReference = null; /** * DefaultChromeClient 's TAG */ private String TAG = DefaultChromeClient.class.getSimpleName(); /** * Android WebChromeClient path ,用于反射,用户是否重写来该方法 */ public static final String ANDROID_WEBCHROMECLIENT_PATH = "android.webkit.WebChromeClient"; /** * WebChromeClient */ private WebChromeClient mWebChromeClient; /** * 包装Flag */ private boolean mIsWrapper = false; /** * Video 处理类 */ private IVideo mIVideo; /** * PermissionInterceptor 权限拦截器 */ private PermissionInterceptor mPermissionInterceptor; /** * 当前 WebView */ private WebView mWebView; /** * Web端触发的定位 mOrigin */ private String mOrigin = null; /** * Web 端触发的定位 Callback 回调成功,或者失败 */ private GeolocationPermissions.Callback mCallback = null; /** * 标志位 */ public static final int FROM_CODE_INTENTION = 0x18; /** * 标识当前是获取定位权限 */ public static final int FROM_CODE_INTENTION_LOCATION = FROM_CODE_INTENTION << 2; /** * AbsAgentWebUIController */ private WeakReference mAgentWebUIController = null; /** * IndicatorController 进度条控制器 */ private IndicatorController mIndicatorController; /** * 文件选择器 */ private Object mFileChooser; DefaultChromeClient(Activity activity, IndicatorController indicatorController, WebChromeClient chromeClient, @Nullable IVideo iVideo, PermissionInterceptor permissionInterceptor, WebView webView) { super(chromeClient); this.mIndicatorController = indicatorController; mIsWrapper = chromeClient != null ? true : false; this.mWebChromeClient = chromeClient; mActivityWeakReference = new WeakReference(activity); this.mIVideo = iVideo; this.mPermissionInterceptor = permissionInterceptor; this.mWebView = webView; mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(webView)); } @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); if (mIndicatorController != null) { mIndicatorController.progress(view, newProgress); } } @Override public void onReceivedTitle(WebView view, String title) { if (mIsWrapper) { super.onReceivedTitle(view, title); } } @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { if (mAgentWebUIController.get() != null) { mAgentWebUIController.get().onJsAlert(view, url, message); } result.confirm(); return true; } @Override public void onReceivedIcon(WebView view, Bitmap icon) { super.onReceivedIcon(view, icon); } @Override public void onGeolocationPermissionsHidePrompt() { super.onGeolocationPermissionsHidePrompt(); } //location @Override public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { onGeolocationPermissionsShowPromptInternal(origin, callback); } private void onGeolocationPermissionsShowPromptInternal(String origin, GeolocationPermissions.Callback callback) { if (mPermissionInterceptor != null) { if (mPermissionInterceptor.intercept(this.mWebView.getUrl(), AgentWebPermissions.LOCATION, "location")) { callback.invoke(origin, false, false); return; } } Activity mActivity = mActivityWeakReference.get(); if (mActivity == null) { callback.invoke(origin, false, false); return; } List deniedPermissions = null; if ((deniedPermissions = AgentWebUtils.getDeniedPermissions(mActivity, AgentWebPermissions.LOCATION)).isEmpty()) { LogUtils.i(TAG, "onGeolocationPermissionsShowPromptInternal:" + true); callback.invoke(origin, true, false); } else { Action mAction = Action.createPermissionsAction(deniedPermissions.toArray(new String[]{})); mAction.setFromIntention(FROM_CODE_INTENTION_LOCATION); mAction.setPermissionListener(mPermissionListener); this.mCallback = callback; this.mOrigin = origin; AgentActionFragment.start(mActivity, mAction); } } private AgentActionFragment.PermissionListener mPermissionListener = new AgentActionFragment.PermissionListener() { @Override public void onRequestPermissionsResult(@NonNull String[] permissions, @NonNull int[] grantResults, Bundle extras) { if (extras.getInt(KEY_FROM_INTENTION) == FROM_CODE_INTENTION_LOCATION) { boolean hasPermission = AgentWebUtils.hasPermission(mActivityWeakReference.get(), permissions); if (mCallback != null) { if (hasPermission) { mCallback.invoke(mOrigin, true, false); } else { mCallback.invoke(mOrigin, false, false); } mCallback = null; mOrigin = null; } if (!hasPermission && null != mAgentWebUIController.get()) { mAgentWebUIController .get() .onPermissionsDeny( AgentWebPermissions.LOCATION, AgentWebPermissions.ACTION_LOCATION, "Location"); } } } }; @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { try { if (this.mAgentWebUIController.get() != null) { this.mAgentWebUIController.get().onJsPrompt(mWebView, url, message, defaultValue, result); } } catch (Exception e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } return true; } @Override public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { if (mAgentWebUIController.get() != null) { mAgentWebUIController.get().onJsConfirm(view, url, message, result); } return true; } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onPermissionRequest(PermissionRequest request) { if (request == null) { return; } final String[] resources = request.getResources(); if (resources == null || resources.length <= 0) { request.deny(); return; } Set resourcesSet = new HashSet<>(Arrays.asList(resources)); ArrayList permissions = new ArrayList<>(resourcesSet.size()); if (resourcesSet.contains(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) { permissions.add(Manifest.permission.CAMERA); } if (resourcesSet.contains(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) { permissions.add(Manifest.permission.RECORD_AUDIO); } if (mPermissionInterceptor != null) { boolean intercept = mPermissionInterceptor.intercept(mWebView.getUrl(), permissions.toArray(new String[]{}), "onPermissionRequest"); if (intercept) { return; } } if (mAgentWebUIController.get() != null) { mAgentWebUIController.get().onPermissionRequest(request); } } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onPermissionRequestCanceled(PermissionRequest request) { super.onPermissionRequestCanceled(request); } @Override public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) { quotaUpdater.updateQuota(totalQuota * 2); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { LogUtils.i(TAG, "openFileChooser>=5.0"); return openFileChooserAboveL(webView, filePathCallback, fileChooserParams); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private boolean openFileChooserAboveL(WebView webView, ValueCallback valueCallbacks, FileChooserParams fileChooserParams) { // LogUtils.i(TAG, "fileChooserParams:" + fileChooserParams.getAcceptTypes() + " getTitle:" + fileChooserParams.getTitle() + " accept:" + Arrays.toString(fileChooserParams.getAcceptTypes()) + " length:" + fileChooserParams.getAcceptTypes().length + " isCaptureEnabled:" + fileChooserParams.isCaptureEnabled() + " " + fileChooserParams.getFilenameHint() + " intent:" + fileChooserParams.createIntent().toString() + " mode:" + fileChooserParams.getMode()); if (valueCallbacks == null) { return false; } Activity mActivity = this.mActivityWeakReference.get(); if (mActivity == null || mActivity.isFinishing()) { return false; } return AgentWebUtils.showFileChooserCompat(mActivity, mWebView, valueCallbacks, fileChooserParams, this.mPermissionInterceptor, null, null, null ); } /** * Android >= 4.1 * * @param uploadFile ValueCallback , File URI callback * @param acceptType * @param capture */ @Override public void openFileChooser(ValueCallback uploadFile, String acceptType, String capture) { /*believe me , i never want to do this */ LogUtils.i(TAG, "openFileChooser>=4.1"); createAndOpenCommonFileChooser(uploadFile, acceptType); } // Android < 3.0 @Override public void openFileChooser(ValueCallback valueCallback) { Log.i(TAG, "openFileChooser<3.0"); createAndOpenCommonFileChooser(valueCallback, "*/*"); } // Android >= 3.0 @Override public void openFileChooser(ValueCallback valueCallback, String acceptType) { Log.i(TAG, "openFileChooser>3.0"); createAndOpenCommonFileChooser(valueCallback, acceptType); } private void createAndOpenCommonFileChooser(ValueCallback valueCallback, String mimeType) { if (valueCallback == null) { return; } Activity mActivity = this.mActivityWeakReference.get(); if (mActivity == null || mActivity.isFinishing()) { valueCallback.onReceiveValue(new Object()); return; } AgentWebUtils.showFileChooserCompat(mActivity, mWebView, null, null, this.mPermissionInterceptor, valueCallback, mimeType, null ); } @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { super.onConsoleMessage(consoleMessage); return true; } @Override public void onShowCustomView(View view, CustomViewCallback callback) { if (mIVideo != null) { mIVideo.onShowCustomView(view, callback); } } @Override public void onHideCustomView() { if (mIVideo != null) { mIVideo.onHideCustomView(); } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultDesignUIController.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.app.Activity; import android.content.DialogInterface; import android.graphics.Color; import android.os.Build; import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.webkit.JsPromptResult; import android.webkit.JsResult; import android.webkit.WebView; import android.widget.TextView; import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.snackbar.Snackbar; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; /** * @author cenxiaozhong * @date 2017/12/8 * @since 3.0.0 */ public class DefaultDesignUIController extends DefaultUIController { private BottomSheetDialog mBottomSheetDialog; private static final int RECYCLERVIEW_ID = 0x1001; private Activity mActivity = null; private WebParentLayout mWebParentLayout; private LayoutInflater mLayoutInflater; @Override public void onJsAlert(WebView view, String url, String message) { onJsAlertInternal(view, message); } private void onJsAlertInternal(WebView view, String message) { Activity mActivity = this.mActivity; if (mActivity == null || mActivity.isFinishing()) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (mActivity.isDestroyed()) { return; } } try { AgentWebUtils.show(view, message, Snackbar.LENGTH_SHORT, Color.WHITE, mActivity.getResources().getColor(R.color.black), null, -1, null); } catch (Throwable throwable) { if (LogUtils.isDebug()){ throwable.printStackTrace(); } } } @Override public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) { super.onJsConfirm(view, url, message, jsResult); } @Override public void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback) { showChooserInternal(view, url, ways, callback); } @Override public void onForceDownloadAlert(String url, final Handler.Callback callback) { super.onForceDownloadAlert(url, callback); } private void showChooserInternal(WebView view, String url, final String[] ways, final Handler.Callback callback) { Activity mActivity; if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (mActivity.isDestroyed()) { return; } } LogUtils.i(TAG, "url:" + url + " ways:" + ways[0]); RecyclerView mRecyclerView; if (mBottomSheetDialog == null) { mBottomSheetDialog = new BottomSheetDialog(mActivity); mRecyclerView = new RecyclerView(mActivity); mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); mRecyclerView.setId(RECYCLERVIEW_ID); mBottomSheetDialog.setContentView(mRecyclerView); } mRecyclerView = (RecyclerView) mBottomSheetDialog.getDelegate().findViewById(RECYCLERVIEW_ID); mRecyclerView.setAdapter(getAdapter(ways, callback)); mBottomSheetDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { if (callback != null) { callback.handleMessage(Message.obtain(null, -1)); } } }); mBottomSheetDialog.show(); } private RecyclerView.Adapter getAdapter(final String[] ways, final Handler.Callback callback) { return new RecyclerView.Adapter() { @Override public BottomSheetHolder onCreateViewHolder(ViewGroup viewGroup, int i) { return new BottomSheetHolder(mLayoutInflater.inflate(android.R.layout.simple_list_item_1, viewGroup, false)); } @Override public void onBindViewHolder(BottomSheetHolder bottomSheetHolder, final int i) { TypedValue outValue = new TypedValue(); mActivity.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); bottomSheetHolder.mTextView.setBackgroundResource(outValue.resourceId); bottomSheetHolder.mTextView.setText(ways[i]); bottomSheetHolder.mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mBottomSheetDialog != null && mBottomSheetDialog.isShowing()) { mBottomSheetDialog.dismiss(); } Message mMessage = Message.obtain(); mMessage.what = i; callback.handleMessage(mMessage); } }); } @Override public int getItemCount() { return ways.length; } }; } private static class BottomSheetHolder extends RecyclerView.ViewHolder { TextView mTextView; public BottomSheetHolder(View itemView) { super(itemView); mTextView = (TextView) itemView.findViewById(android.R.id.text1); } } @Override public void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult) { super.onJsPrompt(view, url, message, defaultValue, jsPromptResult); } @Override protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) { super.bindSupportWebParent(webParentLayout, activity); this.mActivity = activity; this.mWebParentLayout = webParentLayout; mLayoutInflater = LayoutInflater.from(mActivity); } @Override public void onShowMessage(String message, String from) { Activity mActivity; if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (mActivity.isDestroyed()) { return; } } if (!TextUtils.isEmpty(from) && from.contains("performDownload")) { return; } onJsAlertInternal(mWebParentLayout.getWebView(), message); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultDownloadImpl.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.app.Activity; import android.content.Context; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.webkit.WebView; import com.download.library.DownloadImpl; import com.download.library.DownloadListenerAdapter; import com.download.library.Extra; import com.download.library.ResourceRequest; import java.lang.ref.WeakReference; import java.util.concurrent.ConcurrentHashMap; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * @author cenxiaozhong * @date 2017/5/13 */ public class DefaultDownloadImpl implements android.webkit.DownloadListener { /** * Application Context */ protected Context mContext; protected ConcurrentHashMap mDownloadTasks = new ConcurrentHashMap<>(); /** * Activity */ protected WeakReference mActivityWeakReference = null; /** * TAG 用于打印,标识 */ private static final String TAG = DefaultDownloadImpl.class.getSimpleName(); /** * 权限拦截 */ protected PermissionInterceptor mPermissionListener = null; /** * AbsAgentWebUIController */ protected WeakReference mAgentWebUIController; private static Handler mHandler = new Handler(Looper.getMainLooper()); private boolean isInstallDownloader; protected DefaultDownloadImpl(Activity activity, WebView webView, PermissionInterceptor permissionInterceptor) { this.mContext = activity.getApplicationContext(); this.mActivityWeakReference = new WeakReference(activity); this.mPermissionListener = permissionInterceptor; this.mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(webView)); try { DownloadImpl.getInstance(this.mContext); isInstallDownloader = true; } catch (Throwable throwable) { LogUtils.e(TAG, "implementation 'com.download.library:Downloader:x.x.x'"); if (LogUtils.isDebug()) { throwable.printStackTrace(); } isInstallDownloader = false; } } @Override public void onDownloadStart(final String url, final String userAgent, final String contentDisposition, final String mimetype, final long contentLength) { if (!isInstallDownloader) { LogUtils.e(TAG, "unable start download " + url + "; implementation 'com.download.library:Downloader:x.x.x'"); return; } mHandler.post(new Runnable() { @Override public void run() { onDownloadStartInternal(url, userAgent, contentDisposition, mimetype, contentLength); } }); } protected void onDownloadStartInternal(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { if (null == mActivityWeakReference.get() || mActivityWeakReference.get().isFinishing()) { return; } if (null != this.mPermissionListener) { if (this.mPermissionListener.intercept(url, new String[]{}, "download")) { return; } } ResourceRequest resourceRequest = createResourceRequest(url); this.mDownloadTasks.put(url, resourceRequest); preDownload(url); } protected ResourceRequest createResourceRequest(String url) { return DownloadImpl.getInstance(this.mContext).with(url).setEnableIndicator(true).autoOpenIgnoreMD5(); } protected void preDownload(String url) { // 移动数据 if (!isForceRequest(url) && AgentWebUtils.checkNetworkType(mContext) > 1) { showDialog(url); return; } performDownload(url); } protected boolean isForceRequest(String url) { ResourceRequest resourceRequest = mDownloadTasks.get(url); if (null != resourceRequest) { return resourceRequest.getDownloadTask().isForceDownload(); } return false; } protected void forceDownload(final String url) { ResourceRequest resourceRequest = mDownloadTasks.get(url); resourceRequest.setForceDownload(true); performDownload(url); } protected void showDialog(final String url) { Activity mActivity; if (null == (mActivity = mActivityWeakReference.get()) || mActivity.isFinishing()) { return; } AbsAgentWebUIController mAgentWebUIController; if (null != (mAgentWebUIController = this.mAgentWebUIController.get())) { mAgentWebUIController.onForceDownloadAlert(url, createCallback(url)); } } protected Handler.Callback createCallback(final String url) { return new Handler.Callback() { @Override public boolean handleMessage(Message msg) { forceDownload(url); return true; } }; } protected void performDownload(String url) { try { LogUtils.e(TAG, "performDownload:" + url + " exist:" + DownloadImpl.getInstance(this.mContext).exist(url)); // 该链接是否正在下载 if (DownloadImpl.getInstance(mContext).exist(url)) { if (null != mAgentWebUIController.get()) { mAgentWebUIController.get().onShowMessage( mActivityWeakReference.get() .getString(R.string.agentweb_download_task_has_been_exist), "preDownload"); } return; } ResourceRequest resourceRequest = mDownloadTasks.get(url); resourceRequest.addHeader("Cookie", AgentWebConfig.getCookiesByUrl(url)); taskEnqueue(resourceRequest); } catch (Throwable ignore) { if (LogUtils.isDebug()) { ignore.printStackTrace(); } } } protected void taskEnqueue(ResourceRequest resourceRequest) { resourceRequest.enqueue(new DownloadListenerAdapter() { @Override public boolean onResult(Throwable throwable, Uri path, String url, Extra extra) { mDownloadTasks.remove(url); return super.onResult(throwable, path, url, extra); } }); } public static DefaultDownloadImpl create(@NonNull Activity activity, @NonNull WebView webView, @Nullable PermissionInterceptor permissionInterceptor) { return new DefaultDownloadImpl(activity, webView, permissionInterceptor); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultUIController.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.Manifest; import android.app.Activity; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.res.Resources; import android.net.http.SslError; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.webkit.JsPromptResult; import android.webkit.JsResult; import android.webkit.PermissionRequest; import android.webkit.SslErrorHandler; import android.webkit.WebView; import android.widget.EditText; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; /** * @author cenxiaozhong * @date 2017/12/8 * @since 3.0.0 */ public class DefaultUIController extends AbsAgentWebUIController { private AlertDialog mAlertDialog; protected AlertDialog mConfirmDialog; private JsPromptResult mJsPromptResult = null; private JsResult mJsResult = null; private AlertDialog mPromptDialog = null; private Activity mActivity; private WebParentLayout mWebParentLayout; private AlertDialog mAskOpenOtherAppDialog = null; private ProgressDialog mProgressDialog; private Resources mResources = null; @Override public void onJsAlert(WebView view, String url, String message) { AgentWebUtils.toastShowShort(view.getContext().getApplicationContext(), message); } @Override public void onOpenPagePrompt(WebView view, String url, final Handler.Callback callback) { LogUtils.i(TAG, "onOpenPagePrompt"); Activity mActivity; if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (mActivity.isDestroyed()) { return; } } if (mAskOpenOtherAppDialog == null) { mAskOpenOtherAppDialog = new AlertDialog .Builder(mActivity) .setMessage(mResources.getString(R.string.agentweb_leave_app_and_go_other_page, AgentWebUtils.getApplicationName(mActivity))) .setTitle(mResources.getString(R.string.agentweb_tips)) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (callback != null) { callback.handleMessage(Message.obtain(null, -1)); } } })// .setPositiveButton(mResources.getString(R.string.agentweb_leave), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (callback != null) { callback.handleMessage(Message.obtain(null, 1)); } } }) .create(); } mAskOpenOtherAppDialog.show(); } @Override public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) { onJsConfirmInternal(message, jsResult); } @Override public void onSelectItemsPrompt(WebView view, String url, final String[] ways, final Handler.Callback callback) { showChooserInternal(ways, callback); } @Override public void onForceDownloadAlert(String url, final Handler.Callback callback) { onForceDownloadAlertInternal(callback); } private void onForceDownloadAlertInternal(final Handler.Callback callback) { Activity mActivity; if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (mActivity.isDestroyed()) { return; } } AlertDialog mAlertDialog = null; mAlertDialog = new AlertDialog.Builder(mActivity) .setTitle(mResources.getString(R.string.agentweb_tips)) .setMessage(mResources.getString(R.string.agentweb_honeycomblow)) .setNegativeButton(mResources.getString(R.string.agentweb_download), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (dialog != null) { dialog.dismiss(); } if (callback != null) { callback.handleMessage(Message.obtain()); } } })// .setPositiveButton(mResources.getString(R.string.agentweb_cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (dialog != null) { dialog.dismiss(); } } }).create(); mAlertDialog.show(); } private void showChooserInternal(String[] ways, final Handler.Callback callback) { Activity mActivity; if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (mActivity.isDestroyed()) { return; } } mAlertDialog = new AlertDialog.Builder(mActivity) .setSingleChoiceItems(ways, -1, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); LogUtils.i(TAG, "which:" + which); if (callback != null) { Message mMessage = Message.obtain(); mMessage.what = which; callback.handleMessage(mMessage); } } }).setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { dialog.dismiss(); if (callback != null) { callback.handleMessage(Message.obtain(null, -1)); } } }).create(); mAlertDialog.show(); } private void onJsConfirmInternal(String message, JsResult jsResult) { LogUtils.i(TAG, "activity:" + mActivity.hashCode() + " "); Activity mActivity = this.mActivity; if (mActivity == null || mActivity.isFinishing()) { toCancelJsresult(jsResult); return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (mActivity.isDestroyed()) { toCancelJsresult(jsResult); return; } } if (mConfirmDialog == null) { mConfirmDialog = new AlertDialog.Builder(mActivity) .setMessage(message) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { toDismissDialog(mConfirmDialog); toCancelJsresult(mJsResult); } })// .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { toDismissDialog(mConfirmDialog); if (mJsResult != null) { mJsResult.confirm(); } } }) .setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { dialog.dismiss(); toCancelJsresult(mJsResult); } }) .create(); } mConfirmDialog.setMessage(message); this.mJsResult = jsResult; mConfirmDialog.show(); } private void onJsPromptInternal(String message, String defaultValue, JsPromptResult jsPromptResult) { Activity mActivity = this.mActivity; if (mActivity == null || mActivity.isFinishing()) { jsPromptResult.cancel(); return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (mActivity.isDestroyed()) { jsPromptResult.cancel(); return; } } if (mPromptDialog == null) { final EditText et = new EditText(mActivity); et.setText(defaultValue); mPromptDialog = new AlertDialog.Builder(mActivity) .setView(et) .setTitle(message) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { toDismissDialog(mPromptDialog); toCancelJsresult(mJsPromptResult); } })// .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { toDismissDialog(mPromptDialog); if (mJsPromptResult != null) { mJsPromptResult.confirm(et.getText().toString()); } } }) .setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { dialog.dismiss(); toCancelJsresult(mJsPromptResult); } }) .create(); } this.mJsPromptResult = jsPromptResult; mPromptDialog.show(); } @Override public void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult) { onJsPromptInternal(message, defaultValue, jsPromptResult); } @Override public void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) { LogUtils.i(TAG, "mWebParentLayout onMainFrameError:" + mWebParentLayout); if (mWebParentLayout != null) { mWebParentLayout.showPageMainFrameError(); } } @Override public void onShowMainFrame() { if (mWebParentLayout != null) { mWebParentLayout.hideErrorLayout(); } } @Override public void onLoading(String msg) { Activity mActivity; if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (mActivity.isDestroyed()) { return; } } if (mProgressDialog == null) { mProgressDialog = new ProgressDialog(mActivity); } mProgressDialog.setCancelable(false); mProgressDialog.setCanceledOnTouchOutside(false); mProgressDialog.setMessage(msg); mProgressDialog.show(); } @Override public void onCancelLoading() { Activity mActivity; if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (mActivity.isDestroyed()) { return; } } if (mProgressDialog != null && mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } mProgressDialog = null; } @Override public void onShowMessage(String message, String from) { if (!TextUtils.isEmpty(from) && from.contains("performDownload")) { return; } AgentWebUtils.toastShowShort(mActivity.getApplicationContext(), message); } @Override public void onPermissionsDeny(String[] permissions, String permissionType, String action) { // AgentWebUtils.toastShowShort(mActivity.getApplicationContext(), "权限被冻结"); } @Override public void onShowSslCertificateErrorDialog(final WebView view, final SslErrorHandler handler, final SslError error) { AlertDialog.Builder alertDialog = new AlertDialog.Builder(mActivity); String sslErrorMessage; switch (error.getPrimaryError()) { case SslError.SSL_UNTRUSTED: sslErrorMessage = mActivity.getString(R.string.agentweb_message_show_ssl_untrusted); break; case SslError.SSL_EXPIRED: sslErrorMessage = mActivity.getString(R.string.agentweb_message_show_ssl_expired); break; case SslError.SSL_IDMISMATCH: sslErrorMessage = mActivity.getString(R.string.agentweb_message_show_ssl_hostname_mismatch); break; case SslError.SSL_NOTYETVALID: sslErrorMessage = mActivity.getString(R.string.agentweb_message_show_ssl_not_yet_valid); break; default: sslErrorMessage = mActivity.getString(R.string.agentweb_message_show_ssl_error); } sslErrorMessage += mActivity.getString(R.string.agentweb_message_show_continue); alertDialog.setTitle(mActivity.getString(R.string.agentweb_title_ssl_error)); alertDialog.setMessage(sslErrorMessage); alertDialog.setPositiveButton(R.string.agentweb_continue, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Ignore SSL certificate errors handler.proceed(); } }); alertDialog.setNegativeButton(R.string.agentweb_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { handler.cancel(); } }); alertDialog.show(); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onPermissionRequest(final PermissionRequest request) { final String[] resources = request.getResources(); Set resourcesSet = new HashSet<>(Arrays.asList(resources)); ArrayList permissions = new ArrayList<>(resourcesSet.size()); if (resourcesSet.contains(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) { permissions.add(Manifest.permission.CAMERA); } if (resourcesSet.contains(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) { permissions.add(Manifest.permission.RECORD_AUDIO); } if(permissions.isEmpty()){ request.grant(resources); return; } final List denyPermission = AgentWebUtils.getDeniedPermissions(mActivity, permissions.toArray(new String[]{})); if (denyPermission.isEmpty()) { request.grant(resources); } else { Action action = Action.createPermissionsAction(denyPermission.toArray(new String[]{})); action.setPermissionListener(new AgentActionFragment.PermissionListener() { @Override public void onRequestPermissionsResult(@NonNull String[] permissions, @NonNull int[] grantResults, Bundle extras) { List deny = AgentWebUtils.getDeniedPermissions(mActivity, denyPermission.toArray(new String[]{})); if (deny.isEmpty()) { request.grant(resources); } else { request.deny(); } } }); AgentActionFragment.start(mActivity, action); } } private void toCancelJsresult(JsResult result) { if (result != null) { result.cancel(); } } @Override protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) { this.mActivity = activity; this.mWebParentLayout = webParentLayout; mResources = this.mActivity.getResources(); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultWebClient.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.annotation.TargetApi; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.net.Uri; import android.net.http.SslError; import android.os.Build; import android.os.Handler; import android.os.Message; import androidx.annotation.RequiresApi; import android.text.TextUtils; import android.view.KeyEvent; import android.view.View; import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; import com.alipay.sdk.app.H5PayCallback; import com.alipay.sdk.app.PayTask; import com.alipay.sdk.util.H5PayResultModel; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URISyntaxException; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author cenxiaozhong * @since 3.0.0 */ public class DefaultWebClient extends MiddlewareWebClientBase { /** * Activity's WeakReference */ private WeakReference mWeakReference = null; /** * 缩放 */ private static final int CONSTANTS_ABNORMAL_BIG = 7; /** * WebViewClient */ private WebViewClient mWebViewClient; /** * mWebClientHelper */ private boolean webClientHelper = true; /** * intent ' s scheme */ public static final String INTENT_SCHEME = "intent://"; /** * Wechat pay scheme ,用于唤醒微信支付 */ public static final String WEBCHAT_PAY_SCHEME = "weixin://wap/pay?"; /** * 支付宝 */ public static final String ALIPAYS_SCHEME = "alipays://"; /** * http scheme */ public static final String HTTP_SCHEME = "http://"; /** * https scheme */ public static final String HTTPS_SCHEME = "https://"; /** * true 表示当前应用内依赖了 alipay library , false 反之 */ private static final boolean HAS_ALIPAY_LIB; /** * WebViewClient's tag 用于打印 */ private static final String TAG = DefaultWebClient.class.getSimpleName(); /** * 直接打开其他页面 */ public static final int DERECT_OPEN_OTHER_PAGE = 1001; /** * 弹窗咨询用户是否前往其他页面 */ public static final int ASK_USER_OPEN_OTHER_PAGE = DERECT_OPEN_OTHER_PAGE >> 2; /** * 不允许打开其他页面 */ public static final int DISALLOW_OPEN_OTHER_APP = DERECT_OPEN_OTHER_PAGE >> 4; /** * 默认为咨询用户 */ private int mUrlHandleWays = ASK_USER_OPEN_OTHER_PAGE; /** * 是否拦截找不到相应页面的Url,默认拦截 */ private boolean mIsInterceptUnkownUrl = true; /** * AbsAgentWebUIController */ private WeakReference mAgentWebUIController = null; /** * WebView */ private WebView mWebView; /** * 弹窗回调 */ private Handler.Callback mCallback = null; /** * MainFrameErrorMethod */ private Method onMainFrameErrorMethod = null; /** * Alipay PayTask 对象 */ private Object mPayTask; /** * SMS scheme */ public static final String SCHEME_SMS = "sms:"; /** * 缓存当前出现错误的页面 */ private Set mErrorUrlsSet = new HashSet<>(); /** * 缓存等待加载完成的页面 onPageStart()执行之后 ,onPageFinished()执行之前 */ private Set mWaittingFinishSet = new HashSet<>(); static { boolean tag = true; try { Class.forName("com.alipay.sdk.app.PayTask"); } catch (Throwable ignore) { tag = false; } HAS_ALIPAY_LIB = tag; LogUtils.i(TAG, "HAS_ALIPAY_LIB:" + HAS_ALIPAY_LIB); } DefaultWebClient(Builder builder) { super(builder.mClient); this.mWebView = builder.mWebView; this.mWebViewClient = builder.mClient; mWeakReference = new WeakReference(builder.mActivity); this.webClientHelper = builder.mWebClientHelper; mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(builder.mWebView)); mIsInterceptUnkownUrl = builder.mIsInterceptUnkownScheme; if (builder.mUrlHandleWays <= 0) { mUrlHandleWays = ASK_USER_OPEN_OTHER_PAGE; } else { mUrlHandleWays = builder.mUrlHandleWays; } } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { String url = request.getUrl().toString(); if (url.startsWith(HTTP_SCHEME) || url.startsWith(HTTPS_SCHEME)) { return (webClientHelper && HAS_ALIPAY_LIB && isAlipay(view, url)); } if (!webClientHelper) { return super.shouldOverrideUrlLoading(view, request); } if (handleCommonLink(url)) { return true; } // intent if (url.startsWith(INTENT_SCHEME)) { handleIntentUrl(url); LogUtils.i(TAG, "intent url "); return true; } // 微信支付 if (url.startsWith(WEBCHAT_PAY_SCHEME)) { LogUtils.i(TAG, "lookup wechat to pay ~~"); startActivity(url); return true; } if (url.startsWith(ALIPAYS_SCHEME) && lookup(url)) { LogUtils.i(TAG, "alipays url lookup alipay ~~ "); return true; } if (queryActiviesNumber(url) > 0 && deepLink(url)) { LogUtils.i(TAG, "intercept url:" + url); return true; } if (mIsInterceptUnkownUrl) { LogUtils.i(TAG, "intercept UnkownUrl :" + request.getUrl()); return true; } return super.shouldOverrideUrlLoading(view, request); } @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { return super.shouldInterceptRequest(view, url); } @Override public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { super.onReceivedHttpAuthRequest(view, handler, host, realm); } private boolean deepLink(String url) { switch (mUrlHandleWays) { // 直接打开其他App case DERECT_OPEN_OTHER_PAGE: lookup(url); return true; // 咨询用户是否打开其他App case ASK_USER_OPEN_OTHER_PAGE: Activity mActivity = null; if ((mActivity = mWeakReference.get()) == null) { return false; } ResolveInfo resolveInfo = lookupResolveInfo(url); if (null == resolveInfo) { return false; } ActivityInfo activityInfo = resolveInfo.activityInfo; LogUtils.e(TAG, "resolve package:" + resolveInfo.activityInfo.packageName + " app package:" + mActivity.getPackageName()); if (activityInfo != null && !TextUtils.isEmpty(activityInfo.packageName) && activityInfo.packageName.equals(mActivity.getPackageName())) { return lookup(url); } if (mAgentWebUIController.get() != null) { mAgentWebUIController.get() .onOpenPagePrompt(this.mWebView, mWebView.getUrl(), getCallback(url)); } return true; // 默认不打开 default: return false; } } @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { return super.shouldInterceptRequest(view, request); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith(HTTP_SCHEME) || url.startsWith(HTTPS_SCHEME)) { return (webClientHelper && HAS_ALIPAY_LIB && isAlipay(view, url)); } if (!webClientHelper) { return false; } //电话 , 邮箱 , 短信 if (handleCommonLink(url)) { return true; } //Intent scheme if (url.startsWith(INTENT_SCHEME)) { handleIntentUrl(url); return true; } //微信支付 if (url.startsWith(WEBCHAT_PAY_SCHEME)) { startActivity(url); return true; } //支付宝 if (url.startsWith(ALIPAYS_SCHEME) && lookup(url)) { return true; } //打开url 相对应的页面 if (queryActiviesNumber(url) > 0 && deepLink(url)) { LogUtils.i(TAG, "intercept OtherAppScheme"); return true; } // 手机里面没有页面能匹配到该链接 ,拦截下来。 if (mIsInterceptUnkownUrl) { LogUtils.i(TAG, "intercept InterceptUnkownScheme : " + url); return true; } return super.shouldOverrideUrlLoading(view, url); } private int queryActiviesNumber(String url) { try { if (mWeakReference.get() == null) { return 0; } Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); PackageManager mPackageManager = mWeakReference.get().getPackageManager(); List mResolveInfos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); return mResolveInfos == null ? 0 : mResolveInfos.size(); } catch (URISyntaxException ignore) { if (LogUtils.isDebug()) { ignore.printStackTrace(); } return 0; } } private void handleIntentUrl(String intentUrl) { try { Intent intent = null; if (TextUtils.isEmpty(intentUrl) || !intentUrl.startsWith(INTENT_SCHEME)) { return; } if (lookup(intentUrl)) { return; } } catch (Throwable e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } } private ResolveInfo lookupResolveInfo(String url) { try { Intent intent; Activity mActivity = null; if ((mActivity = mWeakReference.get()) == null) { return null; } PackageManager packageManager = mActivity.getPackageManager(); intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); return info; } catch (Throwable ignore) { if (LogUtils.isDebug()) { ignore.printStackTrace(); } } return null; } private boolean lookup(String url) { try { Intent intent; Activity mActivity = null; if ((mActivity = mWeakReference.get()) == null) { return true; } PackageManager packageManager = mActivity.getPackageManager(); intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); // 跳到该应用 if (info != null) { mActivity.startActivity(intent); return true; } } catch (Throwable ignore) { if (LogUtils.isDebug()) { ignore.printStackTrace(); } } return false; } private boolean isAlipay(final WebView view, String url) { try { Activity mActivity = null; if ((mActivity = mWeakReference.get()) == null) { return false; } /** * 推荐采用的新的二合一接口(payInterceptorWithUrl),只需调用一次 */ if (mPayTask == null) { Class clazz = Class.forName("com.alipay.sdk.app.PayTask"); Constructor mConstructor = clazz.getConstructor(Activity.class); mPayTask = mConstructor.newInstance(mActivity); } final PayTask task = (PayTask) mPayTask; boolean isIntercepted = task.payInterceptorWithUrl(url, true, new H5PayCallback() { @Override public void onPayResult(final H5PayResultModel result) { final String url = result.getReturnUrl(); if (!TextUtils.isEmpty(url)) { AgentWebUtils.runInUiThread(new Runnable() { @Override public void run() { view.loadUrl(url); } }); } } }); if (isIntercepted) { LogUtils.i(TAG, "alipay-isIntercepted:" + isIntercepted + " url:" + url); } return isIntercepted; } catch (Throwable ignore) { if (AgentWebConfig.DEBUG) { // ignore.printStackTrace(); } } return false; } private boolean handleCommonLink(String url) { if (url.startsWith(WebView.SCHEME_TEL) || url.startsWith(SCHEME_SMS) || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith(WebView.SCHEME_GEO)) { try { Activity mActivity = null; if ((mActivity = mWeakReference.get()) == null) { return false; } Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); mActivity.startActivity(intent); } catch (ActivityNotFoundException ignored) { if (AgentWebConfig.DEBUG) { ignored.printStackTrace(); } } return true; } return false; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { if (!mWaittingFinishSet.contains(url)) { mWaittingFinishSet.add(url); } super.onPageStarted(view, url, favicon); } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { if (mAgentWebUIController.get() != null) { mAgentWebUIController.get().onShowSslCertificateErrorDialog(view, handler, error); } } /** * MainFrame Error * * @param view * @param errorCode * @param description * @param failingUrl */ @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { LogUtils.i(TAG, "onReceivedError:" + description + " CODE:" + errorCode); if (failingUrl == null && errorCode != -12) { return; } if (errorCode == -1) { return; } if (errorCode != ERROR_HOST_LOOKUP && (failingUrl != null && !failingUrl.equals(view.getUrl()) && !failingUrl.equals(view.getOriginalUrl()))) { return; } onMainFrameError(view, errorCode, description, failingUrl); } @Override public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { if (!mWaittingFinishSet.contains(url)) { mWaittingFinishSet.add(url); } super.doUpdateVisitedHistory(view, url, isReload); } @TargetApi(Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { String failingUrl = request.getUrl().toString(); int errorCode = error.getErrorCode(); if (!request.isForMainFrame()) { return; } if (failingUrl == null && errorCode != ERROR_BAD_URL) { return; } if (errorCode == ERROR_UNKNOWN) { return; } LogUtils.i(TAG, "onReceivedError:" + error.getDescription() + " code:" + error.getErrorCode() + " failingUrl:" + failingUrl + " getUrl:" + view.getUrl() + " getOriginalUrl:" + view.getOriginalUrl()); if (errorCode != ERROR_HOST_LOOKUP && (failingUrl != null && !failingUrl.equals(view.getUrl()) && !failingUrl.equals(view.getOriginalUrl()))) { return; } onMainFrameError(view, error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString()); } private void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) { mErrorUrlsSet.add(failingUrl); // 下面逻辑判断开发者是否重写了 onMainFrameError 方法 , 优先交给开发者处理 if (this.mWebViewClient != null && webClientHelper) { Method mMethod = this.onMainFrameErrorMethod; if (mMethod != null || (this.onMainFrameErrorMethod = mMethod = AgentWebUtils.isExistMethod(mWebViewClient, "onMainFrameError", AbsAgentWebUIController.class, WebView.class, int.class, String.class, String.class)) != null) { try { mMethod.invoke(this.mWebViewClient, mAgentWebUIController.get(), view, errorCode, description, failingUrl); } catch (Throwable ignore) { if (LogUtils.isDebug()) { ignore.printStackTrace(); } } return; } } if (mAgentWebUIController.get() != null) { mAgentWebUIController.get().onMainFrameError(view, errorCode, description, failingUrl); } // this.mWebView.setVisibility(View.GONE); } @Override public void onPageFinished(WebView view, String url) { if (!mErrorUrlsSet.contains(url) && mWaittingFinishSet.contains(url)) { if (mAgentWebUIController.get() != null) { mAgentWebUIController.get().onShowMainFrame(); } } else { view.setVisibility(View.VISIBLE); } if (mWaittingFinishSet.contains(url)) { mWaittingFinishSet.remove(url); } if (!mErrorUrlsSet.isEmpty()) { mErrorUrlsSet.clear(); } super.onPageFinished(view, url); } @Override public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { return super.shouldOverrideKeyEvent(view, event); } private void startActivity(String url) { try { if (mWeakReference.get() == null) { return; } Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); mWeakReference.get().startActivity(intent); } catch (Exception e) { if (LogUtils.isDebug()) { e.printStackTrace(); } } } @Override public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { super.onReceivedHttpError(view, request, errorResponse); } @Override public void onScaleChanged(WebView view, float oldScale, float newScale) { LogUtils.i(TAG, "onScaleChanged:" + oldScale + " n:" + newScale); if (newScale - oldScale > CONSTANTS_ABNORMAL_BIG) { view.setInitialScale((int) (oldScale / newScale * 100)); } } private Handler.Callback getCallback(final String url) { if (this.mCallback != null) { return this.mCallback; } return this.mCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what) { case 1: lookup(url); break; default: return true; } return true; } }; } public static Builder createBuilder() { return new Builder(); } public static class Builder { private Activity mActivity; private WebViewClient mClient; private boolean mWebClientHelper; private PermissionInterceptor mPermissionInterceptor; private WebView mWebView; private boolean mIsInterceptUnkownScheme = true; private int mUrlHandleWays; public Builder setActivity(Activity activity) { this.mActivity = activity; return this; } public Builder setClient(WebViewClient client) { this.mClient = client; return this; } public Builder setWebClientHelper(boolean webClientHelper) { this.mWebClientHelper = webClientHelper; return this; } public Builder setPermissionInterceptor(PermissionInterceptor permissionInterceptor) { this.mPermissionInterceptor = permissionInterceptor; return this; } public Builder setWebView(WebView webView) { this.mWebView = webView; return this; } public Builder setInterceptUnkownUrl(boolean interceptUnkownScheme) { this.mIsInterceptUnkownScheme = interceptUnkownScheme; return this; } public Builder setUrlHandleWays(int urlHandleWays) { this.mUrlHandleWays = urlHandleWays; return this; } public DefaultWebClient build() { return new DefaultWebClient(this); } } public static enum OpenOtherPageWays { /** * 直接打开跳转页 */ DERECT(DefaultWebClient.DERECT_OPEN_OTHER_PAGE), /** * 咨询用户是否打开 */ ASK(DefaultWebClient.ASK_USER_OPEN_OTHER_PAGE), /** * 禁止打开其他页面 */ DISALLOW(DefaultWebClient.DISALLOW_OPEN_OTHER_APP); int code; OpenOtherPageWays(int code) { this.code = code; } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultWebCreator.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.app.Activity; import android.graphics.Color; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; import android.webkit.WebView; import android.widget.FrameLayout; import static com.just.agentweb.AgentWebConfig.WEBVIEW_DEFAULT_TYPE; /** * @author cenxiaozhong * @since 1.0.0 */ public class DefaultWebCreator implements WebCreator { private Activity mActivity; private ViewGroup mViewGroup; private boolean mIsNeedDefaultProgress; private int mIndex; private BaseIndicatorView mProgressView; private ViewGroup.LayoutParams mLayoutParams = null; private int mColor = -1; /** * 单位dp */ private int mHeight; private boolean mIsCreated = false; private IWebLayout mIWebLayout; private BaseIndicatorSpec mBaseIndicatorSpec; private WebView mWebView = null; private FrameLayout mFrameLayout = null; private View mTargetProgress; private static final String TAG = DefaultWebCreator.class.getSimpleName(); private int mWebViewType = WEBVIEW_DEFAULT_TYPE; /** * 使用默认的进度条 * * @param activity * @param viewGroup * @param lp * @param index * @param color * @param mHeight * @param webView * @param webLayout */ protected DefaultWebCreator(@NonNull Activity activity, @Nullable ViewGroup viewGroup, ViewGroup.LayoutParams lp, int index, int color, int mHeight, WebView webView, IWebLayout webLayout) { this.mActivity = activity; this.mViewGroup = viewGroup; this.mIsNeedDefaultProgress = true; this.mIndex = index; this.mColor = color; this.mLayoutParams = lp; this.mHeight = mHeight; this.mWebView = webView; this.mIWebLayout = webLayout; } /** * 关闭进度条 * * @param activity * @param viewGroup * @param lp * @param index * @param webView * @param webLayout */ protected DefaultWebCreator(@NonNull Activity activity, @Nullable ViewGroup viewGroup, ViewGroup.LayoutParams lp, int index, @Nullable WebView webView, IWebLayout webLayout) { this.mActivity = activity; this.mViewGroup = viewGroup; this.mIsNeedDefaultProgress = false; this.mIndex = index; this.mLayoutParams = lp; this.mWebView = webView; this.mIWebLayout = webLayout; } /** * 自定义Indicator * * @param activity * @param viewGroup * @param lp * @param index * @param progressView * @param webView * @param webLayout */ protected DefaultWebCreator(@NonNull Activity activity, @Nullable ViewGroup viewGroup, ViewGroup.LayoutParams lp, int index, BaseIndicatorView progressView, WebView webView, IWebLayout webLayout) { this.mActivity = activity; this.mViewGroup = viewGroup; this.mIsNeedDefaultProgress = false; this.mIndex = index; this.mLayoutParams = lp; this.mProgressView = progressView; this.mWebView = webView; this.mIWebLayout = webLayout; } public void setWebView(WebView webView) { mWebView = webView; } public FrameLayout getFrameLayout() { return mFrameLayout; } public View getTargetProgress() { return mTargetProgress; } public void setTargetProgress(View targetProgress) { this.mTargetProgress = targetProgress; } @Override public DefaultWebCreator create() { if (mIsCreated) { return this; } mIsCreated = true; ViewGroup mViewGroup = this.mViewGroup; if (mViewGroup == null) { mViewGroup = this.mFrameLayout = (FrameLayout) createLayout(); mActivity.setContentView(mViewGroup); } else { if (mIndex == -1) { mViewGroup.addView(this.mFrameLayout = (FrameLayout) createLayout(), mLayoutParams); } else { mViewGroup.addView(this.mFrameLayout = (FrameLayout) createLayout(), mIndex, mLayoutParams); } } return this; } @Override public WebView getWebView() { return mWebView; } @Override public FrameLayout getWebParentLayout() { return mFrameLayout; } @Override public int getWebViewType() { return this.mWebViewType; } private ViewGroup createLayout() { Activity mActivity = this.mActivity; WebParentLayout mFrameLayout = new WebParentLayout(mActivity); mFrameLayout.setId(R.id.web_parent_layout_id); mFrameLayout.setBackgroundColor(Color.WHITE); View target = mIWebLayout == null ? (this.mWebView = (WebView) createWebView()) : webLayout(); FrameLayout.LayoutParams mLayoutParams = new FrameLayout.LayoutParams(-1, -1); mFrameLayout.addView(target, mLayoutParams); mFrameLayout.bindWebView(this.mWebView); LogUtils.i(TAG, " instanceof AgentWebView:" + (this.mWebView instanceof AgentWebView)); if (this.mWebView instanceof AgentWebView) { this.mWebViewType = AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE; } ViewStub mViewStub = new ViewStub(mActivity); mViewStub.setId(R.id.mainframe_error_viewsub_id); mFrameLayout.addView(mViewStub, new FrameLayout.LayoutParams(-1, -1)); if (mIsNeedDefaultProgress) { FrameLayout.LayoutParams lp = null; WebIndicator mWebIndicator = new WebIndicator(mActivity); if (mHeight > 0) { lp = new FrameLayout.LayoutParams(-2, AgentWebUtils.dp2px(mActivity, mHeight)); } else { lp = mWebIndicator.offerLayoutParams(); } if (mColor != -1) { mWebIndicator.setColor(mColor); } lp.gravity = Gravity.TOP; mFrameLayout.addView((View) (this.mBaseIndicatorSpec = mWebIndicator), lp); mWebIndicator.setVisibility(View.GONE); } else if (!mIsNeedDefaultProgress && mProgressView != null) { mFrameLayout.addView((View) (this.mBaseIndicatorSpec = (BaseIndicatorSpec) mProgressView), mProgressView.offerLayoutParams()); mProgressView.setVisibility(View.GONE); } return mFrameLayout; } private View webLayout() { WebView mWebView = null; if ((mWebView = mIWebLayout.getWebView()) == null) { mWebView = createWebView(); mIWebLayout.getLayout().addView(mWebView, -1, -1); LogUtils.i(TAG, "add webview"); } else { this.mWebViewType = AgentWebConfig.WEBVIEW_CUSTOM_TYPE; } this.mWebView = mWebView; return mIWebLayout.getLayout(); } private WebView createWebView() { WebView mWebView = null; if (this.mWebView != null) { mWebView = this.mWebView; this.mWebViewType = AgentWebConfig.WEBVIEW_CUSTOM_TYPE; } else if (AgentWebConfig.IS_KITKAT_OR_BELOW_KITKAT) { mWebView = new AgentWebView(mActivity); this.mWebViewType = AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE; } else { mWebView = new LollipopFixedWebView(mActivity); this.mWebViewType = WEBVIEW_DEFAULT_TYPE; } return mWebView; } @Override public BaseIndicatorSpec offer() { return mBaseIndicatorSpec; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultWebLifeCycleImpl.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.os.Build; import android.webkit.WebView; /** * @author cenxiaozhong * @date 2017/6/3 * @since 2.0.0 */ public class DefaultWebLifeCycleImpl implements WebLifeCycle { private WebView mWebView; DefaultWebLifeCycleImpl(WebView webView) { this.mWebView = webView; } @Override public void onResume() { if (this.mWebView != null) { if (Build.VERSION.SDK_INT >= 11){ this.mWebView.onResume(); } this.mWebView.resumeTimers(); } } @Override public void onPause() { if (this.mWebView != null) { if (Build.VERSION.SDK_INT >= 11){ this.mWebView.onPause(); } this.mWebView.pauseTimers(); } } @Override public void onDestroy() { if(this.mWebView!=null){ this.mWebView.resumeTimers(); } AgentWebUtils.clearWebView(this.mWebView); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/EventHandlerImpl.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.view.KeyEvent; import android.webkit.WebView; /** * IEventHandler 对事件的处理,主要是针对 * 视屏状态进行了处理 , 如果当前状态为 视频状态 * 则先退出视频。 * * @author cenxiaozhong * @date 2017/6/3 * @since 2.0.0 */ public class EventHandlerImpl implements IEventHandler { private WebView mWebView; private EventInterceptor mEventInterceptor; public static final EventHandlerImpl getInstantce(WebView view, EventInterceptor eventInterceptor) { return new EventHandlerImpl(view, eventInterceptor); } public EventHandlerImpl(WebView webView, EventInterceptor eventInterceptor) { this.mWebView = webView; this.mEventInterceptor = eventInterceptor; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { return back(); } return false; } @Override public boolean back() { if (this.mEventInterceptor != null && this.mEventInterceptor.event()) { return true; } if (mWebView != null && mWebView.canGoBack()) { mWebView.goBack(); return true; } return false; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/EventInterceptor.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; /** * @author cenxiaozhong * @date 2017/6/3 * @since 1.0.0 */ public interface EventInterceptor { boolean event(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/HookManager.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; /** * @author cenxiaozhong * @since 1.0.0 */ public class HookManager { public static AgentWeb hookAgentWeb(AgentWeb agentWeb, AgentWeb.AgentBuilder agentBuilder) { return agentWeb; } public static boolean permissionHook(String url,String[]permissions){ return true; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/HttpHeaders.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.net.Uri; import androidx.collection.ArrayMap; import android.text.TextUtils; import java.util.Map; /** * @author cenxiaozhong * @date 2017/7/5 * @since 2.0.0 */ public class HttpHeaders { public static HttpHeaders create() { return new HttpHeaders(); } private final Map> mHeaders; HttpHeaders() { mHeaders = new ArrayMap>(); } public Map getHeaders(String url) { String subUrl = subBaseUrl(url); if (mHeaders.get(subUrl) == null) { Map headers = new ArrayMap<>(); mHeaders.put(subUrl, headers); return headers; } return mHeaders.get(subUrl); } public void additionalHttpHeader(String url, String k, String v) { if (null == url) { return; } url = subBaseUrl(url); Map> mHeaders = getHeaders(); Map headersMap = mHeaders.get(subBaseUrl(url)); if (null == headersMap) { headersMap = new ArrayMap<>(); } headersMap.put(k, v); mHeaders.put(url, headersMap); } public void additionalHttpHeaders(String url, Map headers) { if (null == url) { return; } String subUrl = subBaseUrl(url); Map> mHeaders = getHeaders(); Map headersMap = headers; if (null == headersMap) { headersMap = new ArrayMap<>(); } mHeaders.put(subUrl, headersMap); } public void removeHttpHeader(String url, String k) { if (null == url) { return; } String subUrl = subBaseUrl(url); Map> mHeaders = getHeaders(); Map headersMap = mHeaders.get(subUrl); if (null != headersMap) { headersMap.remove(k); } } public boolean isEmptyHeaders(String url) { url = subBaseUrl(url); Map heads = getHeaders(url); return heads == null || heads.isEmpty(); } public Map> getHeaders() { return this.mHeaders; } private String subBaseUrl(String originUrl) { if (TextUtils.isEmpty(originUrl)) { return originUrl; } Uri originUri = null; try { originUri = Uri.parse(originUrl); } catch (Throwable throwable) { throwable.printStackTrace(); return ""; } if (TextUtils.isEmpty(originUri.getScheme()) || TextUtils.isEmpty(originUri.getAuthority())) { return ""; } return originUri.getScheme() + "://" + originUri.getAuthority(); } @Override public String toString() { return "HttpHeaders{" + "mHeaders=" + mHeaders + '}'; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/IAgentWebSettings.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.webkit.WebView; /** * @author cenxiaozhong * @since 1.0.0 */ public interface IAgentWebSettings { IAgentWebSettings toSetting(WebView webView); T getWebSettings(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/IEventHandler.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.view.KeyEvent; /** * @author cenxiaozhong * @since 1.0.0 */ public interface IEventHandler { boolean onKeyDown(int keyCode, KeyEvent event); boolean back(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/IUrlLoader.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import java.util.Map; /** * @author cenxiaozhong * @date 2017/6/3 * @update 4.0.0 * @since 2.0.0 */ public interface IUrlLoader { void loadUrl(String url); void loadUrl(String url, Map headers); void reload(); void loadData(String data, String mimeType, String encoding); void stopLoading(); void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl); void postUrl(String url, byte[] params); HttpHeaders getHttpHeaders(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/IVideo.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.view.View; import android.webkit.WebChromeClient; /** * @author cenxiaozhong * @date 2017/6/10 * @since 2.0.0 */ public interface IVideo { void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback); void onHideCustomView(); boolean isVideoState(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/IWebIndicator.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; /** * @author cenxiaozhong * @since 1.0.0 */ public interface IWebIndicator { T offer(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/IWebLayout.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.view.ViewGroup; import android.webkit.WebView; /** * Created by cenxiaozhong on 2017/7/1. */ /** * @author cenxiaozhong * @date 2017/7/1 * @update 4.0.0 * @since 1.0.0 */ public interface IWebLayout { /** * * @return WebView 的父控件 */ @NonNull V getLayout(); /** * * @return 返回 WebView 或 WebView 的子View ,返回null AgentWeb 内部会创建适当 WebView */ @Nullable T getWebView(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/IndicatorController.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.webkit.WebView; /** * @author cenxiaozhong * @update 4.0.0 * @since 1.0.0 */ public interface IndicatorController { void progress(WebView v, int newProgress); BaseIndicatorSpec offerIndicator(); void showIndicator(); void setProgress(int newProgress); void finish(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/IndicatorHandler.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.webkit.WebView; /** * @author cenxiaozhong * @since 1.0.0 */ public class IndicatorHandler implements IndicatorController { private BaseIndicatorSpec mBaseIndicatorSpec; @Override public void progress(WebView v, int newProgress) { if (newProgress == 0) { reset(); } else if (newProgress > 0 && newProgress <= 10) { showIndicator(); } else if (newProgress > 10 && newProgress < 95) { setProgress(newProgress); } else { setProgress(newProgress); finish(); } } @Override public BaseIndicatorSpec offerIndicator() { return this.mBaseIndicatorSpec; } public void reset() { if (mBaseIndicatorSpec != null) { mBaseIndicatorSpec.reset(); } } @Override public void finish() { if (mBaseIndicatorSpec != null) { mBaseIndicatorSpec.hide(); } } @Override public void setProgress(int n) { if (mBaseIndicatorSpec != null) { mBaseIndicatorSpec.setProgress(n); } } @Override public void showIndicator() { if (mBaseIndicatorSpec != null) { mBaseIndicatorSpec.show(); } } static IndicatorHandler getInstance() { return new IndicatorHandler(); } IndicatorHandler inJectIndicator(BaseIndicatorSpec baseIndicatorSpec) { this.mBaseIndicatorSpec = baseIndicatorSpec; return this; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/JsAccessEntrace.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.webkit.ValueCallback; /** * @author cenxiaozhong * @date 2017/5/14 * @since 1.0.0 */ public interface JsAccessEntrace extends QuickCallJs { void callJs(String js, ValueCallback callback); void callJs(String js); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/JsAccessEntraceImpl.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.os.Handler; import android.os.Looper; import android.webkit.ValueCallback; import android.webkit.WebView; /** * @author cenxiaozhong * @date 2017/6/3 * @since 1.0.0 */ public class JsAccessEntraceImpl extends BaseJsAccessEntrace { private WebView mWebView; private Handler mHandler = new Handler(Looper.getMainLooper()); public static JsAccessEntraceImpl getInstance(WebView webView) { return new JsAccessEntraceImpl(webView); } private JsAccessEntraceImpl(WebView webView) { super(webView); this.mWebView = webView; } private void safeCallJs(final String s, final ValueCallback valueCallback) { mHandler.post(new Runnable() { @Override public void run() { callJs(s, valueCallback); } }); } @Override public void callJs(String params, final ValueCallback callback) { if (Thread.currentThread() != Looper.getMainLooper().getThread()) { safeCallJs(params, callback); return; } super.callJs(params,callback); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/JsBaseInterfaceHolder.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.os.Build; import android.webkit.JavascriptInterface; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /** * @author cenxiaozhong * @date 2017/5/13 * @since 1.0.0 */ public abstract class JsBaseInterfaceHolder implements JsInterfaceHolder { private AgentWeb.SecurityType mSecurityType; private WebCreator mWebCreator; protected JsBaseInterfaceHolder(WebCreator webCreator, AgentWeb.SecurityType securityType) { this.mSecurityType = securityType; this.mWebCreator = webCreator; } @Override public boolean checkObject(Object v) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { return true; } if (mWebCreator.getWebViewType() == AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE) { return true; } boolean tag = false; Class clazz = v.getClass(); Method[] mMethods = clazz.getMethods(); for (Method mMethod : mMethods) { Annotation[] mAnnotations = mMethod.getAnnotations(); for (Annotation mAnnotation : mAnnotations) { if (mAnnotation instanceof JavascriptInterface) { tag = true; break; } } if (tag) { break; } } return tag; } protected boolean checkSecurity() { return mSecurityType != AgentWeb.SecurityType.STRICT_CHECK ? true : mWebCreator.getWebViewType() == AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE ? true : Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/JsCallJava.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.text.TextUtils; import android.util.Log; import android.webkit.WebView; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.lang.reflect.Method; import java.util.HashMap; public class JsCallJava { private final static String TAG = "JsCallJava"; private final static String RETURN_RESULT_FORMAT = "{\"CODE\": %d, \"result\": %s}"; private static final String MSG_PROMPT_HEADER = "AgentWeb:"; private static final String KEY_OBJ = "obj"; private static final String KEY_METHOD = "method"; private static final String KEY_TYPES = "types"; private static final String KEY_ARGS = "args"; private static final String[] IGNORE_UNSAFE_METHODS = {"getClass", "hashCode", "notify", "notifyAll", "equals", "toString", "wait"}; private HashMap mMethodsMap; private Object mInterfaceObj; private String mInterfacedName; private String mPreloadInterfaceJs; public JsCallJava(Object interfaceObj, String interfaceName) { try { if (TextUtils.isEmpty(interfaceName)) { throw new Exception("injected name can not be null"); } mInterfaceObj = interfaceObj; mInterfacedName = interfaceName; mMethodsMap = new HashMap(); // getMethods会获得所有继承与非继承的方法 Method[] methods = mInterfaceObj.getClass().getMethods(); // 拼接的js脚本可参照备份文件:./library/doc/injected.js StringBuilder sb = new StringBuilder("javascript:(function(b){console.log(\""); sb.append(mInterfacedName); sb.append(" init begin\");var a={queue:[],callback:function(){var d=Array.prototype.slice.call(arguments,0);var c=d.shift();var e=d.shift();this.queue[c].apply(this,d);if(!e){delete this.queue[c]}}};"); for (Method method : methods) { Log.i("Info","method:"+method); String sign; if ((sign = genJavaMethodSign(method)) == null) { continue; } mMethodsMap.put(sign, method); sb.append(String.format("a.%s=", method.getName())); } sb.append("function(){var f=Array.prototype.slice.call(arguments,0);if(f.length<1){throw\""); sb.append(mInterfacedName); sb.append(" call result, message:miss method name\"}var e=[];for(var h=1;h 0) { Class[] methodTypes = currMethod.getParameterTypes(); int currIndex; Class currCls; while (numIndex > 0) { currIndex = numIndex - numIndex / 10 * 10 - 1; currCls = methodTypes[currIndex]; if (currCls == int.class) { values[currIndex] = argsVals.getInt(currIndex); } else if (currCls == long.class) { //WARN: argsJson.getLong(k + defValue) will return a bigger incorrect number values[currIndex] = Long.parseLong(argsVals.getString(currIndex)); } else { values[currIndex] = argsVals.getDouble(currIndex); } numIndex /= 10; } } return getReturn(jsonObject, 200, currMethod.invoke(mInterfaceObj, values), time); } catch (Exception e) { LogUtils.safeCheckCrash(TAG, "call", e); //优先返回详细的错误信息 if (e.getCause() != null) { return getReturn(jsonObject, 500, "method execute result:" + e.getCause().getMessage(), time); } return getReturn(jsonObject, 500, "method execute result:" + e.getMessage(), time); } } else { return getReturn(jsonObject, 500, "call data empty", time); } } private String getReturn(JSONObject reqJson, int stateCode, Object result, long time) { String insertRes; if (result == null) { insertRes = "null"; } else if (result instanceof String) { result = ((String) result).replace("\"", "\\\""); insertRes = "\"".concat(String.valueOf(result)).concat("\""); } else { // 其他类型直接转换 insertRes = String.valueOf(result); // 兼容:如果在解决WebView注入安全漏洞时,js注入采用的是XXX:function(){return prompt(...)}的形式,函数返回类型包括:void、int、boolean、String; // 在返回给网页(onJsPrompt方法中jsPromptResult.confirm)的时候强制返回的是String类型,所以在此将result的值加双引号兼容一下; // insertRes = "\"".concat(String.valueOf(result)).concat("\""); } String resStr = String.format(RETURN_RESULT_FORMAT, stateCode, insertRes); if (LogUtils.isDebug()) { Log.d(TAG, "call time: " + (android.os.SystemClock.uptimeMillis() - time) + ", request: " + reqJson + ", result:" + resStr); } return resStr; } private static String promptMsgFormat(String object, String method, String types, String args) { StringBuilder sb = new StringBuilder(); sb.append("{"); sb.append(KEY_OBJ).append(":").append(object).append(","); sb.append(KEY_METHOD).append(":").append(method).append(","); sb.append(KEY_TYPES).append(":").append(types).append(","); sb.append(KEY_ARGS).append(":").append(args); sb.append("}"); return sb.toString(); } /** * 是否是“Java接口类中方法调用”的内部消息; * * @param message * @return */ static boolean isSafeWebViewCallMsg(String message) { return message.startsWith(MSG_PROMPT_HEADER); } static JSONObject getMsgJSONObject(String message) { message = message.substring(MSG_PROMPT_HEADER.length()); JSONObject jsonObject; try { jsonObject = new JSONObject(message); } catch (JSONException e) { e.printStackTrace(); jsonObject = new JSONObject(); } return jsonObject; } static String getInterfacedName(JSONObject jsonObject) { return jsonObject.optString(KEY_OBJ); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/JsCallback.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.util.Log; import android.webkit.WebView; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.lang.ref.WeakReference; public class JsCallback { private static final String CALLBACK_JS_FORMAT = "javascript:%s.callback(%d, %d %s);"; private int mIndex; private boolean mCouldGoOn; private WeakReference mWebViewRef; private int mIsPermanent; private String mInjectedName; public JsCallback(WebView view, String injectedName, int index) { mCouldGoOn = true; mWebViewRef = new WeakReference(view); mInjectedName = injectedName; mIndex = index; } /** * 向网页执行js回调; * @param args * @throws JsCallbackException */ public void apply (Object... args) throws JsCallbackException { if (mWebViewRef.get() == null) { throw new JsCallbackException("the WebView related to the JsCallback has been recycled"); } if (!mCouldGoOn) { throw new JsCallbackException("the JsCallback isn't permanent,cannot be called more than once"); } StringBuilder sb = new StringBuilder(); for (Object arg : args){ sb.append(","); boolean isStrArg = arg instanceof String; // 有的接口将Json对象转换成了String返回,这里不能加双引号,否则网页会认为是String而不是JavaScript对象; boolean isObjArg = isJavaScriptObject(arg); if (isStrArg && !isObjArg) { sb.append("\""); } sb.append(String.valueOf(arg)); if (isStrArg && !isObjArg) { sb.append("\""); } } String execJs = String.format(CALLBACK_JS_FORMAT, mInjectedName, mIndex, mIsPermanent, sb.toString()); if (LogUtils.isDebug()) { Log.d("JsCallBack", execJs); } mWebViewRef.get().loadUrl(execJs); mCouldGoOn = mIsPermanent > 0; } /** * 是否是JSON(JavaScript Object Notation)对象; * @param obj * @return */ private boolean isJavaScriptObject(Object obj) { if (obj instanceof JSONObject || obj instanceof JSONArray) { return true; } else { String json = obj.toString(); try { new JSONObject(json); } catch (JSONException e) { try { new JSONArray(json); } catch (JSONException e1) { return false; } } return true; } } /** * 一般传入到Java方法的js function是一次性使用的,即在Java层jsCallback.apply(...)之后不能再发起回调了; * 如果需要传入的function能够在当前页面生命周期内多次使用,请在第一次apply前setPermanent(true); * @param value */ public void setPermanent (boolean value) { mIsPermanent = value ? 1 : 0; } public static class JsCallbackException extends Exception { public JsCallbackException (String msg) { super(msg); } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/JsInterfaceHolder.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import java.util.Map; /** * @author cenxiaozhong * @date 2017/5/13 * @since 1.0.0 */ public interface JsInterfaceHolder { JsInterfaceHolder addJavaObjects(Map maps); JsInterfaceHolder addJavaObject(String k, Object v); boolean checkObject(Object v); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/JsInterfaceHolderImpl.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.webkit.WebView; import java.util.Map; import java.util.Set; /** * @author cenxiaozhong * @date 2017/5/13 * @since 1.0.0 */ public class JsInterfaceHolderImpl extends JsBaseInterfaceHolder { private static final String TAG = JsInterfaceHolderImpl.class.getSimpleName(); private WebCreator mWebCreator; private AgentWeb.SecurityType mSecurityType; private WebView mWebView; static JsInterfaceHolderImpl getJsInterfaceHolder(WebCreator webCreator, AgentWeb.SecurityType securityType) { return new JsInterfaceHolderImpl(webCreator, securityType); } JsInterfaceHolderImpl(WebCreator webCreator, AgentWeb.SecurityType securityType) { super(webCreator, securityType); this.mWebCreator = webCreator; this.mWebView = mWebCreator.getWebView(); this.mSecurityType = securityType; } @Override public JsInterfaceHolder addJavaObjects(Map maps) { if (!checkSecurity()) { LogUtils.e(TAG, "The injected object is not safe, give up injection"); return this; } Set> sets = maps.entrySet(); for (Map.Entry mEntry : sets) { Object v = mEntry.getValue(); boolean t = checkObject(v); if (!t) { throw new JsInterfaceObjectException("This object has not offer method javascript to call ,please check addJavascriptInterface annotation was be added"); } else { addJavaObjectDirect(mEntry.getKey(), v); } } return this; } @Override public JsInterfaceHolder addJavaObject(String k, Object v) { if (!checkSecurity()) { return this; } boolean t = checkObject(v); if (!t) { throw new JsInterfaceObjectException("this object has not offer method javascript to call , please check addJavascriptInterface annotation was be added"); } else { addJavaObjectDirect(k, v); } return this; } private JsInterfaceHolder addJavaObjectDirect(String k, Object v) { LogUtils.i(TAG, "k:" + k + " v:" + v); this.mWebView.addJavascriptInterface(v, k); return this; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/JsInterfaceObjectException.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; /** * @author cenxiaozhong * @date 2017/5/13 * @since 1.0.0 */ public class JsInterfaceObjectException extends RuntimeException { JsInterfaceObjectException(String msg){ super(msg); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/LayoutParamsOffer.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.widget.FrameLayout; /** * @author cenxiaozhong * @date 2017/5/12 * @since 1.0.0 */ public interface LayoutParamsOffer { T offerLayoutParams(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/LogUtils.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.util.Log; /** * @author cenxiaozhong * @date 2017/5/28 * @since 1.0.0 */ class LogUtils { private static final String PREFIX = "agentweb-"; static boolean isDebug() { return AgentWebConfig.DEBUG; } static void i(String tag, String message) { if (isDebug()) { Log.i(PREFIX.concat(tag), message); } } static void v(String tag, String message) { if (isDebug()) { Log.v(PREFIX.concat(tag), message); } } static void safeCheckCrash(String tag, String msg, Throwable tr) { if (isDebug()) { throw new RuntimeException(PREFIX.concat(tag) + " " + msg, tr); } else { Log.e(PREFIX.concat(tag), msg, tr); } } static void e(String tag, String msg, Throwable tr) { Log.e(tag, msg, tr); } static void e(String tag, String message) { if (isDebug()) { Log.e(PREFIX.concat(tag), message); } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/LollipopFixedWebView.java ================================================ package com.just.agentweb; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Configuration; import android.os.Build; import android.util.AttributeSet; import android.webkit.WebView; /** * 修复 Android 5.0 & 5.1 打开 WebView 闪退问题: * 参阅 https://stackoverflow.com/questions/41025200/android-view-inflateexception-error-inflating-class-android-webkit-webview */ @SuppressWarnings("unused") public class LollipopFixedWebView extends WebView { public LollipopFixedWebView(Context context) { super(getFixedContext(context)); } public LollipopFixedWebView(Context context, AttributeSet attrs) { super(getFixedContext(context), attrs); } public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) { super(getFixedContext(context), attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(getFixedContext(context), attrs, defStyleAttr, defStyleRes); } public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) { super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing); } public static Context getFixedContext(Context context) { if (Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23) { // Avoid crashing on Android 5 and 6 (API level 21 to 23) return context.createConfigurationContext(new Configuration()); } return context; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/MiddlewareWebChromeBase.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.webkit.WebChromeClient; /** * @author cenxiaozhong * @date 2017/12/16 * @since 3.0.0 */ public class MiddlewareWebChromeBase extends WebChromeClientDelegate { private MiddlewareWebChromeBase mMiddlewareWebChromeBase; protected MiddlewareWebChromeBase(WebChromeClient webChromeClient) { super(webChromeClient); } protected MiddlewareWebChromeBase() { super(null); } @Override final void setDelegate(WebChromeClient delegate) { super.setDelegate(delegate); } final MiddlewareWebChromeBase enq(MiddlewareWebChromeBase middlewareWebChromeBase) { setDelegate(middlewareWebChromeBase); this.mMiddlewareWebChromeBase = middlewareWebChromeBase; return this.mMiddlewareWebChromeBase; } final MiddlewareWebChromeBase next() { return this.mMiddlewareWebChromeBase; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/MiddlewareWebClientBase.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.webkit.WebViewClient; /** * @author cenxiaozhong * @date 2017/12/15 * @since 3.0.0 */ public class MiddlewareWebClientBase extends WebViewClientDelegate { private MiddlewareWebClientBase mMiddleWrareWebClientBase; private static String TAG = MiddlewareWebClientBase.class.getSimpleName(); MiddlewareWebClientBase(MiddlewareWebClientBase client) { super(client); this.mMiddleWrareWebClientBase = client; } protected MiddlewareWebClientBase(WebViewClient client) { super(client); } protected MiddlewareWebClientBase() { super(null); } final MiddlewareWebClientBase next() { return this.mMiddleWrareWebClientBase; } @Override final void setDelegate(WebViewClient delegate) { super.setDelegate(delegate); } final MiddlewareWebClientBase enq(MiddlewareWebClientBase middleWrareWebClientBase) { setDelegate(middleWrareWebClientBase); this.mMiddleWrareWebClientBase = middleWrareWebClientBase; return middleWrareWebClientBase; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/NestedScrollAgentWebView.java ================================================ /* * Copyright (C) LeonDevLifeLog(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.content.Context; import androidx.core.view.MotionEventCompat; import androidx.core.view.NestedScrollingChild; import androidx.core.view.NestedScrollingChildHelper; import androidx.core.view.ViewCompat; import android.util.AttributeSet; import android.view.MotionEvent; /** * 结合CoordinatorLayout可以与Toolbar联动的webview * @author LeonDevLifeLog * @since 4.0.0 */ public class NestedScrollAgentWebView extends AgentWebView implements NestedScrollingChild { private int mLastMotionY; private final int[] mScrollOffset = new int[2]; private final int[] mScrollConsumed = new int[2]; private int mNestedYOffset; private NestedScrollingChildHelper mChildHelper; public NestedScrollAgentWebView(Context context) { super(context); init(); } public NestedScrollAgentWebView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mChildHelper = new NestedScrollingChildHelper(this); setNestedScrollingEnabled(true); } @Override public boolean onTouchEvent(MotionEvent event) { boolean result = false; MotionEvent trackedEvent = MotionEvent.obtain(event); final int action = MotionEventCompat.getActionMasked(event); if (action == MotionEvent.ACTION_DOWN) { mNestedYOffset = 0; } int y = (int) event.getY(); event.offsetLocation(0, mNestedYOffset); switch (action) { case MotionEvent.ACTION_DOWN: mLastMotionY = y; startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); result = super.onTouchEvent(event); break; case MotionEvent.ACTION_MOVE: int deltaY = mLastMotionY - y; if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { deltaY -= mScrollConsumed[1]; trackedEvent.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } mLastMotionY = y - mScrollOffset[1]; int oldY = getScrollY(); int newScrollY = Math.max(0, oldY + deltaY); int dyConsumed = newScrollY - oldY; int dyUnconsumed = deltaY - dyConsumed; if (dispatchNestedScroll(0, dyConsumed, 0, dyUnconsumed, mScrollOffset)) { mLastMotionY -= mScrollOffset[1]; trackedEvent.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } result = super.onTouchEvent(trackedEvent); trackedEvent.recycle(); break; case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: stopNestedScroll(); result = super.onTouchEvent(event); break; } return result; } @Override public void setNestedScrollingEnabled(boolean enabled) { mChildHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return mChildHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return mChildHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { mChildHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return mChildHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/PermissionInterceptor.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; /** * @author cenxiaozhong * @since 3.0.0 */ public interface PermissionInterceptor { boolean intercept(String url, String[] permissions, String action); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/ProcessUtils.java ================================================ package com.just.agentweb; import android.app.ActivityManager; import android.app.Application; import android.content.Context; import android.text.TextUtils; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; /** * Adapted from com.blankj.utilcode.util.ProcessUtils#getCurrentProcessName */ class ProcessUtils { static String getCurrentProcessName(Context context) { String name = getCurrentProcessNameByFile(); if (!TextUtils.isEmpty(name)) return name; name = getCurrentProcessNameByAms(context); if (!TextUtils.isEmpty(name)) return name; name = getCurrentProcessNameByReflect(context); return name; } private static String getCurrentProcessNameByFile() { try { File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline"); BufferedReader mBufferedReader = new BufferedReader(new FileReader(file)); String processName = mBufferedReader.readLine().trim(); mBufferedReader.close(); return processName; } catch (Exception e) { e.printStackTrace(); return ""; } } private static String getCurrentProcessNameByAms(Context context) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am == null) return ""; List info = am.getRunningAppProcesses(); if (info == null || info.size() == 0) return ""; int pid = android.os.Process.myPid(); for (ActivityManager.RunningAppProcessInfo aInfo : info) { if (aInfo.pid == pid) { if (aInfo.processName != null) { return aInfo.processName; } } } return ""; } private static String getCurrentProcessNameByReflect(Context context) { String processName = ""; try { Application app = (Application) context.getApplicationContext(); Field loadedApkField = app.getClass().getField("mLoadedApk"); loadedApkField.setAccessible(true); Object loadedApk = loadedApkField.get(app); Field activityThreadField = loadedApk.getClass().getDeclaredField("mActivityThread"); activityThreadField.setAccessible(true); Object activityThread = activityThreadField.get(loadedApk); Method getProcessName = activityThread.getClass().getDeclaredMethod("getProcessName"); processName = (String) getProcessName.invoke(activityThread); } catch (Exception e) { e.printStackTrace(); } return processName; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/Provider.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; /** * @author cenxiaozhong * @date 2017/7/5 * @since 1.0.0 */ public interface Provider { T provide(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/QuickCallJs.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.os.Build; import androidx.annotation.RequiresApi; import android.webkit.ValueCallback; /** * @author cenxiaozhong * @date 2017/5/29 * @since 1.0.0 */ public interface QuickCallJs { @RequiresApi(Build.VERSION_CODES.KITKAT) void quickCallJs(String method, ValueCallback callback, String... params); void quickCallJs(String method, String... params); void quickCallJs(String method); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/RomUtils.java ================================================ package com.just.agentweb; import android.annotation.SuppressLint; import android.os.Build; import android.os.Environment; import android.text.TextUtils; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.util.Properties; /** *
 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2018/07/04
 *     desc  : utils about rom
 * 
*/ public final class RomUtils { private static final String[] ROM_HUAWEI = {"huawei"}; private static final String VERSION_PROPERTY_HUAWEI = "ro.build.version.emui"; private final static String UNKNOWN = "unknown"; private static RomInfo bean = null; private RomUtils() { throw new UnsupportedOperationException("u can't instantiate me..."); } /** * Return whether the rom is made by huawei. * * @return {@code true}: yes
{@code false}: no */ public static boolean isHuawei() { return ROM_HUAWEI[0].equals(getRomInfo().name); } /** * Return the rom's information. * * @return the rom's information */ public static RomInfo getRomInfo() { if (bean != null) return bean; bean = new RomInfo(); final String brand = getBrand(); final String manufacturer = getManufacturer(); if (isRightRom(brand, manufacturer, ROM_HUAWEI)) { bean.name = ROM_HUAWEI[0]; String version = getRomVersion(VERSION_PROPERTY_HUAWEI); String[] temp = version.split("_"); if (temp.length > 1) { bean.version = temp[1]; } else { bean.version = version; } return bean; }else { bean.name = manufacturer; } bean.version = getRomVersion(""); return bean; } private static boolean isRightRom(final String brand, final String manufacturer, final String... names) { for (String name : names) { if (brand.contains(name) || manufacturer.contains(name)) { return true; } } return false; } private static String getManufacturer() { try { String manufacturer = Build.MANUFACTURER; if (!TextUtils.isEmpty(manufacturer)) { return manufacturer.toLowerCase(); } } catch (Throwable ignore) {/**/} return UNKNOWN; } private static String getBrand() { try { String brand = Build.BRAND; if (!TextUtils.isEmpty(brand)) { return brand.toLowerCase(); } } catch (Throwable ignore) {/**/} return UNKNOWN; } private static String getRomVersion(final String propertyName) { String ret = ""; if (!TextUtils.isEmpty(propertyName)) { ret = getSystemProperty(propertyName); } if (TextUtils.isEmpty(ret) || ret.equals(UNKNOWN)) { try { String display = Build.DISPLAY; if (!TextUtils.isEmpty(display)) { ret = display.toLowerCase(); } } catch (Throwable ignore) {/**/} } if (TextUtils.isEmpty(ret)) { return UNKNOWN; } return ret; } private static String getSystemProperty(final String name) { String prop = getSystemPropertyByShell(name); if (!TextUtils.isEmpty(prop)) return prop; prop = getSystemPropertyByStream(name); if (!TextUtils.isEmpty(prop)) return prop; if (Build.VERSION.SDK_INT < 28) { return getSystemPropertyByReflect(name); } return prop; } private static String getSystemPropertyByShell(final String propName) { String line; BufferedReader input = null; try { Process p = Runtime.getRuntime().exec("getprop " + propName); input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); String ret = input.readLine(); if (ret != null) { return ret; } } catch (IOException ignore) { } finally { if (input != null) { try { input.close(); } catch (IOException ignore) {/**/} } } return ""; } private static String getSystemPropertyByStream(final String key) { try { Properties prop = new Properties(); FileInputStream is = new FileInputStream( new File(Environment.getRootDirectory(), "build.prop") ); prop.load(is); return prop.getProperty(key, ""); } catch (Exception ignore) {/**/} return ""; } private static String getSystemPropertyByReflect(String key) { try { @SuppressLint("PrivateApi") Class clz = Class.forName("android.os.SystemProperties"); Method getMethod = clz.getMethod("get", String.class, String.class); return (String) getMethod.invoke(clz, key, ""); } catch (Exception e) {/**/} return ""; } public static class RomInfo { private String name; private String version; public String getName() { return name; } public String getVersion() { return version; } @Override public String toString() { return "RomInfo{name=" + name + ", version=" + version + "}"; } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/UrlCommonException.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; /** * @author cenxiaozhong * @since 1.0.0 */ public class UrlCommonException extends RuntimeException { public UrlCommonException() { } public UrlCommonException(String msg) { super(msg); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/UrlLoaderImpl.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.os.Handler; import android.os.Looper; import android.webkit.WebView; import java.util.Map; /** * @author cenxiaozhong * @since 2.0.0 */ public class UrlLoaderImpl implements IUrlLoader { private Handler mHandler = null; private WebView mWebView; private HttpHeaders mHttpHeaders; public static final String TAG = UrlLoaderImpl.class.getSimpleName(); UrlLoaderImpl(WebView webView, HttpHeaders httpHeaders) { this.mWebView = webView; if (this.mWebView == null) { new NullPointerException("webview cannot be null ."); } this.mHttpHeaders = httpHeaders; if (this.mHttpHeaders == null) { this.mHttpHeaders = HttpHeaders.create(); } mHandler = new Handler(Looper.getMainLooper()); } private void safeLoadUrl(final String url) { mHandler.post(new Runnable() { @Override public void run() { loadUrl(url); } }); } private void safeReload() { mHandler.post(new Runnable() { @Override public void run() { reload(); } }); } @Override public void loadUrl(String url) { this.loadUrl(url, this.mHttpHeaders.getHeaders(url)); } @Override public void loadUrl(final String url, final Map headers) { if (!AgentWebUtils.isUIThread()) { AgentWebUtils.runInUiThread(new Runnable() { @Override public void run() { loadUrl(url, headers); } }); return; } LogUtils.i(TAG, "loadUrl:" + url + " headers:" + headers); if (headers == null || headers.isEmpty()) { this.mWebView.loadUrl(url); } else { this.mWebView.loadUrl(url, headers); } } @Override public void reload() { if (!AgentWebUtils.isUIThread()) { mHandler.post(new Runnable() { @Override public void run() { reload(); } }); return; } this.mWebView.reload(); } @Override public void loadData(final String data, final String mimeType, final String encoding) { if (!AgentWebUtils.isUIThread()) { mHandler.post(new Runnable() { @Override public void run() { loadData(data, mimeType, encoding); } }); return; } this.mWebView.loadData(data, mimeType, encoding); } @Override public void stopLoading() { if (!AgentWebUtils.isUIThread()) { mHandler.post(new Runnable() { @Override public void run() { stopLoading(); } }); return; } this.mWebView.stopLoading(); } @Override public void loadDataWithBaseURL(final String baseUrl, final String data, final String mimeType, final String encoding, final String historyUrl) { if (!AgentWebUtils.isUIThread()) { mHandler.post(new Runnable() { @Override public void run() { loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); } }); return; } this.mWebView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); } @Override public void postUrl(final String url, final byte[] postData) { if (!AgentWebUtils.isUIThread()) { mHandler.post(new Runnable() { @Override public void run() { postUrl(url, postData); } }); return; } this.mWebView.postUrl(url, postData); } @Override public HttpHeaders getHttpHeaders() { return this.mHttpHeaders == null ? this.mHttpHeaders = HttpHeaders.create() : this.mHttpHeaders; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/VideoImpl.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.app.Activity; import android.content.pm.ActivityInfo; import android.graphics.Color; import android.os.Build; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.widget.FrameLayout; import androidx.core.util.Pair; import java.util.HashSet; import java.util.Set; /** * @author cenxiaozhong */ public class VideoImpl implements IVideo, EventInterceptor { private Activity mActivity; private WebView mWebView; private static final String TAG = VideoImpl.class.getSimpleName(); private Set> mFlags = null; private View mMoiveView = null; private ViewGroup mMoiveParentView = null; private WebChromeClient.CustomViewCallback mCallback; private int mOriginalOrientation; public VideoImpl(Activity mActivity, WebView webView) { this.mActivity = mActivity; this.mWebView = webView; mFlags = new HashSet<>(); } @Override public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { Activity mActivity; if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) { return; } mOriginalOrientation = mActivity.getRequestedOrientation(); mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); Window mWindow = mActivity.getWindow(); Pair mPair = null; // 保存当前屏幕的状态 if ((mWindow.getAttributes().flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) { mPair = new Pair<>(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 0); mWindow.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); mFlags.add(mPair); } if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) && (mWindow.getAttributes().flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) == 0) { mPair = new Pair<>(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 0); mWindow.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); mFlags.add(mPair); } if (mMoiveView != null) { callback.onCustomViewHidden(); return; } if (mWebView != null) { mWebView.setVisibility(View.GONE); } if (mMoiveParentView == null) { FrameLayout mContentView = mActivity.findViewById(android.R.id.content); mMoiveParentView = new FrameLayout(mActivity); mMoiveParentView.setBackgroundColor(Color.BLACK); mContentView.addView(mMoiveParentView); } this.mCallback = callback; mMoiveParentView.addView(this.mMoiveView = view); mMoiveParentView.setVisibility(View.VISIBLE); } @Override public void onHideCustomView() { if (mMoiveView == null) { return; } if (mActivity != null) { mActivity.setRequestedOrientation(mOriginalOrientation); } if (!mFlags.isEmpty()) { for (Pair mPair : mFlags) { mActivity.getWindow().setFlags(mPair.second, mPair.first); } mFlags.clear(); } mMoiveView.setVisibility(View.GONE); if (mMoiveParentView != null && mMoiveView != null) { mMoiveParentView.removeView(mMoiveView); } if (mMoiveParentView != null) { mMoiveParentView.setVisibility(View.GONE); } if (this.mCallback != null) { mCallback.onCustomViewHidden(); } this.mMoiveView = null; if (mWebView != null) { mWebView.setVisibility(View.VISIBLE); } } @Override public boolean isVideoState() { return mMoiveView != null; } @Override public boolean event() { if (isVideoState()) { onHideCustomView(); return true; } else { return false; } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebChromeClient.java ================================================ package com.just.agentweb; /** * @author cenxiaozhong * @date 2019/4/13 * @since 1.0.0 */ public class WebChromeClient extends MiddlewareWebChromeBase{ public WebChromeClient() { } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebChromeClientDelegate.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.Message; import androidx.annotation.RequiresApi; import android.view.View; import android.webkit.ConsoleMessage; import android.webkit.GeolocationPermissions; import android.webkit.JsPromptResult; import android.webkit.JsResult; import android.webkit.PermissionRequest; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebStorage; import android.webkit.WebView; import java.lang.reflect.Method; /** * @author cenxiaozhong * @update WebChromeClientDelegate * @since 1.0.0 */ public class WebChromeClientDelegate extends WebChromeClient { private WebChromeClient mDelegate; protected WebChromeClient getDelegate() { return mDelegate; } public WebChromeClientDelegate(WebChromeClient webChromeClient) { this.mDelegate = webChromeClient; } void setDelegate(WebChromeClient delegate) { this.mDelegate = delegate; } @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); if (this.mDelegate != null) { this.mDelegate.onProgressChanged(view, newProgress); return; } } @Override public void onReceivedTitle(WebView view, String title) { if (this.mDelegate != null) { this.mDelegate.onReceivedTitle(view, title); return; } super.onReceivedTitle(view, title); } @Override public void onReceivedIcon(WebView view, Bitmap icon) { if (this.mDelegate != null) { this.mDelegate.onReceivedIcon(view, icon); return; } super.onReceivedIcon(view, icon); } @Override public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) { if (this.mDelegate != null) { this.mDelegate.onReceivedTouchIconUrl(view, url, precomposed); return; } super.onReceivedTouchIconUrl(view, url, precomposed); } @Override public void onShowCustomView(View view, CustomViewCallback callback) { if (this.mDelegate != null) { this.mDelegate.onShowCustomView(view, callback); return; } super.onShowCustomView(view, callback); } @Override public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { if (this.mDelegate != null) { this.mDelegate.onShowCustomView(view, requestedOrientation, callback); return; } super.onShowCustomView(view, requestedOrientation, callback); } @Override public void onHideCustomView() { if (this.mDelegate != null) { this.mDelegate.onHideCustomView(); return; } super.onHideCustomView(); } @Override public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { if (this.mDelegate != null) { return this.mDelegate.onCreateWindow(view, isDialog, isUserGesture, resultMsg); } return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg); } @Override public void onRequestFocus(WebView view) { if (this.mDelegate != null) { this.mDelegate.onRequestFocus(view); return; } super.onRequestFocus(view); } @Override public void onCloseWindow(WebView window) { if (this.mDelegate != null) { this.mDelegate.onCloseWindow(window); return; } super.onCloseWindow(window); } @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { if (this.mDelegate != null) { return this.mDelegate.onJsAlert(view, url, message, result); } return super.onJsAlert(view, url, message, result); } @Override public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { if (this.mDelegate != null) { return this.mDelegate.onJsConfirm(view, url, message, result); } return super.onJsConfirm(view, url, message, result); } @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { if (this.mDelegate != null) { return this.mDelegate.onJsPrompt(view, url, message, defaultValue, result); } return super.onJsPrompt(view, url, message, defaultValue, result); } @Override public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) { if (this.mDelegate != null) { return this.mDelegate.onJsBeforeUnload(view, url, message, result); } return super.onJsBeforeUnload(view, url, message, result); } @Override @Deprecated public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) { // This default implementation passes the current quota back to WebCore. // WebCore will interpret this that new quota was declined. if (this.mDelegate != null) { this.mDelegate.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater); return; } super.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater); } @Override public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { if (this.mDelegate != null) { this.mDelegate.onGeolocationPermissionsShowPrompt(origin, callback); return; } super.onGeolocationPermissionsShowPrompt(origin, callback); } /** * notify the host application that a request for Geolocation permissions, * made with a previous call to * {@link #onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback) onGeolocationPermissionsShowPrompt()} * has been canceled. Any related UI should therefore be hidden. */ @Override public void onGeolocationPermissionsHidePrompt() { if (this.mDelegate != null) { this.mDelegate.onGeolocationPermissionsHidePrompt(); return; } super.onGeolocationPermissionsHidePrompt(); } @Override @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void onPermissionRequest(PermissionRequest request) { if (this.mDelegate != null) { this.mDelegate.onPermissionRequest(request); return; } super.onPermissionRequest(request); } @Override @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void onPermissionRequestCanceled(PermissionRequest request) { if (this.mDelegate != null) { this.mDelegate.onPermissionRequestCanceled(request); return; } super.onPermissionRequestCanceled(request); } @Override public boolean onJsTimeout() { if (this.mDelegate != null) { return this.mDelegate.onJsTimeout(); } return super.onJsTimeout(); } @Override @Deprecated public void onConsoleMessage(String message, int lineNumber, String sourceID) { if (this.mDelegate != null) { this.mDelegate.onConsoleMessage(message, lineNumber, sourceID); return; } super.onConsoleMessage(message, lineNumber, sourceID); } @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { /*onConsoleMessage(consoleMessage.message(), consoleMessage.lineNumber(), consoleMessage.sourceId());*/ if (this.mDelegate != null) { return this.mDelegate.onConsoleMessage(consoleMessage); } return super.onConsoleMessage(consoleMessage); } @Override public Bitmap getDefaultVideoPoster() { if (this.mDelegate != null) { return this.mDelegate.getDefaultVideoPoster(); } return super.getDefaultVideoPoster(); } @Override public View getVideoLoadingProgressView() { if (this.mDelegate != null) { return this.mDelegate.getVideoLoadingProgressView(); } return super.getVideoLoadingProgressView(); } @Override public void getVisitedHistory(ValueCallback callback) { if (this.mDelegate != null) { this.mDelegate.getVisitedHistory(callback); return; } super.getVisitedHistory(callback); } @Override @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { if (this.mDelegate != null) { return this.mDelegate.onShowFileChooser(webView, filePathCallback, fileChooserParams); } return super.onShowFileChooser(webView, filePathCallback, fileChooserParams); } /** * Android >= 4.1 * * @param uploadFile * @param acceptType * @param capture */ public void openFileChooser(ValueCallback uploadFile, String acceptType, String capture) { commonRefect(this.mDelegate, "openFileChooser", new Object[]{uploadFile, acceptType, capture}, ValueCallback.class, String.class, String.class); } /** * Android < 3.0 * * @param valueCallback */ public void openFileChooser(ValueCallback valueCallback) { commonRefect(this.mDelegate, "openFileChooser", new Object[]{valueCallback}, ValueCallback.class); } /** * Android >= 3.0 * * @param valueCallback * @param acceptType */ public void openFileChooser(ValueCallback valueCallback, String acceptType) { commonRefect(this.mDelegate, "openFileChooser", new Object[]{valueCallback, acceptType}, ValueCallback.class, String.class); } private void commonRefect(WebChromeClient o, String mothed, Object[] os, Class... clazzs) { try { if (o == null) { return; } Class clazz = o.getClass(); Method mMethod = clazz.getMethod(mothed, clazzs); mMethod.invoke(o, os); } catch (Exception ignore) { if (LogUtils.isDebug()) { ignore.printStackTrace(); } } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebCreator.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.webkit.WebView; import android.widget.FrameLayout; /** * @author cenxiaozhong * @since 1.0.0 */ public interface WebCreator extends IWebIndicator { WebCreator create(); WebView getWebView(); FrameLayout getWebParentLayout(); int getWebViewType(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebIndicator.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; /** * @author cenxiaozhong * @since 1.0.0 */ public class WebIndicator extends BaseIndicatorView implements BaseIndicatorSpec { /** * 进度条颜色 */ private int mColor; /** * 进度条的画笔 */ private Paint mPaint; /** * 进度条动画 */ private Animator mAnimator; /** * 控件的宽度 */ private int mTargetWidth = 0; /** * 默认匀速动画最大的时长 */ public static final int MAX_UNIFORM_SPEED_DURATION = 8 * 1000; /** * 默认加速后减速动画最大时长 */ public static final int MAX_DECELERATE_SPEED_DURATION = 450; /** * 结束动画时长 , Fade out 。 */ public static final int DO_END_ANIMATION_DURATION = 600; /** * 当前匀速动画最大的时长 */ private int mCurrentMaxUniformSpeedDuration = MAX_UNIFORM_SPEED_DURATION; /** * 当前加速后减速动画最大时长 */ private int mCurrentMaxDecelerateSpeedDuration = MAX_DECELERATE_SPEED_DURATION; /** * 结束动画时长 */ private int mCurrentDoEndAnimationDuration = DO_END_ANIMATION_DURATION; /** * 当前进度条的状态 */ private int indicatorStatus = 0; public static final int UN_START = 0; public static final int STARTED = 1; public static final int FINISH = 2; private float mCurrentProgress = 0F; /** * 默认的高度 */ public int mWebIndicatorDefaultHeight = 3; public WebIndicator(Context context) { this(context, null); } public WebIndicator(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public WebIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr); } private void init(Context context, AttributeSet attrs, int defStyleAttr) { mPaint = new Paint(); mColor = Color.parseColor("#1aad19"); mPaint.setAntiAlias(true); mPaint.setColor(mColor); mPaint.setDither(true); mPaint.setStrokeCap(Paint.Cap.SQUARE); mTargetWidth = context.getResources().getDisplayMetrics().widthPixels; mWebIndicatorDefaultHeight = AgentWebUtils.dp2px(context, 3); } public void setColor(int color) { this.mColor = color; mPaint.setColor(color); } public void setColor(String color) { this.setColor(Color.parseColor(color)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int wMode = MeasureSpec.getMode(widthMeasureSpec); int w = MeasureSpec.getSize(widthMeasureSpec); int hMode = MeasureSpec.getMode(heightMeasureSpec); int h = MeasureSpec.getSize(heightMeasureSpec); if (wMode == MeasureSpec.AT_MOST) { w = w <= getContext().getResources().getDisplayMetrics().widthPixels ? w : getContext().getResources().getDisplayMetrics().widthPixels; } if (hMode == MeasureSpec.AT_MOST) { h = mWebIndicatorDefaultHeight; } this.setMeasuredDimension(w, h); } @Override protected void onDraw(Canvas canvas) { } @Override protected void dispatchDraw(Canvas canvas) { canvas.drawRect(0, 0, mCurrentProgress / 100 * Float.valueOf(this.getWidth()), this.getHeight(), mPaint); } @Override public void show() { if (getVisibility() == View.GONE) { this.setVisibility(View.VISIBLE); mCurrentProgress = 0f; startAnim(false); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); this.mTargetWidth = getMeasuredWidth(); int screenWidth = getContext().getResources().getDisplayMetrics().widthPixels; if (mTargetWidth >= screenWidth) { mCurrentMaxDecelerateSpeedDuration = MAX_DECELERATE_SPEED_DURATION; mCurrentMaxUniformSpeedDuration = MAX_UNIFORM_SPEED_DURATION; mCurrentDoEndAnimationDuration = MAX_DECELERATE_SPEED_DURATION; } else { //取比值 float rate = this.mTargetWidth / Float.valueOf(screenWidth); mCurrentMaxUniformSpeedDuration = (int) (MAX_UNIFORM_SPEED_DURATION * rate); mCurrentMaxDecelerateSpeedDuration = (int) (MAX_DECELERATE_SPEED_DURATION * rate); mCurrentDoEndAnimationDuration = (int) (DO_END_ANIMATION_DURATION * rate); } LogUtils.i("WebProgress", "CURRENT_MAX_UNIFORM_SPEED_DURATION" + mCurrentMaxUniformSpeedDuration); } public void setProgress(float progress) { if (getVisibility() == View.GONE) { setVisibility(View.VISIBLE); } if (progress < 95f) { return; } if (indicatorStatus != FINISH) { startAnim(true); } } @Override public void hide() { indicatorStatus = FINISH; } private void startAnim(boolean isFinished) { float v = isFinished ? 100 : 95; if (mAnimator != null && mAnimator.isStarted()) { mAnimator.cancel(); } mCurrentProgress = mCurrentProgress == 0f ? 0.00000001f : mCurrentProgress; if (!isFinished) { AnimatorSet animatorSet = new AnimatorSet(); float p1 = v * 0.60f; float p2 = v; ValueAnimator animator = ValueAnimator.ofFloat(mCurrentProgress, p1); ValueAnimator animator0 = ValueAnimator.ofFloat(p1, p2); float residue = 1f - mCurrentProgress / 100 - 0.05f; long duration = (long) (residue * mCurrentMaxUniformSpeedDuration); long duration6 = (long) (duration * 0.6f); long duration4 = (long) (duration * 0.4f); animator.setInterpolator(new LinearInterpolator()); animator.setDuration(duration4); animator.addUpdateListener(mAnimatorUpdateListener); animator0.setInterpolator(new LinearInterpolator()); animator0.setDuration(duration6); animator0.addUpdateListener(mAnimatorUpdateListener); animatorSet.play(animator0).after(animator); animatorSet.start(); this.mAnimator = animatorSet; } else { ValueAnimator segment95Animator = null; if (mCurrentProgress < 95f) { segment95Animator = ValueAnimator.ofFloat(mCurrentProgress, 95); float residue = 1f - mCurrentProgress / 100f - 0.05f; segment95Animator.setDuration((long) (residue * mCurrentMaxDecelerateSpeedDuration)); segment95Animator.setInterpolator(new DecelerateInterpolator()); segment95Animator.addUpdateListener(mAnimatorUpdateListener); } ObjectAnimator mObjectAnimator = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f); mObjectAnimator.setDuration(mCurrentDoEndAnimationDuration); ValueAnimator mValueAnimatorEnd = ValueAnimator.ofFloat(95f, 100f); mValueAnimatorEnd.setDuration(mCurrentDoEndAnimationDuration); mValueAnimatorEnd.addUpdateListener(mAnimatorUpdateListener); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(mObjectAnimator, mValueAnimatorEnd); if (segment95Animator != null) { AnimatorSet animatorSet0 = new AnimatorSet(); animatorSet0.play(animatorSet).after(segment95Animator); animatorSet = animatorSet0; } animatorSet.addListener(mAnimatorListenerAdapter); animatorSet.start(); mAnimator = animatorSet; } indicatorStatus = STARTED; } private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float t = (float) animation.getAnimatedValue(); WebIndicator.this.mCurrentProgress = t; WebIndicator.this.invalidate(); } }; private AnimatorListenerAdapter mAnimatorListenerAdapter = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { doEnd(); } }; @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); /** * animator cause leak , if not cancel; */ if (mAnimator != null && mAnimator.isStarted()) { mAnimator.cancel(); mAnimator = null; } } private void doEnd() { if (indicatorStatus == FINISH && mCurrentProgress == 100f) { setVisibility(GONE); mCurrentProgress = 0f; this.setAlpha(1f); } indicatorStatus = UN_START; } @Override public void reset() { mCurrentProgress = 0; if (mAnimator != null && mAnimator.isStarted()) { mAnimator.cancel(); } } @Override public void setProgress(int newProgress) { setProgress(Float.valueOf(newProgress)); } @Override public LayoutParams offerLayoutParams() { return new LayoutParams(-1, mWebIndicatorDefaultHeight); } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebLifeCycle.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; /** * @author cenxiaozhong * @date 2017/5/30 * @since 1.0.0 */ public interface WebLifeCycle { void onResume(); void onPause(); void onDestroy(); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebListenerManager.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.webkit.DownloadListener; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; /** * @author cenxiaozhong * @date 2017/5/13 * @since 1.0.0 */ public interface WebListenerManager { WebListenerManager setWebChromeClient(WebView webview, WebChromeClient webChromeClient); WebListenerManager setWebViewClient(WebView webView, WebViewClient webViewClient); WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebParentLayout.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.app.Activity; import android.content.Context; import android.graphics.Color; import androidx.annotation.IdRes; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; import android.webkit.WebView; import android.widget.FrameLayout; /** * @author cenxiaozhong * @date 2017/12/8 * @since 3.0.0 */ public class WebParentLayout extends FrameLayout implements Provider { private AbsAgentWebUIController mAgentWebUIController = null; private static final String TAG = WebParentLayout.class.getSimpleName(); @LayoutRes private int mErrorLayoutRes; @IdRes private int mClickId = -1; private View mErrorView; private WebView mWebView; private FrameLayout mErrorLayout = null; WebParentLayout(@NonNull Context context) { this(context, null); LogUtils.i(TAG, "WebParentLayout"); } WebParentLayout(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, -1); } WebParentLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); if (!(context instanceof Activity)) { throw new IllegalArgumentException("WebParentLayout context must be activity or activity sub class ."); } this.mErrorLayoutRes = R.layout.agentweb_error_page; } void bindController(AbsAgentWebUIController agentWebUIController) { this.mAgentWebUIController = agentWebUIController; this.mAgentWebUIController.bindWebParent(this, (Activity) getContext()); } void showPageMainFrameError() { View container = this.mErrorLayout; if (container != null) { container.setVisibility(View.VISIBLE); } else { createErrorLayout(); container = this.mErrorLayout; } View clickView = null; if (mClickId != -1 && (clickView = container.findViewById(mClickId)) != null) { clickView.setClickable(true); } else { container.setClickable(true); } } private void createErrorLayout() { final FrameLayout mFrameLayout = new FrameLayout(getContext()); mFrameLayout.setBackgroundColor(Color.WHITE); mFrameLayout.setId(R.id.mainframe_error_container_id); if (this.mErrorView == null) { LayoutInflater mLayoutInflater = LayoutInflater.from(getContext()); LogUtils.i(TAG, "mErrorLayoutRes:" + mErrorLayoutRes); mLayoutInflater.inflate(mErrorLayoutRes, mFrameLayout, true); } else { mFrameLayout.addView(mErrorView); } ViewStub mViewStub = (ViewStub) this.findViewById(R.id.mainframe_error_viewsub_id); final int index = this.indexOfChild(mViewStub); this.removeViewInLayout(mViewStub); final ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (layoutParams != null) { this.addView(this.mErrorLayout = mFrameLayout, index, layoutParams); } else { this.addView(this.mErrorLayout = mFrameLayout, index); } mFrameLayout.setVisibility(View.VISIBLE); if (mClickId != -1) { final View clickView = mFrameLayout.findViewById(mClickId); if (clickView != null) { clickView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (getWebView() != null) { clickView.setClickable(false); getWebView().reload(); } } }); return; } else { if (LogUtils.isDebug()) { LogUtils.e(TAG, "ClickView is null , cannot bind accurate view to refresh or reload ."); } } } mFrameLayout.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (getWebView() != null) { mFrameLayout.setClickable(false); getWebView().reload(); } } }); } void hideErrorLayout() { View mView = null; if ((mView = this.findViewById(R.id.mainframe_error_container_id)) != null) { mView.setVisibility(View.GONE); } } void setErrorView(@NonNull View errorView) { this.mErrorView = errorView; } void setErrorLayoutRes(@LayoutRes int resLayout, @IdRes int id) { this.mClickId = id; if (this.mClickId <= 0) { this.mClickId = -1; } this.mErrorLayoutRes = resLayout; if (this.mErrorLayoutRes <= 0) { this.mErrorLayoutRes = R.layout.agentweb_error_page; } } @Override public AbsAgentWebUIController provide() { return this.mAgentWebUIController; } void bindWebView(WebView view) { if (this.mWebView == null) { this.mWebView = view; } } WebView getWebView() { return this.mWebView; } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebSecurityCheckLogic.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import androidx.collection.ArrayMap; import android.webkit.WebView; /** * @author cenxiaozhong */ public interface WebSecurityCheckLogic { void dealHoneyComb(WebView view); void dealJsInterface(ArrayMap objects,AgentWeb.SecurityType securityType); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebSecurityController.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; /** * @author cenxiaozhong */ public interface WebSecurityController { void check(T t); } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebSecurityControllerImpl.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.os.Build; import androidx.collection.ArrayMap; import android.webkit.WebView; /** * @author cenxiaozhong */ public class WebSecurityControllerImpl implements WebSecurityController { private WebView mWebView; private ArrayMap mMap; private AgentWeb.SecurityType mSecurityType; public WebSecurityControllerImpl(WebView view, ArrayMap map, AgentWeb.SecurityType securityType) { this.mWebView = view; this.mMap = map; this.mSecurityType = securityType; } @Override public void check(WebSecurityCheckLogic webSecurityCheckLogic) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { webSecurityCheckLogic.dealHoneyComb(mWebView); } if (mMap != null && mSecurityType == AgentWeb.SecurityType.STRICT_CHECK && !mMap.isEmpty()) { webSecurityCheckLogic.dealJsInterface(mMap, mSecurityType); } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebSecurityLogicImpl.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.annotation.TargetApi; import android.os.Build; import androidx.collection.ArrayMap; import android.webkit.WebView; /** * @author cenxiaozhong */ public class WebSecurityLogicImpl implements WebSecurityCheckLogic { private String TAG = this.getClass().getSimpleName(); private int webviewType; public static WebSecurityLogicImpl getInstance(int webViewType) { return new WebSecurityLogicImpl(webViewType); } public WebSecurityLogicImpl(int webViewType) { this.webviewType = webViewType; } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public void dealHoneyComb(WebView view) { if (Build.VERSION_CODES.HONEYCOMB > Build.VERSION.SDK_INT || Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { return; } view.removeJavascriptInterface("searchBoxJavaBridge_"); view.removeJavascriptInterface("accessibility"); view.removeJavascriptInterface("accessibilityTraversal"); } @Override public void dealJsInterface(ArrayMap objects, AgentWeb.SecurityType securityType) { if (securityType == AgentWeb.SecurityType.STRICT_CHECK && this.webviewType != AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { LogUtils.e(TAG, "Give up all inject objects"); objects.clear(); objects = null; System.gc(); } } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebViewClient.java ================================================ package com.just.agentweb; /** * @author cenxiaozhong * @date 2019/4/13 * @since 1.0.0 */ public class WebViewClient extends MiddlewareWebClientBase { public WebViewClient() { } } ================================================ FILE: agentweb-core/src/main/java/com/just/agentweb/WebViewClientDelegate.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb; import android.graphics.Bitmap; import android.net.http.SslError; import android.os.Message; import android.view.KeyEvent; import android.webkit.ClientCertRequest; import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; /** * @author cenxiaozhong * @update WebViewClientDelegate * @date 2017/5/28 */ public class WebViewClientDelegate extends WebViewClient { private WebViewClient mDelegate; private static final String TAG = WebViewClientDelegate.class.getSimpleName(); WebViewClientDelegate(WebViewClient client) { this.mDelegate = client; } protected WebViewClient getDelegate() { return mDelegate; } void setDelegate(WebViewClient delegate) { this.mDelegate = delegate; } @Deprecated @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (mDelegate != null) { return mDelegate.shouldOverrideUrlLoading(view, url); } return super.shouldOverrideUrlLoading(view, url); } @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { if (mDelegate != null) { return mDelegate.shouldOverrideUrlLoading(view, request); } return super.shouldOverrideUrlLoading(view, request); } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { if (mDelegate != null) { mDelegate.onPageStarted(view, url, favicon); return; } super.onPageStarted(view, url, favicon); } @Override public void onPageFinished(WebView view, String url) { if (mDelegate != null) { mDelegate.onPageFinished(view, url); return; } super.onPageFinished(view, url); } @Override public void onLoadResource(WebView view, String url) { if (mDelegate != null) { mDelegate.onLoadResource(view, url); return; } super.onLoadResource(view, url); } @Override public void onPageCommitVisible(WebView view, String url) { if (mDelegate != null) { mDelegate.onPageCommitVisible(view, url); return; } super.onPageCommitVisible(view, url); } @Override @Deprecated public WebResourceResponse shouldInterceptRequest(WebView view, String url) { if (mDelegate != null) { return mDelegate.shouldInterceptRequest(view, url); } return super.shouldInterceptRequest(view, url); } @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { if (mDelegate != null) { return mDelegate.shouldInterceptRequest(view, request); } return super.shouldInterceptRequest(view, request); } @Override @Deprecated public void onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg) { if (mDelegate != null) { mDelegate.onTooManyRedirects(view, cancelMsg, continueMsg); return; } super.onTooManyRedirects(view, cancelMsg, continueMsg); } @Override @Deprecated public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { if (mDelegate != null) { mDelegate.onReceivedError(view, errorCode, description, failingUrl); return; } super.onReceivedError(view, errorCode, description, failingUrl); } @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { if (mDelegate != null) { mDelegate.onReceivedError(view, request, error); return; } super.onReceivedError(view, request, error); } @Override public void onReceivedHttpError( WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { if (mDelegate != null) { mDelegate.onReceivedHttpError(view, request, errorResponse); return; } super.onReceivedHttpError(view, request, errorResponse); } @Override public void onFormResubmission(WebView view, Message dontResend, Message resend) { if (mDelegate != null) { mDelegate.onFormResubmission(view, dontResend, resend); return; } super.onFormResubmission(view, dontResend, resend); } @Override public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { if (mDelegate != null) { mDelegate.doUpdateVisitedHistory(view, url, isReload); return; } super.doUpdateVisitedHistory(view, url, isReload); } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { if (mDelegate != null) { mDelegate.onReceivedSslError(view, handler, error); return; } super.onReceivedSslError(view, handler, error); } @Override public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { if (mDelegate != null) { mDelegate.onReceivedClientCertRequest(view, request); return; } super.onReceivedClientCertRequest(view, request); } @Override public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { if (mDelegate != null) { mDelegate.onReceivedHttpAuthRequest(view, handler, host, realm); return; } super.onReceivedHttpAuthRequest(view, handler, host, realm); } @Override public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { if (mDelegate != null) { return mDelegate.shouldOverrideKeyEvent(view, event); } return super.shouldOverrideKeyEvent(view, event); } @Override public void onUnhandledKeyEvent(WebView view, KeyEvent event) { if (mDelegate != null) { mDelegate.onUnhandledKeyEvent(view, event); return; } super.onUnhandledKeyEvent(view, event); } @Override public void onScaleChanged(WebView view, float oldScale, float newScale) { if (mDelegate != null) { mDelegate.onScaleChanged(view, oldScale, newScale); return; } super.onScaleChanged(view, oldScale, newScale); } @Override public void onReceivedLoginRequest(WebView view, String realm, String account, String args) { if (mDelegate != null) { mDelegate.onReceivedLoginRequest(view, realm, account, args); return; } super.onReceivedLoginRequest(view, realm, account, args); } } ================================================ FILE: agentweb-core/src/main/res/layout/agentweb_error_page.xml ================================================ ================================================ FILE: agentweb-core/src/main/res/values/colors.xml ================================================ #000000 #ffffff #2e2e32 ================================================ FILE: agentweb-core/src/main/res/values/ids.xml ================================================ ================================================ FILE: agentweb-core/src/main/res/values/strings.xml ================================================ The task already exists, do not repeat click to download! Note Wi-Fi disconnected. Continue the download via mobile data network? Download Cancel Download failed! Downloading:%s downloaded:%s You have a new notice Download Tap to continue Coming soon to download the file Camera Files Loading ... leaving %s and opening another app? Go away The selected file can not be larger than %s MB error~ The certificate authority is not trusted. The certificate has expired. The certificate Hostname mismatch. The certificate is not yet valid. SSL Certificate error. Do you want to continue anyway? SSL Certificate Error Continue ================================================ FILE: agentweb-core/src/main/res/values/style.xml ================================================ ================================================ FILE: agentweb-core/src/main/res/values-zh/strings.xml ================================================ 该任务已经存在 , 请勿重复点击下载! 提示 您正在使用手机流量 , 继续下载该文件吗? 下载 取消 下载失败! 当前进度:%s 已下载:%s 您有一条新通知 文件下载 点击打开 即将开始下载文件 相机 文件 加载中 ... 您需要离开%s前往其他应用吗? 离开 证书颁发机构不受信任, 选择的文件不能大于%sMB 出错啦! 点击空白处刷新 ~ 证书已过期, 证书主机名不匹配, 该证书尚未生效, SSL 认证错误, 你确定要访问该网页吗? SSL 认证错误 确定 ================================================ FILE: agentweb-core/src/main/res/xml/web_files_public.xml ================================================ ================================================ FILE: agentweb-core/src/test/java/com/just/agentweb/ExampleUnitTest.java ================================================ package com.just.agentweb; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: agentweb-filechooser/.gitignore ================================================ /build ================================================ FILE: agentweb-filechooser/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'maven-publish' android { compileSdk COMPILE_SDK_VERSION.toInteger() defaultConfig { minSdkVersion 14 namespace 'com.just.agentweb.filechooser' targetSdkVersion TARGET_SDK_VERSION.toInteger() versionCode 2 versionName VERSION_NAME testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions{ abortOnError false } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') testImplementation 'junit:junit:4.12' androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' }) testImplementation 'junit:junit:4.12' compileOnly 'com.google.android.material:material:1.0.0' compileOnly 'androidx.legacy:legacy-support-v4:1.0.0' implementation project(':agentweb-core') } afterEvaluate { publishing { publications { // Creates a Maven publication called "release". release(MavenPublication) { groupId = 'com.github.Justson.AgentWeb' artifactId = 'agentweb-filechooser' version = 'v5.0.7-androidx' } } } } ================================================ FILE: agentweb-filechooser/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keep class com.just.agentweb.** { *; } -dontwarn com.just.agentweb.** ================================================ FILE: agentweb-filechooser/src/androidTest/java/com/just/agentweb/filechooser/ExampleInstrumentedTest.java ================================================ package com.just.agentweb.filechooser; import android.content.Context; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumented test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.just.agentweb.filechooser.test", appContext.getPackageName()); } } ================================================ FILE: agentweb-filechooser/src/main/AndroidManifest.xml ================================================ ================================================ FILE: agentweb-filechooser/src/main/java/com/just/agentweb/filechooser/FileChooser.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb.filechooser; import static com.just.agentweb.AgentActionFragment.KEY_FROM_INTENTION; import static com.just.agentweb.AgentActionFragment.KEY_URI; import static com.just.agentweb.AgentActionFragment.start; import android.app.Activity; import android.content.ClipData; import android.content.ContentResolver; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; import androidx.annotation.NonNull; import com.just.agentweb.AbsAgentWebUIController; import com.just.agentweb.Action; import com.just.agentweb.AgentActionFragment; import com.just.agentweb.AgentWebConfig; import com.just.agentweb.AgentWebPermissions; import com.just.agentweb.AgentWebUtils; import com.just.agentweb.PermissionInterceptor; import org.json.JSONArray; import org.json.JSONObject; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; /** * @author cenxiaozhong * @date 2017/5/22 * @update 4.0.0 */ public class FileChooser { /** * Activity */ private final Activity mActivity; /** * ValueCallback */ private ValueCallback mUriValueCallback; /** * ValueCallback After LOLLIPOP */ private ValueCallback mUriValueCallbacks; /** * Activity Request Code */ public static final int REQUEST_CODE = 0x254; /** * WebChromeClient.FileChooserParams 封装了 Intent ,mAcceptType 等参数 */ private final WebChromeClient.FileChooserParams mFileChooserParams; /** * 如果是通过 JavaScript 打开文件选择器 ,那么 mJsChannelCallback 不能为空 */ private JsChannelCallback mJsChannelCallback; /** * 是否为Js Channel */ private boolean mJsChannel = false; /** * TAG */ private static final String TAG = FileChooser.class.getSimpleName(); /** * 当前 WebView */ private final WebView mWebView; /** * 是否为 Camera State */ private boolean mCameraState = false; /** * 是否调用摄像头后 调用的是摄像模式 默认是拍照 */ private boolean mVideoState = false; /** * 权限拦截 */ private final PermissionInterceptor mPermissionInterceptor; /** * FROM_INTENTION_CODE 用于表示当前Action */ private final int FROM_INTENTION_CODE = 21; /** * 当前 AbsAgentWebUIController */ private WeakReference mAgentWebUIController = null; /** * 选择文件类型 */ private String mAcceptType = "*/*"; /** * 修复某些特定手机拍照后,立刻获取照片为空的情况 */ public static int MAX_WAIT_PHOTO_MS = 8 * 1000; public FileChooser(Builder builder) { this.mActivity = builder.mActivity; this.mUriValueCallback = builder.mUriValueCallback; this.mUriValueCallbacks = builder.mUriValueCallbacks; this.mJsChannel = builder.mJsChannel; this.mFileChooserParams = builder.mFileChooserParams; if (this.mJsChannel) { this.mJsChannelCallback = JsChannelCallback.create(builder.mJsChannelCallback); } this.mWebView = builder.mWebView; this.mPermissionInterceptor = builder.mPermissionInterceptor; this.mAcceptType = builder.mAcceptType; this.mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(this.mWebView)); } public void openFileChooser() { if (!AgentWebUtils.isUIThread()) { AgentWebUtils.runInUiThread(new Runnable() { @Override public void run() { openFileChooser(); } }); return; } openFileChooserInternal(); } private void fileChooser() { List permission = null; if (AgentWebUtils.getDeniedPermissions(mActivity, AgentWebPermissions.MEDIA).isEmpty()) { chooserAction(); } else { Action mAction = Action.createPermissionsAction(AgentWebPermissions.MEDIA); mAction.setFromIntention(FROM_INTENTION_CODE >> 2); mAction.setPermissionListener(mPermissionListener); AgentActionFragment.start(mActivity, mAction); } } private void chooserAction() { Action mAction = new Action(); mAction.setAction(Action.ACTION_FILE); mAction.setChooserListener(getChooserListener()); try { mAction.setIntent(getFileChooserIntent()); AgentActionFragment.start(mActivity, mAction); } catch (Throwable throwable) { if (AgentWebConfig.DEBUG) { throwable.printStackTrace(); } } } private Intent getFileChooserIntent() { Intent mIntent = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mFileChooserParams != null && (mIntent = mFileChooserParams.createIntent()) != null) { // 多选 if (mFileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) { mIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } // mIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); if (mFileChooserParams.getAcceptTypes() != null && mFileChooserParams.getAcceptTypes().length > 1) { mIntent.putExtra(Intent.EXTRA_MIME_TYPES, mFileChooserParams.getAcceptTypes()); } if (Objects.equals(mIntent.getAction(), Intent.ACTION_GET_CONTENT)) { mIntent.setAction(Intent.ACTION_OPEN_DOCUMENT); } return mIntent; } Intent i = new Intent(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { i.setAction(Intent.ACTION_OPEN_DOCUMENT); } else { i.setAction(Intent.ACTION_GET_CONTENT); } i.addCategory(Intent.CATEGORY_OPENABLE); if (TextUtils.isEmpty(this.mAcceptType)) { i.setType("*/*"); } else { i.setType(this.mAcceptType); } i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); return mIntent = Intent.createChooser(i, ""); } private AgentActionFragment.ChooserListener getChooserListener() { return new AgentActionFragment.ChooserListener() { @Override public void onChoiceResult(int requestCode, int resultCode, Intent data) { onIntentResult(requestCode, resultCode, data); } }; } private void openFileChooserInternal() { boolean needVideo = false; // 在此支持视频拍摄 // 是否直接打开文件选择器 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && this.mFileChooserParams != null && this.mFileChooserParams.getAcceptTypes() != null) { boolean needCamera = false; String[] types = this.mFileChooserParams.getAcceptTypes(); for (String typeTmp : types) { if (TextUtils.isEmpty(typeTmp)) { continue; } if (typeTmp.contains("*/") || typeTmp.contains("image/")) { //这是拍照模式 needCamera = true; break; } if (typeTmp.contains("video/")) { //调用摄像机拍摄 这是录像模式 needCamera = true; mVideoState = true; } } if (!needCamera && !needVideo) { chooserAction(); return; } } if (!TextUtils.isEmpty(this.mAcceptType) && !this.mAcceptType.contains("*/") && !this.mAcceptType.contains("image/")) { chooserAction(); return; } if (this.mAgentWebUIController.get() != null) { this.mAgentWebUIController .get() .onSelectItemsPrompt(this.mWebView, mWebView.getUrl(), new String[]{mActivity.getString(com.just.agentweb.R.string.agentweb_camera), mActivity.getString(com.just.agentweb.R.string.agentweb_file_chooser)}, getCallBack()); } } private Handler.Callback getCallBack() { return new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what) { case 0: mCameraState = true; onCameraAction(); break; case 1: mCameraState = false; fileChooser(); break; default: cancel(); break; } return true; } }; } private void onCameraAction() { if (mActivity == null) { return; } if (mPermissionInterceptor != null) { if (mPermissionInterceptor.intercept(FileChooser.this.mWebView.getUrl(), AgentWebPermissions.CAMERA, "camera")) { cancel(); return; } } Action mAction = new Action(); List deniedPermissions = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !(deniedPermissions = checkNeedPermission()).isEmpty()) { mAction.setAction(Action.ACTION_PERMISSION); mAction.setPermissions(deniedPermissions.toArray(new String[]{})); mAction.setFromIntention(FROM_INTENTION_CODE >> 3); mAction.setPermissionListener(this.mPermissionListener); start(mActivity, mAction); } else { openCameraAction(); } } private List checkNeedPermission() { List deniedPermissions = new ArrayList<>(); if (!AgentWebUtils.hasPermission(mActivity, AgentWebPermissions.CAMERA)) { deniedPermissions.add(AgentWebPermissions.CAMERA[0]); } if (!AgentWebUtils.hasPermission(mActivity, AgentWebPermissions.MEDIA)) { deniedPermissions.addAll(Arrays.asList(AgentWebPermissions.MEDIA)); } return deniedPermissions; } private void openCameraAction() { Action mAction = new Action(); if (mVideoState) { //调用摄像 mAction.setAction(Action.ACTION_VIDEO); } else { mAction.setAction(Action.ACTION_CAMERA); } mAction.setChooserListener(this.getChooserListener()); AgentActionFragment.start(mActivity, mAction); } private AgentActionFragment.PermissionListener mPermissionListener = new AgentActionFragment.PermissionListener() { @Override public void onRequestPermissionsResult(@NonNull String[] permissions, @NonNull int[] grantResults, Bundle extras) { boolean tag = true; tag = AgentWebUtils.hasPermission(mActivity, Arrays.asList(permissions)) ? true : false; permissionResult(tag, extras.getInt(KEY_FROM_INTENTION)); } }; private void permissionResult(boolean grant, int fromIntention) { if (fromIntention == FROM_INTENTION_CODE >> 2) { if (grant) { chooserAction(); } else { cancel(); if (null != mAgentWebUIController.get()) { mAgentWebUIController .get() .onPermissionsDeny( AgentWebPermissions.MEDIA, AgentWebPermissions.ACTION_MEDIA, "Open file chooser"); } } } else if (fromIntention == FROM_INTENTION_CODE >> 3) { if (grant) { openCameraAction(); } else { cancel(); if (null != mAgentWebUIController.get()) { mAgentWebUIController .get() .onPermissionsDeny( AgentWebPermissions.CAMERA, AgentWebPermissions.ACTION_CAMERA, "Take photo"); } } } } public void onIntentResult(int requestCode, int resultCode, Intent data) { if (REQUEST_CODE != requestCode) { return; } //用户已经取消 if (resultCode == Activity.RESULT_CANCELED || data == null) { cancel(); return; } if (resultCode != Activity.RESULT_OK) { cancel(); return; } //通过Js获取文件 if (mJsChannel) { convertFileAndCallback(mCameraState ? new Uri[]{data.getParcelableExtra(KEY_URI)} : processData(data)); return; } //5.0以上系统通过input标签获取文件 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { aboveLollipopCheckFilesAndCallback(mCameraState ? new Uri[]{data.getParcelableExtra(KEY_URI)} : processData(data), mCameraState); return; } //4.4以下系统通过input标签获取文件 if (mUriValueCallback == null) { cancel(); return; } if (mCameraState) { // mUriValueCallback.onReceiveValue((Uri) data.getParcelableExtra(KEY_URI)); fileCompressAndValuesCallback((Uri) data.getParcelableExtra(KEY_URI), mUriValueCallback); mUriValueCallback = null; } else { belowLollipopUriCallback(data); } /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) aboveLollipopCheckFilesAndCallback(mCameraState ? new Uri[]{data.getParcelableExtra(KEY_URI)} : processData(data)); else if (mJsChannel) convertFileAndCallback(mCameraState ? new Uri[]{data.getParcelableExtra(KEY_URI)} : processData(data)); else { if (mCameraState && mUriValueCallback != null) mUriValueCallback.onReceiveValue((Uri) data.getParcelableExtra(KEY_URI)); else belowLollipopUriCallback(data); }*/ } private void cancel() { if (mJsChannel) { mJsChannelCallback.call(null); return; } if (mUriValueCallback != null) { try { mUriValueCallback.onReceiveValue(null); mUriValueCallback = null; } catch (Throwable ignored) { if (AgentWebConfig.DEBUG) { ignored.printStackTrace(); } } } if (mUriValueCallbacks != null) { try { mUriValueCallbacks.onReceiveValue(null); mUriValueCallbacks = null; } catch (Throwable ignored) { if (AgentWebConfig.DEBUG) { ignored.printStackTrace(); } } } return; } private void belowLollipopUriCallback(Intent data) { if (data == null) { if (mUriValueCallback != null) { mUriValueCallback.onReceiveValue(Uri.EMPTY); mUriValueCallback = null; } return; } Uri mUri = data.getData(); if (mUriValueCallback != null) { // mUriValueCallback.onReceiveValue(mUri); fileCompressAndValuesCallback(mUri, mUriValueCallback); mUriValueCallback = null; } } private Uri[] processData(Intent data) { Uri[] datas = null; if (data == null) { return datas; } String target = data.getDataString(); if (!TextUtils.isEmpty(target)) { return datas = new Uri[]{Uri.parse(target)}; } ClipData mClipData = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { mClipData = data.getClipData(); } if (mClipData != null && mClipData.getItemCount() > 0) { datas = new Uri[mClipData.getItemCount()]; for (int i = 0; i < mClipData.getItemCount(); i++) { ClipData.Item mItem = mClipData.getItemAt(i); datas[i] = mItem.getUri(); } } return datas; } private void convertFileAndCallback(final Uri[] uris) { String[] paths = null; if (uris == null || uris.length == 0 || (paths = AgentWebUtils.uriToPath(mActivity, uris)) == null || paths.length == 0) { mJsChannelCallback.call(null); return; } FileCompressor.getInstance().fileCompress("customize", uris, new ValueCallback() { @Override public void onReceiveValue(Uri[] value) { String[] compressFilePath = AgentWebUtils.uriToPath(mActivity, value); if (compressFilePath == null || compressFilePath.length == 0) { mJsChannelCallback.call(null); return; } int sum = 0; for (String path : compressFilePath) { if (TextUtils.isEmpty(path)) { continue; } File mFile = new File(path); if (!mFile.exists()) { continue; } sum += mFile.length(); } if (sum > AgentWebConfig.MAX_FILE_LENGTH) { if (mAgentWebUIController.get() != null) { mAgentWebUIController.get().onShowMessage(mActivity.getString(com.just.agentweb.R.string.agentweb_max_file_length_limit, (AgentWebConfig.MAX_FILE_LENGTH / 1024 / 1024) + ""), "convertFileAndCallback"); } mJsChannelCallback.call(null); return; } AsyncTask.THREAD_POOL_EXECUTOR.execute(new CovertFileThread(mJsChannelCallback, compressFilePath)); } }); } private static void fileCompressAndValuesCallback(final Uri[] datas, final ValueCallback valueCallback) { FileCompressor.getInstance().fileCompress("system", datas, new ValueCallback() { @Override public void onReceiveValue(Uri[] value) { if (valueCallback != null) { valueCallback.onReceiveValue(value); } } }); } private static void fileCompressAndValuesCallback(final Uri datas, final ValueCallback valueCallback) { FileCompressor.getInstance().fileCompress("system", new Uri[]{datas}, new ValueCallback() { @Override public void onReceiveValue(Uri[] value) { if (valueCallback != null) { if (value != null && value.length > 0) { valueCallback.onReceiveValue(value[0]); } else { valueCallback.onReceiveValue(Uri.EMPTY); } } } }); } /** * 经过多次的测试,在小米 MIUI , 华为 ,多部分为 Android 6.0 左右系统相机获取到的文件 * length为0 ,导致前端 ,获取到的文件, 作预览的时候不正常 ,等待5S左右文件又正常了 , 所以这里做了阻塞等待处理, * * @param datas * @param isCamera */ private void aboveLollipopCheckFilesAndCallback(final Uri[] datas, boolean isCamera) { if (mUriValueCallbacks == null) { return; } if (null != datas && datas.length > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { ContentResolver contentResolver = mActivity.getContentResolver(); final int takeFlags = (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); for (int i = 0; i < datas.length; i++) { try { contentResolver.takePersistableUriPermission(datas[i], takeFlags); } catch (Throwable throwable) { if (AgentWebConfig.DEBUG) { throwable.printStackTrace(); } } } } if (!isCamera) { fileCompressAndValuesCallback(datas == null ? new Uri[]{} : datas, mUriValueCallbacks); // mUriValueCallbacks.onReceiveValue(datas == null ? new Uri[]{} : datas); mUriValueCallbacks = null; return; } if (mAgentWebUIController.get() == null) { mUriValueCallbacks.onReceiveValue(null); mUriValueCallbacks = null; return; } String[] paths = AgentWebUtils.uriToPath(mActivity, datas); if (paths == null || paths.length == 0) { mUriValueCallbacks.onReceiveValue(null); mUriValueCallbacks = null; return; } final String path = paths[0]; mAgentWebUIController.get().onLoading(mActivity.getString(com.just.agentweb.R.string.agentweb_loading)); AsyncTask.THREAD_POOL_EXECUTOR.execute(new WaitPhotoRunnable(path, new AboveLCallback(mUriValueCallbacks, datas, mAgentWebUIController))); mUriValueCallbacks = null; } private static final class AboveLCallback implements Handler.Callback { private ValueCallback mValueCallback; private Uri[] mUris; private WeakReference controller; private AboveLCallback(ValueCallback valueCallbacks, Uri[] uris, WeakReference controller) { this.mValueCallback = valueCallbacks; this.mUris = uris; this.controller = controller; } @Override public boolean handleMessage(final Message msg) { AgentWebUtils.runInUiThread(new Runnable() { @Override public void run() { FileChooser.AboveLCallback.this.safeHandleMessage(msg); } }); return false; } private void safeHandleMessage(Message msg) { if (mValueCallback != null) { fileCompressAndValuesCallback(mUris, mValueCallback); // mValueCallback.onReceiveValue(mUris); } if (controller != null && controller.get() != null) { controller.get().onCancelLoading(); } } } private static final class WaitPhotoRunnable implements Runnable { private String path; private Handler.Callback mCallback; private WaitPhotoRunnable(String path, Handler.Callback callback) { this.path = path; this.mCallback = callback; } @Override public void run() { if (TextUtils.isEmpty(path) || !new File(path).exists()) { if (mCallback != null) { mCallback.handleMessage(Message.obtain(null, -1)); } return; } int ms = 0; while (ms <= MAX_WAIT_PHOTO_MS) { ms += 300; SystemClock.sleep(300); File mFile = new File(path); if (mFile.length() > 0) { if (mCallback != null) { mCallback.handleMessage(Message.obtain(null, 1)); mCallback = null; } break; } } if (ms > MAX_WAIT_PHOTO_MS) { if (mCallback != null) { mCallback.handleMessage(Message.obtain(null, -1)); } } mCallback = null; path = null; } } // 必须执行在子线程, 会阻塞直到文件转换完成; public static Queue convertFile(String[] paths) throws Exception { if (paths == null || paths.length == 0) { return null; } int tmp = Runtime.getRuntime().availableProcessors() + 1; int result = paths.length > tmp ? tmp : paths.length; Executor mExecutor = Executors.newFixedThreadPool(result); final Queue mQueue = new LinkedBlockingQueue<>(); CountDownLatch mCountDownLatch = new CountDownLatch(paths.length); int i = 1; for (String path : paths) { if (TextUtils.isEmpty(path)) { mCountDownLatch.countDown(); continue; } mExecutor.execute(new EncodeFileRunnable(path, mQueue, mCountDownLatch, i++)); } mCountDownLatch.await(); if (!((ThreadPoolExecutor) mExecutor).isShutdown()) { ((ThreadPoolExecutor) mExecutor).shutdownNow(); } return mQueue; } static class EncodeFileRunnable implements Runnable { private String filePath; private Queue mQueue; private CountDownLatch mCountDownLatch; private int id; public EncodeFileRunnable(String filePath, Queue queue, CountDownLatch countDownLatch, int id) { this.filePath = filePath; this.mQueue = queue; this.mCountDownLatch = countDownLatch; this.id = id; } @Override public void run() { InputStream is = null; ByteArrayOutputStream os = null; try { File mFile = new File(filePath); Log.e(TAG, "encode file:" + mFile.length()); if (mFile.exists()) { is = new FileInputStream(mFile); if (is == null) { return; } os = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int len; while ((len = is.read(b, 0, 1024)) != -1) { os.write(b, 0, len); } mQueue.offer(new FileParcel(id, mFile.getAbsolutePath(), Base64.encodeToString(os.toByteArray(), Base64.DEFAULT))); } else { } } catch (Throwable e) { e.printStackTrace(); } finally { AgentWebUtils.closeIO(is); AgentWebUtils.closeIO(os); mCountDownLatch.countDown(); } } } static String convertFileParcelObjectsToJson(Collection collection) { if (collection == null || collection.size() == 0) { return null; } Iterator mFileParcels = collection.iterator(); JSONArray mJSONArray = new JSONArray(); try { while (mFileParcels.hasNext()) { JSONObject jo = new JSONObject(); FileParcel mFileParcel = mFileParcels.next(); jo.put("contentPath", mFileParcel.getContentPath()); jo.put("fileBase64", mFileParcel.getFileBase64()); jo.put("mId", mFileParcel.getId()); mJSONArray.put(jo); } } catch (Throwable throwable) { throwable.printStackTrace(); } return mJSONArray + ""; } static class CovertFileThread implements Runnable { private WeakReference mJsChannelCallback; private String[] paths; private CovertFileThread(JsChannelCallback JsChannelCallback, String[] paths) { this.mJsChannelCallback = new WeakReference(JsChannelCallback); this.paths = paths; } @Override public void run() { String name = Thread.currentThread().getName(); Thread.currentThread().setName("agentweb-thread"); try { Queue mQueue = convertFile(paths); String result = convertFileParcelObjectsToJson(mQueue); if (mJsChannelCallback != null && mJsChannelCallback.get() != null) { mJsChannelCallback.get().call(result); } } catch (Exception e) { e.printStackTrace(); } finally { Thread.currentThread().setName(name); } } } static class JsChannelCallback { WeakReference callback = null; JsChannelCallback(Handler.Callback callback) { this.callback = new WeakReference(callback); } public static JsChannelCallback create(Handler.Callback callback) { return new JsChannelCallback(callback); } void call(String value) { if (this.callback != null && this.callback.get() != null) { this.callback.get().handleMessage(Message.obtain(null, "JsChannelCallback".hashCode(), value)); } } } public static Builder newBuilder(Activity activity, WebView webView) { return new Builder().setActivity(activity).setWebView(webView); } public static final class Builder { private Activity mActivity; private ValueCallback mUriValueCallback; private ValueCallback mUriValueCallbacks; private WebChromeClient.FileChooserParams mFileChooserParams; private boolean mJsChannel = false; private WebView mWebView; private PermissionInterceptor mPermissionInterceptor; private String mAcceptType = "*/*"; private Handler.Callback mJsChannelCallback; public Builder setAcceptType(String acceptType) { this.mAcceptType = acceptType; return this; } public Builder setPermissionInterceptor(PermissionInterceptor permissionInterceptor) { mPermissionInterceptor = permissionInterceptor; return this; } public Builder setActivity(Activity activity) { mActivity = activity; return this; } public Builder setUriValueCallback(ValueCallback uriValueCallback) { mUriValueCallback = uriValueCallback; mJsChannel = false; mUriValueCallbacks = null; return this; } public Builder setUriValueCallbacks(ValueCallback uriValueCallbacks) { mUriValueCallbacks = uriValueCallbacks; mUriValueCallback = null; mJsChannel = false; return this; } public Builder setFileChooserParams(WebChromeClient.FileChooserParams fileChooserParams) { mFileChooserParams = fileChooserParams; return this; } public Builder setJsChannelCallback(Handler.Callback jsChannelCallback) { this.mJsChannelCallback = jsChannelCallback; mJsChannel = true; mUriValueCallback = null; mUriValueCallbacks = null; return this; } public Builder setWebView(WebView webView) { mWebView = webView; return this; } public FileChooser build() { return new FileChooser(this); } } } ================================================ FILE: agentweb-filechooser/src/main/java/com/just/agentweb/filechooser/FileCompressor.java ================================================ package com.just.agentweb.filechooser; import android.net.Uri; import android.webkit.ValueCallback; import java.io.Serializable; /** * @author cenxiaozhong * @date 2021/11/26 * @since 1.0.0 */ public class FileCompressor implements Serializable { private static FileCompressor sInstance = null; private FileCompressEngine mFileCompressEngine; FileCompressor() { } public static final FileCompressor getInstance() { if (sInstance == null) { synchronized (FileCompressor.class) { if (sInstance == null) { sInstance = new FileCompressor(); } } } return sInstance; } public void registerFileCompressEngine(FileCompressEngine valueCallback) { this.mFileCompressEngine = valueCallback; } public void unregisterFileCompressEngine(FileCompressEngine valueCallback) { this.mFileCompressEngine = null; } void fileCompress(String type, Uri[] uri, ValueCallback callback) { if (mFileCompressEngine == null) { callback.onReceiveValue(uri); } else { mFileCompressEngine.compressFile(type, uri, callback); } } public interface FileCompressEngine { void compressFile(String type, Uri[] uri, ValueCallback callback); } } ================================================ FILE: agentweb-filechooser/src/main/java/com/just/agentweb/filechooser/FileParcel.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb.filechooser; import android.os.Parcel; import android.os.Parcelable; /** * @author cenxiaozhong * @date 2017/5/24 */ public class FileParcel implements Parcelable { private int mId; private String mContentPath; private String mFileBase64; protected FileParcel(Parcel in) { mId = in.readInt(); mContentPath = in.readString(); mFileBase64 = in.readString(); } public FileParcel(int id, String contentPath, String fileBase64) { this.mId = id; this.mContentPath = contentPath; this.mFileBase64 = fileBase64; } public static final Creator CREATOR = new Creator() { @Override public FileParcel createFromParcel(Parcel in) { return new FileParcel(in); } @Override public FileParcel[] newArray(int size) { return new FileParcel[size]; } }; public int getId() { return mId; } public void setId(int id) { this.mId = id; } public String getContentPath() { return mContentPath; } public void setContentPath(String contentPath) { this.mContentPath = contentPath; } public String getFileBase64() { return mFileBase64; } public void setFileBase64(String fileBase64) { this.mFileBase64 = fileBase64; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mId); dest.writeString(mContentPath); dest.writeString(mFileBase64); } @Override public String toString() { return "FileParcel{" + "mId=" + mId + ", mContentPath='" + mContentPath + '\'' + ", mFileBase64='" + mFileBase64 + '\'' + '}'; } } ================================================ FILE: agentweb-filechooser/src/main/res/values/strings.xml ================================================ ================================================ FILE: agentweb-filechooser/src/test/java/com/just/agentweb/filechooser/ExampleUnitTest.java ================================================ package com.just.agentweb.filechooser; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { mavenCentral() jcenter() google() maven { url "https://jitpack.io" } maven { url 'https://maven.google.com/' name 'Google' } } dependencies { classpath "com.android.tools.build:gradle:8.1.4" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20" } } allprojects { repositories { mavenCentral() maven { url "https://jitpack.io" } jcenter() google() maven { url 'https://maven.google.com/' name 'Google' } } } task clean(type: Delete) { delete rootProject.buildDir } //tasks.getByPath(":agentweb-core:mavenAndroidJavadocs").enabled = false //tasks.getByPath(":agentweb-download:mavenAndroidJavadocs").enabled = false //tasks.getByPath(":agentweb-filechooser:mavenAndroidJavadocs").enabled = false ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m COMPILE_SDK_VERSION=34 BUILD_TOOL_VERSION=34.0.0 SUPPORT_LIB_VERSION=34.0.0 TARGET_SDK_VERSION=34 VERSION_NAME="5.0.8" android.useAndroidX=true android.enableJetifier=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true #systemProp.http.proxyHost=127.0.0.1 #systemProp.http.proxyPort=1087 #systemProp.https.proxyHost=127.0.0.1 #systemProp.https.proxyPort=1087 android.injected.testOnly=false android.nonFinalResIds=false #org.gradle.java.home=/Applications/Android\ Studio.app/Contents/jbr/Contents/Home/ ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle onStart up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to onStart the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: jitpack.yml ================================================ jdk: - openjdk17 ================================================ FILE: releasenote.md ================================================ * v_5.0.0 更新 * ActionActivity 重构, 使用Fragment 替代 Activity,解决多进程使用问题 * 新增 WebRTC Sample * 新增 FileCompressor ,允许选择文件后对文件进行操作,如文件压缩,图片方向调整等 * DefaultWebClient#onReceivedSslError 添加默认处理 * 文件选择器开放多选 * fix #777 ,FileChooserParams.createIntent() 导致AcceptTypes丢失问题 * androidx Grade version upgrade to 7.0.2 * 新增 AgentWebCompat.setDataDirectorySuffix(context) 修复 Using WebView from more than one process 崩溃 * v_4.1.1 更新 * [#587](https://github.com/Justson/AgentWeb/pull/587) input 支持视屏拍摄 * [#614](https://github.com/Justson/AgentWeb/pull/614)修复上传文件选择的兼容性bug * 重构了Download * 最小SDK提升到了 14 * v_4.0.3 更新 * 部分手机下载过程中~声音一直响 [#523](https://github.com/Justson/AgentWeb/issues/523) * 抽离[Downloader](https://github.com/Justson/Downloader) * 放弃反射回调WebViewClient#methods,使用洋葱模型的Middleware代替 * v_4.0.2 更新 * 修复断点续传时进度计算错误 * 修复无法通过`Extra`关闭进度通知 * v_4.0.0 更新 * `AgentWeb` 拆分出 `AgentWeb-Download` 、 `AgentWeb-FileChooser` 、`AgentWeb-core` 三个库,用户可以按需选择 * 重新设计了 `AgentWeb-Download` * 删除了 `DownloadListener` 、`DefaultMsgConfig` 以及相关API * 旧废弃的API,4.0.0 直接删除,不在提供兼容 * 部分类和API重命名 * `Fragment`和`Activity`构建一致。[#227](https://github.com/Justson/AgentWeb/issues/227) * 从AgentWeb-core删除 `BaseAgentWebFragment`和`BaseAgentWebActivity` ,于Sample形式提供参考 * v_3.1.0 更新 * `WebProgress` 进度条动画更细腻 * 修复部分机型拍照文件大小为0情况 * 更新了`FileUpLoadChooserImpl` * v_3.0.0 更新 * 加入 `MiddlewareWebChromeBase` 中间件 ,支持多个 `WebChromeClient` * 加入 `MiddlewareWebClientBase`中间件 , 支持多个 `WebViewClient` * 加入了默认的错误页,并支持自定义错误页 * 加入 `AgentWebUIController` ,统一控制UI * 支持拦截未知的页面 * 支持调起其他应用 * v_2.0.1 更新 * 支持并行下载 , 修复 #114 #109 * v_2.0.0 更新 * 加入动态权限 * 拍照 * v_1.2.6 更新 * 修复Android 4.4以下布局错乱 * v_1.2.5 提示信息支持配置 * 提示信息支持配置 * v_1.2.4 更新 * 支持传入 IWebLayout ,支持下拉回弹,下拉刷新效果 * v_1.2.3 更新 * 新增下载结果回调 * v_1.2.2 更新 * 修复已知 Bug * v_1.2.1 更新 * 支持调起支付宝 , 微信支付 * v_1.2.0 更新 * 全面支持全屏视频 * v_1.1.2 更新 * 完善功能 ================================================ FILE: sample/.gitignore ================================================ /build map.txt ================================================ FILE: sample/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdk COMPILE_SDK_VERSION.toInteger() buildToolsVersion BUILD_TOOL_VERSION defaultConfig { applicationId "com.just.agentweb.sample" namespace 'com.just.agentweb.sample' minSdkVersion 19 targetSdkVersion TARGET_SDK_VERSION.toInteger() multiDexEnabled true versionCode 5 versionName VERSION_NAME testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } signingConfigs { release { storeFile file("./keystore/keystore.jks") storePassword "admin123" keyAlias "agentweb" keyPassword "admin123" } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { assets.srcDirs = ['src/main/assets', 'src/main/assets/'] } } lintOptions { abortOnError false checkReleaseBuilds false } lintOptions { abortOnError false } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } sourceSets { main { jniLibs.srcDirs = ['libs'] } } } dependencies { api fileTree(include: ['*.jar'], dir: 'libs') androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' }) api 'androidx.appcompat:appcompat:1.5.0' api 'com.google.android.material:material:1.6.1' // compile "com.android.support:support-v4:${SUPPORT_LIB_VERSION}" testImplementation 'junit:junit:4.12' implementation 'com.github.Justson:Downloader:v5.0.4-androidx' // api project(':agentweb-core') // api project(':agentweb-filechooser') implementation 'io.github.justson:agentweb-core:v5.1.1-androidx' implementation 'io.github.justson:agentweb-filechooser:v5.1.1-androidx' // debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.1' // releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' // testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' // compile files('libs/alipaysdk-20170922.jar') implementation 'us.feras.mdv:markdownview:1.1.0' implementation 'com.lcodecorex:tkrefreshlayout:1.0.7' implementation 'com.github.lzyzsd:jsbridge:1.0.4' implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.0.3' implementation 'com.scwang.smartrefresh:SmartRefreshHeader:1.0.3' implementation 'com.tencent.sonic:sdk:2.0.0' implementation 'com.coolindicator.sdk:coolindicator:1.0.0-beta' implementation 'com.squareup.picasso:picasso:2.71828' implementation 'com.github.Justson:flying-pigeon:v1.0.7' implementation 'com.github.Justson:dispatch-queue:v1.0.5' implementation('com.github.Ferfalk:SimpleSearchView:0.2.0', { exclude group: 'com.android.support' }) implementation 'top.zibin:Luban:1.1.8' } ================================================ FILE: sample/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/cenxiaozhong/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.create.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile #-keep public class * extends android.webkit.WebChromeClient{ #*; #} -keep class com.just.agentweb.** { *; } -dontwarn com.just.agentweb.** -keepclassmembers class com.just.agentweb.sample.common.AndroidInterface{ *; } -keepclassmembers class com.just.agentweb.sample.common.SonicJavaScriptInterface{ *; } -dontshrink -dontpreverify -dontoptimize -dontusemixedcaseclassnames -flattenpackagehierarchy -allowaccessmodification -printmapping map.txt -optimizationpasses 7 -verbose -keepattributes Exceptions,InnerClasses -dontskipnonpubliclibraryclasses -dontskipnonpubliclibraryclassmembers -ignorewarnings -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -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 java.lang.Throwable {*;} -keep public class * extends java.lang.Exception {*;} #-libraryjars -keep class com.alipay.android.app.IAlixPay{*;} -keep class com.alipay.android.app.IAlixPay$Stub{*;} -keep class com.alipay.android.app.IRemoteServiceCallback{*;} -keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;} -keep class com.alipay.sdk.app.PayTask{ public *;} -keep class com.alipay.sdk.app.AuthTask{ public *;} -keep class com.alipay.sdk.app.H5PayCallback { ; ; } -keep class com.alipay.android.phone.mrpc.core.** { *; } -keep class com.alipay.apmobilesecuritysdk.** { *; } -keep class com.alipay.mobile.framework.service.annotation.** { *; } -keep class com.alipay.mobilesecuritysdk.face.** { *; } -keep class com.alipay.tscenter.biz.rpc.** { *; } -keep class org.json.alipay.** { *; } -keep class com.alipay.tscenter.** { *; } -keep class com.ta.utdid2.** { *;} -keep class com.ut.device.** { *;} -keepclasseswithmembernames class * { native ; } -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet); } -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet, int); } -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } # adding this in to preserve line numbers so that the stack traces # can be remapped -renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable ================================================ FILE: sample/src/androidTest/java/com/just/agentweb/sample/ExampleInstrumentedTest.java ================================================ package com.just.agentweb.sample; import android.content.Context; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.just.library.agentweb", appContext.getPackageName()); } } ================================================ FILE: sample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: sample/src/main/assets/js_interaction/button.css ================================================ .button { color: #666; background-color: #EEE; border-color: #EEE; font-weight: 300; font-size: 16px; font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; text-decoration: none; text-align: center; line-height: 40px; height: 40px; padding: 0 40px; margin: 0; display: inline-block; appearance: none; cursor: pointer; border: none; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; -webkit-transition-property: all; transition-property: all; -webkit-transition-duration: .3s; transition-duration: .3s; /* * Disabled State * * The disabled state uses the class .disabled, is-disabled, * and the form attribute disabled="disabled". * The use of !important is only added because this is a state * that must be applied to all buttons when in a disabled state. */ } .button-glow { -webkit-animation-duration: 3s; animation-duration: 3s; -webkit-animation-iteration-count: infinite; animation-iteration-count: infinite; -webkit-animation-name: glowing; animation-name: glowing; } .button-rounded { border-radius: 4px; } /* * Base Colors * * Create colors for buttons * (.button-primary, .button-secondary, etc.) */ .button-primary, .button-primary-flat { background-color: #1B9AF7; border-color: #1B9AF7; color: #FFF; } /* * Border Buttons * * These buttons have no fill they only have a * border to define their hit target. */ .button-border, .button-border-thin, .button-border-thick { background: none; border-width: 2px; border-style: solid; line-height: 36px; } ================================================ FILE: sample/src/main/assets/js_interaction/hello.html ================================================
================================================ FILE: sample/src/main/assets/jsbridge/demo.html ================================================ js调用java

================================================ FILE: sample/src/main/assets/sms/sms.html ================================================ 电话短信邮件
电话短信邮件跳转

电话 短信 邮件 打开应用内部页面 打开微信

================================================ FILE: sample/src/main/assets/upload_file/event.js ================================================ // ---------- 事件绑定与删除绑定 ---------- // function bindEvent(element, eventName, func) { var events = element['the'+eventName]; //用于保存某个事件序列 if(!events) { //如果不存在一个序列,则创建它,并加入HTML标记当中的onEvent = function(){}形式的绑定 events = element['the'+eventName] = []; if (element['on'+eventName]) { events.push(element['on'+eventName]); } } //检测是否为重复绑定 for(var i=0; i= events.length) { events.push(func); } // 重新定义这个事件的执行方式 element['on'+eventName] = function(event) { event = event || (function() { //修复IE的事件对象 var e = window.event; e.preventDefault = function() { e.returnValue = false; } e.stopPropagation = function() { e.cancelBubble = true; } //根据需要继续修复 return e; })(); //顺序执行这些函数 for(var i=0; i 上传身份证

上传身份证

选择文件
================================================ FILE: sample/src/main/assets/upload_file/upload.css ================================================ .file { position: relative; display: inline-block; background: #D0EEFF; border: 1px solid #99D3F5; border-radius: 4px; padding: 4px 12px; overflow: hidden; color: #1E88C7; text-decoration: none; text-indent: 0; line-height: 20px; } .file input { position: absolute; font-size: 100px; right: 0; top: 0; opacity: 0; } .file:hover { background: #AADFFD; border-color: #78C3F3; color: #004974; text-decoration: none; } ================================================ FILE: sample/src/main/assets/upload_file/uploadfile.html ================================================ 上传身份证

上传身份证

选择文件
暂无视屏

上传视频

选择文件
================================================ FILE: sample/src/main/java/com/just/agentweb/sample/activity/AutoHidenToolbarActivity.java ================================================ package com.just.agentweb.sample.activity; import android.os.Bundle; import com.google.android.material.appbar.AppBarLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import android.view.KeyEvent; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.just.agentweb.AgentWeb; import com.just.agentweb.NestedScrollAgentWebView; import com.just.agentweb.sample.R; public class AutoHidenToolbarActivity extends AppCompatActivity implements View.OnClickListener { private AgentWeb mAgentWeb; private CoordinatorLayout main; private Toolbar toolbar; /** * 后退 */ private TextView btnBack; /** * 前进 */ private TextView btnForward; /** * 刷新 */ private TextView btnRefresh; /** * 菜单 */ private TextView btnMenu; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_auto_hiden_toolbar); initView(); setSupportActionBar(toolbar); NestedScrollAgentWebView webView = new NestedScrollAgentWebView(this); CoordinatorLayout.LayoutParams lp = new CoordinatorLayout.LayoutParams(-1, -1); lp.setBehavior(new AppBarLayout.ScrollingViewBehavior()); mAgentWeb = AgentWeb.with(this) .setAgentWebParent(main, 1, lp)//lp记得设置behavior属性 .useDefaultIndicator() .setWebView(webView) .createAgentWeb() .ready() .go("http://m.jd.com/"); } private void initView() { main = findViewById(R.id.main); toolbar = findViewById(R.id.toolbar); btnBack = (TextView) findViewById(R.id.btn_back); btnForward = (TextView) findViewById(R.id.btn_forward); btnRefresh = (TextView) findViewById(R.id.btn_refresh); btnMenu = (TextView) findViewById(R.id.btn_menu); btnBack.setOnClickListener(this); btnForward.setOnClickListener(this); btnRefresh.setOnClickListener(this); btnMenu.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_back: if (mAgentWeb.getWebCreator().getWebView().canGoBack()) { mAgentWeb.back(); } else { Toast.makeText(this, "无法后退", Toast.LENGTH_SHORT).show(); } break; case R.id.btn_forward: if (mAgentWeb.getWebCreator().getWebView().canGoForward()) { mAgentWeb.getWebCreator().getWebView().goForward(); } else { Toast.makeText(this, "无法前进", Toast.LENGTH_SHORT).show(); } break; case R.id.btn_refresh: mAgentWeb.getWebCreator().getWebView().reload(); break; default: Toast.makeText(this, "这是菜单选项", Toast.LENGTH_SHORT).show(); break; } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (mAgentWeb.handleKeyEvent(keyCode, event)) { return true; } return super.onKeyDown(keyCode, event); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/activity/BaseWebActivity.java ================================================ package com.just.agentweb.sample.activity; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.widget.LinearLayout; import android.widget.TextView; import com.just.agentweb.AgentWeb; import com.just.agentweb.DefaultWebClient; import com.just.agentweb.WebChromeClient; import com.just.agentweb.WebViewClient; import com.just.agentweb.sample.R; import com.just.agentweb.sample.widget.WebLayout; /** * Created by cenxiaozhong on 2017/5/26. *

* source code https://github.com/Justson/AgentWeb */ public class BaseWebActivity extends AppCompatActivity { protected AgentWeb mAgentWeb; private LinearLayout mLinearLayout; private Toolbar mToolbar; private TextView mTitleTextView; private AlertDialog mAlertDialog; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web); mLinearLayout = (LinearLayout) this.findViewById(R.id.container); mToolbar = (Toolbar) this.findViewById(R.id.toolbar); mToolbar.setTitleTextColor(Color.WHITE); mToolbar.setTitle(""); mTitleTextView = (TextView) this.findViewById(R.id.toolbar_title); this.setSupportActionBar(mToolbar); if (getSupportActionBar() != null) { // Enable the Up button getSupportActionBar().setDisplayHomeAsUpEnabled(true); } mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showDialog(); } }); long p = System.currentTimeMillis(); mAgentWeb = AgentWeb.with(this) .setAgentWebParent(mLinearLayout, new LinearLayout.LayoutParams(-1, -1)) .useDefaultIndicator() .setWebChromeClient(mWebChromeClient) .setWebViewClient(mWebViewClient) .setMainFrameErrorView(com.just.agentweb.R.layout.agentweb_error_page, -1) .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) .setWebLayout(new WebLayout(this)) .setOpenOtherPageWays(DefaultWebClient.OpenOtherPageWays.ASK)//打开其他应用时,弹窗咨询用户是否前往其他应用 .interceptUnkownUrl() //拦截找不到相关页面的Scheme .createAgentWeb() .ready() .go(getUrl()); //mAgentWeb.getUrlLoader().loadUrl(getUrl()); long n = System.currentTimeMillis(); Log.i("Info", "init used time:" + (n - p)); } private com.just.agentweb.WebViewClient mWebViewClient = new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { return super.shouldOverrideUrlLoading(view, request); } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { //do you work Log.i("Info", "BaseWebActivity onPageStarted"); } }; private com.just.agentweb.WebChromeClient mWebChromeClient = new WebChromeClient() { @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); if (mTitleTextView != null) { mTitleTextView.setText(title); } } }; public String getUrl() { return "https://m.jd.com/"; } private void showDialog() { if (mAlertDialog == null) { mAlertDialog = new AlertDialog.Builder(this) .setMessage("您确定要关闭该页面吗?") .setNegativeButton("再逛逛", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (mAlertDialog != null) { mAlertDialog.dismiss(); } } })// .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (mAlertDialog != null) { mAlertDialog.dismiss(); } BaseWebActivity.this.finish(); } }).create(); } mAlertDialog.show(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (mAgentWeb.handleKeyEvent(keyCode, event)) { return true; } return super.onKeyDown(keyCode, event); } @Override protected void onPause() { mAgentWeb.getWebLifeCycle().onPause(); super.onPause(); } @Override protected void onResume() { mAgentWeb.getWebLifeCycle().onResume(); super.onResume(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.i("Info", "onResult:" + requestCode + " onResult:" + resultCode); super.onActivityResult(requestCode, resultCode, data); } @Override protected void onDestroy() { super.onDestroy(); //mAgentWeb.destroy(); mAgentWeb.getWebLifeCycle().onDestroy(); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/activity/CommonActivity.java ================================================ package com.just.agentweb.sample.activity; import android.content.Intent; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import androidx.appcompat.app.AppCompatActivity; import android.view.KeyEvent; import android.widget.FrameLayout; import com.just.agentweb.sample.R; import com.just.agentweb.sample.common.FragmentKeyDown; import com.just.agentweb.sample.fragment.AgentWebFragment; import com.just.agentweb.sample.fragment.BounceWebFragment; import com.just.agentweb.sample.fragment.CustomIndicatorFragment; import com.just.agentweb.sample.fragment.CustomSettingsFragment; import com.just.agentweb.sample.fragment.CustomWebViewFragment; import com.just.agentweb.sample.fragment.JsAgentWebFragment; import com.just.agentweb.sample.fragment.JsbridgeWebFragment; import com.just.agentweb.sample.fragment.SmartRefreshWebFragment; import com.just.agentweb.sample.fragment.VasSonicFragment; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_BOUNCE_EFFACT; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_CUSTOM_PROGRESSBAR; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_CUSTOM_WEBVIEW_SETTINGS; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_CUTSTOM_WEBVIEW; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_FILE_DOWNLOAD; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_INPUT_TAG_PROBLEM; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_JSBRIDGE_SAMPLE; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_JS_JAVA_COMMUNICATION; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_JS_JAVA_COMUNICATION_UPLOAD_FILE; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_LINKS; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_MAP; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_PULL_DOWN_REFRESH; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_USE_IN_FRAGMENT; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_VASSONIC_SAMPLE; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_VIDEO_FULL_SCREEN; import static com.just.agentweb.sample.activity.MainActivity.FLAG_GUIDE_DICTIONARY_WEBRTC; import static com.just.agentweb.sample.sonic.SonicJavaScriptInterface.PARAM_CLICK_TIME; /** * Created by cenxiaozhong on 2017/5/23. * source code https://github.com/Justson/AgentWeb */ public class CommonActivity extends AppCompatActivity { private FrameLayout mFrameLayout; public static final String TYPE_KEY = "type_key"; private FragmentManager mFragmentManager; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_common); mFrameLayout = (FrameLayout) this.findViewById(R.id.container_framelayout); int key = getIntent().getIntExtra(TYPE_KEY, -1); mFragmentManager = this.getSupportFragmentManager(); openFragment(key); } private AgentWebFragment mAgentWebFragment; private void openFragment(int key) { FragmentTransaction ft = mFragmentManager.beginTransaction(); Bundle mBundle = null; switch (key) { /*Fragment 使用AgenWeb*/ case FLAG_GUIDE_DICTIONARY_USE_IN_FRAGMENT: //项目中请使用常量代替0 , 代码可读性更高 /*下载文件*/ case FLAG_GUIDE_DICTIONARY_FILE_DOWNLOAD: ft.add(R.id.container_framelayout, mAgentWebFragment = AgentWebFragment.getInstance(mBundle = new Bundle()), AgentWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "http://android.myapp.com/"); break; /*input标签上传文件*/ case FLAG_GUIDE_DICTIONARY_INPUT_TAG_PROBLEM: ft.add(R.id.container_framelayout, mAgentWebFragment = AgentWebFragment.getInstance(mBundle = new Bundle()), AgentWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "file:///android_asset/upload_file/uploadfile.html"); break; /*Js上传文件*/ case FLAG_GUIDE_DICTIONARY_JS_JAVA_COMUNICATION_UPLOAD_FILE: ft.add(R.id.container_framelayout, mAgentWebFragment = AgentWebFragment.getInstance(mBundle = new Bundle()), AgentWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "file:///android_asset/upload_file/jsuploadfile.html"); break; /*Js*/ case FLAG_GUIDE_DICTIONARY_JS_JAVA_COMMUNICATION: ft.add(R.id.container_framelayout, mAgentWebFragment = JsAgentWebFragment.getInstance(mBundle = new Bundle()), JsAgentWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "file:///android_asset/js_interaction/hello.html"); break; /*webrtc*/ case FLAG_GUIDE_DICTIONARY_WEBRTC: ft.add(R.id.container_framelayout, mAgentWebFragment = AgentWebFragment.getInstance(mBundle = new Bundle()), AgentWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "https://jeromeetienne.github.io/AR.js/three.js/examples/mobile-performance.html"); break; /*优酷全屏播放视屏*/ case FLAG_GUIDE_DICTIONARY_VIDEO_FULL_SCREEN: ft.add(R.id.container_framelayout, mAgentWebFragment = AgentWebFragment.getInstance(mBundle = new Bundle()), AgentWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "https://m.youku.com/alipay_video/id_XNTExMjg3Njg1Mg==.html?spm=a2hww.12630578.drawer1.dzj1_1"); // mBundle.putString(AgentWebFragment.URL_KEY, "https://v.qq.com/x/page/i0530nu6z1a.html"); break; /*淘宝自定义进度条*/ case FLAG_GUIDE_DICTIONARY_CUSTOM_PROGRESSBAR: ft.add(R.id.container_framelayout, mAgentWebFragment = CustomIndicatorFragment.getInstance(mBundle = new Bundle()), CustomIndicatorFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "https://m.taobao.com/?sprefer=sypc00"); break; /*豌豆荚*/ case FLAG_GUIDE_DICTIONARY_CUSTOM_WEBVIEW_SETTINGS: ft.add(R.id.container_framelayout, mAgentWebFragment = CustomSettingsFragment.getInstance(mBundle = new Bundle()), CustomSettingsFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "https://m.wandoujia.com/"); break; /*短信*/ case FLAG_GUIDE_DICTIONARY_LINKS: ft.add(R.id.container_framelayout, mAgentWebFragment = AgentWebFragment.getInstance(mBundle = new Bundle()), AgentWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "file:///android_asset/sms/sms.html"); break; /* 自定义 WebView */ case FLAG_GUIDE_DICTIONARY_CUTSTOM_WEBVIEW: ft.add(R.id.container_framelayout, mAgentWebFragment = CustomWebViewFragment.getInstance(mBundle = new Bundle()), CustomWebViewFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, ""); break; /*回弹效果*/ case FLAG_GUIDE_DICTIONARY_BOUNCE_EFFACT: ft.add(R.id.container_framelayout, mAgentWebFragment = BounceWebFragment.getInstance(mBundle = new Bundle()), BounceWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "http://m.mogujie.com/?f=mgjlm&ptp=_qd._cps______3069826.152.1.0"); break; /*JsBridge 演示*/ case FLAG_GUIDE_DICTIONARY_JSBRIDGE_SAMPLE: ft.add(R.id.container_framelayout, mAgentWebFragment = JsbridgeWebFragment.getInstance(mBundle = new Bundle()), JsbridgeWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "file:///android_asset/jsbridge/demo.html"); break; /*SmartRefresh 下拉刷新*/ case FLAG_GUIDE_DICTIONARY_PULL_DOWN_REFRESH: ft.add(R.id.container_framelayout, mAgentWebFragment = SmartRefreshWebFragment.getInstance(mBundle = new Bundle()), SmartRefreshWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "http://www.163.com/"); break; /*地图*/ case FLAG_GUIDE_DICTIONARY_MAP: ft.add(R.id.container_framelayout, mAgentWebFragment = AgentWebFragment.getInstance(mBundle = new Bundle()), AgentWebFragment.class.getName()); mBundle.putString(AgentWebFragment.URL_KEY, "https://map.baidu.com/mobile/webapp/index/index/#index/index/foo=bar/vt=map"); break; /*首屏秒开*/ case FLAG_GUIDE_DICTIONARY_VASSONIC_SAMPLE: ft.add(R.id.container_framelayout, mAgentWebFragment = VasSonicFragment.create(mBundle = new Bundle()), AgentWebFragment.class.getName()); mBundle.putLong(PARAM_CLICK_TIME, getIntent().getLongExtra(PARAM_CLICK_TIME, -1L)); mBundle.putString(AgentWebFragment.URL_KEY, "http://mc.vip.qq.com/demo/indexv3"); break; default: break; } ft.commit(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); //一定要保证 mAentWebFragemnt 回调 // mAgentWebFragment.onActivityResult(requestCode, resultCode, data); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { AgentWebFragment mAgentWebFragment = this.mAgentWebFragment; if (mAgentWebFragment != null) { FragmentKeyDown mFragmentKeyDown = mAgentWebFragment; if (mFragmentKeyDown.onFragmentKeyDown(keyCode, event)) { return true; } else { return super.onKeyDown(keyCode, event); } } return super.onKeyDown(keyCode, event); } @Override protected void onDestroy() { super.onDestroy(); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/activity/ContainerActivity.java ================================================ package com.just.agentweb.sample.activity; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.appcompat.app.AppCompatActivity; import com.just.agentweb.sample.R; import com.just.agentweb.sample.fragment.EasyWebFragment; /** * Created by cenxiaozhong on 2017/7/22. */ public class ContainerActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_common); Fragment mFragment=null; getSupportFragmentManager() .beginTransaction() .add(R.id.container_framelayout,mFragment= EasyWebFragment.getInstance(new Bundle()),EasyWebFragment.class.getName()) .show(mFragment) .commit(); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/activity/EasyWebActivity.java ================================================ package com.just.agentweb.sample.activity; import android.graphics.Color; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; import android.text.TextUtils; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; import android.widget.LinearLayout; import android.widget.TextView; import com.just.agentweb.sample.R; import com.just.agentweb.sample.base.BaseAgentWebActivity; /** * Created by cenxiaozhong on 2017/7/22. *

*/ public class EasyWebActivity extends BaseAgentWebActivity { private TextView mTitleTextView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web); LinearLayout mLinearLayout = (LinearLayout) this.findViewById(R.id.container); Toolbar mToolbar = (Toolbar) this.findViewById(R.id.toolbar); mToolbar.setTitleTextColor(Color.WHITE); mToolbar.setTitle(""); mTitleTextView = (TextView) this.findViewById(R.id.toolbar_title); this.setSupportActionBar(mToolbar); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); } mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EasyWebActivity.this.finish(); } }); } @NonNull @Override protected ViewGroup getAgentWebParent() { return (ViewGroup) this.findViewById(R.id.container); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (mAgentWeb != null && mAgentWeb.handleKeyEvent(keyCode, event)) { return true; } return super.onKeyDown(keyCode, event); } @Override protected int getIndicatorColor() { return Color.parseColor("#ff0000"); } @Override protected void setTitle(WebView view, String title) { super.setTitle(view, title); if (!TextUtils.isEmpty(title)) { if (title.length() > 10) { title = title.substring(0, 10).concat("..."); } } mTitleTextView.setText(title); } @Override protected int getIndicatorHeight() { return 3; } @Nullable @Override protected String getUrl() { return "https://www.baidu.com/"; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/activity/ExternalActivity.java ================================================ package com.just.agentweb.sample.activity; import android.os.Bundle; import androidx.annotation.Nullable; import android.util.Log; /** * @author cenxiaozhong * @date 2019-05-19 * @since 1.0.0 */ public class ExternalActivity extends WebActivity { public static final String TAG = ExternalActivity.class.getSimpleName(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public String getUrl() { String url = getIntent().getData().getQueryParameter("url"); Log.e(TAG, " url:" + url); return url; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/activity/MainActivity.java ================================================ package com.just.agentweb.sample.activity; import android.content.Intent; import android.graphics.Color; import android.os.Build; import android.os.Bundle; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import com.flyingpigeon.library.ServiceManager; import com.flyingpigeon.library.annotations.thread.MainThread; import com.just.agentweb.AgentWebConfig; import com.just.agentweb.sample.R; import com.just.agentweb.sample.api.Api; import com.just.agentweb.sample.common.GuideItemEntity; import com.just.agentweb.sample.fragment.AgentWebFragment; import static com.just.agentweb.sample.sonic.SonicJavaScriptInterface.PARAM_CLICK_TIME; /** * source code https://github.com/Justson/AgentWeb */ public class MainActivity extends AppCompatActivity { private ListView mListView; private Toolbar mToolbar; private TextView mTitleTextView; private static final String TAG = MainActivity.class.getSimpleName(); public static final int FLAG_GUIDE_DICTIONARY_USE_IN_ACTIVITY = 0x01; public static final int FLAG_GUIDE_DICTIONARY_USE_IN_FRAGMENT = FLAG_GUIDE_DICTIONARY_USE_IN_ACTIVITY << 1; public static final int FLAG_GUIDE_DICTIONARY_FILE_DOWNLOAD = FLAG_GUIDE_DICTIONARY_USE_IN_FRAGMENT << 1; public static final int FLAG_GUIDE_DICTIONARY_INPUT_TAG_PROBLEM = FLAG_GUIDE_DICTIONARY_FILE_DOWNLOAD << 1; public static final int FLAG_GUIDE_DICTIONARY_JS_JAVA_COMMUNICATION = FLAG_GUIDE_DICTIONARY_INPUT_TAG_PROBLEM << 1; public static final int FLAG_GUIDE_DICTIONARY_VIDEO_FULL_SCREEN = FLAG_GUIDE_DICTIONARY_JS_JAVA_COMMUNICATION << 1; public static final int FLAG_GUIDE_DICTIONARY_CUSTOM_PROGRESSBAR = FLAG_GUIDE_DICTIONARY_VIDEO_FULL_SCREEN << 1; public static final int FLAG_GUIDE_DICTIONARY_CUSTOM_WEBVIEW_SETTINGS = FLAG_GUIDE_DICTIONARY_CUSTOM_PROGRESSBAR << 1; public static final int FLAG_GUIDE_DICTIONARY_LINKS = FLAG_GUIDE_DICTIONARY_CUSTOM_WEBVIEW_SETTINGS << 1; public static final int FLAG_GUIDE_DICTIONARY_BOUNCE_EFFACT = FLAG_GUIDE_DICTIONARY_LINKS << 1; public static final int FLAG_GUIDE_DICTIONARY_JSBRIDGE_SAMPLE = FLAG_GUIDE_DICTIONARY_BOUNCE_EFFACT << 1; public static final int FLAG_GUIDE_DICTIONARY_EXTENDS_BASE_ACT = FLAG_GUIDE_DICTIONARY_JSBRIDGE_SAMPLE << 1; public static final int FLAG_GUIDE_DICTIONARY_EXTENDS_BASE_FRAG = FLAG_GUIDE_DICTIONARY_EXTENDS_BASE_ACT << 1; public static final int FLAG_GUIDE_DICTIONARY_PULL_DOWN_REFRESH = FLAG_GUIDE_DICTIONARY_EXTENDS_BASE_FRAG << 1; public static final int FLAG_GUIDE_DICTIONARY_MAP = FLAG_GUIDE_DICTIONARY_PULL_DOWN_REFRESH << 1; public static final int FLAG_GUIDE_DICTIONARY_VASSONIC_SAMPLE = FLAG_GUIDE_DICTIONARY_MAP << 1; public static final int FLAG_GUIDE_DICTIONARY_LINKAGE_WITH_TOOLBAR = FLAG_GUIDE_DICTIONARY_VASSONIC_SAMPLE << 1; public static final int FLAG_GUIDE_DICTIONARY_CUTSTOM_WEBVIEW = FLAG_GUIDE_DICTIONARY_LINKAGE_WITH_TOOLBAR << 1; public static final int FLAG_GUIDE_DICTIONARY_JS_JAVA_COMUNICATION_UPLOAD_FILE = FLAG_GUIDE_DICTIONARY_CUTSTOM_WEBVIEW << 1; public static final int FLAG_GUIDE_DICTIONARY_COMMON_FILE_DOWNLOAD = FLAG_GUIDE_DICTIONARY_JS_JAVA_COMUNICATION_UPLOAD_FILE << 1; public static final int FLAG_GUIDE_DICTIONARY_IPC = FLAG_GUIDE_DICTIONARY_COMMON_FILE_DOWNLOAD << 1; public static final int FLAG_GUIDE_DICTIONARY_WEBRTC = FLAG_GUIDE_DICTIONARY_IPC << 1; public static final GuideItemEntity[] datas = new GuideItemEntity[]{ new GuideItemEntity("Activity 使用 AgentWeb", FLAG_GUIDE_DICTIONARY_USE_IN_ACTIVITY), new GuideItemEntity("Fragment 使用 AgentWeb ", FLAG_GUIDE_DICTIONARY_USE_IN_FRAGMENT), new GuideItemEntity("IPC WebView独立进程", FLAG_GUIDE_DICTIONARY_IPC), new GuideItemEntity("WebRTC 使用", FLAG_GUIDE_DICTIONARY_WEBRTC), new GuideItemEntity("H5文件下载", FLAG_GUIDE_DICTIONARY_FILE_DOWNLOAD), new GuideItemEntity("input标签文件上传", FLAG_GUIDE_DICTIONARY_INPUT_TAG_PROBLEM), new GuideItemEntity("Js 通信文件上传,兼用Android 4.4Kitkat", FLAG_GUIDE_DICTIONARY_JS_JAVA_COMUNICATION_UPLOAD_FILE), new GuideItemEntity("Js 通信", FLAG_GUIDE_DICTIONARY_JS_JAVA_COMMUNICATION), new GuideItemEntity("Video 视频全屏播放", FLAG_GUIDE_DICTIONARY_VIDEO_FULL_SCREEN), new GuideItemEntity("自定义进度条", FLAG_GUIDE_DICTIONARY_CUSTOM_PROGRESSBAR), new GuideItemEntity("自定义设置", FLAG_GUIDE_DICTIONARY_CUSTOM_WEBVIEW_SETTINGS), new GuideItemEntity("电话 , 信息 , 邮件", FLAG_GUIDE_DICTIONARY_LINKS), new GuideItemEntity("自定义 WebView", FLAG_GUIDE_DICTIONARY_CUTSTOM_WEBVIEW), new GuideItemEntity("下拉回弹效果", FLAG_GUIDE_DICTIONARY_BOUNCE_EFFACT), new GuideItemEntity("Jsbridge 例子", FLAG_GUIDE_DICTIONARY_JSBRIDGE_SAMPLE), new GuideItemEntity("继承 BaseAgentWebActivity", FLAG_GUIDE_DICTIONARY_EXTENDS_BASE_ACT), new GuideItemEntity("继承 BaseAgentWebFragment", FLAG_GUIDE_DICTIONARY_EXTENDS_BASE_FRAG), new GuideItemEntity("SmartRefresh 下拉刷新", FLAG_GUIDE_DICTIONARY_PULL_DOWN_REFRESH), new GuideItemEntity("地图", FLAG_GUIDE_DICTIONARY_MAP), new GuideItemEntity("VasSonic 首屏秒开", FLAG_GUIDE_DICTIONARY_VASSONIC_SAMPLE), new GuideItemEntity("与ToolBar联动", FLAG_GUIDE_DICTIONARY_LINKAGE_WITH_TOOLBAR), new GuideItemEntity("原生文件下载", FLAG_GUIDE_DICTIONARY_COMMON_FILE_DOWNLOAD), }; @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mToolbar = (Toolbar) this.findViewById(R.id.toolbar); mToolbar.setTitleTextColor(Color.WHITE); mToolbar.setTitle(""); mTitleTextView = (TextView) this.findViewById(R.id.toolbar_title); mTitleTextView.setText("AgentWeb 使用指南"); this.setSupportActionBar(mToolbar); if (getSupportActionBar() != null) { // Enable the Up button getSupportActionBar().setDisplayHomeAsUpEnabled(true); } mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MainActivity.this.finish(); } }); mListView = (ListView) this.findViewById(R.id.listView); mListView.setAdapter(new MainAdapter()); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { doClick(position); } }); if (AgentWebConfig.DEBUG) { Log.i("Info", "Debug 模式"); } else { Log.i("Info", "release 模式"); } AgentWebConfig.debug(); ServiceManager.getInstance().publish(mApi); } private Api mApi = new Api() { @MainThread // default callback on the bind Thread , if you wanna it callback on mainThread , may be you should add MainThread annotation @Override public void onReady() { Log.e(TAG, "web process onReady, i am runing on main process , received web procecss onready signal."); } }; private void doClick(int position) { int index = datas[position].getGuideDictionary(); switch (index) { /* Activity agentWeb */ case FLAG_GUIDE_DICTIONARY_USE_IN_ACTIVITY: startActivity(new Intent(this, WebActivity.class)); break; case FLAG_GUIDE_DICTIONARY_USE_IN_FRAGMENT: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_USE_IN_FRAGMENT)); break; case FLAG_GUIDE_DICTIONARY_FILE_DOWNLOAD: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_FILE_DOWNLOAD)); break; case FLAG_GUIDE_DICTIONARY_WEBRTC: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_WEBRTC)); break; case FLAG_GUIDE_DICTIONARY_INPUT_TAG_PROBLEM: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_INPUT_TAG_PROBLEM)); break; case FLAG_GUIDE_DICTIONARY_JS_JAVA_COMUNICATION_UPLOAD_FILE: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_JS_JAVA_COMUNICATION_UPLOAD_FILE)); break; case FLAG_GUIDE_DICTIONARY_JS_JAVA_COMMUNICATION: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_JS_JAVA_COMMUNICATION)); break; case FLAG_GUIDE_DICTIONARY_VIDEO_FULL_SCREEN: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_VIDEO_FULL_SCREEN)); break; case FLAG_GUIDE_DICTIONARY_CUSTOM_PROGRESSBAR: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_CUSTOM_PROGRESSBAR)); break; case FLAG_GUIDE_DICTIONARY_CUSTOM_WEBVIEW_SETTINGS: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_CUSTOM_WEBVIEW_SETTINGS)); break; case FLAG_GUIDE_DICTIONARY_LINKS: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_LINKS)); break; case FLAG_GUIDE_DICTIONARY_CUTSTOM_WEBVIEW: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_CUTSTOM_WEBVIEW)); break; case FLAG_GUIDE_DICTIONARY_BOUNCE_EFFACT: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_BOUNCE_EFFACT)); break; case FLAG_GUIDE_DICTIONARY_JSBRIDGE_SAMPLE: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_JSBRIDGE_SAMPLE)); break; case FLAG_GUIDE_DICTIONARY_EXTENDS_BASE_ACT: startActivity(new Intent(this, EasyWebActivity.class)); break; case FLAG_GUIDE_DICTIONARY_EXTENDS_BASE_FRAG: startActivity(new Intent(this, ContainerActivity.class)); break; case FLAG_GUIDE_DICTIONARY_PULL_DOWN_REFRESH: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_PULL_DOWN_REFRESH)); break; case FLAG_GUIDE_DICTIONARY_MAP: startActivity(new Intent(this, CommonActivity.class) .putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_MAP)); break; case FLAG_GUIDE_DICTIONARY_VASSONIC_SAMPLE: startActivity(new Intent(this, CommonActivity.class).putExtra(CommonActivity.TYPE_KEY, FLAG_GUIDE_DICTIONARY_VASSONIC_SAMPLE) .putExtra(PARAM_CLICK_TIME, System.currentTimeMillis())); break; case FLAG_GUIDE_DICTIONARY_LINKAGE_WITH_TOOLBAR: startActivity(new Intent(this, AutoHidenToolbarActivity.class)); break; case FLAG_GUIDE_DICTIONARY_COMMON_FILE_DOWNLOAD: startActivity(new Intent(this, NativeDownloadActivity.class)); break; case FLAG_GUIDE_DICTIONARY_IPC: startActivity(new Intent(this, RemoteWebViewlActivity.class).putExtra(AgentWebFragment.URL_KEY, "https://m.vip.com/?source=www&jump_https=1")); break; default: break; } } @Override protected void onDestroy() { super.onDestroy(); ServiceManager.getInstance().unpublish(mApi); } public class MainAdapter extends BaseAdapter { @Override public int getCount() { return datas.length; } @Override public Object getItem(int position) { return datas[position]; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder mViewHolder; if (convertView == null) { mViewHolder = new ViewHolder(); View mView = MainActivity.this.getLayoutInflater().inflate(R.layout.listview_main, parent, false); mViewHolder.mTextView = (TextView) mView.findViewById(R.id.content); mView.setTag(mViewHolder); convertView = mView; } else { mViewHolder = (ViewHolder) convertView.getTag(); } mViewHolder.mTextView.setText(datas[position].getGuideTitle()); return convertView; } } class ViewHolder { TextView mTextView; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/activity/NativeDownloadActivity.java ================================================ package com.just.agentweb.sample.activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.download.library.DownloadException; import com.download.library.DownloadImpl; import com.download.library.DownloadListenerAdapter; import com.download.library.DownloadTask; import com.download.library.Downloader; import com.download.library.Extra; import com.just.agentweb.sample.R; import com.squareup.picasso.Picasso; import java.util.ArrayList; import java.util.Locale; //import com.download.library.DownloadException; //import com.download.library.DownloadImpl; //import com.download.library.DownloadListenerAdapter; //import com.download.library.DownloadTask; //import com.download.library.Downloader; //import com.download.library.Extra; //import com.download.library.Runtime; /** * @author ringle-android * @date 19-2-12 * @since 1.0.0 */ public class NativeDownloadActivity extends AppCompatActivity { private RecyclerView mRecyclerView; private Toolbar mToolbar; private TextView mTitleTextView; private ArrayList mDownloadTasks = new ArrayList(); private static final String TAG = NativeDownloadActivity.class.getSimpleName(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_native_download); createDatasource(); mToolbar = (Toolbar) this.findViewById(R.id.toolbar); mToolbar.setTitleTextColor(Color.WHITE); mToolbar.setTitle(""); mTitleTextView = (TextView) this.findViewById(R.id.toolbar_title); mTitleTextView.setText("原生下载"); mRecyclerView = this.findViewById(R.id.download_recyclerview); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mRecyclerView.setItemAnimator(new DefaultItemAnimator()); mRecyclerView.setAdapter(new NativeDownloadAdapter()); /*new Thread(new Runnable() { @Override public void run() { File file = DownloadImpl.getInstance().with(getApplicationContext()).url("http://shouji.360tpcdn.com/170918/93d1695d87df5a0c0002058afc0361f1/com.ss.android.article.news_636.apk").setDownloadingListener(new DownloadListenerAdapter() { @Override public void onProgress(String url, long downloaded, long length, long usedTime) { super.onProgress(url, downloaded, length, usedTime); Log.i(TAG, " downloaded:" + downloaded); } @Override public boolean onResult(Throwable throwable, Uri path, String url, Extra extra) { Log.i(TAG, "downloaded onResult isSuccess:" + (throwable == null) + " url:" + url + " Thread:" + Thread.currentThread().getName() + " uri:" + path.toString()); return super.onResult(throwable, path, url, extra); } }).get(); Log.i(TAG, " download success:" + ((File) file).length()); } }).start();*/ /*DownloadImpl.getInstance() .with(getApplicationContext()) .setEnableIndicator(true) .url("http://shouji.360tpcdn.com/170918/f7aa8587561e4031553316ada312ab38/com.tencent.qqlive_13049.apk") .enqueue(new DownloadListenerAdapter() { @Override public void onProgress(String url, long downloaded, long length, long usedTime) { super.onProgress(url, downloaded, length, usedTime); Log.i(TAG, " progress:" + downloaded + " url:" + url); } @Override public boolean onResult(Throwable throwable, Uri path, String url, Extra extra) { Log.i(TAG, " path:" + path + " url:" + url + " length:" + new File(path.getPath()).length()); return super.onResult(throwable, path, url, extra); } }); File file = new File(this.getCacheDir(), "测试.apk"); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } DownloadImpl.getInstance() .with(getApplicationContext()) .target(file) .url("http://shouji.360tpcdn.com/170918/93d1695d87df5a0c0002058afc0361f1/com.ss.android.article.news_636.apk") .enqueue(new DownloadListenerAdapter() { @Override public void onProgress(String url, long downloaded, long length, long usedTime) { super.onProgress(url, downloaded, length, usedTime); Log.i(TAG, " progress:" + downloaded + " url:" + url); } @Override public boolean onResult(Throwable throwable, Uri path, String url, Extra extra) { Log.i(TAG, " path:" + path + " url:" + url + " length:" + new File(path.getPath()).length()); return super.onResult(throwable, path, url, extra); } });*/ /* DownloadImpl.getInstance() .with(getApplicationContext()) .target(new File(Runtime.getInstance().getDir(this, true).getAbsolutePath() + "/" + "com.ss.android.article.news_636.apk"), this.getPackageName() + ".DownloadFileProvider")//自定义路径需指定目录和authority(FileContentProvide),需要相对应匹配才能启动通知,和自动打开文件 .setUniquePath(false)//是否唯一路径 .setForceDownload(true)//不管网络类型 .setRetry(4)//下载异常,自动重试,最多重试4次 .setBlockMaxTime(60000L) //以8KB位单位,默认60s ,如果60s内无法从网络流中读满8KB数据,则抛出异常 。 .setConnectTimeOut(10000L)//连接10超时 .addHeader("xx","cookie")//添加请求头 .setDownloadTimeOut(Long.MAX_VALUE)//下载最大时长 .setOpenBreakPointDownload(true)//打开断点续传 .setParallelDownload(true)//打开多线程下载 .autoOpenWithMD5("93d1695d87df5a0c0002058afc0361f1")//校验md5通过后自动打开该文件,校验失败会回调异常 // .autoOpenIgnoreMD5() // .closeAutoOpen() .quickProgress()//快速连续回调进度,默认1.2s回调一次 .url("http://shouji.360tpcdn.com/170918/93d1695d87df5a0c0002058afc0361f1/com.ss.android.article.news_636.apk") .enqueue(new DownloadListenerAdapter() { @Override public void onStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength, Extra extra) { super.onStart(url, userAgent, contentDisposition, mimetype, contentLength, extra); } @MainThread //加上该注解,自动回调到主线程 @Override public void onProgress(String url, long downloaded, long length, long usedTime) { super.onProgress(url, downloaded, length, usedTime); Log.i(TAG, " progress:" + downloaded + " url:" + url); } @Override public boolean onResult(Throwable throwable, Uri path, String url, Extra extra) { String md5 = Runtime.getInstance().md5(new File(path.getPath())); Log.i(TAG, " path:" + path + " url:" + url + " length:" + new File(path.getPath()).length() + " md5:" + md5 + " extra.getFileMD5:" + extra.getFileMD5()); return super.onResult(throwable, path, url, extra); } });*/ } private class NativeDownloadAdapter extends RecyclerView.Adapter { @NonNull @Override public NativeDownloadViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerview_item_download, viewGroup, false); return new NativeDownloadViewHolder(v); } @Override public void onBindViewHolder(@NonNull final NativeDownloadViewHolder nativeDownloadViewHolder, final int i) { final DownloadBean downloadBean = mDownloadTasks.get(i); Picasso.get().load(downloadBean.imageUrl) .resize(100, 100) .centerCrop(). transform(new RoundTransform(NativeDownloadActivity.this.getApplicationContext())) .into(nativeDownloadViewHolder.mIconIv); nativeDownloadViewHolder.mStatusButton.setEnabled(true); nativeDownloadViewHolder.mStatusButton.setTag(downloadBean); if (downloadBean.getTotalsLength() > 0L) { int mProgress = (int) ((downloadBean.getLoaded()) / Float.valueOf(downloadBean.getTotalsLength()) * 100); Log.e(TAG, "mProgress:" + mProgress + " position:" + i); nativeDownloadViewHolder.mProgressBar.setProgress(mProgress); nativeDownloadViewHolder.mCurrentProgress.setText("当前进度" + byte2FitMemorySize(downloadBean.getLoaded()) + "/" + byte2FitMemorySize(downloadBean.getTotalsLength()) + " 耗时:" + ((downloadBean.getUsedTime()) / 1000) + "s"); } else { nativeDownloadViewHolder.mProgressBar.setProgress(0); nativeDownloadViewHolder.mCurrentProgress.setText("当前进度,已下载:" + byte2FitMemorySize(downloadBean.getLoaded()) + " 耗时:" + ((downloadBean.getUsedTime()) / 1000) + "s"); } Log.e(TAG, "status:" + downloadBean.getStatus() + " position:" + i); if (downloadBean.getStatus() == DownloadTask.STATUS_NEW) { nativeDownloadViewHolder.mStatusButton.setText("开始"); } else if (downloadBean.getStatus() == DownloadTask.STATUS_PENDDING) { nativeDownloadViewHolder.mStatusButton.setText("等待中..."); nativeDownloadViewHolder.mStatusButton.setEnabled(false); } else if (downloadBean.getStatus() == DownloadTask.STATUS_PAUSED) { nativeDownloadViewHolder.mStatusButton.setText("继续"); } else if (downloadBean.getStatus() == DownloadTask.STATUS_DOWNLOADING) { nativeDownloadViewHolder.mStatusButton.setText("暂停"); } else if (downloadBean.getStatus() == DownloadTask.STATUS_CANCELED || downloadBean.getStatus() == DownloadTask.STATUS_ERROR) { nativeDownloadViewHolder.mStatusButton.setText("出错"); nativeDownloadViewHolder.mStatusButton.setEnabled(false); } else { nativeDownloadViewHolder.mStatusButton.setText("已完成"); nativeDownloadViewHolder.mStatusButton.setEnabled(false); } nativeDownloadViewHolder.mStatusButton.setOnClickListener(new View.OnClickListener() { long lastTime = SystemClock.elapsedRealtime(); @Override public void onClick(View v) { if (SystemClock.elapsedRealtime() - lastTime <= 500) { return; } lastTime = SystemClock.elapsedRealtime(); if (downloadBean.getStatus() == DownloadTask.STATUS_NEW) { nativeDownloadViewHolder.mStatusButton.setText("等待中..."); nativeDownloadViewHolder.mStatusButton.setEnabled(false); boolean isStarted = DownloadImpl.getInstance(getApplicationContext()).enqueue(downloadBean); if (!isStarted) { bindViewHolder(nativeDownloadViewHolder, i); } } else if (downloadBean.getStatus() == DownloadTask.STATUS_PENDDING) { } else if (downloadBean.getStatus() == DownloadTask.STATUS_DOWNLOADING) { DownloadTask downloadTask = DownloadImpl.getInstance(getApplicationContext()).pause(downloadBean.getUrl()); if (downloadTask != null) { nativeDownloadViewHolder.mStatusButton.setText("继续"); } else { bindViewHolder(nativeDownloadViewHolder, i); } } else if (downloadBean.getStatus() == DownloadTask.STATUS_PAUSED) { boolean isStarted = DownloadImpl.getInstance(getApplicationContext()).resume(downloadBean.getUrl()); nativeDownloadViewHolder.mStatusButton.setText("等待中..."); nativeDownloadViewHolder.mStatusButton.setEnabled(false); if (!isStarted) { bindViewHolder(nativeDownloadViewHolder, i); } } else if (downloadBean.getStatus() == DownloadTask.STATUS_CANCELED) { } else { nativeDownloadViewHolder.mStatusButton.setEnabled(false); nativeDownloadViewHolder.mStatusButton.setText("已完成"); } } }); downloadBean.setDownloadListenerAdapter(new DownloadListenerAdapter() { @Override public void onStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength, Extra extra) { nativeDownloadViewHolder.mStatusButton.setText("暂停"); nativeDownloadViewHolder.mStatusButton.setEnabled(true); Log.i(TAG, " isRunning:" + DownloadImpl.getInstance(getApplicationContext()).isRunning(url)); } @MainThread //回调到主线程,添加该注释 @Override public void onProgress(String url, long downloaded, long length, long usedTime) { if (nativeDownloadViewHolder.mStatusButton.getTag() != downloadBean) { Log.e(TAG, "onProgress item recycle"); return; } int mProgress = (int) ((downloaded) / Float.valueOf(length) * 100); Log.i(TAG, "onProgress:" + mProgress + " url:" + url + " Thread:" + Thread.currentThread().getName()); nativeDownloadViewHolder.mProgressBar.setProgress(mProgress); if (length <= 0) { nativeDownloadViewHolder.mCurrentProgress.setText("当前进度,已下载:" + byte2FitMemorySize(downloaded) + " 耗时:" + ((downloadBean.getUsedTime()) / 1000) + "s"); } else { nativeDownloadViewHolder.mCurrentProgress.setText("当前进度" + byte2FitMemorySize(downloaded) + "/" + byte2FitMemorySize(length) + " 耗时:" + ((downloadBean.getUsedTime()) / 1000) + "s"); } } @Override public boolean onResult(Throwable throwable, Uri uri, String url, Extra extra) { if (nativeDownloadViewHolder.mStatusButton.getTag() != downloadBean) { Log.e(TAG, "item recycle"); return super.onResult(throwable, uri, url, extra); } Log.i(TAG, "onResult isSuccess:" + (throwable == null) + " url:" + url + " Thread:" + Thread.currentThread().getName() + " uri:" + uri.toString() + " isPaused:" + DownloadImpl.getInstance(getApplicationContext()).isPaused(url)); nativeDownloadViewHolder.mStatusButton.setEnabled(false); if (throwable == null) { nativeDownloadViewHolder.mStatusButton.setText("已完成"); } else if (throwable instanceof DownloadException) { DownloadException downloadException = (DownloadException) throwable; if (downloadException.getCode() == Downloader.ERROR_USER_PAUSE) { nativeDownloadViewHolder.mStatusButton.setText("继续"); nativeDownloadViewHolder.mStatusButton.setEnabled(true); } else { nativeDownloadViewHolder.mStatusButton.setText("出错"); } Toast.makeText(NativeDownloadActivity.this, downloadException.getMsg(), 1).show(); } return super.onResult(throwable, uri, url, extra); } }); } @Override public int getItemCount() { return mDownloadTasks.size(); } } private static String byte2FitMemorySize(final long byteNum) { if (byteNum < 0) { return ""; } else if (byteNum < 1024) { return String.format(Locale.getDefault(), "%.1fB", (double) byteNum); } else if (byteNum < 1048576) { return String.format(Locale.getDefault(), "%.1fKB", (double) byteNum / 1024); } else if (byteNum < 1073741824) { return String.format(Locale.getDefault(), "%.1fMB", (double) byteNum / 1048576); } else { return String.format(Locale.getDefault(), "%.1fGB", (double) byteNum / 1073741824); } } private class NativeDownloadViewHolder extends RecyclerView.ViewHolder { ProgressBar mProgressBar; Button mStatusButton; ImageView mIconIv; private final TextView mCurrentProgress; public NativeDownloadViewHolder(@NonNull View itemView) { super(itemView); mIconIv = itemView.findViewById(R.id.icon_iv); mStatusButton = itemView.findViewById(R.id.start_button); mProgressBar = itemView.findViewById(R.id.progressBar); mProgressBar.setMax(100); mCurrentProgress = itemView.findViewById(R.id.current_progress); } } public static class DownloadBean extends DownloadTask { public String title; public String imageUrl; public DownloadBean(String title, String imageUrl, String url) { this.title = title; this.imageUrl = imageUrl; this.mUrl = url; } @Override protected DownloadBean setDownloadListenerAdapter(DownloadListenerAdapter downloadListenerAdapter) { return (DownloadBean) super.setDownloadListenerAdapter(downloadListenerAdapter); } @Override public DownloadBean setUrl(String url) { return (DownloadBean) super.setUrl(url); } @Override public DownloadBean setContext(Context context) { return (DownloadBean) super.setContext(context); } @Override public DownloadBean setEnableIndicator(boolean enableIndicator) { return (DownloadBean) super.setEnableIndicator(enableIndicator); } @Override public DownloadBean setRetry(int retry) { return (DownloadBean) super.setRetry(retry); } @Override public DownloadBean setQuickProgress(boolean quickProgress) { return (DownloadBean) super.setQuickProgress(quickProgress); } @Override public DownloadBean autoOpenIgnoreMD5() { return (DownloadBean) super.autoOpenIgnoreMD5(); } } public static class RoundTransform implements com.squareup.picasso.Transformation { private Context mContext; public RoundTransform(Context context) { mContext = context; } @Override public Bitmap transform(Bitmap source) { int widthLight = source.getWidth(); int heightLight = source.getHeight(); int radius = dp2px(mContext, 8); // 圆角半径 Bitmap output = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); Paint paintColor = new Paint(); paintColor.setFlags(Paint.ANTI_ALIAS_FLAG); RectF rectF = new RectF(new Rect(0, 0, widthLight, heightLight)); canvas.drawRoundRect(rectF, radius, radius, paintColor); // canvas.drawRoundRect(rectF, widthLight / 5, heightLight / 5, paintColor); Paint paintImage = new Paint(); paintImage.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)); canvas.drawBitmap(source, 0, 0, paintImage); source.recycle(); return output; } @Override public String key() { return "roundcorner"; } } public void createDatasource() { DownloadBean downloadBean = new DownloadBean("QQ", "http://p18.qhimg.com/dr/72__/t0111cb71dabfd83b21.png", "https://d71329e5c0be6cdc2b46d0df2b4bd841.dd.cdntips.com/imtt.dd.qq.com/16891/apk/06AB1F5B0A51BEFD859B2B0D6B9ED9D9.apk?mkey=5d47b9f223f7bc0d&f=1806&fsname=com.tencent.mobileqq_8.1.0_1232.apk&csr=1bbd&cip=35.247.154.248&proto=https"); downloadBean.setQuickProgress(true); downloadBean.setRetry(4); downloadBean.setContext(this.getApplicationContext()); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("支付宝", "http://p18.qhimg.com/dr/72__/t01a16bcd9acd07d029.png", "http://shouji.360tpcdn.com/170919/e7f5386759129f378731520a4c953213/com.eg.android.AlipayGphone_115.apk"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("UC", "http://p19.qhimg.com/dr/72__/t01195d02b486ef8ebe.png", "http://shouji.360tpcdn.com/170919/9f1c0f93a445d7d788519f38fdb3de77/com.UCMobile_704.apk"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("腾讯视频", "http://p18.qhimg.com/dr/72__/t01ed14e0ab1a768377.png", "http://shouji.360tpcdn.com/170918/f7aa8587561e4031553316ada312ab38/com.tencent.qqlive_13049.apk"); downloadBean.setContext(this.getApplicationContext()); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("头条", "http://p15.qhimg.com/dr/72__/t013d31024ae54d9c35.png", "http://shouji.360tpcdn.com/170918/93d1695d87df5a0c0002058afc0361f1/com.ss.android.article.news_636.apk"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("应用宝", "https://pp.myapp.com/ma_icon/0/icon_5848_1565090584/96", "http://imtt.dd.qq.com/16891/myapp/channel_78665107_1000047_48e7227d3afeb842447c73c4b7af2509.apk?hsr=5848"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("附近越爱", "https://pp.myapp.com/ma_icon/0/icon_52396134_1563435176/96", "https://wxz.myapp.com/16891/apk/66339C385B32951E838F89AFDBB8AFBF.apk?fsname=com.wangjiang.fjya_5.6.3_98.apk&hsr=4d5s"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("白菜二手车", "https://pp.myapp.com/ma_icon/0/icon_52728407_1565231751/96", "http://imtt.dd.qq.com/16891/myapp/channel_78665107_1000047_48e7227d3afeb842447c73c4b7af2509.apk?hsr=5848&fsname=YYB.998886.dad220fda3959275efcb77f06835b974.1000047.apk"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("老鹰抓小鸡", "https://pp.myapp.com/ma_icon/0/icon_12097212_1555095310/96", "http://183.235.254.177/cache/112.29.208.41/imtt.dd.qq.com/16891/myapp/channel_78665107_1000047_48e7227d3afeb842447c73c4b7af2509.apk?mkey=5d5016b578e7f75c&f=184b&hsr=5848&fsname=YYB.998886.2e4a1c0f5a55b75a2e7a10c0b53a3491.1000047.apk&cip=120.231.209.169&proto=http&ich_args2=6-11231103023581_c2af2d3056e749ee2654202c210b6535_10004303_9c896229d2c0f7d3903d518939a83798_e03b546f591096a2b6182b487572fb16"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("2345手机助手", "https://pp.myapp.com/ma_icon/0/icon_10427994_1565164413/96", "https://wxz.myapp.com/16891/apk/14004450452AC52D15749001DBD0E4EA.apk?fsname=com.market2345_7.0_115.apk&hsr=4d5s"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("随手借", "https://pp.myapp.com/ma_pic2/0/shot_12170461_3_1564367665/550", "https://fb187cdbcc69278c9f1e6ce8e7257596.dd.cdntips.com/wxz.myapp.com/16891/apk/B505BB2B5D831592D5E190BAD5E66CCA.apk?mkey=5d50161b78e7f75c&f=1026&fsname=audaque.SuiShouJie_4.11.11_49.apk&hsr=4d5s&cip=120.231.209.169&proto=https"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("极光单词", "https://pp.myapp.com/ma_pic2/0/shot_52835037_1_1564713577/550", "https://6b7e49d6fab5c817409329478a000160.dd.cdntips.com/wxz.myapp.com/16891/apk/C721DE2D7E4538772FA98C1E9830F92F.apk?mkey=5d5017df78e7f75c&f=9870&fsname=com.qingclass.jgdc_2.0.4_9.apk&hsr=4d5s&cip=120.231.209.169&proto=https"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("帮帮测", "https://pp.myapp.com/ma_pic2/0/shot_52499136_3_1561616032/550", "https://fb187cdbcc69278c9f1e6ce8e7257596.dd.cdntips.com/wxz.myapp.com/16891/5571F5786B8E9F15058BE615B419A28B.apk?mkey=5d50176c78e7f75c&f=8ea4&fsname=com.bangbangce.mm_4.1.4_3104.apk&hsr=4d5s&cip=120.231.209.169&proto=https"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("速贷之家", "https://pp.myapp.com/ma_pic2/0/shot_42330202_2_1564649857/550", "https://3e25603914f997244c41c1ed7fbedfb5.dd.cdntips.com/wxz.myapp.com/16891/apk/7AADD4A8C9D404FB97378EA3CA2E69E6.apk?mkey=5d50172c78e7f75c&f=184b&fsname=com.yeer.sdzj_3.2.8_328.apk&hsr=4d5s&cip=120.231.209.169&proto=https"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("中原消费金融", "https://pp.myapp.com/ma_pic2/0/shot_52471681_2_1565161792/550", "https://f437b8a1a8be40951a91f58666e659d0.dd.cdntips.com/wxz.myapp.com/16891/apk/B1C6CC0DB7D412DA47A3A446E28D9C09.apk?mkey=5d5014fb78e7f75c&f=24c5&fsname=com.hnzycfc.zyxj_3.0.1_52.apk&hsr=4d5s&cip=120.231.209.169&proto=https"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("店长直聘", "https://pp.myapp.com/ma_icon/0/icon_12216213_1564373730/96", "https://f437b8a1a8be40951a91f58666e659d0.dd.cdntips.com/wxz.myapp.com/16891/apk/FA29D09A6CD550DCBEBC1D89EA392109.apk?mkey=5d5014b478e7f75c&f=1849&fsname=com.hpbr.directhires_4.31_403010.apk&hsr=4d5s&cip=120.231.209.169&proto=https"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("淘卷吧", "https://pp.myapp.com/ma_icon/0/icon_42320744_1564583832/96", "https://11473001bb572df6cb60e7e0821a4586.dd.cdntips.com/wxz.myapp.com/16891/apk/4AA997287EEA4A96C2DFD97CEE0180AD.apk?mkey=5d50148f78e7f75c&f=24c5&fsname=com.ciyun.oneshop_7.07_69.apk&hsr=4d5s&cip=120.231.209.169&proto=https"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("本地寻爱", "https://pp.myapp.com/ma_icon/0/icon_53268261_1564479560/96", "https://ce7ce9c885b5c04b6771ea454e096946.dd.cdntips.com/wxz.myapp.com/16891/apk/AAB98D7BDAFB390FA4D37F6CBD910992.apk?mkey=5d50142d78e7f75c&f=07b4&fsname=com.kaitai.bdxa_5.6.3_98.apk&hsr=4d5s&cip=120.231.209.169&proto=https"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("360借条", "https://pp.myapp.com/ma_icon/0/icon_42379225_1564124706/96", "https://e2983106ebfb9f560ff3a8e230faa981.dd.cdntips.com/wxz.myapp.com/16891/apk/DEB654116EC627ABA4DB12A6E777EAAD.apk?mkey=5d5015d578e7f75c&f=1026&fsname=com.qihoo.loan_1.5.4_213.apk&hsr=4d5s&cip=120.231.209.169&proto=https"); downloadBean.setContext(this.getApplicationContext()); downloadBean.setEnableIndicator(false); downloadBean.setQuickProgress(true); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("淘宝", "https://pp.myapp.com/ma_icon/0/icon_5080_1564463763/96", "http://shouji.360tpcdn.com/170901/ec1eaad9d0108b30d8bd602da9954bb7/com.taobao.taobao_161.apk"); downloadBean.setContext(this.getApplicationContext()); mDownloadTasks.add(downloadBean); //http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx?0.04400023248109086 downloadBean = new DownloadBean("分块传输,图片", "http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx?0.04400023248109086", "http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx?0.04400023248109086"); downloadBean.setContext(this.getApplicationContext()); downloadBean.autoOpenIgnoreMD5(); mDownloadTasks.add(downloadBean); downloadBean = new DownloadBean("也爱直播", "https://pp.myapp.com/ma_icon/0/icon_10472625_1555686747/96", "https://a46fefcd092f5f917ed1ee349b85d3b7.dd.cdntips.com/wxz.myapp.com/16891/F9B7FA7EC195FC453AE9082F826E6B28.apk?mkey=5d4c6bdc78e5058d&f=1806&fsname=com.tiange.hz.paopao8_4.4.1_441.apk&hsr=4d5s&cip=120.229.35.120&proto=https"); downloadBean.setContext(this.getApplicationContext()); downloadBean.autoOpenIgnoreMD5().setQuickProgress(true); mDownloadTasks.add(downloadBean); // } public static int dp2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } @Override protected void onDestroy() { super.onDestroy(); DownloadImpl.getInstance(getApplicationContext()).cancelAll(); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/activity/RemoteWebViewlActivity.java ================================================ package com.just.agentweb.sample.activity; import android.os.Bundle; import androidx.annotation.Nullable; import android.util.Log; import com.flyingpigeon.library.Pigeon; import com.flyingpigeon.library.ServiceManager; import com.flyingpigeon.library.annotations.Route; import com.flyingpigeon.library.annotations.thread.MainThread; import com.just.agentweb.sample.api.Api; import com.just.agentweb.sample.provider.ServiceProvider; import com.queue.library.GlobalQueue; /** * @author cenxiaozhong * @since 1.0.0 */ public class RemoteWebViewlActivity extends WebActivity { public static final String TAG = RemoteWebViewlActivity.class.getSimpleName(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ServiceManager.getInstance().publish(this); GlobalQueue.getMainQueue().postRunnable(new Runnable() { @Override public void run() { sayYes(); } }, 500); } private void sayYes() { Pigeon pigeon = Pigeon.newBuilder(this.getApplicationContext()).setAuthority(ServiceProvider.class).build(); Api api = pigeon.create(Api.class); api.onReady(); } @Override public String getUrl() { String url = getIntent().getStringExtra("url_key"); Log.e(TAG, " url:" + url); return url; } /** * follow this , you could invoke this method anywhere * * Pigeon pigeon = Pigeon.newBuilder(this.getApplicationContext()).setAuthority("WebServiceProvider.class").build(); * pigeon.route("/load/newUrl").withString("url_key", "http://baidu.com").fly(); * @param in */ @Route("/load/newUrl") @MainThread public void loadNewUrl(Bundle in) { mAgentWeb.getUrlLoader().loadUrl(in.getString("url_key")); } @Override protected void onDestroy() { super.onDestroy(); ServiceManager.getInstance().unpublish(this); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/activity/WebActivity.java ================================================ package com.just.agentweb.sample.activity; /** * Created by cenxiaozhong on 2017/5/22. *

* */ public class WebActivity extends BaseWebActivity { @Override public String getUrl() { return super.getUrl(); } @Override protected void onStart() { super.onStart(); } @Override protected void onResume() { super.onResume(); //测试Cookies /*try { String targetUrl=""; Log.i("Info","cookies:"+ AgentWebConfig.getCookiesByUrl(targetUrl="http://www.jd.com")); AgentWebConfig.removeAllCookies(new ValueCallback() { @Override public void onReceiveValue(Boolean value) { Log.i("Info","onResume():"+value); } }); String tagInfo=AgentWebConfig.getCookiesByUrl(targetUrl); Log.i("Info","tag:"+tagInfo); AgentWebConfig.syncCookie("http://www.jd.com","ID=IDHl3NVU0N3ltZm9OWHhubHVQZW1BRThLdGhLaFc5TnVtQWd1S2g1REcwNVhTS3RXQVFBQEBFDA984906B62C444931EA0"); String tag=AgentWebConfig.getCookiesByUrl(targetUrl); Log.i("Info","tag:"+tag); AgentWebConfig.removeSessionCookies(); Log.i("Info","removeSessionCookies:"+AgentWebConfig.getCookiesByUrl(targetUrl)); } catch (Exception e){ e.printStackTrace(); }*/ } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/api/Api.java ================================================ package com.just.agentweb.sample.api; /** * @author xiaozhongcen * @date 20-8-18 * @since 1.0.0 */ public interface Api { void onReady(); } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/app/App.java ================================================ package com.just.agentweb.sample.app; import android.app.Application; import android.content.Context; import android.content.Intent; import com.just.agentweb.AgentWebCompat; import com.just.agentweb.sample.service.WebService; import com.queue.library.GlobalQueue; /** * Created by cenxiaozhong on 2017/5/23. * source code https://github.com/Justson/AgentWeb */ public class App extends Application { @Override public void onCreate() { super.onCreate(); /** * 说明, WebView 初处初始化耗时 250ms 左右。 * 提前初始化WebView ,好处可以提升页面初始化速度,减少白屏时间, * 坏处,拖慢了App 冷启动速度,如果 WebView 配合 VasSonic 使用, * 建议不要在此处提前初始化 WebView 。 */ // WebView mWebView=new WebView(new MutableContextWrapper(this)); // if (LeakCanary.isInAnalyzerProcess(this)) { // // This process is dedicated to LeakCanary for heap analysis. // // You should not init your app in this process. // return; // } // LeakCanary.install(this); // Normal app init code... //implementation 'com.github.Justson:dispatch-queue:v1.0.5' GlobalQueue.getMainQueue().postRunnableInIdleRunning(new Runnable() { @Override public void run() { try { startService(new Intent(App.this, WebService.class)); } catch (Throwable throwable) { } } }); } public static Context mContext; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); mContext = base; AgentWebCompat.setDataDirectorySuffix(base); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/base/BaseAgentWebActivity.java ================================================ package com.just.agentweb.sample.base; import android.content.Intent; import android.os.Bundle; import androidx.annotation.ColorInt; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; import com.just.agentweb.AgentWeb; import com.just.agentweb.AgentWebSettingsImpl; import com.just.agentweb.AgentWebUIControllerImplBase; import com.just.agentweb.DefaultWebClient; import com.just.agentweb.IAgentWebSettings; import com.just.agentweb.IWebLayout; import com.just.agentweb.MiddlewareWebChromeBase; import com.just.agentweb.MiddlewareWebClientBase; import com.just.agentweb.PermissionInterceptor; import com.just.agentweb.WebChromeClient; import com.just.agentweb.WebViewClient; /** * Created by cenxiaozhong on 2017/7/22. *

* source code https://github.com/Justson/AgentWeb */ public abstract class BaseAgentWebActivity extends AppCompatActivity { protected AgentWeb mAgentWeb; private AgentWebUIControllerImplBase mAgentWebUIController; private ErrorLayoutEntity mErrorLayoutEntity; private MiddlewareWebChromeBase mMiddleWareWebChrome; private MiddlewareWebClientBase mMiddleWareWebClient; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void setContentView(@LayoutRes int layoutResID) { super.setContentView(layoutResID); buildAgentWeb(); } @Override public void setContentView(View view) { super.setContentView(view); buildAgentWeb(); } protected void buildAgentWeb() { ErrorLayoutEntity mErrorLayoutEntity = getErrorLayoutEntity(); mAgentWeb = AgentWeb.with(this) .setAgentWebParent(getAgentWebParent(), new ViewGroup.LayoutParams(-1, -1)) .useDefaultIndicator(getIndicatorColor(), getIndicatorHeight()) .setWebChromeClient(getWebChromeClient()) .setWebViewClient(getWebViewClient()) .setWebView(getWebView()) .setPermissionInterceptor(getPermissionInterceptor()) .setWebLayout(getWebLayout()) .setAgentWebUIController(getAgentWebUIController()) .interceptUnkownUrl() .setOpenOtherPageWays(getOpenOtherAppWay()) .useMiddlewareWebChrome(getMiddleWareWebChrome()) .useMiddlewareWebClient(getMiddleWareWebClient()) .setAgentWebWebSettings(getAgentWebSettings()) .setMainFrameErrorView(mErrorLayoutEntity.layoutRes, mErrorLayoutEntity.reloadId) .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) .createAgentWeb() .ready() .go(getUrl()); } protected @NonNull ErrorLayoutEntity getErrorLayoutEntity() { if (this.mErrorLayoutEntity == null) { this.mErrorLayoutEntity = new ErrorLayoutEntity(); } return mErrorLayoutEntity; } protected AgentWeb getAgentWeb() { return this.mAgentWeb; } protected static class ErrorLayoutEntity { private int layoutRes = com.just.agentweb.R.layout.agentweb_error_page; private int reloadId; public void setLayoutRes(int layoutRes) { this.layoutRes = layoutRes; if (layoutRes <= 0) { layoutRes = -1; } } public void setReloadId(int reloadId) { this.reloadId = reloadId; if (reloadId <= 0) { reloadId = -1; } } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (mAgentWeb != null && mAgentWeb.handleKeyEvent(keyCode, event)) { return true; } return super.onKeyDown(keyCode, event); } @Override protected void onPause() { if (mAgentWeb != null) { mAgentWeb.getWebLifeCycle().onPause(); } super.onPause(); } @Override protected void onResume() { if (mAgentWeb != null) { mAgentWeb.getWebLifeCycle().onResume(); } super.onResume(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); } @Override protected void onDestroy() { if (mAgentWeb != null) { mAgentWeb.getWebLifeCycle().onDestroy(); } super.onDestroy(); } protected @Nullable String getUrl() { return null; } public @Nullable IAgentWebSettings getAgentWebSettings() { return AgentWebSettingsImpl.getInstance(); } protected abstract @NonNull ViewGroup getAgentWebParent(); protected @Nullable WebChromeClient getWebChromeClient() { return null; } protected @ColorInt int getIndicatorColor() { return -1; } protected int getIndicatorHeight() { return -1; } protected @Nullable WebViewClient getWebViewClient() { return null; } protected @Nullable WebView getWebView() { return null; } protected @Nullable IWebLayout getWebLayout() { return null; } protected @Nullable PermissionInterceptor getPermissionInterceptor() { return null; } public @Nullable AgentWebUIControllerImplBase getAgentWebUIController() { return null; } public @Nullable DefaultWebClient.OpenOtherPageWays getOpenOtherAppWay() { return null; } protected @NonNull MiddlewareWebChromeBase getMiddleWareWebChrome() { return this.mMiddleWareWebChrome = new MiddlewareWebChromeBase() { @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); setTitle(view, title); } }; } protected void setTitle(WebView view, String title) { } protected @NonNull MiddlewareWebClientBase getMiddleWareWebClient() { return this.mMiddleWareWebClient = new MiddlewareWebClientBase() { }; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/base/BaseAgentWebFragment.java ================================================ package com.just.agentweb.sample.base; import android.content.Intent; import android.os.Bundle; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; import com.just.agentweb.AgentWeb; import com.just.agentweb.AgentWebSettingsImpl; import com.just.agentweb.AgentWebUIControllerImplBase; import com.just.agentweb.DefaultWebClient; import com.just.agentweb.IAgentWebSettings; import com.just.agentweb.IWebLayout; import com.just.agentweb.MiddlewareWebChromeBase; import com.just.agentweb.MiddlewareWebClientBase; import com.just.agentweb.PermissionInterceptor; import com.just.agentweb.WebChromeClient; import com.just.agentweb.WebViewClient; /** * Created by cenxiaozhong on 2017/7/22. * source code https://github.com/Justson/AgentWeb */ public abstract class BaseAgentWebFragment extends Fragment { protected AgentWeb mAgentWeb; private MiddlewareWebChromeBase mMiddleWareWebChrome; private MiddlewareWebClientBase mMiddleWareWebClient; private ErrorLayoutEntity mErrorLayoutEntity; private AgentWebUIControllerImplBase mAgentWebUIController; @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ErrorLayoutEntity mErrorLayoutEntity = getErrorLayoutEntity(); mAgentWeb = AgentWeb.with(this) .setAgentWebParent(getAgentWebParent(), new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)) .useDefaultIndicator(getIndicatorColor(), getIndicatorHeight()) .setWebView(getWebView()) .setWebLayout(getWebLayout()) .setAgentWebWebSettings(getAgentWebSettings()) .setWebViewClient(getWebViewClient()) .setPermissionInterceptor(getPermissionInterceptor()) .setWebChromeClient(getWebChromeClient()) .interceptUnkownUrl() .setOpenOtherPageWays(DefaultWebClient.OpenOtherPageWays.ASK) .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) .setAgentWebUIController(getAgentWebUIController()) .setMainFrameErrorView(mErrorLayoutEntity.layoutRes, mErrorLayoutEntity.reloadId) .useMiddlewareWebChrome(getMiddleWareWebChrome()) .useMiddlewareWebClient(getMiddleWareWebClient()) .createAgentWeb()// .ready()// .go(getUrl()); } protected void setTitle(WebView view, String title) { } protected @NonNull ErrorLayoutEntity getErrorLayoutEntity() { if (this.mErrorLayoutEntity == null) { this.mErrorLayoutEntity = new ErrorLayoutEntity(); } return mErrorLayoutEntity; } protected @Nullable AgentWebUIControllerImplBase getAgentWebUIController() { return mAgentWebUIController; } protected static class ErrorLayoutEntity { private int layoutRes = com.just.agentweb.R.layout.agentweb_error_page; private int reloadId; public void setLayoutRes(int layoutRes) { this.layoutRes = layoutRes; if (layoutRes <= 0) { layoutRes = -1; } } public void setReloadId(int reloadId) { this.reloadId = reloadId; if (reloadId <= 0) { reloadId = -1; } } } @Override public void onPause() { if (mAgentWeb != null) { mAgentWeb.getWebLifeCycle().onPause(); } super.onPause(); } @Override public void onResume() { if (mAgentWeb != null) { mAgentWeb.getWebLifeCycle().onResume(); } super.onResume(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); } protected @Nullable String getUrl() { return ""; } @Override public void onDestroy() { if (mAgentWeb != null) { mAgentWeb.getWebLifeCycle().onDestroy(); } super.onDestroy(); } protected @Nullable IAgentWebSettings getAgentWebSettings() { return AgentWebSettingsImpl.getInstance(); } protected @Nullable WebChromeClient getWebChromeClient() { return null; } protected abstract @NonNull ViewGroup getAgentWebParent(); protected @ColorInt int getIndicatorColor() { return -1; } protected int getIndicatorHeight() { return -1; } protected @Nullable WebViewClient getWebViewClient() { return null; } protected @Nullable WebView getWebView() { return null; } protected @Nullable IWebLayout getWebLayout() { return null; } protected @Nullable PermissionInterceptor getPermissionInterceptor() { return null; } protected @NonNull MiddlewareWebChromeBase getMiddleWareWebChrome() { return this.mMiddleWareWebChrome = new MiddlewareWebChromeBase() { @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); setTitle(view, title); } }; } protected @NonNull MiddlewareWebClientBase getMiddleWareWebClient() { return this.mMiddleWareWebClient = new MiddlewareWebClientBase() { }; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/base/FragmentKeyDown.java ================================================ package com.just.agentweb.sample.base; import android.view.KeyEvent; /** * Created by cenxiaozhong * source code https://github.com/Justson/AgentWeb */ public interface FragmentKeyDown { boolean onFragmentKeyDown(int keyCode, KeyEvent event); } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/behavior/BottomNavigationViewBehavior.java ================================================ /* * Copyright (C) LeonDevLifeLog(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb.sample.behavior; import android.content.Context; import com.google.android.material.appbar.AppBarLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.view.ViewCompat; import android.util.AttributeSet; import android.view.View; /** * 与toolbar联动隐藏底部菜单 * * @author LeonDevLifeLog * @date 2018-02-24 08:59 * @since V4.0.0 */ public class BottomNavigationViewBehavior extends CoordinatorLayout.Behavior { public BottomNavigationViewBehavior() { } public BottomNavigationViewBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) { ((CoordinatorLayout.LayoutParams) child.getLayoutParams()).topMargin = parent .getMeasuredHeight() - child.getMeasuredHeight(); return super.onLayoutChild(parent, child, layoutDirection); } @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { return dependency instanceof AppBarLayout; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { //得到依赖View的滑动距离 int top = ((AppBarLayout.Behavior) ((CoordinatorLayout.LayoutParams) dependency .getLayoutParams()).getBehavior()).getTopAndBottomOffset(); //因为BottomNavigation的滑动与ToolBar是反向的,所以取负值 ViewCompat.setTranslationY(child, -(top * child.getMeasuredHeight() / dependency .getMeasuredHeight())); return false; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/client/MiddlewareChromeClient.java ================================================ package com.just.agentweb.sample.client; import android.util.Log; import android.webkit.JsResult; import android.webkit.WebView; import com.just.agentweb.MiddlewareWebChromeBase; /** * Created by cenxiaozhong on 2017/12/16. * After agentweb 3.0.0 , allow dev to custom self WebChromeClient's MiddleWare . */ public class MiddlewareChromeClient extends MiddlewareWebChromeBase { public MiddlewareChromeClient() { } @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { Log.i("Info","onJsAlert:"+url); return super.onJsAlert(view, url, message, result); } @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); Log.i("Info","onProgressChanged:"); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/client/MiddlewareWebViewClient.java ================================================ package com.just.agentweb.sample.client; import android.util.Log; import android.webkit.WebResourceRequest; import android.webkit.WebView; import com.just.agentweb.MiddlewareWebClientBase; /** * Created by cenxiaozhong on 2017/12/16. * * * 方法的执行顺序,例如下面用了7个中间件一个 WebViewClient * * .useMiddlewareWebClient(getMiddlewareWebClient()) // 1 * .useMiddlewareWebClient(getMiddlewareWebClient()) // 2 * .useMiddlewareWebClient(getMiddlewareWebClient()) // 3 * .useMiddlewareWebClient(getMiddlewareWebClient()) // 4 * .useMiddlewareWebClient(getMiddlewareWebClient()) // 5 * .useMiddlewareWebClient(getMiddlewareWebClient()) // 6 * .useMiddlewareWebClient(getMiddlewareWebClient()) // 7 * DefaultWebClient // 8 * .setWebViewClient(mWebViewClient) // 9 * * * 典型的洋葱模型 * 对象内部的方法执行顺序: 1->2->3->4->5->6->7->8->9->8->7->6->5->4->3->2->1 * * * 中断中间件的执行, 删除super.methodName(...) 这行即可 * */ public class MiddlewareWebViewClient extends MiddlewareWebClientBase { public MiddlewareWebViewClient() { } private static int count = 1; @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { Log.i("Info", "MiddlewareWebViewClient -- > shouldOverrideUrlLoading:" + request.getUrl().toString() + " c:" + (count++)); return super.shouldOverrideUrlLoading(view, request); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Log.i("Info", "MiddlewareWebViewClient -- > shouldOverrideUrlLoading:" + url + " c:" + (count++)); return super.shouldOverrideUrlLoading(view, url); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/client/SonicWebViewClient.java ================================================ package com.just.agentweb.sample.client; import android.annotation.TargetApi; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebView; import com.just.agentweb.MiddlewareWebClientBase; import com.tencent.sonic.sdk.SonicSession; /** * Created by cenxiaozhong on 2017/12/17. */ public class SonicWebViewClient extends MiddlewareWebClientBase { private SonicSession sonicSession; public SonicWebViewClient(SonicSession sonicSession) { this.sonicSession=sonicSession; } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if (sonicSession != null) { sonicSession.getSessionClient().pageFinish(url); } } @TargetApi(21) @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { return shouldInterceptRequest(view, request.getUrl().toString()); } @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { if (sonicSession != null) { return (WebResourceResponse) sonicSession.getSessionClient().requestResource(url); } return null; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/common/AndroidInterface.java ================================================ package com.just.agentweb.sample.common; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.webkit.JavascriptInterface; import android.widget.Toast; import com.just.agentweb.AgentWeb; /** * Created by cenxiaozhong on 2017/5/14. * source code https://github.com/Justson/AgentWeb */ public class AndroidInterface { private Handler deliver = new Handler(Looper.getMainLooper()); private AgentWeb agent; private Context context; public AndroidInterface(AgentWeb agent, Context context) { this.agent = agent; this.context = context; } @JavascriptInterface public void callAndroid(final String msg) { deliver.post(new Runnable() { @Override public void run() { Log.i("Info", "main Thread:" + Thread.currentThread()); Toast.makeText(context.getApplicationContext(), "" + msg, Toast.LENGTH_LONG).show(); } }); Log.i("Info", "Thread:" + Thread.currentThread()); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/common/CommonWebChromeClient.java ================================================ package com.just.agentweb.sample.common; import android.util.Log; import android.webkit.WebView; import com.just.agentweb.WebChromeClient; /** * @author cenxiaozhong * @date 2019/2/19 * @since 1.0.0 */ public class CommonWebChromeClient extends WebChromeClient { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); Log.i("CommonWebChromeClient", "onProgressChanged:" + newProgress + " view:" + view); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/common/CustomSettings.java ================================================ package com.just.agentweb.sample.common; import android.app.Activity; import android.os.Build; import android.webkit.DownloadListener; import android.webkit.WebView; import com.just.agentweb.AbsAgentWebSettings; import com.just.agentweb.AgentWeb; import com.just.agentweb.DefaultDownloadImpl; import com.just.agentweb.IAgentWebSettings; import com.just.agentweb.WebListenerManager; /** * Created by cenxiaozhong on 2017/5/26. * source code https://github.com/Justson/AgentWeb */ public class CustomSettings extends AbsAgentWebSettings { public CustomSettings(Activity activity) { super(); this.mActivity = activity; } private AgentWeb mAgentWeb; private Activity mActivity; @Override protected void bindAgentWebSupport(AgentWeb agentWeb) { this.mAgentWeb = agentWeb; } @Override public IAgentWebSettings toSetting(WebView webView) { super.toSetting(webView); getWebSettings().setBlockNetworkImage(false);//是否阻塞加载网络图片 协议http or https getWebSettings().setAllowFileAccess(false); //允许加载本地文件html file协议, 这可能会造成不安全 , 建议重写关闭 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { getWebSettings().setAllowFileAccessFromFileURLs(false); //通过 file mUrl 加载的 Javascript 读取其他的本地文件 .建议关闭 getWebSettings().setAllowUniversalAccessFromFileURLs(false);//允许通过 file mUrl 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源 } getWebSettings().setNeedInitialFocus(true); getWebSettings().setDefaultTextEncodingName("gb2312");//设置编码格式 getWebSettings().setDefaultFontSize(16); getWebSettings().setMinimumFontSize(12);//设置 WebView 支持的最小字体大小,默认为 8 getWebSettings().setGeolocationEnabled(true); getWebSettings().setUserAgentString(getWebSettings().getUserAgentString().concat("agentweb/3.1.0")); return this; } @Override public WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener) { return super.setDownloader(webView, DefaultDownloadImpl.create(this.mActivity , webView, mAgentWeb.getPermissionInterceptor())); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/common/FragmentKeyDown.java ================================================ package com.just.agentweb.sample.common; import android.view.KeyEvent; /** * Created by cenxiaozhong on 2017/5/23. * source code https://github.com/Justson/AgentWeb */ public interface FragmentKeyDown { boolean onFragmentKeyDown(int keyCode, KeyEvent event); } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/common/GuideItemEntity.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb.sample.common; /** * @author cenxiaozhong * @date 2018/7/15 * @since 1.0.0 */ public class GuideItemEntity { private String guideTitle; private int guideDictionary; private int extra; public GuideItemEntity(String guideTitle, int guideDictionary) { this.guideTitle = guideTitle; this.guideDictionary = guideDictionary; } public String getGuideTitle() { return guideTitle; } public void setGuideTitle(String guideTitle) { this.guideTitle = guideTitle; } public int getGuideDictionary() { return guideDictionary; } public void setGuideDictionary(int guideDictionary) { this.guideDictionary = guideDictionary; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/common/UIController.java ================================================ package com.just.agentweb.sample.common; import android.app.Activity; import android.os.Handler; import android.util.Log; import android.webkit.WebView; import com.just.agentweb.AgentWebUIControllerImplBase; /** * Created by cenxiaozhong on 2017/12/23. */ /** * 如果你需要修改某一个AgentWeb 内部的某一个弹窗 ,请看下面的例子 * 注意写法一定要参照 DefaultUIController 的写法 ,因为UI自由定制,但是回调的方式是固定的,并且一定要回调。 */ public class UIController extends AgentWebUIControllerImplBase { private Activity mActivity; public UIController(Activity activity){ this.mActivity=activity; } @Override public void onShowMessage(String message, String from) { super.onShowMessage(message,from); Log.i(TAG,"message:"+message); } @Override public void onSelectItemsPrompt(WebView view, String url, String[] items, Handler.Callback callback) { super.onSelectItemsPrompt(view,url,items,callback); // 使用默认的UI } /** * 修改文件选择的弹窗 */ /* @Override public void onSelectItemsPrompt(WebView view, String mUrl, String[] ways, final Handler.Callback callback) { //super.onSelectItemsPrompt(view,mUrl,ways,callback); //这行应该注释或者删除掉 final AlertDialog mAlertDialog = new AlertDialog.Builder(mActivity)// .setSingleChoiceItems(ways, -1, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); if (callback != null) { Message mMessage = Message.obtain(); mMessage.what = which; //mMessage.what 必须等于ways的index callback.handleMessage(mMessage); //最后callback一定要回调 } } }).setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { dialog.dismiss(); if (callback != null) { callback.handleMessage(Message.obtain(null, -1)); //-1表示取消 //最后callback一定要回调 } } }).create(); mAlertDialog.show(); }*/ } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/fragment/AgentWebFragment.java ================================================ package com.just.agentweb.sample.fragment; import android.annotation.SuppressLint; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.net.http.SslError; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.webkit.MimeTypeMap; import android.webkit.SslErrorHandler; import android.webkit.ValueCallback; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.download.library.DownloadImpl; import com.download.library.DownloadListenerAdapter; import com.download.library.Extra; import com.download.library.ResourceRequest; import com.ferfalk.simplesearchview.SimpleSearchView; import com.google.gson.Gson; import com.just.agentweb.AbsAgentWebSettings; import com.just.agentweb.AgentWeb; import com.just.agentweb.AgentWebConfig; import com.just.agentweb.AgentWebUtils; import com.just.agentweb.DefaultDownloadImpl; import com.just.agentweb.DefaultWebClient; import com.just.agentweb.IAgentWebSettings; import com.just.agentweb.MiddlewareWebChromeBase; import com.just.agentweb.MiddlewareWebClientBase; import com.just.agentweb.PermissionInterceptor; import com.just.agentweb.WebChromeClient; import com.just.agentweb.WebListenerManager; import com.just.agentweb.filechooser.FileCompressor; import com.just.agentweb.sample.R; import com.just.agentweb.sample.app.App; import com.just.agentweb.sample.client.MiddlewareChromeClient; import com.just.agentweb.sample.client.MiddlewareWebViewClient; import com.just.agentweb.sample.common.CommonWebChromeClient; import com.just.agentweb.sample.common.FragmentKeyDown; import com.just.agentweb.sample.common.UIController; import com.just.agentweb.sample.utils.FileUtils; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Locale; import java.util.Objects; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.widget.PopupMenu; import androidx.fragment.app.Fragment; import top.zibin.luban.Luban; /** * Created by cenxiaozhong on 2017/5/15. * source code https://github.com/Justson/AgentWeb */ public class AgentWebFragment extends Fragment implements FragmentKeyDown, FileCompressor.FileCompressEngine { private ImageView mBackImageView; private View mLineView; private ImageView mFinishImageView; private TextView mTitleTextView; protected AgentWeb mAgentWeb; public static final String URL_KEY = "url_key"; private ImageView mMoreImageView; private PopupMenu mPopupMenu; /** * 用于方便打印测试 */ private Gson mGson = new Gson(); public static final String TAG = AgentWebFragment.class.getSimpleName(); private MiddlewareWebClientBase mMiddleWareWebClient; private MiddlewareWebChromeBase mMiddleWareWebChrome; public static AgentWebFragment getInstance(Bundle bundle) { AgentWebFragment mAgentWebFragment = new AgentWebFragment(); if (bundle != null) { mAgentWebFragment.setArguments(bundle); } return mAgentWebFragment; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_agentweb, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mAgentWeb = AgentWeb.with(this)// .setAgentWebParent((LinearLayout) view, -1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))//传入AgentWeb的父控件。 .useDefaultIndicator(-1, 3)//设置进度条颜色与高度,-1为默认值,高度为2,单位为dp。 .setAgentWebWebSettings(getSettings())//设置 IAgentWebSettings。 .setWebViewClient(mWebViewClient)//WebViewClient , 与 WebView 使用一致 ,但是请勿获取WebView调用setWebViewClient(xx)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。 .setWebChromeClient(new CommonWebChromeClient()) //WebChromeClient .setPermissionInterceptor(mPermissionInterceptor) //权限拦截 2.0.0 加入。 .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) //严格模式 Android 4.2.2 以下会放弃注入对象 ,使用AgentWebView没影响。 .setAgentWebUIController(new UIController(getActivity())) //自定义UI AgentWeb3.0.0 加入。 .setMainFrameErrorView(com.just.agentweb.R.layout.agentweb_error_page, -1) //参数1是错误显示的布局,参数2点击刷新控件ID -1表示点击整个布局都刷新, AgentWeb 3.0.0 加入。 .useMiddlewareWebChrome(getMiddlewareWebChrome()) //设置WebChromeClient中间件,支持多个WebChromeClient,AgentWeb 3.0.0 加入。 .additionalHttpHeader(getUrl(), "cookie", "41bc7ddf04a26b91803f6b11817a5a1c") .useMiddlewareWebClient(getMiddlewareWebClient()) //设置WebViewClient中间件,支持多个WebViewClient, AgentWeb 3.0.0 加入。 .setOpenOtherPageWays(DefaultWebClient.OpenOtherPageWays.ASK)//打开其他页面时,弹窗质询用户前往其他应用 AgentWeb 3.0.0 加入。 .interceptUnkownUrl() //拦截找不到相关页面的Url AgentWeb 3.0.0 加入。 .createAgentWeb()//创建AgentWeb。 .ready()//设置 WebSettings。 .go(getUrl()); //WebView载入该url地址的页面并显示。 AgentWebConfig.debug(); initView(view); // AgentWeb 没有把WebView的功能全面覆盖 ,所以某些设置 AgentWeb 没有提供 , 请从WebView方面入手设置。 mAgentWeb.getWebCreator().getWebView().setOverScrollMode(WebView.OVER_SCROLL_NEVER); //mAgentWeb.getWebCreator().getWebView() 获取WebView . // mAgentWeb.getWebCreator().getWebView().setOnLongClickListener(); // Runtime.getInstance().setFileComparatorFactory(new FileComparator.FileComparatorFactory() { // @Override // public FileComparator newFileComparator() { // return new FileComparator() { // @Override // public int compare(String url, File originFile, String inputMD5, String originFileMD5) { // return FileComparator.COMPARE_RESULT_SUCCESSFUL; // } // }; // } // }); } protected PermissionInterceptor mPermissionInterceptor = new PermissionInterceptor() { /** * PermissionInterceptor 能达到 url1 允许授权, url2 拒绝授权的效果。 * @param url * @param permissions * @param action * @return true 该Url对应页面请求权限进行拦截 ,false 表示不拦截。 */ @Override public boolean intercept(String url, String[] permissions, String action) { Log.i(TAG, "mUrl:" + url + " permission:" + mGson.toJson(permissions) + " action:" + action); return false; } }; /** * @return IAgentWebSettings */ public IAgentWebSettings getSettings() { return new AbsAgentWebSettings() { private AgentWeb mAgentWeb; @Override protected void bindAgentWebSupport(AgentWeb agentWeb) { this.mAgentWeb = agentWeb; } /** * AgentWeb 4.0.0 内部删除了 DownloadListener 监听 ,以及相关API ,将 Download 部分完全抽离出来独立一个库, * 如果你需要使用 AgentWeb Download 部分 , 请依赖上 compile 'com.download.library:Downloader:4.1.1' , * 如果你需要监听下载结果,请自定义 AgentWebSetting , New 出 DefaultDownloadImpl * 实现进度或者结果监听,例如下面这个例子,如果你不需要监听进度,或者下载结果,下面 setDownloader 的例子可以忽略。 * @param webView * @param downloadListener * @return WebListenerManager */ @Override public WebListenerManager setDownloader(WebView webView, android.webkit.DownloadListener downloadListener) { return super.setDownloader(webView, new DefaultDownloadImpl(getActivity(), webView, this.mAgentWeb.getPermissionInterceptor()) { @Override protected ResourceRequest createResourceRequest(String url) { return DownloadImpl.getInstance(getContext()) .url(url) .quickProgress() .addHeader("", "") .setEnableIndicator(true) .autoOpenIgnoreMD5() .setRetry(5) .setBlockMaxTime(100000L); } @Override protected void taskEnqueue(ResourceRequest resourceRequest) { resourceRequest.enqueue(new DownloadListenerAdapter() { @Override public void onStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength, Extra extra) { super.onStart(url, userAgent, contentDisposition, mimetype, contentLength, extra); } @MainThread @Override public void onProgress(String url, long downloaded, long length, long usedTime) { super.onProgress(url, downloaded, length, usedTime); } @Override public boolean onResult(Throwable throwable, Uri path, String url, Extra extra) { return super.onResult(throwable, path, url, extra); } }); } }); } }; } /** * 页面空白,请检查scheme是否加上, scheme://host:port/path?query&query 。 * * @return mUrl */ public String getUrl() { String target = ""; if (TextUtils.isEmpty(target = this.getArguments().getString(URL_KEY))) { target = "http://cw.gzyunjuchuang.com/"; } // return "http://ggzy.sqzwfw.gov.cn/WebBuilderDS/WebbuilderMIS/attach/downloadZtbAttach.jspx?attachGuid=af982055-3d76-4b00-b5ab-36dee1f90b11&appUrlFlag=sqztb&siteGuid=7eb5f7f1-9041-43ad-8e13-8fcb82ea831a"; return target; } protected com.just.agentweb.WebChromeClient mWebChromeClient = new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); Log.i(TAG, "onProgressChanged:" + newProgress + " view:" + view); } @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); if (mTitleTextView != null && !TextUtils.isEmpty(title)) { if (title.length() > 10) { title = title.substring(0, 10).concat("..."); } } mTitleTextView.setText(title); } }; /** * 注意,重写WebViewClient的方法,super.xxx()请务必正确调用, 如果没有调用super.xxx(),则无法执行DefaultWebClient的方法 * 可能会影响到AgentWeb自带提供的功能,尽可能调用super.xxx()来完成洋葱模型 */ protected com.just.agentweb.WebViewClient mWebViewClient = new com.just.agentweb.WebViewClient() { private HashMap timer = new HashMap<>(); @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { super.onReceivedError(view, request, error); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { return super.shouldOverrideUrlLoading(view, request); } @Nullable @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { return super.shouldInterceptRequest(view, request); } // @Override public boolean shouldOverrideUrlLoading(final WebView view, String url) { Log.i(TAG, "view:" + new Gson().toJson(view.getHitTestResult())); Log.i(TAG, "mWebViewClient shouldOverrideUrlLoading:" + url); //优酷想唤起自己应用播放该视频 , 下面拦截地址返回 true 则会在应用内 H5 播放 ,禁止优酷唤起播放该视频, 如果返回 false , DefaultWebClient 会根据intent 协议处理 该地址 , 首先匹配该应用存不存在 ,如果存在 , 唤起该应用播放 , 如果不存在 , 则跳到应用市场下载该应用 . if (url.startsWith("intent://") && url.contains("com.youku.phone")) { return true; } /*else if (isAlipay(view, mUrl)) //1.2.5开始不用调用该方法了 ,只要引入支付宝sdk即可 , DefaultWebClient 默认会处理相应url调起支付宝 return true;*/ return super.shouldOverrideUrlLoading(view, url); } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); Log.i(TAG, "mUrl:" + url + " onPageStarted target:" + getUrl()); timer.put(url, System.currentTimeMillis()); if (url.equals(getUrl())) { pageNavigator(View.GONE); } else { pageNavigator(View.VISIBLE); } } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if (timer.get(url) != null) { long overTime = System.currentTimeMillis(); Long startTime = timer.get(url); Log.i(TAG, " page mUrl:" + url + " used time:" + (overTime - startTime)); } } /*错误页回调该方法 , 如果重写了该方法, 上面传入了布局将不会显示 , 交由开发者实现,注意参数对齐。*/ /* public void onMainFrameError(AbsAgentWebUIController agentWebUIController, WebView view, int errorCode, String description, String failingUrl) { Log.i(TAG, "AgentWebFragment onMainFrameError"); agentWebUIController.onMainFrameError(view,errorCode,description,failingUrl); }*/ @Override public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { super.onReceivedHttpError(view, request, errorResponse); // Log.i(TAG, "onReceivedHttpError:" + 3 + " request:" + mGson.toJson(request) + " errorResponse:" + mGson.toJson(errorResponse)); } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); super.onReceivedSslError(view, handler, error); } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { super.onReceivedError(view, errorCode, description, failingUrl); // Log.i(TAG, "onReceivedError:" + errorCode + " description:" + description + " errorResponse:" + failingUrl); } }; @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); /** * 2.0.0开始 废弃该api ,没有api代替 ,使用 ActionActivity 绕过该方法 ,降低使用门槛,4.0.0 删除该API。 */ // mAgentWeb.uploadFileResult(requestCode, resultCode, data); } private SimpleSearchView mSimpleSearchView; private ImageView mSearchImageView; protected void initView(View view) { mBackImageView = (ImageView) view.findViewById(R.id.iv_back); mLineView = view.findViewById(R.id.view_line); mFinishImageView = (ImageView) view.findViewById(R.id.iv_finish); mTitleTextView = (TextView) view.findViewById(R.id.toolbar_title); mBackImageView.setOnClickListener(mOnClickListener); mFinishImageView.setOnClickListener(mOnClickListener); mMoreImageView = (ImageView) view.findViewById(R.id.iv_more); mMoreImageView.setOnClickListener(mOnClickListener); mSearchImageView = view.findViewById(R.id.iv_search); mSearchImageView.setOnClickListener(mOnClickListener); mSimpleSearchView = view.findViewById(R.id.search_view); pageNavigator(View.GONE); mSimpleSearchView.setHint("请输入网址"); EditText editText = mSimpleSearchView.findViewById(com.ferfalk.simplesearchview.R.id.searchEditText); editText.setImeOptions(EditorInfo.IME_ACTION_GO); // mSimpleSearchView.setSearchBackground(new ColorDrawable(getColorPrimary())); mSimpleSearchView.setOnQueryTextListener(new SimpleSearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { String completeUrl = s; if (!completeUrl.startsWith("http")) { completeUrl = "http://" + s; } mAgentWeb.getUrlLoader().loadUrl(completeUrl); return false; } @Override public boolean onQueryTextChange(String s) { return false; } @Override public boolean onQueryTextCleared() { return false; } }); FileCompressor.getInstance().registerFileCompressEngine(this); } // public int getColorPrimary() { // TypedValue typedValue = new TypedValue(); // requireActivity().getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true); // return typedValue.data; // } private void pageNavigator(int tag) { mBackImageView.setVisibility(tag); mLineView.setVisibility(tag); } private View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.iv_back: // true表示AgentWeb处理了该事件 if (!mAgentWeb.back()) { AgentWebFragment.this.getActivity().finish(); } break; case R.id.iv_finish: AgentWebFragment.this.getActivity().finish(); break; case R.id.iv_more: showPoPup(v); break; case R.id.iv_search: mSimpleSearchView.showSearch(); break; default: break; } } }; /** * 打开浏览器 * * @param targetUrl 外部浏览器打开的地址 */ private void openBrowser(String targetUrl) { if (TextUtils.isEmpty(targetUrl) || targetUrl.startsWith("file://")) { Toast.makeText(this.getContext(), targetUrl + " 该链接无法使用浏览器打开。", Toast.LENGTH_SHORT).show(); return; } Intent intent = new Intent(); intent.setAction("android.intent.action.VIEW"); Uri mUri = Uri.parse(targetUrl); intent.setData(mUri); startActivity(intent); } /** * 显示更多菜单 * * @param view 菜单依附在该View下面 */ private void showPoPup(View view) { if (mPopupMenu == null) { mPopupMenu = new PopupMenu(this.getActivity(), view); mPopupMenu.inflate(R.menu.toolbar_menu); mPopupMenu.setOnMenuItemClickListener(mOnMenuItemClickListener); } mPopupMenu.show(); } /** * 菜单事件 */ private PopupMenu.OnMenuItemClickListener mOnMenuItemClickListener = new PopupMenu.OnMenuItemClickListener() { @SuppressLint("NonConstantResourceId") @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.refresh: if (mAgentWeb != null) { mAgentWeb.getUrlLoader().reload(); // 刷新 } return true; case R.id.copy: if (mAgentWeb != null) { toCopy(AgentWebFragment.this.getContext(), mAgentWeb.getWebCreator().getWebView().getUrl()); } return true; case R.id.default_browser: if (mAgentWeb != null) { openBrowser(mAgentWeb.getWebCreator().getWebView().getUrl()); } return true; case R.id.default_clean: toCleanWebCache(); return true; case R.id.error_website: loadErrorWebSite(); // test DownloadingService // LogUtils.i(TAG, " :" + mDownloadingService + " " + (mDownloadingService == null ? "" : mDownloadingService.isShutdown()) + " :" + mExtraService); // if (mDownloadingService != null && !mDownloadingService.isShutdown()) { // mExtraService = mDownloadingService.shutdownNow(); // LogUtils.i(TAG, "mExtraService::" + mExtraService); // return true; // } // if (mExtraService != null) { // mExtraService.performReDownload(); // } return true; default: return false; } } }; /** * 测试错误页的显示 */ private void loadErrorWebSite() { if (mAgentWeb != null) { mAgentWeb.getUrlLoader().loadUrl("http://www.unkownwebsiteblog.me"); } } /** * 清除 WebView 缓存 */ private void toCleanWebCache() { if (this.mAgentWeb != null) { //清理所有跟WebView相关的缓存 ,数据库, 历史记录 等。 this.mAgentWeb.clearWebCache(); Toast.makeText(getActivity(), "已清理缓存", Toast.LENGTH_SHORT).show(); //清空所有 AgentWeb 硬盘缓存,包括 WebView 的缓存 , AgentWeb 下载的图片 ,视频 ,apk 等文件。 // AgentWebConfig.clearDiskCache(this.getContext()); } } /** * 复制字符串 * * @param context * @param text */ private void toCopy(Context context, String text) { ClipboardManager mClipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); mClipboardManager.setPrimaryClip(ClipData.newPlainText(null, text)); } @Override public void onResume() { mAgentWeb.getWebLifeCycle().onResume();//恢复 super.onResume(); } @Override public void onPause() { mAgentWeb.getWebLifeCycle().onPause(); //暂停应用内所有WebView , 调用mWebView.resumeTimers();/mAgentWeb.getWebLifeCycle().onResume(); 恢复。 super.onPause(); } @Override public boolean onFragmentKeyDown(int keyCode, KeyEvent event) { if (mSimpleSearchView.onBackPressed()) { return true; } return mAgentWeb.handleKeyEvent(keyCode, event); } @Override public void onDestroyView() { mAgentWeb.getWebLifeCycle().onDestroy(); FileCompressor.getInstance().unregisterFileCompressEngine(this); super.onDestroyView(); } /** * MiddlewareWebClientBase 是 AgentWeb 3.0.0 提供一个强大的功能, * 如果用户需要使用 AgentWeb 提供的功能, 不想重写 WebClientView方 * 法覆盖AgentWeb提供的功能,那么 MiddlewareWebClientBase 是一个 * 不错的选择 。 * * @return */ protected MiddlewareWebClientBase getMiddlewareWebClient() { return this.mMiddleWareWebClient = new MiddlewareWebViewClient() { /** * * @param view * @param url * @return */ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Log.e(TAG, "MiddlewareWebClientBase#shouldOverrideUrlLoading url:" + url); /*if (url.startsWith("agentweb")) { // 拦截 url,不执行 DefaultWebClient#shouldOverrideUrlLoading Log.i(TAG, "agentweb scheme ~"); return true; }*/ if (super.shouldOverrideUrlLoading(view, url)) { // 执行 DefaultWebClient#shouldOverrideUrlLoading return true; } // do you work return false; } @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { Log.e(TAG, "MiddlewareWebClientBase#shouldOverrideUrlLoading request url:" + request.getUrl().toString()); return super.shouldOverrideUrlLoading(view, request); } @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { if (request.isForMainFrame() && error.getErrorCode() != -1) { super.onReceivedError(view, request, error); } } }; } protected MiddlewareWebChromeBase getMiddlewareWebChrome() { return this.mMiddleWareWebChrome = new MiddlewareChromeClient() { }; } /** * 选择文件后回调该方法, 这里可以做文件压缩 / 也可以做图片的方向调整 * * @param type customize/system , customize 表示通过js方式获取文件, 把文件 * 转成base64的方式返回给js,这种方式兼容性高,但是存在文件过大转成base64时 * 字符串长度过长,导致与js通信失败问题,所以很有必要压缩文件, 尽量控制字符串长度在512kb以内。 *

* system 这种方式,是由input/file 标签触发的文件选择,这种方式缺点是在Android 4.4 不回调 * fileChooser,存在兼容性问题,但是经过升级,基本可以忽略了,api 的兼容性越来越好了, 回调 * 返回是于uri形式,所以不存在文件大小问题,作图片预览也很快。(推荐这种方式) * @param uri 文件的uri * @param callback */ @Override public void compressFile(String type, final Uri[] uri, ValueCallback callback) { Log.e(TAG, "compressFile type:" + type); if ("system".equals(type)) { // input/file 标签触发的文件选择,这种方式不存在性能问题,可压缩也可以不压缩,具体看自己业务要求 callback.onReceiveValue(uri); return; } // customize.equals(type) 这种方式强烈建议文件压缩 if (uri == null || uri.length == 0) { callback.onReceiveValue(uri); } else { final String[] paths = AgentWebUtils.uriToPath(getActivity(), uri); if (paths == null || paths.length == 0) { callback.onReceiveValue(uri); return; } AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { try { Uri[] result = new Uri[paths.length]; for (int i = 0; i < paths.length; i++) { String filePath = paths[i]; String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(FileUtils.getExtensionByFilePath(filePath)); if (TextUtils.isEmpty(mimeType) || !mimeType.startsWith("image")) { result[i] = uri[i]; } else { File origin = new File(filePath); File file = Luban.with(App.mContext).ignoreBy(100).setTargetDir(AgentWebUtils.getAgentWebFilePath(App.mContext)).get(filePath); Log.e(TAG, "原文件大小:" + byte2FitMemorySize(origin.length())); Log.e(TAG, "压缩后文件大小:" + byte2FitMemorySize(file.length())); Uri fileUri = AgentWebUtils.getUriFromFile(App.mContext, file); result[i] = fileUri; } } AgentWebUtils.runInUiThread(() -> callback.onReceiveValue(result)); } catch (IOException e) { e.printStackTrace(); AgentWebUtils.runInUiThread(() -> callback.onReceiveValue(uri)); } }); } } private static String byte2FitMemorySize(final long byteNum) { if (byteNum < 0) { return "shouldn't be less than zero!"; } else if (byteNum < 1024) { return String.format(Locale.getDefault(), "%.1fB", (double) byteNum); } else if (byteNum < 1048576) { return String.format(Locale.getDefault(), "%.1fKB", (double) byteNum / 1024); } else if (byteNum < 1073741824) { return String.format(Locale.getDefault(), "%.1fMB", (double) byteNum / 1048576); } else { return String.format(Locale.getDefault(), "%.1fGB", (double) byteNum / 1073741824); } } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/fragment/BounceWebFragment.java ================================================ package com.just.agentweb.sample.fragment; import android.graphics.Color; import android.os.Bundle; import androidx.annotation.Nullable; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; import com.just.agentweb.AgentWeb; import com.just.agentweb.DefaultWebClient; import com.just.agentweb.IWebLayout; import com.just.agentweb.sample.R; import com.just.agentweb.sample.widget.WebLayout; /** * Created by cenxiaozhong on 2017/7/1. * source code https://github.com/Justson/AgentWeb */ public class BounceWebFragment extends AgentWebFragment { public static BounceWebFragment getInstance(Bundle bundle) { BounceWebFragment mBounceWebFragment = new BounceWebFragment(); if (mBounceWebFragment != null){ mBounceWebFragment.setArguments(bundle); } return mBounceWebFragment; } @Override public String getUrl() { return super.getUrl(); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { mAgentWeb = AgentWeb.with(this) .setAgentWebParent((ViewGroup) view, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)) .useDefaultIndicator(-1, 2) .setAgentWebWebSettings(getSettings()) .setWebViewClient(mWebViewClient) .setWebChromeClient(mWebChromeClient) .setWebLayout(getWebLayout()) .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) .interceptUnkownUrl() .setOpenOtherPageWays(DefaultWebClient.OpenOtherPageWays.ASK) .setMainFrameErrorView(com.just.agentweb.R.layout.agentweb_error_page, -1) .createAgentWeb()// .ready()// .go(getUrl()); // 得到 AgentWeb 最底层的控件 addBGChild((FrameLayout) mAgentWeb.getWebCreator().getWebParentLayout()); initView(view); } protected IWebLayout getWebLayout() { return new WebLayout(getActivity()); } protected void addBGChild(FrameLayout frameLayout) { TextView mTextView = new TextView(frameLayout.getContext()); mTextView.setText("技术由 AgentWeb 提供"); mTextView.setTextSize(16); mTextView.setTextColor(Color.parseColor("#727779")); frameLayout.setBackgroundColor(Color.parseColor("#272b2d")); FrameLayout.LayoutParams mFlp = new FrameLayout.LayoutParams(-2, -2); mFlp.gravity = Gravity.CENTER_HORIZONTAL; final float scale = frameLayout.getContext().getResources().getDisplayMetrics().density; mFlp.topMargin = (int) (15 * scale + 0.5f); frameLayout.addView(mTextView, 0, mFlp); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/fragment/CustomIndicatorFragment.java ================================================ package com.just.agentweb.sample.fragment; import android.os.Bundle; import androidx.annotation.Nullable; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import com.just.agentweb.AgentWeb; import com.just.agentweb.AgentWebSettingsImpl; import com.just.agentweb.DefaultWebClient; import com.just.agentweb.sample.widget.CoolIndicatorLayout; /** * Created by cenxiaozhong on 2017/5/26. * source code https://github.com/Justson/AgentWeb */ public class CustomIndicatorFragment extends AgentWebFragment { public static CustomIndicatorFragment getInstance(Bundle bundle) { CustomIndicatorFragment mCustomIndicatorFragment = new CustomIndicatorFragment(); if (bundle != null){ mCustomIndicatorFragment.setArguments(bundle); } return mCustomIndicatorFragment; } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { // CommonIndicator mCommonIndicator=new CommonIndicator(this.getActivity()); // FrameLayout.LayoutParams lp=new FrameLayout.LayoutParams(-2,-2); // lp.gravity= Gravity.CENTER; // ProgressBar mProgressBar=new ProgressBar(this.getActivity()); // mProgressBar.setBackground(this.getResources().getDrawable(R.drawable.indicator_shape)); // mCommonIndicator.addView(mProgressBar,lp); CoolIndicatorLayout mCoolIndicatorLayout = new CoolIndicatorLayout(this.getActivity()); this.mAgentWeb = AgentWeb.with(this)// .setAgentWebParent((ViewGroup) view, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)) .setCustomIndicator(mCoolIndicatorLayout) .setAgentWebWebSettings(AgentWebSettingsImpl.getInstance()) .setWebViewClient(mWebViewClient) .setPermissionInterceptor(mPermissionInterceptor) .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) .interceptUnkownUrl() .setOpenOtherPageWays(DefaultWebClient.OpenOtherPageWays.ASK) .createAgentWeb()// .ready()// .go(getUrl()); initView(view); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/fragment/CustomSettingsFragment.java ================================================ package com.just.agentweb.sample.fragment; import android.os.Bundle; import com.just.agentweb.IAgentWebSettings; import com.just.agentweb.sample.common.CustomSettings; /** * Created by cenxiaozhong on 2017/5/26. * source code https://github.com/Justson/AgentWeb */ public class CustomSettingsFragment extends AgentWebFragment { public static AgentWebFragment getInstance(Bundle bundle) { CustomSettingsFragment mCustomSettingsFragment = new CustomSettingsFragment(); if (bundle != null){ mCustomSettingsFragment.setArguments(bundle); } return mCustomSettingsFragment; } @Override public IAgentWebSettings getSettings() { return new CustomSettings(getActivity()); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/fragment/CustomWebViewFragment.java ================================================ package com.just.agentweb.sample.fragment; import android.os.Bundle; import androidx.annotation.Nullable; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.EditText; import android.widget.LinearLayout; import com.just.agentweb.AgentWeb; import com.just.agentweb.sample.R; import us.feras.mdv.MarkdownView; /** * Created by cenxiaozhong on 2017/6/17. * source code https://github.com/Justson/AgentWeb */ public class CustomWebViewFragment extends AgentWebFragment { private MarkdownView mMarkdownWebView; private EditText markdownEditText; public static final CustomWebViewFragment getInstance(Bundle bundle) { CustomWebViewFragment mCustomWebViewFragment = new CustomWebViewFragment(); if (bundle != null) { mCustomWebViewFragment.setArguments(bundle); } return mCustomWebViewFragment; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.markdown_view, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED); //MarkdownView 是 WebView 的一个子类 mMarkdownWebView = new MarkdownView(getActivity()); markdownEditText = (EditText) view.findViewById(R.id.markdownText); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); lp.weight = 1f; mAgentWeb = AgentWeb.with(this)// .setAgentWebParent((ViewGroup) view, lp)// .closeIndicator()// .setWebViewClient(mWebViewClient) .setWebView(mMarkdownWebView) .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) .createAgentWeb()// .ready()// .go(null); String text = "## AgentWeb 功能\n" + "***\n\n" + "1. 支持进度条以及自定义进度条\n" + "2. 支持文件下载\n" + "3. 支持文件下载断点续传\n" + "4. 支持下载通知形式提示进度\n" + "5. 简化 Javascript 通信 \n" + "6. 支持 Android 4.4 Kitkat 以及其他版本文件上传\n" + "7. 支持注入 Cookies\n" + "8. 加强 Web 安全\n" + "9. 支持全屏播放视频\n" + "10. 兼容低版本 Js 安全通信\n" + "11. 更省电 。\n" + "12. 支持调起微信支付\n" + "13. 支持调起支付宝(请参照sample)\n" + "14. 默认支持定位"; markdownEditText.setText(text); updateMarkdownView(); markdownEditText.addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable s) { } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { updateMarkdownView(); } }); initView(view); } private void updateMarkdownView() { mMarkdownWebView.loadMarkdown(markdownEditText.getText().toString()); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/fragment/EasyWebFragment.java ================================================ package com.just.agentweb.sample.fragment; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.PopupMenu; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; import android.widget.ImageView; import android.widget.TextView; import com.just.agentweb.sample.R; import com.just.agentweb.sample.base.BaseAgentWebFragment; /** * Created by cenxiaozhong on 2017/7/22. */ public class EasyWebFragment extends BaseAgentWebFragment { private ViewGroup mViewGroup; private ImageView mBackImageView; private View mLineView; private ImageView mFinishImageView; private TextView mTitleTextView; private ImageView mMoreImageView; private PopupMenu mPopupMenu; public static EasyWebFragment getInstance(Bundle bundle) { EasyWebFragment mEasyWebFragment = new EasyWebFragment(); if (bundle != null) { mEasyWebFragment.setArguments(bundle); } return mEasyWebFragment; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return mViewGroup = (ViewGroup) inflater.inflate(R.layout.fragment_agentweb, container, false); } @NonNull @Override protected ViewGroup getAgentWebParent() { return (ViewGroup) this.mViewGroup.findViewById(R.id.linearLayout); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); initView(view); } protected void initView(View view) { mBackImageView = (ImageView) view.findViewById(R.id.iv_back); mLineView = view.findViewById(R.id.view_line); mFinishImageView = (ImageView) view.findViewById(R.id.iv_finish); mTitleTextView = (TextView) view.findViewById(R.id.toolbar_title); mBackImageView.setOnClickListener(mOnClickListener); mFinishImageView.setOnClickListener(mOnClickListener); mMoreImageView = (ImageView) view.findViewById(R.id.iv_more); mMoreImageView.setVisibility(View.GONE); pageNavigator(View.GONE); } private void pageNavigator(int tag) { mBackImageView.setVisibility(tag); mLineView.setVisibility(tag); } private View.OnClickListener mOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { if (v.getId() == R.id.iv_back) { if (!mAgentWeb.back()) { EasyWebFragment.this.getActivity().finish(); } } else if (v.getId() == R.id.iv_finish) { EasyWebFragment.this.getActivity().finish(); } } }; @Override protected void setTitle(WebView view, String title) { super.setTitle(view, title); if (!TextUtils.isEmpty(title)) { if (title.length() > 10) { title = title.substring(0, 10).concat("..."); } } mTitleTextView.setText(title); } @Nullable @Override protected String getUrl() { return "https://m.v.qq.com/index.html"; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/fragment/JsAgentWebFragment.java ================================================ package com.just.agentweb.sample.fragment; import android.os.Build; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.webkit.ValueCallback; import android.widget.LinearLayout; import com.just.agentweb.sample.common.AndroidInterface; import com.just.agentweb.sample.R; import org.json.JSONObject; /** * Created by cenxiaozhong on 2017/5/26. * source code https://github.com/Justson/AgentWeb */ public class JsAgentWebFragment extends AgentWebFragment { public static final JsAgentWebFragment getInstance(Bundle bundle) { JsAgentWebFragment mJsAgentWebFragment = new JsAgentWebFragment(); if (bundle != null){ mJsAgentWebFragment.setArguments(bundle); } return mJsAgentWebFragment; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return super.onCreateView(inflater, container, savedInstanceState); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { LinearLayout mLinearLayout= (LinearLayout) view; LayoutInflater.from(getContext()).inflate(R.layout.fragment_js,mLinearLayout,true); super.onViewCreated(view, savedInstanceState); if(mAgentWeb!=null){ //注入对象 mAgentWeb.getJsInterfaceHolder().addJavaObject("android",new AndroidInterface(mAgentWeb,this.getActivity())); } view.findViewById(R.id.callJsNoParamsButton).setOnClickListener(mOnClickListener); view.findViewById(R.id.callJsOneParamsButton).setOnClickListener(mOnClickListener); view.findViewById(R.id.callJsMoreParamsButton).setOnClickListener(mOnClickListener); view.findViewById(R.id.jsJavaCommunicationButton).setOnClickListener(mOnClickListener); } private View.OnClickListener mOnClickListener=new View.OnClickListener() { @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override public void onClick(View v) { switch (v.getId()){ case R.id.callJsNoParamsButton: mAgentWeb.getJsAccessEntrace().quickCallJs("callByAndroid"); break; case R.id.callJsOneParamsButton: mAgentWeb.getJsAccessEntrace().quickCallJs("callByAndroidParam","Hello ! Agentweb"); break; case R.id.callJsMoreParamsButton: mAgentWeb.getJsAccessEntrace().quickCallJs("callByAndroidMoreParams", new ValueCallback() { @Override public void onReceiveValue(String value) { Log.i("Info","value:"+value); } },getJson(),"say:", " Hello! Agentweb"); break; case R.id.jsJavaCommunicationButton: mAgentWeb.getJsAccessEntrace().quickCallJs("callByAndroidInteraction","你好Js"); break; } } }; private String getJson(){ String result=""; try { JSONObject mJSONObject=new JSONObject(); mJSONObject.put("id",1); mJSONObject.put("name","Agentweb"); mJSONObject.put("age",18); result= mJSONObject.toString(); }catch (Exception e){ } return result; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/fragment/JsbridgeWebFragment.java ================================================ package com.just.agentweb.sample.fragment; import android.graphics.Bitmap; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.widget.LinearLayout; import com.github.lzyzsd.jsbridge.BridgeHandler; import com.github.lzyzsd.jsbridge.BridgeWebView; import com.github.lzyzsd.jsbridge.BridgeWebViewClient; import com.github.lzyzsd.jsbridge.CallBackFunction; import com.google.gson.Gson; import com.just.agentweb.AgentWeb; import com.just.agentweb.WebViewClient; import androidx.annotation.Nullable; /** * Created by cenxiaozhong on 2017/7/1. * source code https://github.com/Justson/AgentWeb */ public class JsbridgeWebFragment extends AgentWebFragment { public static JsbridgeWebFragment getInstance(Bundle bundle) { JsbridgeWebFragment mJsbridgeWebFragment = new JsbridgeWebFragment(); if (mJsbridgeWebFragment != null) { mJsbridgeWebFragment.setArguments(bundle); } return mJsbridgeWebFragment; } private BridgeWebView mBridgeWebView; @Override public String getUrl() { return super.getUrl(); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { mBridgeWebView = new BridgeWebView(getActivity()); mAgentWeb = AgentWeb.with(this) .setAgentWebParent((ViewGroup) view, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)) .useDefaultIndicator(-1, 2) .setAgentWebWebSettings(getSettings()) .setWebChromeClient(mWebChromeClient) .setWebViewClient(getWebViewClient()) .setWebView(mBridgeWebView) .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) // .setDownloadListener(mDownloadListener) 4.0.0 删除该API .createAgentWeb()// .ready()// .go(getUrl()); initView(view); mBridgeWebView.registerHandler("submitFromWeb", new BridgeHandler() { @Override public void handler(String data, CallBackFunction function) { function.onCallBack("submitFromWeb exe, response data 中文 from Java"); } }); User user = new User(); Location location = new Location(); location.address = "SDU"; user.location = location; user.name = "Agentweb --> Jsbridge"; mBridgeWebView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() { @Override public void onCallBack(String data) { Log.i(TAG, "data:" + data); } }); mBridgeWebView.send("hello"); } private WebViewClient getWebViewClient() { return new WebViewClient() { BridgeWebViewClient mBridgeWebViewClient = new BridgeWebViewClient(mBridgeWebView); @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (mBridgeWebViewClient.shouldOverrideUrlLoading(view, url)) { return true; } return super.shouldOverrideUrlLoading(view, url); } @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (mBridgeWebViewClient.shouldOverrideUrlLoading(view, request.getUrl().toString())) { return true; } } return super.shouldOverrideUrlLoading(view, request); } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); mBridgeWebViewClient.onPageFinished(view, url); } }; } static class Location { String address; } static class User { String name; Location location; String testStr; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/fragment/SmartRefreshWebFragment.java ================================================ package com.just.agentweb.sample.fragment; import android.graphics.Color; import android.os.Bundle; import androidx.annotation.Nullable; import android.view.View; import android.webkit.WebView; import android.widget.FrameLayout; import com.just.agentweb.IWebLayout; import com.just.agentweb.sample.widget.SmartRefreshWebLayout; import com.scwang.smartrefresh.layout.SmartRefreshLayout; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.listener.OnRefreshListener; /** * Created by cenxiaozhong on 2017/7/1. * source code https://github.com/Justson/AgentWeb */ public class SmartRefreshWebFragment extends BounceWebFragment { public static SmartRefreshWebFragment getInstance(Bundle bundle) { SmartRefreshWebFragment mSmartRefreshWebFragment = new SmartRefreshWebFragment(); if (mSmartRefreshWebFragment != null) { mSmartRefreshWebFragment.setArguments(bundle); } return mSmartRefreshWebFragment; } private SmartRefreshWebLayout mSmartRefreshWebLayout = null; @Override public String getUrl() { return super.getUrl(); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); final SmartRefreshLayout mSmartRefreshLayout = (SmartRefreshLayout) this.mSmartRefreshWebLayout.getLayout(); final WebView mWebView = this.mSmartRefreshWebLayout.getWebView(); mSmartRefreshLayout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(RefreshLayout refreshlayout) { mAgentWeb.getUrlLoader().reload(); mSmartRefreshLayout.postDelayed(new Runnable() { @Override public void run() { mSmartRefreshLayout.finishRefresh(); } }, 2000); } }); mSmartRefreshLayout.autoRefresh(); } @Override protected IWebLayout getWebLayout() { return this.mSmartRefreshWebLayout = new SmartRefreshWebLayout(this.getActivity()); } @Override protected void addBGChild(FrameLayout frameLayout) { frameLayout.setBackgroundColor(Color.TRANSPARENT); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/fragment/VasSonicFragment.java ================================================ package com.just.agentweb.sample.fragment; import android.content.Intent; import android.os.Bundle; import androidx.annotation.Nullable; import android.view.View; import com.just.agentweb.MiddlewareWebClientBase; import com.just.agentweb.sample.sonic.SonicImpl; import com.just.agentweb.sample.sonic.SonicJavaScriptInterface; import static com.just.agentweb.sample.sonic.SonicJavaScriptInterface.PARAM_CLICK_TIME; /** * Created by cenxiaozhong on 2017/12/18. * * If you wanna use VasSonic to fast open first page , please * follow as sample to update your code; */ public class VasSonicFragment extends AgentWebFragment { private SonicImpl mSonicImpl; public static VasSonicFragment create(Bundle bundle){ VasSonicFragment mVasSonicFragment =new VasSonicFragment(); if(bundle!=null){ mVasSonicFragment.setArguments(bundle); } return mVasSonicFragment; } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { // 1. 首先创建SonicImpl mSonicImpl = new SonicImpl(this.getArguments().getString(URL_KEY), this.getContext()); // 2. 调用 onCreateSession mSonicImpl.onCreateSession(); //3. 创建AgentWeb ,注意创建AgentWeb的时候应该使用加入SonicWebViewClient中间件 super.onViewCreated(view, savedInstanceState); // 创建 AgentWeb 注意的 go("") 传入的 mUrl 应该null 或者"" //4. 注入 JavaScriptInterface mAgentWeb.getJsInterfaceHolder().addJavaObject("sonic", new SonicJavaScriptInterface(mSonicImpl.getSonicSessionClient(), new Intent().putExtra(PARAM_CLICK_TIME,getArguments().getLong(PARAM_CLICK_TIME)).putExtra("loadUrlTime", System.currentTimeMillis()))); //5. 最后绑定AgentWeb mSonicImpl.bindAgentWeb(mAgentWeb); } //在步骤3的时候应该传入给AgentWeb @Override public MiddlewareWebClientBase getMiddlewareWebClient() { return mSonicImpl.createSonicClientMiddleWare(); } //getUrl 应该为null @Override public String getUrl() { return null; } @Override public void onDestroyView() { super.onDestroyView(); //销毁SonicSession if(mSonicImpl !=null){ mSonicImpl.destrory(); } } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/provider/ServiceProvider.java ================================================ package com.just.agentweb.sample.provider; import com.flyingpigeon.library.ServiceContentProvider; /** * @author xiaozhongcen * @date 20-8-18 * @since 1.0.0 */ public class ServiceProvider extends ServiceContentProvider { } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/provider/WebServiceProvider.java ================================================ package com.just.agentweb.sample.provider; import com.flyingpigeon.library.ServiceContentProvider; /** * @author xiaozhongcen * @date 20-8-18 * @since 1.0.0 */ public class WebServiceProvider extends ServiceContentProvider { } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/service/WebService.java ================================================ package com.just.agentweb.sample.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; import androidx.annotation.Nullable; import android.util.Log; import android.webkit.WebView; /** * @author xiaozhongcen * @date 20-8-18 * @since 1.0.0 * 提前初始化进程减少白屏 */ public class WebService extends Service { private static final String TAG = WebService.class.getSimpleName(); @Override public void onCreate() { super.onCreate(); Log.e(TAG, "init process"); try { new WebView(this.getApplicationContext()); }catch (Throwable throwable){ throwable.printStackTrace(); } } @Nullable @Override public IBinder onBind(Intent intent) { return null; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/sonic/DefaultSonicRuntimeImpl.java ================================================ /* * Tencent is pleased to support the open source community by making VasSonic available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved. * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. * * */ package com.just.agentweb.sample.sonic; import android.content.Context; import android.os.Build; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import android.webkit.CookieManager; import android.webkit.WebResourceResponse; import com.tencent.sonic.sdk.BuildConfig; import com.tencent.sonic.sdk.SonicRuntime; import com.tencent.sonic.sdk.SonicSessionClient; import java.io.File; import java.io.InputStream; import java.util.List; import java.util.Map; /** * the sonic host application must implement SonicRuntime to do right things. */ public class DefaultSonicRuntimeImpl extends SonicRuntime { public DefaultSonicRuntimeImpl(Context context) { super(context); } /** * 获取用户UA信息 * @return */ @Override public String getUserAgent() { return "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Mobile Safari/537.36"; } /** * 获取用户ID信息 * @return */ @Override public String getCurrentUserAccount() { return "sonic-demo-master"; } @Override public String getCookie(String url) { CookieManager cookieManager = CookieManager.getInstance(); return cookieManager.getCookie(url); } @Override public void log(String tag, int level, String message) { switch (level) { case Log.ERROR: Log.e(tag, message); break; case Log.INFO: Log.i(tag, message); break; default: Log.d(tag, message); } } @Override public Object createWebResourceResponse(String mimeType, String encoding, InputStream data, Map headers) { WebResourceResponse resourceResponse = new WebResourceResponse(mimeType, encoding, data); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { resourceResponse.setResponseHeaders(headers); } return resourceResponse; } @Override public void showToast(CharSequence text, int duration) { } @Override public void notifyError(SonicSessionClient client, String url, int errorCode) { } @Override public boolean isSonicUrl(String url) { return true; } @Override public boolean setCookie(String url, List cookies) { if (!TextUtils.isEmpty(url) && cookies != null && cookies.size() > 0) { CookieManager cookieManager = CookieManager.getInstance(); for (String cookie : cookies) { cookieManager.setCookie(url, cookie); } return true; } return false; } @Override public boolean isNetworkValid() { return true; } @Override public void postTaskToThread(Runnable task, long delayMillis) { Thread thread = new Thread(task, "SonicThread"); thread.start(); } @Override public File getSonicCacheDir() { if (BuildConfig.DEBUG) { String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "sonic/"; File file = new File(path.trim()); if(!file.exists()){ file.mkdir(); } return file; } return super.getSonicCacheDir(); } @Override public String getHostDirectAddress(String url) { return null; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/sonic/SonicImpl.java ================================================ package com.just.agentweb.sample.sonic; import android.content.Context; import com.just.agentweb.AgentWeb; import com.just.agentweb.MiddlewareWebClientBase; import com.tencent.sonic.sdk.SonicConfig; import com.tencent.sonic.sdk.SonicEngine; import com.tencent.sonic.sdk.SonicSession; import com.tencent.sonic.sdk.SonicSessionConfig; /** * Created by cenxiaozhong on 2017/12/17. */ public class SonicImpl { private SonicSession sonicSession; private Context mContext; private String url; private SonicSessionClientImpl sonicSessionClient; public SonicImpl(String url , Context context){ this.url=url; this.mContext=context; } /** */ public void onCreateSession() { SonicSessionConfig.Builder sessionConfigBuilder = new SonicSessionConfig.Builder(); sessionConfigBuilder.setSupportLocalServer(true); SonicEngine.createInstance(new DefaultSonicRuntimeImpl(mContext.getApplicationContext()), new SonicConfig.Builder().build()); // create sonic session and run sonic flow sonicSession = SonicEngine.getInstance().createSession(url, sessionConfigBuilder.build()); if (null != sonicSession) { sonicSession.bindClient(sonicSessionClient = new SonicSessionClientImpl()); } else { // throw new UnknownError("create session fail!"); // Toast.makeText(this, "create sonic session fail!", Toast.LENGTH_LONG).show(); } } public SonicSessionClientImpl getSonicSessionClient(){ return this.sonicSessionClient; } /** * 不使用中间件,使用普通的 WebViewClient 也是可以的。 * @return MiddlewareWebClientBase */ public MiddlewareWebClientBase createSonicClientMiddleWare(){ return new SonicWebViewClient(sonicSession); } public void bindAgentWeb(AgentWeb agentWeb){ if (sonicSessionClient != null) { sonicSessionClient.bindWebView(agentWeb); sonicSessionClient.clientReady(); } else { // default mode agentWeb.getUrlLoader().loadUrl(url); } } public void destrory(){ if(sonicSession!=null){ sonicSession.destroy(); } } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/sonic/SonicJavaScriptInterface.java ================================================ /* * Tencent is pleased to support the open source community by making VasSonic available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved. * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. * * */ package com.just.agentweb.sample.sonic; import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.webkit.JavascriptInterface; import com.tencent.sonic.sdk.SonicDiffDataCallback; import org.json.JSONObject; /** * Sonic javaScript Interface (Android API Level >= 17) */ public class SonicJavaScriptInterface { private final SonicSessionClientImpl sessionClient; private final Intent intent; public static final String PARAM_CLICK_TIME = "clickTime"; public static final String PARAM_LOAD_URL_TIME = "loadUrlTime"; public SonicJavaScriptInterface(SonicSessionClientImpl sessionClient, Intent intent) { this.sessionClient = sessionClient; this.intent = intent; } @JavascriptInterface public void getDiffData() { // the callback function of demo page is hardcode as 'getDiffDataCallback' getDiffData2("getDiffDataCallback"); } @JavascriptInterface public void getDiffData2(final String jsCallbackFunc) { Log.i("Info","getDiffData2"); if (null != sessionClient) { sessionClient.getDiffData(new SonicDiffDataCallback() { @Override public void callback(final String resultData) { Runnable callbackRunnable = new Runnable() { @Override public void run() { String jsCode = "javascript:" + jsCallbackFunc + "('"+ toJsString(resultData) + "')"; sessionClient.getWebView().loadUrl(jsCode); } }; if (Looper.getMainLooper() == Looper.myLooper()) { callbackRunnable.run(); } else { new Handler(Looper.getMainLooper()).post(callbackRunnable); } } }); } } @JavascriptInterface public String getPerformance() { long clickTime = intent.getLongExtra(PARAM_CLICK_TIME, -1); long loadUrlTime = intent.getLongExtra(PARAM_LOAD_URL_TIME, -1); try { JSONObject result = new JSONObject(); result.put(PARAM_CLICK_TIME, clickTime); result.put(PARAM_LOAD_URL_TIME, loadUrlTime); Log.i("Info","getPerformance"); return result.toString(); } catch (Exception e) { } return ""; } /* * * From RFC 4627, "All Unicode characters may be placed within the quotation marks except * for the characters that must be escaped: quotation mark, * reverse solidus, and the control characters (U+0000 through U+001F)." */ private static String toJsString(String value) { if (value == null) { return "null"; } StringBuilder out = new StringBuilder(1024); for (int i = 0, length = value.length(); i < length; i++) { char c = value.charAt(i); switch (c) { case '"': case '\\': case '/': out.append('\\').append(c); break; case '\t': out.append("\\t"); break; case '\b': out.append("\\b"); break; case '\n': out.append("\\n"); break; case '\r': out.append("\\r"); break; case '\f': out.append("\\f"); break; default: if (c <= 0x1F) { out.append(String.format("\\u%04x", (int) c)); } else { out.append(c); } break; } } return out.toString(); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/sonic/SonicSessionClientImpl.java ================================================ /* * Tencent is pleased to support the open source community by making VasSonic available. * * Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved. * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. * * */ package com.just.agentweb.sample.sonic; import android.os.Bundle; import android.webkit.WebView; import com.just.agentweb.AgentWeb; import com.tencent.sonic.sdk.SonicSessionClient; import java.util.HashMap; /** * a implement of SonicSessionClient which need to connect webview and content data. */ public class SonicSessionClientImpl extends SonicSessionClient { private AgentWeb mAgentWeb; public void bindWebView(AgentWeb agentWeb) { this.mAgentWeb = agentWeb; } public WebView getWebView() { return this.mAgentWeb.getWebCreator().getWebView(); } @Override public void loadUrl(String url, Bundle extraData) { this.mAgentWeb.getUrlLoader().loadUrl(url); } @Override public void loadDataWithBaseUrl(String baseUrl, String data, String mimeType, String encoding, String historyUrl) { this.mAgentWeb.getUrlLoader().loadDataWithBaseURL(baseUrl,data,mimeType,encoding,historyUrl); } @Override public void loadDataWithBaseUrlAndHeader(String baseUrl, String data, String mimeType, String encoding, String historyUrl, HashMap headers) { loadDataWithBaseUrl(baseUrl, data, mimeType, encoding, historyUrl); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/sonic/SonicWebViewClient.java ================================================ package com.just.agentweb.sample.sonic; import android.annotation.TargetApi; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebView; import com.just.agentweb.MiddlewareWebClientBase; import com.tencent.sonic.sdk.SonicSession; /** * Created by cenxiaozhong on 2017/12/17. */ public class SonicWebViewClient extends MiddlewareWebClientBase { private SonicSession sonicSession; public SonicWebViewClient(SonicSession sonicSession) { this.sonicSession=sonicSession; } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if (sonicSession != null) { sonicSession.getSessionClient().pageFinish(url); } } @TargetApi(21) @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { return shouldInterceptRequest(view, request.getUrl().toString()); } @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { if (sonicSession != null) { return (WebResourceResponse) sonicSession.getSessionClient().requestResource(url); } return null; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/utils/FileUtils.java ================================================ package com.just.agentweb.sample.utils; /** * @author cenxiaozhong * @date 2021/11/27 * @since 1.0.0 */ public class FileUtils { public static String getExtensionByFilePath(String filePath){ String fe = ""; int i = filePath.lastIndexOf('.'); if (i > 0) { fe = filePath.substring(i+1); } System.out.println("File extension is : "+fe); return fe; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/utils/ProcessUtils.java ================================================ package com.just.agentweb.sample.utils; import android.app.ActivityManager; import android.app.Application; import android.content.Context; import android.text.TextUtils; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; /** * Adapted from com.blankj.utilcode.util.ProcessUtils#getCurrentProcessName */ public class ProcessUtils { public static String getCurrentProcessName(Context context) { String name = getCurrentProcessNameByFile(); if (!TextUtils.isEmpty(name)) return name; name = getCurrentProcessNameByAms(context); if (!TextUtils.isEmpty(name)) return name; name = getCurrentProcessNameByReflect(context); return name; } private static String getCurrentProcessNameByFile() { try { File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline"); BufferedReader mBufferedReader = new BufferedReader(new FileReader(file)); String processName = mBufferedReader.readLine().trim(); mBufferedReader.close(); return processName; } catch (Exception e) { e.printStackTrace(); return ""; } } private static String getCurrentProcessNameByAms(Context context) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am == null) return ""; List info = am.getRunningAppProcesses(); if (info == null || info.size() == 0) return ""; int pid = android.os.Process.myPid(); for (ActivityManager.RunningAppProcessInfo aInfo : info) { if (aInfo.pid == pid) { if (aInfo.processName != null) { return aInfo.processName; } } } return ""; } private static String getCurrentProcessNameByReflect(Context context) { String processName = ""; try { Application app = (Application) context.getApplicationContext(); Field loadedApkField = app.getClass().getField("mLoadedApk"); loadedApkField.setAccessible(true); Object loadedApk = loadedApkField.get(app); Field activityThreadField = loadedApk.getClass().getDeclaredField("mActivityThread"); activityThreadField.setAccessible(true); Object activityThread = activityThreadField.get(loadedApk); Method getProcessName = activityThread.getClass().getDeclaredMethod("getProcessName"); processName = (String) getProcessName.invoke(activityThread); } catch (Exception e) { e.printStackTrace(); } return processName; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/utils/WebCompat.java ================================================ package com.just.agentweb.sample.utils; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActivityManager; import android.app.Application; import android.content.Context; import android.os.Build; import android.os.Environment; import android.text.TextUtils; import android.webkit.WebView; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.channels.FileLock; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; public class WebCompat { /** * fix Using WebView from more than one process * * @param context */ public static void setDataDirectorySuffix(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { return; } try { Set pathSet = new HashSet<>(); String suffix = ""; String dataPath = context.getDataDir().getAbsolutePath(); String webViewDir = "/app_webview"; String huaweiWebViewDir = "/app_hws_webview"; String lockFile = "/webview_data.lock"; String processName = getCurrentProcessName(context); if (!TextUtils.equals(context.getPackageName(), processName)) {//判断不等于默认进程名称 suffix = TextUtils.isEmpty(processName) ? context.getPackageName() : processName; WebView.setDataDirectorySuffix(suffix); suffix = "_" + suffix; pathSet.add(dataPath + webViewDir + suffix + lockFile); if (isHuawei()) { pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile); } } else { //主进程 suffix = "_" + processName; pathSet.add(dataPath + webViewDir + lockFile);//默认未添加进程名后缀 pathSet.add(dataPath + webViewDir + suffix + lockFile);//系统自动添加了进程名后缀 if (isHuawei()) {//部分华为手机更改了webview目录名 pathSet.add(dataPath + huaweiWebViewDir + lockFile); pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile); } } for (String path : pathSet) { File file = new File(path); if (file.exists()) { tryLockOrRecreateFile(file); break; } } } catch (Exception e) { e.printStackTrace(); } } @TargetApi(Build.VERSION_CODES.P) private static void tryLockOrRecreateFile(File file) { try { FileLock tryLock = new RandomAccessFile(file, "rw").getChannel().tryLock(); if (tryLock != null) { tryLock.close(); } else { createFile(file, file.delete()); } } catch (Exception e) { e.printStackTrace(); boolean deleted = false; if (file.exists()) { deleted = file.delete(); } createFile(file, deleted); } } private static void createFile(File file, boolean deleted) { try { if (deleted && !file.exists()) { file.createNewFile(); } } catch (Exception e) { e.printStackTrace(); } } /********Adapted from com.blankj.utilcode.util.ProcessUtils#getCurrentProcessName******************/ private static String getCurrentProcessName(Context context) { String name = getCurrentProcessNameByFile(); if (!TextUtils.isEmpty(name)) return name; name = getCurrentProcessNameByAms(context); if (!TextUtils.isEmpty(name)) return name; name = getCurrentProcessNameByReflect(context); return name; } private static String getCurrentProcessNameByFile() { try { File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline"); BufferedReader mBufferedReader = new BufferedReader(new FileReader(file)); String processName = mBufferedReader.readLine().trim(); mBufferedReader.close(); return processName; } catch (Exception e) { e.printStackTrace(); return ""; } } private static String getCurrentProcessNameByAms(Context context) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am == null) return ""; List info = am.getRunningAppProcesses(); if (info == null || info.size() == 0) return ""; int pid = android.os.Process.myPid(); for (ActivityManager.RunningAppProcessInfo aInfo : info) { if (aInfo.pid == pid) { if (aInfo.processName != null) { return aInfo.processName; } } } return ""; } private static String getCurrentProcessNameByReflect(Context context) { String processName = ""; try { Application app = (Application) context.getApplicationContext(); Field loadedApkField = app.getClass().getField("mLoadedApk"); loadedApkField.setAccessible(true); Object loadedApk = loadedApkField.get(app); Field activityThreadField = loadedApk.getClass().getDeclaredField("mActivityThread"); activityThreadField.setAccessible(true); Object activityThread = activityThreadField.get(loadedApk); Method getProcessName = activityThread.getClass().getDeclaredMethod("getProcessName"); processName = (String) getProcessName.invoke(activityThread); } catch (Exception e) { e.printStackTrace(); } return processName; } /***********************RomUtils**********************************/ private static final String[] ROM_HUAWEI = {"huawei"}; private static final String VERSION_PROPERTY_HUAWEI = "ro.build.version.emui"; private final static String UNKNOWN = "unknown"; private static RomInfo bean = null; /** * Return whether the rom is made by huawei. * * @return {@code true}: yes
{@code false}: no */ private static boolean isHuawei() { return ROM_HUAWEI[0].equals(getRomInfo().name); } /** * Return the rom's information. * * @return the rom's information */ private static RomInfo getRomInfo() { if (bean != null) return bean; bean = new RomInfo(); final String brand = getBrand(); final String manufacturer = getManufacturer(); if (isRightRom(brand, manufacturer, ROM_HUAWEI)) { bean.name = ROM_HUAWEI[0]; String version = getRomVersion(VERSION_PROPERTY_HUAWEI); String[] temp = version.split("_"); if (temp.length > 1) { bean.version = temp[1]; } else { bean.version = version; } return bean; } else { bean.name = manufacturer; } bean.version = getRomVersion(""); return bean; } private static boolean isRightRom(final String brand, final String manufacturer, final String... names) { for (String name : names) { if (brand.contains(name) || manufacturer.contains(name)) { return true; } } return false; } private static String getManufacturer() { try { String manufacturer = Build.MANUFACTURER; if (!TextUtils.isEmpty(manufacturer)) { return manufacturer.toLowerCase(); } } catch (Throwable ignore) {/**/} return UNKNOWN; } private static String getBrand() { try { String brand = Build.BRAND; if (!TextUtils.isEmpty(brand)) { return brand.toLowerCase(); } } catch (Throwable ignore) {/**/} return UNKNOWN; } private static String getRomVersion(final String propertyName) { String ret = ""; if (!TextUtils.isEmpty(propertyName)) { ret = getSystemProperty(propertyName); } if (TextUtils.isEmpty(ret) || ret.equals(UNKNOWN)) { try { String display = Build.DISPLAY; if (!TextUtils.isEmpty(display)) { ret = display.toLowerCase(); } } catch (Throwable ignore) {/**/} } if (TextUtils.isEmpty(ret)) { return UNKNOWN; } return ret; } private static String getSystemProperty(final String name) { String prop = getSystemPropertyByShell(name); if (!TextUtils.isEmpty(prop)) return prop; prop = getSystemPropertyByStream(name); if (!TextUtils.isEmpty(prop)) return prop; if (Build.VERSION.SDK_INT < 28) { return getSystemPropertyByReflect(name); } return prop; } private static String getSystemPropertyByShell(final String propName) { String line; BufferedReader input = null; try { Process p = Runtime.getRuntime().exec("getprop " + propName); input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); String ret = input.readLine(); if (ret != null) { return ret; } } catch (IOException ignore) { } finally { if (input != null) { try { input.close(); } catch (IOException ignore) {/**/} } } return ""; } private static String getSystemPropertyByStream(final String key) { try { Properties prop = new Properties(); FileInputStream is = new FileInputStream( new File(Environment.getRootDirectory(), "build.prop") ); prop.load(is); return prop.getProperty(key, ""); } catch (Exception ignore) {/**/} return ""; } private static String getSystemPropertyByReflect(String key) { try { @SuppressLint("PrivateApi") Class clz = Class.forName("android.os.SystemProperties"); Method getMethod = clz.getMethod("get", String.class, String.class); return (String) getMethod.invoke(clz, key, ""); } catch (Exception e) {/**/} return ""; } private static class RomInfo { private String name; private String version; public String getName() { return name; } public String getVersion() { return version; } @Override public String toString() { return "RomInfo{name=" + name + ", version=" + version + "}"; } } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/widget/CommonIndicator.java ================================================ package com.just.agentweb.sample.widget; import android.content.Context; import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import com.just.agentweb.BaseIndicatorView; /** * Created by cenxiaozhong on 2017/5/26. * source code https://github.com/Justson/AgentWeb */ public class CommonIndicator extends BaseIndicatorView { public CommonIndicator(Context context) { super(context); } public CommonIndicator(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public CommonIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public void show() { this.setVisibility(View.VISIBLE); } @Override public void hide() { this.setVisibility(View.GONE); } @Override public LayoutParams offerLayoutParams() { return new FrameLayout.LayoutParams(-1, -1); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/widget/CoolIndicatorLayout.java ================================================ /* * Copyright (C) Justson(https://github.com/Justson/AgentWeb) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.just.agentweb.sample.widget; import android.app.Activity; import android.content.Context; import android.os.Build; import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import com.coolindicator.sdk.CoolIndicator; import com.just.agentweb.AgentWebUtils; import com.just.agentweb.BaseIndicatorView; /** * @author cenxiaozhong * @date 2018/2/23 * @since 1.0.0 */ public class CoolIndicatorLayout extends BaseIndicatorView { private static final String TAG = CoolIndicatorLayout.class.getSimpleName(); private CoolIndicator mCoolIndicator = null; public CoolIndicatorLayout(Context context) { this(context, null); } public CoolIndicatorLayout(Context context, @Nullable AttributeSet attrs) { this(context, attrs, -1); } public CoolIndicatorLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mCoolIndicator = CoolIndicator.create((Activity) context); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mCoolIndicator.setProgressDrawable(context.getResources().getDrawable(com.coolindicator.sdk.R.drawable.default_drawable_indicator, context.getTheme())); } else { mCoolIndicator.setProgressDrawable(context.getResources().getDrawable(com.coolindicator.sdk.R.drawable.default_drawable_indicator)); } this.addView(mCoolIndicator, offerLayoutParams()); } @Override public void show() { this.setVisibility(View.VISIBLE); mCoolIndicator.start(); } @Override public void setProgress(int newProgress) { } @Override public void hide() { mCoolIndicator.complete(); } @Override public LayoutParams offerLayoutParams() { return new FrameLayout.LayoutParams(-1, AgentWebUtils.dp2px(getContext(), 3)); } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/widget/SmartRefreshWebLayout.java ================================================ package com.just.agentweb.sample.widget; import android.app.Activity; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; import com.just.agentweb.IWebLayout; import com.just.agentweb.sample.R; import com.scwang.smartrefresh.layout.SmartRefreshLayout; /** * Created by cenxiaozhong on 2017/8/14. */ public class SmartRefreshWebLayout implements IWebLayout { private final SmartRefreshLayout mSmartRefreshLayout; private final WebView mWebView; public SmartRefreshWebLayout(Activity activity){ View mView=activity.getLayoutInflater().inflate(R.layout.fragment_srl_web,null); View smarkView = mView.findViewById(R.id.smarkLayout); mSmartRefreshLayout = (SmartRefreshLayout) smarkView; mWebView = (WebView) mSmartRefreshLayout.findViewById(R.id.webView); } @NonNull @Override public ViewGroup getLayout() { return mSmartRefreshLayout; } @Nullable @Override public WebView getWebView() { return mWebView; } } ================================================ FILE: sample/src/main/java/com/just/agentweb/sample/widget/WebLayout.java ================================================ package com.just.agentweb.sample.widget; import android.app.Activity; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.view.LayoutInflater; import android.view.ViewGroup; import android.webkit.WebView; import com.just.agentweb.IWebLayout; import com.just.agentweb.sample.R; import com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout; /** * Created by cenxiaozhong on 2017/7/1. * source code https://github.com/Justson/AgentWeb */ public class WebLayout implements IWebLayout { private Activity mActivity; private final TwinklingRefreshLayout mTwinklingRefreshLayout; private WebView mWebView = null; public WebLayout(Activity activity) { this.mActivity = activity; mTwinklingRefreshLayout = (TwinklingRefreshLayout) LayoutInflater.from(activity).inflate(R.layout.fragment_twk_web, null); mTwinklingRefreshLayout.setPureScrollModeOn(); mWebView = (WebView) mTwinklingRefreshLayout.findViewById(R.id.webView); } @NonNull @Override public ViewGroup getLayout() { return mTwinklingRefreshLayout; } @Nullable @Override public WebView getWebView() { return mWebView; } } ================================================ FILE: sample/src/main/res/drawable/btn_shape.xml ================================================ ================================================ FILE: sample/src/main/res/drawable/btn_shape_s.xml ================================================ ================================================ FILE: sample/src/main/res/drawable/ic_baseline_search_24.xml ================================================ ================================================ FILE: sample/src/main/res/drawable/indicator_shape.xml ================================================ ================================================ FILE: sample/src/main/res/drawable/iv_back_selector.xml ================================================ ================================================ FILE: sample/src/main/res/drawable/selector_drawable_for_btn.xml ================================================ ================================================ FILE: sample/src/main/res/drawable-v21/ripple_for_btn.xml ================================================ ================================================ FILE: sample/src/main/res/drawable-v21/selector_drawable_for_btn.xml ================================================ ================================================ FILE: sample/src/main/res/layout/activity_auto_hiden_toolbar.xml ================================================ ================================================ FILE: sample/src/main/res/layout/activity_common.xml ================================================ ================================================ FILE: sample/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: sample/src/main/res/layout/activity_native_download.xml ================================================ ================================================ FILE: sample/src/main/res/layout/activity_web.xml ================================================ ================================================ FILE: sample/src/main/res/layout/fragment_agentweb.xml ================================================ ================================================ FILE: sample/src/main/res/layout/fragment_js.xml ================================================