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 extends String, ?>) 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!NoteWi-Fi disconnected. Continue the download via mobile data network?DownloadCancelDownload failed!Downloading:%sdownloaded:%sYou have a new noticeDownloadTap to continueComing soon to download the fileCameraFilesLoading ...leaving %s and opening another app?Go awayThe selected file can not be larger than %s MBerror~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 ErrorContinue
================================================
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
================================================