mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-13 03:43:41 +00:00
fix: finish migration to main WebView profile library
This commit is contained in:
1 parent
adeb7a2895
commit
d1b2ae42af
534 files changed
-70217
No files matched your search
@@ -1,66 +0,0 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
|
||||
// Private host/plugin bridge. These settings never originate from extension JS.
|
||||
class WebViewProfileSettings extends InAppWebViewSettings {
|
||||
WebViewProfileSettings({required this.profileId, required bool debug, super.userAgent})
|
||||
: super(javaScriptEnabled: true, transparentBackground: true, isInspectable: debug);
|
||||
|
||||
final String profileId;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() => {...super.toMap(), 'gopeedProfileId': profileId};
|
||||
}
|
||||
|
||||
class WebViewProfile {
|
||||
WebViewProfile(this.id);
|
||||
|
||||
final String id;
|
||||
static const _channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_cookiemanager');
|
||||
|
||||
Future<void> remove() async {
|
||||
await _call('gopeed.removeProfile', {});
|
||||
}
|
||||
|
||||
Future<void> prepare(String proxyUrl) async {
|
||||
await _call('gopeed.prepareProfile', {'proxyUrl': proxyUrl});
|
||||
}
|
||||
|
||||
Future<dynamic> _call(String method, Map<String, dynamic> arguments) =>
|
||||
_channel.invokeMethod(method, {...arguments, 'gopeedProfileId': id});
|
||||
|
||||
Future<List<Cookie>> getCookies({required WebUri url}) async {
|
||||
final values = await _call('getCookies', {'url': url.toString()}) as List? ?? [];
|
||||
return values.map((value) => Cookie.fromMap(Map<String, dynamic>.from(value as Map))!).toList();
|
||||
}
|
||||
|
||||
Future<void> setCookie({
|
||||
required WebUri url,
|
||||
required String name,
|
||||
required String value,
|
||||
String? domain,
|
||||
String path = '/',
|
||||
int? expiresDate,
|
||||
bool isSecure = false,
|
||||
bool isHttpOnly = false,
|
||||
}) async {
|
||||
await _call('setCookie', {
|
||||
'url': url.toString(),
|
||||
'name': name,
|
||||
'value': value,
|
||||
'domain': domain,
|
||||
'path': path,
|
||||
'expiresDate': expiresDate?.toString(),
|
||||
'isSecure': isSecure,
|
||||
'isHttpOnly': isHttpOnly,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteCookie({required WebUri url, required String name, String? domain, String path = '/'}) async {
|
||||
await _call('deleteCookie', {'url': url.toString(), 'name': name, 'domain': domain, 'path': path});
|
||||
}
|
||||
|
||||
Future<void> deleteAllCookies() async {
|
||||
await _call('deleteAllCookies', {});
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import '../../core/common/start_config.dart';
|
||||
import '../../util/log_util.dart';
|
||||
import '../../util/util.dart';
|
||||
import 'server.dart';
|
||||
import 'webview_profile.dart';
|
||||
|
||||
String buildWebViewExecuteScript({
|
||||
required String channelName,
|
||||
|
||||
Vendored
-38
@@ -1,38 +0,0 @@
|
||||
# Native WebView host bridge
|
||||
|
||||
Vendored unmodified Dart facades and native sources from pub.dev:
|
||||
|
||||
- flutter_inappwebview_macos 1.1.2
|
||||
- flutter_inappwebview_ios 1.1.2
|
||||
- flutter_inappwebview_android 1.1.3
|
||||
|
||||
Each package retains its upstream license. Versions and original archive hashes
|
||||
are available in the parent commit's pubspec.lock.
|
||||
|
||||
Local native changes are limited to:
|
||||
|
||||
- `GopeedProfiles`: remove profiles on uninstall and prepare a persistent profile and apply the internal loopback
|
||||
proxy before creating a WebView. Apple uses SOCKS5 to cover HTTP as well as HTTPS;
|
||||
Android uses the application-wide HTTP proxy override completion callback.
|
||||
- `InAppWebViewSettings` and `InAppWebView`: read the host-only `gopeedProfileId`
|
||||
setting and attach the profile before navigation.
|
||||
- `MyCookieManager`: route cookie operations to the requested profile, capturing
|
||||
the store per operation so asynchronous calls cannot cross profiles.
|
||||
- macOS `InAppWebView`: resign input focus before disposing a view so AppKit
|
||||
cannot retain its profile through the responder chain after closing the page.
|
||||
|
||||
`lib/app/rpc/webview_profile.dart` uses the existing plugin method channel and
|
||||
serializes these internal settings. No extension JavaScript API is added.
|
||||
|
||||
Apple requires macOS 14 / iOS 17 for named persistent stores and proxy settings.
|
||||
Android requires `MULTI_PROFILE` and `PROXY_OVERRIDE`; unsupported runtimes return
|
||||
an explicit unavailable error rather than opening a shared, unproxied browser.
|
||||
|
||||
When updating upstream, refresh these three packages, reapply the above changes,
|
||||
and run the shared Go provider integration tests against the normal Flutter app
|
||||
on a native Apple host (see `internal/webview/rpcprovider/README.md`). Do not patch the
|
||||
user's pub cache: dependency_overrides selects these tracked sources reproducibly.
|
||||
|
||||
Android uses AndroidX WebKit 1.13.0 for profile-scoped full browsing-data deletion.
|
||||
Loaded profile shells are queued durably for deletion before WebViews initialize
|
||||
at the next process start. No global cookie/cache clearing is used for uninstall.
|
||||
@@ -1,103 +0,0 @@
|
||||
## 1.1.3
|
||||
|
||||
- Updated flutter_inappwebview_platform_interface version to ^1.3.0
|
||||
|
||||
## 1.1.2
|
||||
|
||||
- Removed webview/plugin_scripts_js/ConsoleLogJS.java file, use native WebChromeClient.onConsoleMessage instead
|
||||
|
||||
## 1.1.1
|
||||
|
||||
- Updated flutter_inappwebview_platform_interface version to ^1.2.0
|
||||
|
||||
## 1.1.0+4
|
||||
|
||||
- Updated flutter_inappwebview_platform_interface version
|
||||
|
||||
## 1.1.0+3
|
||||
|
||||
- Fixed compilation error
|
||||
|
||||
## 1.1.0+2
|
||||
|
||||
- Updated pubspec.yaml
|
||||
|
||||
## 1.1.0+1
|
||||
|
||||
- Downgraded androidx.appcompat:appcompat:1.7.0 to androidx.appcompat:appcompat:1.6.1
|
||||
- Added `-dontwarn android.window.BackEvent` proguard rule
|
||||
|
||||
## 1.1.0
|
||||
|
||||
- Updated androidx.webkit:webkit:1.8.0 to androidx.webkit:webkit:1.12.0
|
||||
- Updated androidx.browser:browser:1.6.0 to androidx.browser:browser:1.8.0
|
||||
- Updates minimum supported SDK version to Flutter 3.24/Dart 3.5.
|
||||
- Removed unsupported WebViewFeature.SUPPRESS_ERROR_PAGE
|
||||
- Merged "Remove references to deprecated v1 Android embedding" [#2176](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2176) (thanks to [gmackall](https://github.com/gmackall))
|
||||
|
||||
## 1.0.13
|
||||
|
||||
- Fixed "Android emulator using API 34 fails to draw on resume sometimes" [#1981](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1981)
|
||||
|
||||
## 1.0.12
|
||||
|
||||
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.10`
|
||||
|
||||
## 1.0.11
|
||||
|
||||
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.9`
|
||||
- Fix typos (thanks to [michalsrutek](https://github.com/michalsrutek))
|
||||
|
||||
## 1.0.10
|
||||
|
||||
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.8`
|
||||
- Implemented `PlatformCustomPathHandler` class
|
||||
|
||||
## 1.0.9
|
||||
|
||||
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.7`
|
||||
- Fixed "Cloudflare Turnstile failure" [#1738](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738)
|
||||
- Fixed `InAppWebViewController.callAsyncJavaScript` issue when the last line of the `functionBody` parameter includes a code comment
|
||||
|
||||
## 1.0.8
|
||||
|
||||
- Implemented `InAppWebViewSettings.interceptOnlyAsyncAjaxRequests`
|
||||
- Implemented `PlatformInAppWebViewController.clearFormData` method
|
||||
- Implemented `PlatformCookieManager.removeSessionCookies` method
|
||||
- Updated `useShouldInterceptAjaxRequest` automatic infer logic
|
||||
- Updated `CookieManager` methods return value
|
||||
|
||||
## 1.0.7
|
||||
|
||||
- Merged "Fixed error in InterceptAjaxRequestJS 'Failed to set responseType property'" [#1904](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1904) (thanks to [EArminjon](https://github.com/EArminjon))
|
||||
- Fixed shouldInterceptAjaxRequest javascript code when overriding XMLHttpRequest.open method parameters
|
||||
|
||||
## 1.0.6
|
||||
|
||||
- Fixed "getFavicons: _TypeError: type '_Map<String, dynamic>' is not a subtype of type 'Iterable<dynamic>'" [#1897](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1897)
|
||||
- Fixed "onClosed not considering back navigation or up button / close button in ChromeSafariBrowser when using noHistory: true" [#1882](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1882)
|
||||
|
||||
## 1.0.5
|
||||
|
||||
- Call `super.dispose();` on `InAppBrowser` and `ChromeSafari` implementations
|
||||
|
||||
## 1.0.4
|
||||
|
||||
- Throw platform exception when ProcessGlobalConfig.apply throws an error on the native side to be able to catch it on Flutter side
|
||||
|
||||
## 1.0.3
|
||||
|
||||
- Updated `ContentBlockerHandler` CSS_DISPLAY_NONE action type and `JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_JS_SOURCE` javascript implementation code
|
||||
|
||||
## 1.0.2
|
||||
|
||||
- Updated `flutter_inappwebview_platform_interface` version dependency to `1.0.2`
|
||||
- Fixed "Crash when starting ChromeSafariBrowser on Android java.lang.NoSuchMethodError: No virtual method isEngagementSignalsApiAvailable" [#1881](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1881)
|
||||
|
||||
## 1.0.1
|
||||
|
||||
- Updated README
|
||||
|
||||
## 1.0.0
|
||||
|
||||
Initial release.
|
||||
@@ -1,201 +0,0 @@
|
||||
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
|
||||
result 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 2023 Lorenzo Pichilli
|
||||
|
||||
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.
|
||||
@@ -1,13 +0,0 @@
|
||||
# flutter\_inappwebview\_android
|
||||
|
||||
The Android WebView implementation of [`flutter_inappwebview`](https://pub.dev/packages/flutter_inappwebview).
|
||||
|
||||
## Usage
|
||||
|
||||
This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin),
|
||||
which means you can simply use `flutter_inappwebview`
|
||||
normally. This package will be automatically included in your app when you do,
|
||||
so you do not need to add it to your `pubspec.yaml`.
|
||||
|
||||
However, if you `import` this package to use any of its APIs directly, you
|
||||
should add it to your `pubspec.yaml` as usual.
|
||||
@@ -1,57 +0,0 @@
|
||||
group 'com.pichillilorenzo.flutter_inappwebview_android'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
// Conditional for compatibility with AGP <4.2.
|
||||
if (project.android.hasProperty("namespace")) {
|
||||
namespace 'com.pichillilorenzo.flutter_inappwebview_android'
|
||||
}
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
consumerProguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation 'androidx.webkit:webkit:1.13.0'
|
||||
implementation 'androidx.browser:browser:1.8.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
# WebView
|
||||
-keepattributes *JavascriptInterface*
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
-keepclassmembers class * extends android.webkit.WebViewClient {
|
||||
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
|
||||
public boolean *(android.webkit.WebView, java.lang.String);
|
||||
public void *(android.webkit.webView, jav.lang.String);
|
||||
}
|
||||
-keepclassmembers class com.pichillilorenzo.flutter_inappwebview_android$JavaScriptBridgeInterface {
|
||||
<fields>;
|
||||
<methods>;
|
||||
public *;
|
||||
private *;
|
||||
}
|
||||
-keep class com.pichillilorenzo.flutter_inappwebview_android.** { *; }
|
||||
|
||||
-dontwarn android.window.BackEvent
|
||||
@@ -1 +0,0 @@
|
||||
rootProject.name = 'flutter_inappwebview_android'
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.pichillilorenzo.flutter_inappwebview_android">
|
||||
<application>
|
||||
<activity
|
||||
android:theme="@style/AppTheme"
|
||||
android:exported="false"
|
||||
android:name="com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.InAppBrowserActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" />
|
||||
<activity
|
||||
android:theme="@style/ThemeTransparent"
|
||||
android:exported="false"
|
||||
android:name="com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.ChromeCustomTabsActivity" />
|
||||
<activity
|
||||
android:theme="@style/ThemeTransparent"
|
||||
android:exported="false"
|
||||
android:name="com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.TrustedWebActivity" />
|
||||
<activity
|
||||
android:theme="@style/ThemeTransparent"
|
||||
android:exported="false"
|
||||
android:name="com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.ChromeCustomTabsActivitySingleInstance"
|
||||
android:launchMode="singleInstance"/>
|
||||
<activity
|
||||
android:theme="@style/ThemeTransparent"
|
||||
android:exported="false"
|
||||
android:name="com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.TrustedWebActivitySingleInstance"
|
||||
android:launchMode="singleInstance"/>
|
||||
<receiver
|
||||
android:name="com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.ActionBroadcastReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<meta-data
|
||||
android:name="io.flutter.embedded_views_preview"
|
||||
android:value="true" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name=
|
||||
"android.support.customtabs.action.CustomTabsService" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
-162
@@ -1,162 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import androidx.webkit.Profile;
|
||||
import androidx.webkit.WebStorageCompat;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import androidx.webkit.ProfileStore;
|
||||
import androidx.webkit.ProxyConfig;
|
||||
import androidx.webkit.ProxyController;
|
||||
import androidx.webkit.WebViewFeature;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
// Internal host bridge: profile creation and proxy completion precede navigation.
|
||||
public final class GopeedProfiles {
|
||||
private static final java.util.Map<android.webkit.WebView, String> views = new java.util.IdentityHashMap<>();
|
||||
|
||||
public static void viewCreated(android.webkit.WebView view, String id) { views.put(view, id); }
|
||||
public static void viewDestroyed(android.webkit.WebView view) { views.remove(view); }
|
||||
private static SharedPreferences preferences;
|
||||
private static final Set<String> cleared = new HashSet<>();
|
||||
private static final String DELETIONS = "pendingDeletions";
|
||||
|
||||
static void initialize(Context context) {
|
||||
if (preferences != null) return;
|
||||
preferences = context.getSharedPreferences("gopeed_webview_profiles", Context.MODE_PRIVATE);
|
||||
if (!WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE)) return;
|
||||
for (String id : deletions()) {
|
||||
try {
|
||||
// Do this before getProfile/getOrCreateProfile load any profile.
|
||||
ProfileStore.getInstance().deleteProfile(id);
|
||||
setPending(id, false);
|
||||
} catch (RuntimeException ignored) {
|
||||
// Keep the durable entry and reject use until removal succeeds.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> deletions() {
|
||||
return new HashSet<>(preferences.getStringSet(DELETIONS, new HashSet<>()));
|
||||
}
|
||||
|
||||
private static void setPending(String id, boolean pending) {
|
||||
Set<String> ids = deletions();
|
||||
if (pending) ids.add(id); else ids.remove(id);
|
||||
if (!preferences.edit().putStringSet(DELETIONS, ids).commit())
|
||||
throw new IllegalStateException("Unable to persist WebView profile removal");
|
||||
}
|
||||
|
||||
static void remove(MethodCall call, MethodChannel.Result result) {
|
||||
waitForViews(call, result, 100);
|
||||
}
|
||||
|
||||
private static void waitForViews(MethodCall call, MethodChannel.Result result, int attempts) {
|
||||
String id = call.argument("gopeedProfileId");
|
||||
if (views.containsValue(id)) {
|
||||
if (attempts <= 0) {
|
||||
result.error("PROFILE_REMOVE_FAILED", "WebView pages have not finished closing", null);
|
||||
return;
|
||||
}
|
||||
// The plugin destroys WebViews asynchronously after about:blank loads.
|
||||
// Wait for actual native destruction, not just Dart widget disposal.
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(
|
||||
() -> waitForViews(call, result, attempts - 1), 100);
|
||||
return;
|
||||
}
|
||||
removeData(call, result);
|
||||
}
|
||||
|
||||
private static void removeData(MethodCall call, MethodChannel.Result result) {
|
||||
if (!WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE)) {
|
||||
result.error("UNAVAILABLE", "Android WebView profile removal is unavailable", null);
|
||||
return;
|
||||
}
|
||||
String id = call.argument("gopeedProfileId");
|
||||
try {
|
||||
java.util.UUID.fromString(id);
|
||||
setPending(id, true);
|
||||
try {
|
||||
ProfileStore.getInstance().deleteProfile(id);
|
||||
setPending(id, false);
|
||||
cleared.remove(id);
|
||||
result.success(true);
|
||||
return;
|
||||
} catch (IllegalStateException loadedProfile) {
|
||||
// Android forbids deleting a profile loaded in this process, even
|
||||
// after every WebView has been destroyed. Clear all its data now;
|
||||
// remove the empty profile at the next process start.
|
||||
}
|
||||
if (!WebViewFeature.isFeatureSupported(WebViewFeature.DELETE_BROWSING_DATA)) {
|
||||
result.error("PROFILE_REMOVE_FAILED", "Restart Gopeed to finish removing WebView profile data", null);
|
||||
return;
|
||||
}
|
||||
Profile profile = ProfileStore.getInstance().getProfile(id);
|
||||
if (profile == null) {
|
||||
setPending(id, false);
|
||||
result.success(true);
|
||||
return;
|
||||
}
|
||||
profile.getGeolocationPermissions().clearAll();
|
||||
WebStorageCompat.deleteBrowsingData(profile.getWebStorage(), () -> {
|
||||
cleared.add(id);
|
||||
result.success(true);
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
result.error("PROFILE_REMOVE_FAILED", "Unable to remove WebView profile data", null);
|
||||
}
|
||||
}
|
||||
|
||||
private static String appliedProxy;
|
||||
private static String pendingProxy;
|
||||
private static final java.util.List<MethodChannel.Result> pending = new java.util.ArrayList<>();
|
||||
static void prepare(MethodCall call, MethodChannel.Result result) {
|
||||
if (!WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE) ||
|
||||
!WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
|
||||
result.error("UNAVAILABLE", "Android WebView does not support isolated profiles and proxy override", null);
|
||||
return;
|
||||
}
|
||||
String id = call.argument("gopeedProfileId");
|
||||
String proxy = call.argument("proxyUrl");
|
||||
try {
|
||||
java.util.UUID.fromString(id);
|
||||
Uri uri = Uri.parse(proxy);
|
||||
if (!"http".equals(uri.getScheme()) || !"127.0.0.1".equals(uri.getHost()) || uri.getPort() <= 0)
|
||||
throw new IllegalArgumentException("Invalid host proxy configuration");
|
||||
if (deletions().contains(id)) {
|
||||
if (!cleared.contains(id)) throw new IllegalStateException("Profile removal requires restart");
|
||||
// Reinstallation may reuse the now-empty profile. Do not delete its
|
||||
// new data on the next start.
|
||||
setPending(id, false);
|
||||
cleared.remove(id);
|
||||
}
|
||||
ProfileStore.getInstance().getOrCreateProfile(id);
|
||||
if (proxy.equals(appliedProxy)) { result.success(true); return; }
|
||||
if (pendingProxy != null) {
|
||||
if (proxy.equals(pendingProxy)) pending.add(result);
|
||||
else result.error("UNAVAILABLE", "A different WebView proxy is being configured", null);
|
||||
return;
|
||||
}
|
||||
pendingProxy = proxy;
|
||||
pending.add(result);
|
||||
ProxyConfig config = new ProxyConfig.Builder().addProxyRule(proxy).removeImplicitRules().build();
|
||||
ProxyController.getInstance().setProxyOverride(config, task -> new android.os.Handler(android.os.Looper.getMainLooper()).post(task), () -> {
|
||||
appliedProxy = proxy;
|
||||
pendingProxy = null;
|
||||
java.util.List<MethodChannel.Result> completed = new java.util.ArrayList<>(pending);
|
||||
pending.clear();
|
||||
for (MethodChannel.Result callback : completed) callback.success(true);
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
if (pendingProxy != null && pending.contains(result)) {
|
||||
pendingProxy = null;
|
||||
java.util.List<MethodChannel.Result> failed = new java.util.ArrayList<>(pending);
|
||||
pending.clear();
|
||||
for (MethodChannel.Result callback : failed) callback.error("UNAVAILABLE", "Unable to configure WebView profile and proxy", null);
|
||||
} else result.error("UNAVAILABLE", "Unable to configure WebView profile and proxy", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface ISettings<T> {
|
||||
@NonNull ISettings<T> parse(@NonNull Map<String, Object> settings);
|
||||
@NonNull Map<String, Object> toMap();
|
||||
@NonNull Map<String, Object> getRealSettings(@NonNull T obj);
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
public class InAppWebViewFileProvider extends FileProvider {
|
||||
|
||||
public static final String fileProviderAuthorityExtension = "flutter_inappwebview_android.fileprovider";
|
||||
|
||||
}
|
||||
-225
@@ -1,225 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.ChromeSafariBrowserManager;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs.NoHistoryCustomTabsActivityCallbacks;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.credential_database.CredentialDatabaseHandler;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.headless_in_app_webview.HeadlessInAppWebViewManager;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.InAppBrowserManager;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.print_job.PrintJobManager;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.process_global_config.ProcessGlobalConfigManager;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.proxy.ProxyManager;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.service_worker.ServiceWorkerManager;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.tracing.TracingControllerManager;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.webview.FlutterWebViewFactory;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.webview.InAppWebViewManager;
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.platform.PlatformViewRegistry;
|
||||
import io.flutter.embedding.android.FlutterView;
|
||||
|
||||
public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
|
||||
|
||||
protected static final String LOG_TAG = "InAppWebViewFlutterPL";
|
||||
|
||||
@Nullable
|
||||
public PlatformUtil platformUtil;
|
||||
@Nullable
|
||||
public InAppBrowserManager inAppBrowserManager;
|
||||
@Nullable
|
||||
public HeadlessInAppWebViewManager headlessInAppWebViewManager;
|
||||
@Nullable
|
||||
public ChromeSafariBrowserManager chromeSafariBrowserManager;
|
||||
@Nullable
|
||||
public NoHistoryCustomTabsActivityCallbacks noHistoryCustomTabsActivityCallbacks;
|
||||
@Nullable
|
||||
public InAppWebViewManager inAppWebViewManager;
|
||||
@Nullable
|
||||
public MyCookieManager myCookieManager;
|
||||
@Nullable
|
||||
public CredentialDatabaseHandler credentialDatabaseHandler;
|
||||
@Nullable
|
||||
public MyWebStorage myWebStorage;
|
||||
@Nullable
|
||||
public ServiceWorkerManager serviceWorkerManager;
|
||||
@Nullable
|
||||
public WebViewFeatureManager webViewFeatureManager;
|
||||
@Nullable
|
||||
public ProxyManager proxyManager;
|
||||
@Nullable
|
||||
public PrintJobManager printJobManager;
|
||||
@Nullable
|
||||
public TracingControllerManager tracingControllerManager;
|
||||
@Nullable
|
||||
public ProcessGlobalConfigManager processGlobalConfigManager;
|
||||
public FlutterWebViewFactory flutterWebViewFactory;
|
||||
public Context applicationContext;
|
||||
public BinaryMessenger messenger;
|
||||
public FlutterPlugin.FlutterAssets flutterAssets;
|
||||
@Nullable
|
||||
public ActivityPluginBinding activityPluginBinding;
|
||||
@Nullable
|
||||
public Activity activity;
|
||||
public FlutterView flutterView;
|
||||
|
||||
public InAppWebViewFlutterPlugin() {}
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(FlutterPluginBinding binding) {
|
||||
this.flutterAssets = binding.getFlutterAssets();
|
||||
|
||||
// Shared.activity could be null or not.
|
||||
// It depends on who is called first between onAttachedToEngine event and onAttachedToActivity event.
|
||||
//
|
||||
// See https://github.com/pichillilorenzo/flutter_inappwebview/issues/390#issuecomment-647039084
|
||||
onAttachedToEngine(
|
||||
binding.getApplicationContext(), binding.getBinaryMessenger(), this.activity, binding.getPlatformViewRegistry(), null);
|
||||
}
|
||||
|
||||
private void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger, Activity activity, PlatformViewRegistry platformViewRegistry, FlutterView flutterView) {
|
||||
this.applicationContext = applicationContext;
|
||||
GopeedProfiles.initialize(applicationContext);
|
||||
this.activity = activity;
|
||||
this.messenger = messenger;
|
||||
this.flutterView = flutterView;
|
||||
|
||||
inAppBrowserManager = new InAppBrowserManager(this);
|
||||
headlessInAppWebViewManager = new HeadlessInAppWebViewManager(this);
|
||||
chromeSafariBrowserManager = new ChromeSafariBrowserManager(this);
|
||||
noHistoryCustomTabsActivityCallbacks = new NoHistoryCustomTabsActivityCallbacks(this);
|
||||
flutterWebViewFactory = new FlutterWebViewFactory(this);
|
||||
platformViewRegistry.registerViewFactory(
|
||||
FlutterWebViewFactory.VIEW_TYPE_ID, flutterWebViewFactory);
|
||||
|
||||
platformUtil = new PlatformUtil(this);
|
||||
inAppWebViewManager = new InAppWebViewManager(this);
|
||||
myCookieManager = new MyCookieManager(this);
|
||||
myWebStorage = new MyWebStorage(this);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
serviceWorkerManager = new ServiceWorkerManager(this);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
credentialDatabaseHandler = new CredentialDatabaseHandler(this);
|
||||
}
|
||||
webViewFeatureManager = new WebViewFeatureManager(this);
|
||||
proxyManager = new ProxyManager(this);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
printJobManager = new PrintJobManager(this);
|
||||
}
|
||||
tracingControllerManager = new TracingControllerManager(this);
|
||||
processGlobalConfigManager = new ProcessGlobalConfigManager(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
||||
if (platformUtil != null) {
|
||||
platformUtil.dispose();
|
||||
platformUtil = null;
|
||||
}
|
||||
if (inAppBrowserManager != null) {
|
||||
inAppBrowserManager.dispose();
|
||||
inAppBrowserManager = null;
|
||||
}
|
||||
if (headlessInAppWebViewManager != null) {
|
||||
headlessInAppWebViewManager.dispose();
|
||||
headlessInAppWebViewManager = null;
|
||||
}
|
||||
if (chromeSafariBrowserManager != null) {
|
||||
chromeSafariBrowserManager.dispose();
|
||||
chromeSafariBrowserManager = null;
|
||||
}
|
||||
if (noHistoryCustomTabsActivityCallbacks != null) {
|
||||
noHistoryCustomTabsActivityCallbacks.dispose();
|
||||
noHistoryCustomTabsActivityCallbacks = null;
|
||||
}
|
||||
if (myCookieManager != null) {
|
||||
myCookieManager.dispose();
|
||||
myCookieManager = null;
|
||||
}
|
||||
if (myWebStorage != null) {
|
||||
myWebStorage.dispose();
|
||||
myWebStorage = null;
|
||||
}
|
||||
if (credentialDatabaseHandler != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
credentialDatabaseHandler.dispose();
|
||||
credentialDatabaseHandler = null;
|
||||
}
|
||||
if (inAppWebViewManager != null) {
|
||||
inAppWebViewManager.dispose();
|
||||
inAppWebViewManager = null;
|
||||
}
|
||||
if (serviceWorkerManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
serviceWorkerManager.dispose();
|
||||
serviceWorkerManager = null;
|
||||
}
|
||||
if (webViewFeatureManager != null) {
|
||||
webViewFeatureManager.dispose();
|
||||
webViewFeatureManager = null;
|
||||
}
|
||||
if (proxyManager != null) {
|
||||
proxyManager.dispose();
|
||||
proxyManager = null;
|
||||
}
|
||||
if (printJobManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
printJobManager.dispose();
|
||||
printJobManager = null;
|
||||
}
|
||||
if (tracingControllerManager != null) {
|
||||
tracingControllerManager.dispose();
|
||||
tracingControllerManager = null;
|
||||
}
|
||||
if (processGlobalConfigManager != null) {
|
||||
processGlobalConfigManager.dispose();
|
||||
processGlobalConfigManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) {
|
||||
this.activityPluginBinding = activityPluginBinding;
|
||||
this.activity = activityPluginBinding.getActivity();
|
||||
|
||||
if (noHistoryCustomTabsActivityCallbacks != null) {
|
||||
this.activity.getApplication().registerActivityLifecycleCallbacks(noHistoryCustomTabsActivityCallbacks.activityLifecycleCallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivityForConfigChanges() {
|
||||
if (activity != null && noHistoryCustomTabsActivityCallbacks != null) {
|
||||
this.activity.getApplication().unregisterActivityLifecycleCallbacks(noHistoryCustomTabsActivityCallbacks.activityLifecycleCallbacks);
|
||||
}
|
||||
|
||||
activityPluginBinding = null;
|
||||
activity = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) {
|
||||
this.activityPluginBinding = activityPluginBinding;
|
||||
this.activity = activityPluginBinding.getActivity();
|
||||
|
||||
if (noHistoryCustomTabsActivityCallbacks != null) {
|
||||
this.activity.getApplication().registerActivityLifecycleCallbacks(noHistoryCustomTabsActivityCallbacks.activityLifecycleCallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivity() {
|
||||
if (activity != null && noHistoryCustomTabsActivityCallbacks != null) {
|
||||
this.activity.getApplication().unregisterActivityLifecycleCallbacks(noHistoryCustomTabsActivityCallbacks.activityLifecycleCallbacks);
|
||||
}
|
||||
|
||||
activityPluginBinding = null;
|
||||
activity = null;
|
||||
}
|
||||
}
|
||||
-456
@@ -1,456 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.CookieSyncManager;
|
||||
import android.webkit.ValueCallback;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.webkit.CookieManagerCompat;
|
||||
import androidx.webkit.WebViewFeature;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class MyCookieManager extends ChannelDelegateImpl {
|
||||
protected static final String LOG_TAG = "MyCookieManager";
|
||||
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_cookiemanager";
|
||||
@Nullable
|
||||
public static CookieManager cookieManager;
|
||||
private CookieManager selectedCookieManager;
|
||||
@Nullable
|
||||
public InAppWebViewFlutterPlugin plugin;
|
||||
|
||||
public MyCookieManager(@NonNull final InAppWebViewFlutterPlugin plugin) {
|
||||
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (cookieManager == null) {
|
||||
cookieManager = getCookieManager();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
if (call.method.equals("gopeed.removeProfile")) {
|
||||
GopeedProfiles.remove(call, result);
|
||||
return;
|
||||
}
|
||||
if (call.method.equals("gopeed.prepareProfile")) {
|
||||
GopeedProfiles.prepare(call, result);
|
||||
return;
|
||||
}
|
||||
String profileID = call.argument("gopeedProfileId");
|
||||
if (profileID != null && !profileID.isEmpty()) {
|
||||
if (!androidx.webkit.WebViewFeature.isFeatureSupported(androidx.webkit.WebViewFeature.MULTI_PROFILE) ||
|
||||
androidx.webkit.ProfileStore.getInstance().getProfile(profileID) == null) {
|
||||
result.error("UNAVAILABLE", "WebView profile is not initialized", null);
|
||||
return;
|
||||
}
|
||||
selectedCookieManager = androidx.webkit.ProfileStore.getInstance().getProfile(profileID).getCookieManager();
|
||||
}
|
||||
init();
|
||||
try {
|
||||
switch (call.method) {
|
||||
case "setCookie":
|
||||
{
|
||||
String url = (String) call.argument("url");
|
||||
String name = (String) call.argument("name");
|
||||
String value = (String) call.argument("value");
|
||||
String domain = (String) call.argument("domain");
|
||||
String path = (String) call.argument("path");
|
||||
String expiresDateString = (String) call.argument("expiresDate");
|
||||
Long expiresDate = (expiresDateString != null ? new Long(expiresDateString) : null);
|
||||
Integer maxAge = (Integer) call.argument("maxAge");
|
||||
Boolean isSecure = (Boolean) call.argument("isSecure");
|
||||
Boolean isHttpOnly = (Boolean) call.argument("isHttpOnly");
|
||||
String sameSite = (String) call.argument("sameSite");
|
||||
setCookie(url,
|
||||
name,
|
||||
value,
|
||||
domain,
|
||||
path,
|
||||
expiresDate,
|
||||
maxAge,
|
||||
isSecure,
|
||||
isHttpOnly,
|
||||
sameSite,
|
||||
result);
|
||||
}
|
||||
break;
|
||||
case "getCookies":
|
||||
result.success(getCookies((String) call.argument("url")));
|
||||
break;
|
||||
case "deleteCookie":
|
||||
{
|
||||
String url = (String) call.argument("url");
|
||||
String name = (String) call.argument("name");
|
||||
String domain = (String) call.argument("domain");
|
||||
String path = (String) call.argument("path");
|
||||
deleteCookie(url, name, domain, path, result);
|
||||
}
|
||||
break;
|
||||
case "deleteCookies":
|
||||
{
|
||||
String url = (String) call.argument("url");
|
||||
String domain = (String) call.argument("domain");
|
||||
String path = (String) call.argument("path");
|
||||
deleteCookies(url, domain, path, result);
|
||||
}
|
||||
break;
|
||||
case "deleteAllCookies":
|
||||
deleteAllCookies(result);
|
||||
break;
|
||||
case "removeSessionCookies":
|
||||
removeSessionCookies(result);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
} finally { selectedCookieManager = null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiating CookieManager will load the Chromium task taking a 100ish ms so we do it lazily
|
||||
* to make sure it's done on a background thread as needed.
|
||||
*
|
||||
* https://github.com/facebook/react-native/blob/1903f6680d9750e244d97c3cd4a9f755a9a47c61/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java#L132
|
||||
*/
|
||||
static private @Nullable CookieManager getCookieManager() {
|
||||
if (cookieManager == null) {
|
||||
try {
|
||||
cookieManager = CookieManager.getInstance();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=559720
|
||||
return null;
|
||||
} catch (Exception exception) {
|
||||
String message = exception.getMessage();
|
||||
// We cannot catch MissingWebViewPackageException as it is in a private / system API
|
||||
// class. This validates the exception's message to ensure we are only handling this
|
||||
// specific exception.
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/webkit/WebViewFactory.java#348
|
||||
if (message != null
|
||||
&& exception
|
||||
.getClass()
|
||||
.getCanonicalName()
|
||||
.equals("android.webkit.WebViewFactory.MissingWebViewPackageException")) {
|
||||
return null;
|
||||
} else {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cookieManager;
|
||||
}
|
||||
|
||||
public void setCookie(String url,
|
||||
String name,
|
||||
String value,
|
||||
String domain,
|
||||
String path,
|
||||
Long expiresDate,
|
||||
Integer maxAge,
|
||||
Boolean isSecure,
|
||||
Boolean isHttpOnly,
|
||||
String sameSite,
|
||||
final MethodChannel.Result result) {
|
||||
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
|
||||
if (cookieManager == null) {
|
||||
result.success(false);
|
||||
return;
|
||||
}
|
||||
|
||||
String cookieValue = name + "=" + value + "; Path=" + path;
|
||||
|
||||
if (domain != null)
|
||||
cookieValue += "; Domain=" + domain;
|
||||
|
||||
if (expiresDate != null)
|
||||
cookieValue += "; Expires=" + getCookieExpirationDate(expiresDate);
|
||||
|
||||
if (maxAge != null)
|
||||
cookieValue += "; Max-Age=" + maxAge.toString();
|
||||
|
||||
if (isSecure != null && isSecure)
|
||||
cookieValue += "; Secure";
|
||||
|
||||
if (isHttpOnly != null && isHttpOnly)
|
||||
cookieValue += "; HttpOnly";
|
||||
|
||||
if (sameSite != null)
|
||||
cookieValue += "; SameSite=" + sameSite;
|
||||
|
||||
cookieValue += ";";
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.setCookie(url, cookieValue, new ValueCallback<Boolean>() {
|
||||
@Override
|
||||
public void onReceiveValue(Boolean successful) {
|
||||
result.success(successful);
|
||||
}
|
||||
});
|
||||
cookieManager.flush();
|
||||
}
|
||||
else if (plugin != null) {
|
||||
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext);
|
||||
cookieSyncMngr.startSync();
|
||||
cookieManager.setCookie(url, cookieValue);
|
||||
cookieSyncMngr.stopSync();
|
||||
cookieSyncMngr.sync();
|
||||
result.success(true);
|
||||
} else {
|
||||
cookieManager.setCookie(url, cookieValue);
|
||||
result.success(true);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getCookies(final String url) {
|
||||
|
||||
final List<Map<String, Object>> cookieListMap = new ArrayList<>();
|
||||
|
||||
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
|
||||
if (cookieManager == null) return cookieListMap;
|
||||
|
||||
List<String> cookies = new ArrayList<>();
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.GET_COOKIE_INFO)) {
|
||||
cookies = CookieManagerCompat.getCookieInfo(cookieManager, url);
|
||||
} else {
|
||||
String cookiesString = cookieManager.getCookie(url);
|
||||
if (cookiesString != null) {
|
||||
cookies = Arrays.asList(cookiesString.split(";"));
|
||||
}
|
||||
}
|
||||
|
||||
for (String cookie : cookies) {
|
||||
String[] cookieParams = cookie.split(";");
|
||||
if (cookieParams.length == 0) continue;
|
||||
|
||||
String[] nameValue = cookieParams[0].split("=", 2);
|
||||
String name = nameValue[0].trim();
|
||||
String value = (nameValue.length > 1) ? nameValue[1].trim() : "";
|
||||
|
||||
Map<String, Object> cookieMap = new HashMap<>();
|
||||
cookieMap.put("name", name);
|
||||
cookieMap.put("value", value);
|
||||
cookieMap.put("expiresDate", null);
|
||||
cookieMap.put("isSessionOnly", null);
|
||||
cookieMap.put("domain", null);
|
||||
cookieMap.put("sameSite", null);
|
||||
cookieMap.put("isSecure", null);
|
||||
cookieMap.put("isHttpOnly", null);
|
||||
cookieMap.put("path", null);
|
||||
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.GET_COOKIE_INFO)) {
|
||||
cookieMap.put("isSecure", false);
|
||||
cookieMap.put("isHttpOnly", false);
|
||||
|
||||
for (int i = 1; i < cookieParams.length; i++) {
|
||||
String[] cookieParamNameValue = cookieParams[i].split("=", 2);
|
||||
String cookieParamName = cookieParamNameValue[0].trim();
|
||||
String cookieParamValue = (cookieParamNameValue.length > 1) ? cookieParamNameValue[1].trim() : "";
|
||||
|
||||
if (cookieParamName.equalsIgnoreCase("Expires")) {
|
||||
try {
|
||||
final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss z", Locale.US);
|
||||
Date expiryDate = sdf.parse(cookieParamValue);
|
||||
if (expiryDate != null) {
|
||||
cookieMap.put("expiresDate", expiryDate.getTime());
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
Log.e(LOG_TAG, "", e);
|
||||
}
|
||||
} else if (cookieParamName.equalsIgnoreCase("Max-Age")) {
|
||||
try {
|
||||
long maxAge = Long.parseLong(cookieParamValue);
|
||||
cookieMap.put("expiresDate", System.currentTimeMillis() + maxAge);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e(LOG_TAG, "", e);
|
||||
}
|
||||
} else if (cookieParamName.equalsIgnoreCase("Domain")) {
|
||||
cookieMap.put("domain", cookieParamValue);
|
||||
} else if (cookieParamName.equalsIgnoreCase("SameSite")) {
|
||||
cookieMap.put("sameSite", cookieParamValue);
|
||||
} else if (cookieParamName.equalsIgnoreCase("Secure")) {
|
||||
cookieMap.put("isSecure", true);
|
||||
} else if (cookieParamName.equalsIgnoreCase("HttpOnly")) {
|
||||
cookieMap.put("isHttpOnly", true);
|
||||
} else if (cookieParamName.equalsIgnoreCase("Path")) {
|
||||
cookieMap.put("path", cookieParamValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cookieListMap.add(cookieMap);
|
||||
}
|
||||
return cookieListMap;
|
||||
|
||||
}
|
||||
|
||||
public void deleteCookie(String url, String name, String domain, String path, final MethodChannel.Result result) {
|
||||
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
|
||||
if (cookieManager == null) {
|
||||
result.success(false);
|
||||
return;
|
||||
}
|
||||
|
||||
String cookieValue = name + "=; Path=" + path + "; Max-Age=-1";
|
||||
|
||||
if (domain != null)
|
||||
cookieValue += "; Domain=" + domain;
|
||||
|
||||
cookieValue += ";";
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.setCookie(url, cookieValue, new ValueCallback<Boolean>() {
|
||||
@Override
|
||||
public void onReceiveValue(Boolean successful) {
|
||||
result.success(successful);
|
||||
}
|
||||
});
|
||||
cookieManager.flush();
|
||||
}
|
||||
else if (plugin != null) {
|
||||
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext);
|
||||
cookieSyncMngr.startSync();
|
||||
cookieManager.setCookie(url, cookieValue);
|
||||
cookieSyncMngr.stopSync();
|
||||
cookieSyncMngr.sync();
|
||||
result.success(true);
|
||||
} else {
|
||||
cookieManager.setCookie(url, cookieValue);
|
||||
result.success(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteCookies(String url, String domain, String path, final MethodChannel.Result result) {
|
||||
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
|
||||
if (cookieManager == null) {
|
||||
result.success(false);
|
||||
return;
|
||||
}
|
||||
|
||||
CookieSyncManager cookieSyncMngr = null;
|
||||
|
||||
String cookiesString = cookieManager.getCookie(url);
|
||||
if (cookiesString != null) {
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && plugin != null) {
|
||||
cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext);
|
||||
cookieSyncMngr.startSync();
|
||||
}
|
||||
|
||||
String[] cookies = cookiesString.split(";");
|
||||
for (String cookie : cookies) {
|
||||
String[] nameValue = cookie.split("=", 2);
|
||||
String name = nameValue[0].trim();
|
||||
|
||||
String cookieValue = name + "=; Path=" + path + "; Max-Age=-1";
|
||||
|
||||
if (domain != null)
|
||||
cookieValue += "; Domain=" + domain;
|
||||
|
||||
cookieValue += ";";
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
cookieManager.setCookie(url, cookieValue, null);
|
||||
else
|
||||
cookieManager.setCookie(url, cookieValue);
|
||||
}
|
||||
|
||||
if (cookieSyncMngr != null) {
|
||||
cookieSyncMngr.stopSync();
|
||||
cookieSyncMngr.sync();
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
cookieManager.flush();
|
||||
}
|
||||
result.success(true);
|
||||
}
|
||||
|
||||
public void deleteAllCookies(final MethodChannel.Result result) {
|
||||
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
|
||||
if (cookieManager == null) {
|
||||
result.success(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.removeAllCookies(new ValueCallback<Boolean>() {
|
||||
@Override
|
||||
public void onReceiveValue(Boolean successful) {
|
||||
result.success(successful);
|
||||
}
|
||||
});
|
||||
cookieManager.flush();
|
||||
}
|
||||
else if (plugin != null) {
|
||||
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext);
|
||||
cookieSyncMngr.startSync();
|
||||
cookieManager.removeAllCookie();
|
||||
cookieSyncMngr.stopSync();
|
||||
cookieSyncMngr.sync();
|
||||
result.success(true);
|
||||
} else {
|
||||
cookieManager.removeAllCookie();
|
||||
result.success(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSessionCookies(final MethodChannel.Result result) {
|
||||
final CookieManager cookieManager = selectedCookieManager != null ? selectedCookieManager : getCookieManager();
|
||||
if (cookieManager == null) {
|
||||
result.success(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.removeSessionCookies(new ValueCallback<Boolean>() {
|
||||
@Override
|
||||
public void onReceiveValue(Boolean successful) {
|
||||
result.success(successful);
|
||||
}
|
||||
});
|
||||
cookieManager.flush();
|
||||
}
|
||||
else if (plugin != null) {
|
||||
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(plugin.applicationContext);
|
||||
cookieSyncMngr.startSync();
|
||||
cookieManager.removeSessionCookie();
|
||||
cookieSyncMngr.stopSync();
|
||||
cookieSyncMngr.sync();
|
||||
result.success(true);
|
||||
} else {
|
||||
cookieManager.removeSessionCookie();
|
||||
result.success(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getCookieExpirationDate(Long timestamp) {
|
||||
final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss z", Locale.US);
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
return sdf.format(new Date(timestamp));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
plugin = null;
|
||||
}
|
||||
}
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android;
|
||||
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebStorage;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class MyWebStorage extends ChannelDelegateImpl {
|
||||
protected static final String LOG_TAG = "MyWebStorage";
|
||||
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_webstoragemanager";
|
||||
|
||||
@Nullable
|
||||
public static WebStorage webStorageManager;
|
||||
@Nullable
|
||||
public InAppWebViewFlutterPlugin plugin;
|
||||
|
||||
public MyWebStorage(@NonNull final InAppWebViewFlutterPlugin plugin) {
|
||||
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (webStorageManager == null) {
|
||||
webStorageManager = WebStorage.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
init();
|
||||
|
||||
switch (call.method) {
|
||||
case "getOrigins":
|
||||
getOrigins(result);
|
||||
break;
|
||||
case "deleteAllData":
|
||||
if (webStorageManager != null) {
|
||||
webStorageManager.deleteAllData();
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "deleteOrigin":
|
||||
{
|
||||
if (webStorageManager != null) {
|
||||
String origin = (String) call.argument("origin");
|
||||
webStorageManager.deleteOrigin(origin);
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "getQuotaForOrigin":
|
||||
{
|
||||
String origin = (String) call.argument("origin");
|
||||
getQuotaForOrigin(origin, result);
|
||||
}
|
||||
break;
|
||||
case "getUsageForOrigin":
|
||||
{
|
||||
String origin = (String) call.argument("origin");
|
||||
getUsageForOrigin(origin, result);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
public void getOrigins(final MethodChannel.Result result) {
|
||||
if (webStorageManager == null) {
|
||||
result.success(new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
webStorageManager.getOrigins(new ValueCallback<Map>() {
|
||||
@Override
|
||||
public void onReceiveValue(Map value) {
|
||||
List<Map<String, Object>> origins = new ArrayList<>();
|
||||
for(Object key : value.keySet()) {
|
||||
WebStorage.Origin originObj = (WebStorage.Origin) value.get(key);
|
||||
|
||||
Map<String, Object> originInfo = new HashMap<>();
|
||||
originInfo.put("origin", originObj.getOrigin());
|
||||
originInfo.put("quota", originObj.getQuota());
|
||||
originInfo.put("usage", originObj.getUsage());
|
||||
|
||||
origins.add(originInfo);
|
||||
}
|
||||
result.success(origins);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void getQuotaForOrigin(String origin, final MethodChannel.Result result) {
|
||||
if (webStorageManager == null) {
|
||||
result.success(0);
|
||||
return;
|
||||
}
|
||||
webStorageManager.getQuotaForOrigin(origin, new ValueCallback<Long>() {
|
||||
@Override
|
||||
public void onReceiveValue(Long value) {
|
||||
result.success(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void getUsageForOrigin(String origin, final MethodChannel.Result result) {
|
||||
if (webStorageManager == null) {
|
||||
result.success(0);
|
||||
return;
|
||||
}
|
||||
webStorageManager.getUsageForOrigin(origin, new ValueCallback<Long>() {
|
||||
@Override
|
||||
public void onReceiveValue(Long value) {
|
||||
result.success(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
plugin = null;
|
||||
}
|
||||
}
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class PlatformUtil extends ChannelDelegateImpl {
|
||||
protected static final String LOG_TAG = "PlatformUtil";
|
||||
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_platformutil";
|
||||
|
||||
@Nullable
|
||||
public InAppWebViewFlutterPlugin plugin;
|
||||
|
||||
public PlatformUtil(final InAppWebViewFlutterPlugin plugin) {
|
||||
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) {
|
||||
switch (call.method) {
|
||||
case "getSystemVersion":
|
||||
result.success(String.valueOf(Build.VERSION.SDK_INT));
|
||||
break;
|
||||
case "formatDate":
|
||||
long date = (long) call.argument("date");
|
||||
String format = (String) call.argument("format");
|
||||
Locale locale = PlatformUtil.getLocaleFromString((String) call.argument("locale"));
|
||||
String timezone = (String) call.argument("timezone");
|
||||
if (timezone == null) {
|
||||
timezone = "UTC";
|
||||
}
|
||||
result.success(PlatformUtil.formatDate(date, format, locale, TimeZone.getTimeZone(timezone)));
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
public static Locale getLocaleFromString(@Nullable String locale) {
|
||||
if (locale == null) {
|
||||
return Locale.US;
|
||||
}
|
||||
String[] localeSplit = locale.split("_");
|
||||
String language = localeSplit[0];
|
||||
String country = localeSplit.length > 1 ? localeSplit[1] : "";
|
||||
String variant = localeSplit.length > 2 ? localeSplit[2] : "";
|
||||
return new Locale(language, country, variant);
|
||||
}
|
||||
|
||||
public static String formatDate(long date, String format, Locale locale, TimeZone timezone) {
|
||||
final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
|
||||
sdf.setTimeZone(timezone);
|
||||
return sdf.format(new Date(date));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
plugin = null;
|
||||
}
|
||||
}
|
||||
-386
@@ -1,386 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.http.SslCertificate;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowMetrics;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.Size2D;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.SyncBaseCallbackResultImpl;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class Util {
|
||||
|
||||
static final String LOG_TAG = "Util";
|
||||
public static final String ANDROID_ASSET_URL = "file:///android_asset/";
|
||||
|
||||
private Util() {}
|
||||
|
||||
public static String getUrlAsset(InAppWebViewFlutterPlugin plugin, String assetFilePath) throws IOException {
|
||||
String key = plugin.flutterAssets.getAssetFilePathByName(assetFilePath);
|
||||
InputStream is = null;
|
||||
IOException e = null;
|
||||
|
||||
try {
|
||||
is = getFileAsset(plugin, assetFilePath);
|
||||
} catch (IOException ex) {
|
||||
e = ex;
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ex) {
|
||||
e = ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e != null) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
return ANDROID_ASSET_URL + key;
|
||||
}
|
||||
|
||||
public static InputStream getFileAsset(InAppWebViewFlutterPlugin plugin, String assetFilePath) throws IOException {
|
||||
String key = plugin.flutterAssets.getAssetFilePathByName(assetFilePath);
|
||||
AssetManager mg = plugin.applicationContext.getResources().getAssets();
|
||||
return mg.open(key);
|
||||
}
|
||||
|
||||
public static <T> T invokeMethodAndWaitResult(final @NonNull MethodChannel channel,
|
||||
final @NonNull String method, final @Nullable Object arguments,
|
||||
final @NonNull SyncBaseCallbackResultImpl<T> callback) throws InterruptedException {
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
channel.invokeMethod(method, arguments, callback);
|
||||
}
|
||||
});
|
||||
callback.latch.await();
|
||||
return callback.result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PrivateKeyAndCertificates loadPrivateKeyAndCertificate(@NonNull InAppWebViewFlutterPlugin plugin,
|
||||
@NonNull String certificatePath,
|
||||
@Nullable String certificatePassword,
|
||||
@NonNull String keyStoreType) {
|
||||
PrivateKeyAndCertificates privateKeyAndCertificates = null;
|
||||
InputStream certificateFileStream = null;
|
||||
|
||||
try {
|
||||
certificateFileStream = getFileAsset(plugin, certificatePath);
|
||||
} catch (IOException ignored) {}
|
||||
|
||||
try {
|
||||
if (certificateFileStream == null) {
|
||||
certificateFileStream = new FileInputStream(certificatePath);
|
||||
}
|
||||
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
|
||||
keyStore.load(certificateFileStream, (certificatePassword != null ? certificatePassword : "").toCharArray());
|
||||
|
||||
Enumeration<String> aliases = keyStore.aliases();
|
||||
String alias = aliases.nextElement();
|
||||
|
||||
Key key = keyStore.getKey(alias, (certificatePassword != null ? certificatePassword : "").toCharArray());
|
||||
if (key instanceof PrivateKey) {
|
||||
PrivateKey privateKey = (PrivateKey)key;
|
||||
Certificate cert = keyStore.getCertificate(alias);
|
||||
X509Certificate[] certificates = new X509Certificate[1];
|
||||
certificates[0] = (X509Certificate)cert;
|
||||
privateKeyAndCertificates = new PrivateKeyAndCertificates(privateKey, certificates);
|
||||
}
|
||||
certificateFileStream.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "", e);
|
||||
} finally {
|
||||
if (certificateFileStream != null) {
|
||||
try {
|
||||
certificateFileStream.close();
|
||||
} catch (IOException ex) {
|
||||
Log.e(LOG_TAG, "", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return privateKeyAndCertificates;
|
||||
}
|
||||
|
||||
public static class PrivateKeyAndCertificates {
|
||||
|
||||
public X509Certificate[] certificates;
|
||||
public PrivateKey privateKey;
|
||||
|
||||
public PrivateKeyAndCertificates(PrivateKey privateKey, X509Certificate[] certificates) {
|
||||
this.privateKey = privateKey;
|
||||
this.certificates = certificates;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static HttpURLConnection makeHttpRequest(String urlString, String method, @Nullable Map<String, String> headers) {
|
||||
HttpURLConnection urlConnection = null;
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
urlConnection = (HttpURLConnection) url.openConnection();
|
||||
urlConnection.setRequestMethod(method);
|
||||
if (headers != null) {
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
urlConnection.setRequestProperty(header.getKey(), header.getValue());
|
||||
}
|
||||
}
|
||||
urlConnection.setConnectTimeout(15000); // 15 seconds
|
||||
urlConnection.setReadTimeout(15000); // 15 seconds
|
||||
urlConnection.setDoInput(true);
|
||||
urlConnection.setInstanceFollowRedirects(true);
|
||||
if ("GET".equalsIgnoreCase(method)) {
|
||||
urlConnection.setDoOutput(false);
|
||||
}
|
||||
urlConnection.connect();
|
||||
return urlConnection;
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (!(e instanceof SSLHandshakeException)) {
|
||||
Log.e(LOG_TAG, "", e);
|
||||
}
|
||||
if (urlConnection != null) {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* SslCertificate class does not has a public getter for the underlying
|
||||
* X509Certificate, we can only do this by hack. This only works for Android 4.0+
|
||||
* https://groups.google.com/forum/#!topic/android-developers/eAPJ6b7mrmg
|
||||
*/
|
||||
public static X509Certificate getX509CertFromSslCertHack(SslCertificate sslCert) {
|
||||
X509Certificate x509Certificate = null;
|
||||
|
||||
Bundle bundle = SslCertificate.saveState(sslCert);
|
||||
byte[] bytes = bundle.getByteArray("x509-certificate");
|
||||
|
||||
if (bytes == null) {
|
||||
x509Certificate = null;
|
||||
} else {
|
||||
try {
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
|
||||
x509Certificate = (X509Certificate) cert;
|
||||
} catch (CertificateException e) {
|
||||
x509Certificate = null;
|
||||
}
|
||||
}
|
||||
|
||||
return x509Certificate;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
public static String JSONStringify(@Nullable Object value) {
|
||||
if (value == null) {
|
||||
return "null";
|
||||
}
|
||||
if (value instanceof Map) {
|
||||
return new JSONObject((Map<String, Object>) value).toString();
|
||||
} else if (value instanceof List) {
|
||||
return new JSONArray((List<Object>) value).toString();
|
||||
} else if (value instanceof String) {
|
||||
return JSONObject.quote((String) value);
|
||||
} else {
|
||||
return JSONObject.wrap(value).toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean objEquals(@Nullable Object a, @Nullable Object b) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
return Objects.equals(a, b);
|
||||
}
|
||||
return (a == b) || (a != null && a.equals(b));
|
||||
}
|
||||
|
||||
public static String replaceAll(String s, String oldString, String newString) {
|
||||
return TextUtils.join(newString, s.split(Pattern.quote(oldString)));
|
||||
}
|
||||
|
||||
public static void log(String tag, String message) {
|
||||
// Split by line, then ensure each line can fit into Log's maximum length.
|
||||
for (int i = 0, length = message.length(); i < length; i++) {
|
||||
int newline = message.indexOf('\n', i);
|
||||
newline = newline != -1 ? newline : length;
|
||||
do {
|
||||
int end = Math.min(newline, i + 4000);
|
||||
Log.d(tag, message.substring(i, end));
|
||||
i = end;
|
||||
} while (i < newline);
|
||||
}
|
||||
}
|
||||
|
||||
public static float getPixelDensity(Context context) {
|
||||
return context.getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
public static Size2D getFullscreenSize(Context context) {
|
||||
Size2D fullscreenSize = new Size2D(-1, -1);
|
||||
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
if (wm != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
final WindowMetrics metrics = wm.getCurrentWindowMetrics();
|
||||
// Gets all excluding insets
|
||||
final WindowInsets windowInsets = metrics.getWindowInsets();
|
||||
Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
|
||||
| WindowInsets.Type.displayCutout());
|
||||
int insetsWidth = insets.right + insets.left;
|
||||
int insetsHeight = insets.top + insets.bottom;
|
||||
final Rect bounds = metrics.getBounds();
|
||||
fullscreenSize.setWidth(bounds.width() - insetsWidth);
|
||||
fullscreenSize.setHeight(bounds.height() - insetsHeight);
|
||||
} else {
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
wm.getDefaultDisplay().getMetrics(displayMetrics);
|
||||
fullscreenSize.setWidth(displayMetrics.widthPixels);
|
||||
fullscreenSize.setHeight(displayMetrics.heightPixels);
|
||||
}
|
||||
}
|
||||
return fullscreenSize;
|
||||
}
|
||||
|
||||
public static boolean isClass(String className) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isIPv6(String address) {
|
||||
try {
|
||||
Inet6Address.getByName(address);
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String normalizeIPv6(String address) throws Exception {
|
||||
if (!Util.isIPv6(address)) {
|
||||
throw new Exception("Invalid address: " + address);
|
||||
}
|
||||
return InetAddress.getByName(address).getCanonicalHostName();
|
||||
}
|
||||
|
||||
public static <T> T getOrDefault(Map<String, Object> map, String key, T defaultValue) {
|
||||
return map.containsKey(key) ? (T) map.get(key) : defaultValue;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static byte[] readAllBytes(@Nullable InputStream inputStream) {
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int bufLen = 4 * 0x400; // 4KB
|
||||
byte[] buf = new byte[bufLen];
|
||||
int readLen;
|
||||
IOException exception = null;
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
byte[] data = null;
|
||||
|
||||
try {
|
||||
while ((readLen = inputStream.read(buf, 0, bufLen)) != -1)
|
||||
outputStream.write(buf, 0, readLen);
|
||||
|
||||
data = outputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && exception != null) {
|
||||
exception.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && exception != null) {
|
||||
exception.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <O> Object invokeMethodIfExists(final O o, final String methodName, Object... args) {
|
||||
Method[] methods = o.getClass().getMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getName().equals(methodName)) {
|
||||
try {
|
||||
return method.invoke(o, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Drawable drawableFromBytes(Context context, byte[] data) {
|
||||
return new BitmapDrawable(context.getResources(), BitmapFactory.decodeByteArray(data, 0, data.length));
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.webkit.WebViewFeature;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class WebViewFeatureManager extends ChannelDelegateImpl {
|
||||
protected static final String LOG_TAG = "WebViewFeatureManager";
|
||||
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_webviewfeature";
|
||||
|
||||
@Nullable
|
||||
public InAppWebViewFlutterPlugin plugin;
|
||||
|
||||
public WebViewFeatureManager(@NonNull final InAppWebViewFlutterPlugin plugin) {
|
||||
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
switch (call.method) {
|
||||
case "isFeatureSupported":
|
||||
String feature = (String) call.argument("feature");
|
||||
result.success(WebViewFeature.isFeatureSupported(feature));
|
||||
break;
|
||||
case "isStartupFeatureSupported":
|
||||
if (plugin != null && plugin.activity != null) {
|
||||
String startupFeature = (String) call.argument("startupFeature");
|
||||
result.success(WebViewFeature.isStartupFeatureSupported(plugin.activity, startupFeature));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
plugin = null;
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
|
||||
public class ActionBroadcastReceiver extends BroadcastReceiver {
|
||||
protected static final String LOG_TAG = "ActionBroadcastReceiver";
|
||||
public static final String KEY_ACTION_ID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_ID";
|
||||
public static final String KEY_ACTION_VIEW_ID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_VIEW_ID";
|
||||
public static final String KEY_ACTION_MANAGER_ID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_MANAGER_ID";
|
||||
public static final String KEY_URL_TITLE = "android.intent.extra.SUBJECT";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int clickedId = intent.getIntExtra(CustomTabsIntent.EXTRA_REMOTEVIEWS_CLICKED_ID, -1);
|
||||
String url = intent.getDataString();
|
||||
if (url != null) {
|
||||
Bundle b = intent.getExtras();
|
||||
String viewId = b.getString(KEY_ACTION_VIEW_ID);
|
||||
String managerId = b.getString(KEY_ACTION_MANAGER_ID);
|
||||
|
||||
if (managerId != null) {
|
||||
ChromeSafariBrowserManager chromeSafariBrowserManager = ChromeSafariBrowserManager.shared.get(managerId);
|
||||
if (chromeSafariBrowserManager != null) {
|
||||
if (clickedId == -1) {
|
||||
int id = b.getInt(KEY_ACTION_ID);
|
||||
String title = b.getString(KEY_URL_TITLE);
|
||||
|
||||
ChromeCustomTabsActivity browser = chromeSafariBrowserManager.browsers.get(viewId);
|
||||
if (browser != null && browser.channelDelegate != null) {
|
||||
browser.channelDelegate.onItemActionPerform(id, url, title);
|
||||
}
|
||||
} else {
|
||||
ChromeCustomTabsActivity browser = chromeSafariBrowserManager.browsers.get(viewId);
|
||||
if (browser != null && browser.channelDelegate != null) {
|
||||
browser.channelDelegate.onSecondaryItemActionPerform(browser.getResources().getResourceName(clickedId), url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-462
@@ -1,462 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams;
|
||||
import androidx.browser.customtabs.CustomTabsCallback;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.browser.customtabs.CustomTabsService;
|
||||
import androidx.browser.customtabs.CustomTabsSession;
|
||||
import androidx.browser.customtabs.EngagementSignalsCallback;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.R;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.AndroidResource;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.CustomTabsActionButton;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.CustomTabsMenuItem;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.CustomTabsSecondaryToolbar;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class ChromeCustomTabsActivity extends Activity implements Disposable {
|
||||
protected static final String LOG_TAG = "CustomTabsActivity";
|
||||
public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_chromesafaribrowser_";
|
||||
|
||||
public String id;
|
||||
@Nullable
|
||||
public CustomTabsIntent.Builder builder;
|
||||
public ChromeCustomTabsSettings customSettings = new ChromeCustomTabsSettings();
|
||||
public CustomTabActivityHelper customTabActivityHelper = new CustomTabActivityHelper();
|
||||
@Nullable
|
||||
public CustomTabsSession customTabsSession;
|
||||
public final static int CHROME_CUSTOM_TAB_REQUEST_CODE = 100;
|
||||
public final static int NO_HISTORY_CHROME_CUSTOM_TAB_REQUEST_CODE = 101;
|
||||
protected boolean onOpened = false;
|
||||
protected boolean onCompletedInitialLoad = false;
|
||||
protected boolean isBindSuccess = false;
|
||||
@Nullable
|
||||
public ChromeSafariBrowserManager manager;
|
||||
@Nullable
|
||||
public String initialUrl;
|
||||
@Nullable
|
||||
public List<String> initialOtherLikelyURLs;
|
||||
@Nullable
|
||||
public Map<String, String> initialHeaders;
|
||||
@Nullable
|
||||
public String initialReferrer;
|
||||
public List<CustomTabsMenuItem> menuItems = new ArrayList<>();
|
||||
@Nullable
|
||||
public CustomTabsActionButton actionButton;
|
||||
@Nullable
|
||||
public CustomTabsSecondaryToolbar secondaryToolbar;
|
||||
@Nullable
|
||||
public ChromeCustomTabsChannelDelegate channelDelegate;
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.chrome_custom_tabs_layout);
|
||||
|
||||
Bundle b = getIntent().getExtras();
|
||||
if (b == null) return;
|
||||
|
||||
id = b.getString("id");
|
||||
|
||||
String managerId = b.getString("managerId");
|
||||
manager = ChromeSafariBrowserManager.shared.get(managerId);
|
||||
if (manager == null || manager.plugin == null || manager.plugin.messenger == null) return;
|
||||
|
||||
manager.browsers.put(id, this);
|
||||
|
||||
MethodChannel channel = new MethodChannel(manager.plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
|
||||
channelDelegate = new ChromeCustomTabsChannelDelegate(this, channel);
|
||||
|
||||
initialUrl = b.getString("url");
|
||||
initialHeaders = (Map<String, String>) b.getSerializable("headers");
|
||||
initialReferrer = b.getString("referrer");
|
||||
initialOtherLikelyURLs = b.getStringArrayList("otherLikelyURLs");
|
||||
|
||||
customSettings = new ChromeCustomTabsSettings();
|
||||
customSettings.parse((HashMap<String, Object>) b.getSerializable("settings"));
|
||||
actionButton = CustomTabsActionButton.fromMap((Map<String, Object>) b.getSerializable("actionButton"));
|
||||
secondaryToolbar = CustomTabsSecondaryToolbar.fromMap((Map<String, Object>) b.getSerializable("secondaryToolbar"));
|
||||
List<Map<String, Object>> menuItemList = (List<Map<String, Object>>) b.getSerializable("menuItemList");
|
||||
for (Map<String, Object> menuItem : menuItemList) {
|
||||
menuItems.add(CustomTabsMenuItem.fromMap(menuItem));
|
||||
}
|
||||
|
||||
if (customSettings.noHistory && manager.plugin.noHistoryCustomTabsActivityCallbacks != null) {
|
||||
manager.plugin.noHistoryCustomTabsActivityCallbacks.noHistoryBrowserIDs.put(id, id);
|
||||
}
|
||||
|
||||
final ChromeCustomTabsActivity chromeCustomTabsActivity = this;
|
||||
|
||||
customTabActivityHelper.setConnectionCallback(new CustomTabActivityHelper.ConnectionCallback() {
|
||||
@Override
|
||||
public void onCustomTabsConnected() {
|
||||
customTabsConnected();
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onServiceConnected();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCustomTabsDisconnected() {
|
||||
chromeCustomTabsActivity.close();
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
|
||||
customTabActivityHelper.setCustomTabsCallback(new CustomTabsCallback() {
|
||||
@Override
|
||||
public void onNavigationEvent(int navigationEvent, Bundle extras) {
|
||||
if (navigationEvent == TAB_SHOWN && !onOpened) {
|
||||
onOpened = true;
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onOpened();
|
||||
}
|
||||
}
|
||||
|
||||
if (navigationEvent == NAVIGATION_FINISHED && !onCompletedInitialLoad) {
|
||||
onCompletedInitialLoad = true;
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onCompletedInitialLoad();
|
||||
}
|
||||
}
|
||||
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onNavigationEvent(navigationEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extraCallback(@NonNull String callbackName, Bundle args) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageChannelReady(Bundle extras) {
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onMessageChannelReady();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostMessage(@NonNull String message, Bundle extras) {
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onPostMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelationshipValidationResult(@CustomTabsService.Relation int relation,
|
||||
@NonNull Uri requestedOrigin,
|
||||
boolean result, Bundle extras) {
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onRelationshipValidationResult(relation, requestedOrigin, result);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void launchUrl(@NonNull String url,
|
||||
@Nullable Map<String, String> headers,
|
||||
@Nullable String referrer,
|
||||
@Nullable List<String> otherLikelyURLs) {
|
||||
launchUrlWithSession(customTabsSession, url, headers, referrer, otherLikelyURLs);
|
||||
}
|
||||
|
||||
public void launchUrlWithSession(@Nullable CustomTabsSession session,
|
||||
@NonNull String url,
|
||||
@Nullable Map<String, String> headers,
|
||||
@Nullable String referrer,
|
||||
@Nullable List<String> otherLikelyURLs) {
|
||||
mayLaunchUrl(url, otherLikelyURLs);
|
||||
builder = new CustomTabsIntent.Builder(session);
|
||||
prepareCustomTabs();
|
||||
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
prepareCustomTabsIntent(customTabsIntent);
|
||||
|
||||
CustomTabActivityHelper.openCustomTab(this, customTabsIntent, Uri.parse(url), headers,
|
||||
referrer != null ? Uri.parse(referrer) : null, CHROME_CUSTOM_TAB_REQUEST_CODE);
|
||||
}
|
||||
|
||||
public boolean mayLaunchUrl(@Nullable String url, @Nullable List<String> otherLikelyURLs) {
|
||||
Uri uri = url != null ? Uri.parse(url) : null;
|
||||
|
||||
List<Bundle> bundleOtherLikelyURLs = new ArrayList<>();
|
||||
if (otherLikelyURLs != null) {
|
||||
Bundle bundleOtherLikelyURL = new Bundle();
|
||||
for (String otherLikelyURL : otherLikelyURLs) {
|
||||
bundleOtherLikelyURL.putString(CustomTabsService.KEY_URL, otherLikelyURL);
|
||||
}
|
||||
}
|
||||
return customTabActivityHelper.mayLaunchUrl(uri, null, bundleOtherLikelyURLs);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void customTabsConnected() {
|
||||
customTabsSession = customTabActivityHelper.getSession();
|
||||
|
||||
if (customTabsSession != null) {
|
||||
try {
|
||||
Bundle bundle = new Bundle();
|
||||
if (customTabsSession.isEngagementSignalsApiAvailable(bundle)) {
|
||||
customTabsSession.setEngagementSignalsCallback(new EngagementSignalsCallback() {
|
||||
@Override
|
||||
public void onVerticalScrollEvent(boolean isDirectionUp, @NonNull Bundle extras) {
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onVerticalScrollEvent(isDirectionUp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGreatestScrollPercentageIncreased(int scrollPercentage, @NonNull Bundle extras) {
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onGreatestScrollPercentageIncreased(scrollPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionEnded(boolean didUserInteract, @NonNull Bundle extras) {
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onSessionEnded(didUserInteract);
|
||||
}
|
||||
}
|
||||
}, bundle);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.d(LOG_TAG, "Custom Tabs Engagement Signals API not supported", e);
|
||||
}
|
||||
}
|
||||
|
||||
// avoid webpage reopen if isBindSuccess is false: onServiceConnected->launchUrl
|
||||
if (isBindSuccess && initialUrl != null) {
|
||||
launchUrl(initialUrl, initialHeaders, initialReferrer, initialOtherLikelyURLs);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareCustomTabs() {
|
||||
if (builder == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (customSettings.addDefaultShareMenuItem != null) {
|
||||
builder.setShareState(customSettings.addDefaultShareMenuItem ?
|
||||
CustomTabsIntent.SHARE_STATE_ON : CustomTabsIntent.SHARE_STATE_OFF);
|
||||
} else {
|
||||
builder.setShareState(customSettings.shareState);
|
||||
}
|
||||
|
||||
CustomTabColorSchemeParams.Builder defaultColorSchemeBuilder = new CustomTabColorSchemeParams.Builder();
|
||||
if (customSettings.toolbarBackgroundColor != null && !customSettings.toolbarBackgroundColor.isEmpty()) {
|
||||
defaultColorSchemeBuilder.setToolbarColor(Color.parseColor(customSettings.toolbarBackgroundColor));
|
||||
}
|
||||
if (customSettings.navigationBarColor != null && !customSettings.navigationBarColor.isEmpty()) {
|
||||
defaultColorSchemeBuilder.setNavigationBarColor(Color.parseColor(customSettings.navigationBarColor));
|
||||
}
|
||||
if (customSettings.navigationBarDividerColor != null && !customSettings.navigationBarDividerColor.isEmpty()) {
|
||||
defaultColorSchemeBuilder.setNavigationBarDividerColor(Color.parseColor(customSettings.navigationBarDividerColor));
|
||||
}
|
||||
if (customSettings.secondaryToolbarColor != null && !customSettings.secondaryToolbarColor.isEmpty()) {
|
||||
defaultColorSchemeBuilder.setSecondaryToolbarColor(Color.parseColor(customSettings.secondaryToolbarColor));
|
||||
}
|
||||
builder.setDefaultColorSchemeParams(defaultColorSchemeBuilder.build());
|
||||
|
||||
builder.setShowTitle(customSettings.showTitle);
|
||||
builder.setUrlBarHidingEnabled(customSettings.enableUrlBarHiding);
|
||||
builder.setInstantAppsEnabled(customSettings.instantAppsEnabled);
|
||||
if (customSettings.startAnimations.size() == 2) {
|
||||
builder.setStartAnimations(this,
|
||||
customSettings.startAnimations.get(0).getIdentifier(this),
|
||||
customSettings.startAnimations.get(1).getIdentifier(this));
|
||||
}
|
||||
if (customSettings.exitAnimations.size() == 2) {
|
||||
builder.setExitAnimations(this,
|
||||
customSettings.exitAnimations.get(0).getIdentifier(this),
|
||||
customSettings.exitAnimations.get(1).getIdentifier(this));
|
||||
}
|
||||
|
||||
for (CustomTabsMenuItem menuItem : menuItems) {
|
||||
builder.addMenuItem(menuItem.getLabel(),
|
||||
createPendingIntent(menuItem.getId()));
|
||||
}
|
||||
|
||||
if (actionButton != null) {
|
||||
byte[] data = actionButton.getIcon();
|
||||
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
|
||||
bitmapOptions.inMutable = true;
|
||||
Bitmap bmp = BitmapFactory.decodeByteArray(
|
||||
data, 0, data.length, bitmapOptions
|
||||
);
|
||||
builder.setActionButton(bmp, actionButton.getDescription(),
|
||||
createPendingIntent(actionButton.getId()),
|
||||
actionButton.isShouldTint());
|
||||
}
|
||||
|
||||
if (secondaryToolbar != null) {
|
||||
AndroidResource layout = secondaryToolbar.getLayout();
|
||||
RemoteViews remoteViews = new RemoteViews(layout.getDefPackage(), layout.getIdentifier(this));
|
||||
int[] clickableIDs = new int[secondaryToolbar.getClickableIDs().size()];
|
||||
for (int i = 0, length = secondaryToolbar.getClickableIDs().size(); i < length; i++) {
|
||||
AndroidResource clickableID = secondaryToolbar.getClickableIDs().get(i);
|
||||
clickableIDs[i] = clickableID.getIdentifier(this);
|
||||
}
|
||||
builder.setSecondaryToolbarViews(remoteViews, clickableIDs, getSecondaryToolbarOnClickPendingIntent());
|
||||
}
|
||||
}
|
||||
|
||||
public PendingIntent getSecondaryToolbarOnClickPendingIntent() {
|
||||
Intent broadcastIntent = new Intent(this, ActionBroadcastReceiver.class);
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id);
|
||||
extras.putString(ActionBroadcastReceiver.KEY_ACTION_MANAGER_ID, manager != null ? manager.id : null);
|
||||
broadcastIntent.putExtras(extras);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
return PendingIntent.getBroadcast(
|
||||
this, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
} else {
|
||||
return PendingIntent.getBroadcast(
|
||||
this, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareCustomTabsIntent(CustomTabsIntent customTabsIntent) {
|
||||
if (customSettings.packageName != null)
|
||||
customTabsIntent.intent.setPackage(customSettings.packageName);
|
||||
else
|
||||
customTabsIntent.intent.setPackage(CustomTabsHelper.getPackageNameToUse(this));
|
||||
|
||||
if (customSettings.keepAliveEnabled)
|
||||
CustomTabsHelper.addKeepAliveExtra(this, customTabsIntent.intent);
|
||||
|
||||
if (customSettings.alwaysUseBrowserUI)
|
||||
CustomTabsIntent.setAlwaysUseBrowserUI(customTabsIntent.intent);
|
||||
}
|
||||
|
||||
public void updateActionButton(@NonNull byte[] icon, @NonNull String description) {
|
||||
if (customTabsSession == null || actionButton == null) {
|
||||
return;
|
||||
}
|
||||
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
|
||||
bitmapOptions.inMutable = true;
|
||||
Bitmap bmp = BitmapFactory.decodeByteArray(
|
||||
icon, 0, icon.length, bitmapOptions
|
||||
);
|
||||
customTabsSession.setActionButton(bmp, description);
|
||||
actionButton.setIcon(icon);
|
||||
actionButton.setDescription(description);
|
||||
}
|
||||
|
||||
public void updateSecondaryToolbar(CustomTabsSecondaryToolbar secondaryToolbar) {
|
||||
if (customTabsSession == null) {
|
||||
return;
|
||||
}
|
||||
AndroidResource layout = secondaryToolbar.getLayout();
|
||||
RemoteViews remoteViews = new RemoteViews(layout.getDefPackage(), layout.getIdentifier(this));
|
||||
int[] clickableIDs = new int[secondaryToolbar.getClickableIDs().size()];
|
||||
for (int i = 0, length = secondaryToolbar.getClickableIDs().size(); i < length; i++) {
|
||||
AndroidResource clickableID = secondaryToolbar.getClickableIDs().get(i);
|
||||
clickableIDs[i] = clickableID.getIdentifier(this);
|
||||
}
|
||||
customTabsSession.setSecondaryToolbarViews(remoteViews, clickableIDs, getSecondaryToolbarOnClickPendingIntent());
|
||||
this.secondaryToolbar = secondaryToolbar;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
isBindSuccess = customTabActivityHelper.bindCustomTabsService(this);
|
||||
|
||||
if (!isBindSuccess && initialUrl != null) {
|
||||
// chrome process not running, start tab directly
|
||||
launchUrlWithSession(null, initialUrl, initialHeaders, initialReferrer, initialOtherLikelyURLs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
customTabActivityHelper.unbindCustomTabsService(this);
|
||||
isBindSuccess = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == CHROME_CUSTOM_TAB_REQUEST_CODE) {
|
||||
close();
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private PendingIntent createPendingIntent(int actionSourceId) {
|
||||
Intent actionIntent = new Intent(this, ActionBroadcastReceiver.class);
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putInt(ActionBroadcastReceiver.KEY_ACTION_ID, actionSourceId);
|
||||
extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id);
|
||||
extras.putString(ActionBroadcastReceiver.KEY_ACTION_MANAGER_ID, manager != null ? manager.id : null);
|
||||
actionIntent.putExtras(extras);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
return PendingIntent.getBroadcast(
|
||||
this, actionSourceId, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
} else {
|
||||
return PendingIntent.getBroadcast(
|
||||
this, actionSourceId, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
onStop();
|
||||
onDestroy();
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.dispose();
|
||||
channelDelegate = null;
|
||||
}
|
||||
if (manager != null) {
|
||||
if (manager.browsers.containsKey(id)) {
|
||||
manager.browsers.put(id, null);
|
||||
}
|
||||
}
|
||||
manager = null;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
onStop();
|
||||
onDestroy();
|
||||
customTabsSession = null;
|
||||
finish();
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.onClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
public class ChromeCustomTabsActivitySingleInstance extends ChromeCustomTabsActivity {
|
||||
|
||||
protected static final String LOG_TAG = "ChromeCustomTabsActivitySingleInstance";
|
||||
|
||||
}
|
||||
-251
@@ -1,251 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabsService;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.CustomTabsSecondaryToolbar;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class ChromeCustomTabsChannelDelegate extends ChannelDelegateImpl {
|
||||
@Nullable
|
||||
private ChromeCustomTabsActivity chromeCustomTabsActivity;
|
||||
|
||||
public ChromeCustomTabsChannelDelegate(@NonNull ChromeCustomTabsActivity chromeCustomTabsActivity, @NonNull MethodChannel channel) {
|
||||
super(channel);
|
||||
this.chromeCustomTabsActivity = chromeCustomTabsActivity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull final MethodCall call, @NonNull final MethodChannel.Result result) {
|
||||
switch (call.method) {
|
||||
case "launchUrl":
|
||||
if (chromeCustomTabsActivity != null) {
|
||||
String url = (String) call.argument("url");
|
||||
if (url != null) {
|
||||
Map<String, String> headers = (Map<String, String>) call.argument("headers");
|
||||
String referrer = (String) call.argument("referrer");
|
||||
List<String> otherLikelyURLs = (List<String>) call.argument("otherLikelyURLs");
|
||||
chromeCustomTabsActivity.launchUrl(url, headers, referrer, otherLikelyURLs);
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "mayLaunchUrl":
|
||||
if (chromeCustomTabsActivity != null) {
|
||||
String url = (String) call.argument("url");
|
||||
List<String> otherLikelyURLs = (List<String>) call.argument("otherLikelyURLs");
|
||||
result.success(chromeCustomTabsActivity.mayLaunchUrl(url, otherLikelyURLs));
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "updateActionButton":
|
||||
if (chromeCustomTabsActivity != null) {
|
||||
byte[] icon = (byte[]) call.argument("icon");
|
||||
String description = (String) call.argument("description");
|
||||
chromeCustomTabsActivity.updateActionButton(icon, description);
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "validateRelationship":
|
||||
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
|
||||
Integer relation = (Integer) call.argument("relation");
|
||||
String origin = (String) call.argument("origin");
|
||||
result.success(chromeCustomTabsActivity.customTabsSession.validateRelationship(relation, Uri.parse(origin), null));
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "updateSecondaryToolbar":
|
||||
if (chromeCustomTabsActivity != null) {
|
||||
CustomTabsSecondaryToolbar secondaryToolbar = CustomTabsSecondaryToolbar.fromMap((Map<String, Object>) call.argument("secondaryToolbar"));
|
||||
chromeCustomTabsActivity.updateSecondaryToolbar(secondaryToolbar);
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "requestPostMessageChannel":
|
||||
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
|
||||
String sourceOrigin = (String) call.argument("sourceOrigin");
|
||||
String targetOrigin = (String) call.argument("targetOrigin");
|
||||
result.success(chromeCustomTabsActivity.customTabsSession.requestPostMessageChannel(Uri.parse(sourceOrigin),
|
||||
targetOrigin != null ? Uri.parse(targetOrigin) : null, new Bundle()));
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "postMessage":
|
||||
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
|
||||
String message = (String) call.argument("message");
|
||||
result.success(chromeCustomTabsActivity.customTabsSession.postMessage(message, new Bundle()));
|
||||
} else {
|
||||
result.success(CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR);
|
||||
}
|
||||
break;
|
||||
case "isEngagementSignalsApiAvailable":
|
||||
if (chromeCustomTabsActivity != null && chromeCustomTabsActivity.customTabsSession != null) {
|
||||
try {
|
||||
result.success(chromeCustomTabsActivity.customTabsSession.isEngagementSignalsApiAvailable(new Bundle()));
|
||||
} catch (Throwable e) {
|
||||
result.success(false);
|
||||
}
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "close":
|
||||
if (chromeCustomTabsActivity != null) {
|
||||
chromeCustomTabsActivity.onStop();
|
||||
chromeCustomTabsActivity.onDestroy();
|
||||
chromeCustomTabsActivity.close();
|
||||
|
||||
if (chromeCustomTabsActivity.manager != null && chromeCustomTabsActivity.manager.plugin != null &&
|
||||
chromeCustomTabsActivity.manager.plugin.activity != null) {
|
||||
Activity activity = chromeCustomTabsActivity.manager.plugin.activity;
|
||||
// https://stackoverflow.com/a/41596629/4637638
|
||||
Intent myIntent = new Intent(activity, activity.getClass());
|
||||
myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
myIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
activity.startActivity(myIntent);
|
||||
}
|
||||
chromeCustomTabsActivity.dispose();
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
public void onServiceConnected() {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
channel.invokeMethod("onServiceConnected", obj);
|
||||
}
|
||||
|
||||
public void onOpened() {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
channel.invokeMethod("onOpened", obj);
|
||||
}
|
||||
|
||||
public void onCompletedInitialLoad() {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
channel.invokeMethod("onCompletedInitialLoad", obj);
|
||||
}
|
||||
|
||||
public void onNavigationEvent(int navigationEvent) {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("navigationEvent", navigationEvent);
|
||||
channel.invokeMethod("onNavigationEvent", obj);
|
||||
}
|
||||
|
||||
public void onClosed() {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
channel.invokeMethod("onClosed", obj);
|
||||
}
|
||||
|
||||
public void onItemActionPerform(int id, String url, String title) {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("id", id);
|
||||
obj.put("url", url);
|
||||
obj.put("title", title);
|
||||
channel.invokeMethod("onItemActionPerform", obj);
|
||||
}
|
||||
|
||||
public void onSecondaryItemActionPerform(String name, String url) {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("name", name);
|
||||
obj.put("url", url);
|
||||
channel.invokeMethod("onSecondaryItemActionPerform", obj);
|
||||
}
|
||||
|
||||
public void onRelationshipValidationResult(int relation, @NonNull Uri requestedOrigin, boolean result) {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("relation", relation);
|
||||
obj.put("requestedOrigin", requestedOrigin.toString());
|
||||
obj.put("result", result);
|
||||
channel.invokeMethod("onRelationshipValidationResult", obj);
|
||||
}
|
||||
|
||||
public void onMessageChannelReady() {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
channel.invokeMethod("onMessageChannelReady", obj);
|
||||
}
|
||||
|
||||
public void onPostMessage(@NonNull String message) {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("message", message);
|
||||
channel.invokeMethod("onPostMessage", obj);
|
||||
}
|
||||
|
||||
public void onVerticalScrollEvent(boolean isDirectionUp) {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("isDirectionUp", isDirectionUp);
|
||||
channel.invokeMethod("onVerticalScrollEvent", obj);
|
||||
}
|
||||
|
||||
public void onGreatestScrollPercentageIncreased(int scrollPercentage) {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("scrollPercentage", scrollPercentage);
|
||||
channel.invokeMethod("onGreatestScrollPercentageIncreased", obj);
|
||||
}
|
||||
|
||||
public void onSessionEnded(boolean didUserInteract) {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("didUserInteract", didUserInteract);
|
||||
channel.invokeMethod("onSessionEnded", obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
chromeCustomTabsActivity = null;
|
||||
}
|
||||
}
|
||||
-187
@@ -1,187 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.browser.trusted.ScreenOrientation;
|
||||
import androidx.browser.trusted.TrustedWebActivityDisplayMode;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.ISettings;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.AndroidResource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChromeCustomTabsSettings implements ISettings<ChromeCustomTabsActivity> {
|
||||
|
||||
final static String LOG_TAG = "ChromeCustomTabsSettings";
|
||||
|
||||
@Deprecated
|
||||
public Boolean addDefaultShareMenuItem;
|
||||
public Integer shareState = CustomTabsIntent.SHARE_STATE_DEFAULT;
|
||||
public Boolean showTitle = true;
|
||||
@Nullable
|
||||
public String toolbarBackgroundColor;
|
||||
@Nullable
|
||||
public String navigationBarColor;
|
||||
@Nullable
|
||||
public String navigationBarDividerColor;
|
||||
@Nullable
|
||||
public String secondaryToolbarColor;
|
||||
public Boolean enableUrlBarHiding = false;
|
||||
public Boolean instantAppsEnabled = false;
|
||||
public String packageName;
|
||||
public Boolean keepAliveEnabled = false;
|
||||
public Boolean isSingleInstance = false;
|
||||
public Boolean noHistory = false;
|
||||
public Boolean isTrustedWebActivity = false;
|
||||
public List<String> additionalTrustedOrigins = new ArrayList<>();
|
||||
public TrustedWebActivityDisplayMode displayMode = null;
|
||||
public Integer screenOrientation = ScreenOrientation.DEFAULT;
|
||||
public List<AndroidResource> startAnimations = new ArrayList<>();
|
||||
public List<AndroidResource> exitAnimations = new ArrayList<>();
|
||||
public Boolean alwaysUseBrowserUI = false;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ChromeCustomTabsSettings parse(@NonNull Map<String, Object> options) {
|
||||
for (Map.Entry<String, Object> pair : options.entrySet()) {
|
||||
String key = pair.getKey();
|
||||
Object value = pair.getValue();
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case "addDefaultShareMenuItem":
|
||||
addDefaultShareMenuItem = (Boolean) value;
|
||||
break;
|
||||
case "shareState":
|
||||
shareState = (Integer) value;
|
||||
break;
|
||||
case "showTitle":
|
||||
showTitle = (Boolean) value;
|
||||
break;
|
||||
case "toolbarBackgroundColor":
|
||||
toolbarBackgroundColor = (String) value;
|
||||
break;
|
||||
case "navigationBarColor":
|
||||
navigationBarColor = (String) value;
|
||||
break;
|
||||
case "navigationBarDividerColor":
|
||||
navigationBarDividerColor = (String) value;
|
||||
break;
|
||||
case "secondaryToolbarColor":
|
||||
secondaryToolbarColor = (String) value;
|
||||
break;
|
||||
case "enableUrlBarHiding":
|
||||
enableUrlBarHiding = (Boolean) value;
|
||||
break;
|
||||
case "instantAppsEnabled":
|
||||
instantAppsEnabled = (Boolean) value;
|
||||
break;
|
||||
case "packageName":
|
||||
packageName = (String) value;
|
||||
break;
|
||||
case "keepAliveEnabled":
|
||||
keepAliveEnabled = (Boolean) value;
|
||||
break;
|
||||
case "isSingleInstance":
|
||||
isSingleInstance = (Boolean) value;
|
||||
break;
|
||||
case "noHistory":
|
||||
noHistory = (Boolean) value;
|
||||
break;
|
||||
case "isTrustedWebActivity":
|
||||
isTrustedWebActivity = (Boolean) value;
|
||||
break;
|
||||
case "additionalTrustedOrigins":
|
||||
additionalTrustedOrigins = (List<String>) value;
|
||||
break;
|
||||
case "displayMode":
|
||||
Map<String, Object> displayModeMap = (Map<String, Object>) value;
|
||||
String displayModeType = (String) displayModeMap.get("type");
|
||||
if (displayModeType != null) {
|
||||
switch (displayModeType) {
|
||||
case "IMMERSIVE_MODE":
|
||||
boolean isSticky = (boolean) displayModeMap.get("isSticky");
|
||||
int layoutInDisplayCutoutMode = (int) displayModeMap.get("displayCutoutMode");
|
||||
displayMode = new TrustedWebActivityDisplayMode.ImmersiveMode(isSticky, layoutInDisplayCutoutMode);
|
||||
break;
|
||||
case "DEFAULT_MODE":
|
||||
displayMode = new TrustedWebActivityDisplayMode.DefaultMode();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "screenOrientation":
|
||||
screenOrientation = (Integer) value;
|
||||
break;
|
||||
case "startAnimations":
|
||||
List<Map<String, Object>> startAnimationsList = (List<Map<String, Object>>) value;
|
||||
for (Map<String, Object> startAnimation : startAnimationsList) {
|
||||
AndroidResource androidResource = AndroidResource.fromMap(startAnimation);
|
||||
if (androidResource != null) {
|
||||
startAnimations.add(AndroidResource.fromMap(startAnimation));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "exitAnimations":
|
||||
List<Map<String, Object>> exitAnimationsList = (List<Map<String, Object>>) value;
|
||||
for (Map<String, Object> exitAnimation : exitAnimationsList) {
|
||||
AndroidResource androidResource = AndroidResource.fromMap(exitAnimation);
|
||||
if (androidResource != null) {
|
||||
exitAnimations.add(AndroidResource.fromMap(exitAnimation));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "alwaysUseBrowserUI":
|
||||
alwaysUseBrowserUI = (Boolean) value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("addDefaultShareMenuItem", addDefaultShareMenuItem);
|
||||
options.put("showTitle", showTitle);
|
||||
options.put("toolbarBackgroundColor", toolbarBackgroundColor);
|
||||
options.put("navigationBarColor", navigationBarColor);
|
||||
options.put("navigationBarDividerColor", navigationBarDividerColor);
|
||||
options.put("secondaryToolbarColor", secondaryToolbarColor);
|
||||
options.put("enableUrlBarHiding", enableUrlBarHiding);
|
||||
options.put("instantAppsEnabled", instantAppsEnabled);
|
||||
options.put("packageName", packageName);
|
||||
options.put("keepAliveEnabled", keepAliveEnabled);
|
||||
options.put("isSingleInstance", isSingleInstance);
|
||||
options.put("noHistory", noHistory);
|
||||
options.put("isTrustedWebActivity", isTrustedWebActivity);
|
||||
options.put("additionalTrustedOrigins", additionalTrustedOrigins);
|
||||
options.put("screenOrientation", screenOrientation);
|
||||
options.put("alwaysUseBrowserUI", alwaysUseBrowserUI);
|
||||
return options;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Map<String, Object> getRealSettings(@NonNull ChromeCustomTabsActivity chromeCustomTabsActivity) {
|
||||
Map<String, Object> realOptions = toMap();
|
||||
if (chromeCustomTabsActivity != null) {
|
||||
Intent intent = chromeCustomTabsActivity.getIntent();
|
||||
if (intent != null) {
|
||||
realOptions.put("packageName", intent.getPackage());
|
||||
realOptions.put("keepAliveEnabled", intent.hasExtra(CustomTabsHelper.EXTRA_CUSTOM_TABS_KEEP_ALIVE));
|
||||
}
|
||||
}
|
||||
return realOptions;
|
||||
}
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabsClient;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.Util;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class ChromeSafariBrowserManager extends ChannelDelegateImpl {
|
||||
protected static final String LOG_TAG = "ChromeBrowserManager";
|
||||
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_chromesafaribrowser";
|
||||
|
||||
@Nullable
|
||||
public InAppWebViewFlutterPlugin plugin;
|
||||
public String id;
|
||||
public static final Map<String, ChromeSafariBrowserManager> shared = new HashMap<>();
|
||||
public final Map<String, ChromeCustomTabsActivity> browsers = new HashMap<>();
|
||||
|
||||
public ChromeSafariBrowserManager(final InAppWebViewFlutterPlugin plugin) {
|
||||
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
|
||||
this.id = UUID.randomUUID().toString();
|
||||
this.plugin = plugin;
|
||||
shared.put(this.id, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(final MethodCall call, final MethodChannel.Result result) {
|
||||
final String viewId = (String) call.argument("id");
|
||||
|
||||
switch (call.method) {
|
||||
case "open":
|
||||
if (plugin != null && plugin.activity != null) {
|
||||
String url = (String) call.argument("url");
|
||||
HashMap<String, Object> headers = (HashMap<String, Object>) call.argument("headers");
|
||||
String referrer = (String) call.argument("referrer");
|
||||
ArrayList<String> otherLikelyURLs = (ArrayList<String>) call.argument("otherLikelyURLs");
|
||||
HashMap<String, Object> settings = (HashMap<String, Object>) call.argument("settings");
|
||||
HashMap<String, Object> actionButton = (HashMap<String, Object>) call.argument("actionButton");
|
||||
HashMap<String, Object> secondaryToolbar = (HashMap<String, Object>) call.argument("secondaryToolbar");
|
||||
List<HashMap<String, Object>> menuItemList = (List<HashMap<String, Object>>) call.argument("menuItemList");
|
||||
open(plugin.activity, viewId, url, headers, referrer, otherLikelyURLs, settings, actionButton, secondaryToolbar, menuItemList, result);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "isAvailable":
|
||||
if (plugin != null && plugin.activity != null) {
|
||||
result.success(CustomTabActivityHelper.isAvailable(plugin.activity));
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "getMaxToolbarItems":
|
||||
result.success(CustomTabsIntent.getMaxToolbarItems());
|
||||
break;
|
||||
case "getPackageName":
|
||||
if (plugin != null && plugin.activity != null) {
|
||||
ArrayList<String> packages = (ArrayList<String>) call.argument("packages");
|
||||
Boolean ignoreDefault = (Boolean) call.argument("ignoreDefault");
|
||||
result.success(CustomTabsClient.getPackageName(plugin.activity, packages, ignoreDefault));
|
||||
} else {
|
||||
result.success(null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
public void open(Activity activity, String viewId, @Nullable String url, @Nullable HashMap<String, Object> headers,
|
||||
@Nullable String referrer, @Nullable ArrayList<String> otherLikelyURLs,
|
||||
HashMap<String, Object> settings, HashMap<String, Object> actionButton,
|
||||
HashMap<String, Object> secondaryToolbar,
|
||||
List<HashMap<String, Object>> menuItemList, MethodChannel.Result result) {
|
||||
|
||||
Intent intent = null;
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString("url", url);
|
||||
extras.putString("id", viewId);
|
||||
extras.putString("managerId", this.id);
|
||||
extras.putSerializable("headers", headers);
|
||||
extras.putString("referrer", referrer);
|
||||
extras.putSerializable("otherLikelyURLs", otherLikelyURLs);
|
||||
extras.putSerializable("settings", settings);
|
||||
extras.putSerializable("actionButton", (Serializable) actionButton);
|
||||
extras.putSerializable("secondaryToolbar", (Serializable) secondaryToolbar);
|
||||
extras.putSerializable("menuItemList", (Serializable) menuItemList);
|
||||
|
||||
Boolean isSingleInstance = Util.<Boolean>getOrDefault(settings, "isSingleInstance", false);
|
||||
Boolean isTrustedWebActivity = Util.<Boolean>getOrDefault(settings, "isTrustedWebActivity", false);
|
||||
if (CustomTabActivityHelper.isAvailable(activity)) {
|
||||
intent = new Intent(activity, !isSingleInstance ?
|
||||
(!isTrustedWebActivity ? ChromeCustomTabsActivity.class : TrustedWebActivity.class) :
|
||||
(!isTrustedWebActivity ? ChromeCustomTabsActivitySingleInstance.class : TrustedWebActivitySingleInstance.class));
|
||||
intent.putExtras(extras);
|
||||
Boolean noHistory = Util.<Boolean>getOrDefault(settings, "noHistory", false);
|
||||
if (noHistory) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
}
|
||||
activity.startActivity(intent);
|
||||
result.success(true);
|
||||
return;
|
||||
}
|
||||
|
||||
result.error(LOG_TAG, "ChromeCustomTabs is not available!", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
Collection<ChromeCustomTabsActivity> browserList = browsers.values();
|
||||
for (ChromeCustomTabsActivity browser : browserList) {
|
||||
if (browser != null) {
|
||||
browser.close();
|
||||
browser.dispose();
|
||||
}
|
||||
}
|
||||
browsers.clear();
|
||||
shared.remove(this.id);
|
||||
plugin = null;
|
||||
}
|
||||
}
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Browser;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabsCallback;
|
||||
import androidx.browser.customtabs.CustomTabsClient;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.browser.customtabs.CustomTabsServiceConnection;
|
||||
import androidx.browser.customtabs.CustomTabsSession;
|
||||
import androidx.browser.trusted.TrustedWebActivityIntent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This is a helper class to manage the connection to the Custom Tabs Service.
|
||||
*/
|
||||
public class CustomTabActivityHelper implements ServiceConnectionCallback {
|
||||
private CustomTabsSession mCustomTabsSession;
|
||||
private CustomTabsClient mClient;
|
||||
private CustomTabsServiceConnection mConnection;
|
||||
private ConnectionCallback mConnectionCallback;
|
||||
private CustomTabsCallback mCustomTabsCallback;
|
||||
|
||||
/**
|
||||
* Opens the URL on a Custom Tab if possible.
|
||||
*
|
||||
* @param activity The host activity.
|
||||
* @param intent a intent to be used if Custom Tabs is available.
|
||||
* @param uri the Uri to be opened.
|
||||
*/
|
||||
public static void openCustomTab(Activity activity,
|
||||
Intent intent,
|
||||
Uri uri,
|
||||
@Nullable Map<String, String> headers,
|
||||
@Nullable Uri referrer,
|
||||
int requestCode) {
|
||||
intent.setData(uri);
|
||||
if (headers != null) {
|
||||
Bundle bundleHeaders = new Bundle();
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
bundleHeaders.putString(header.getKey(), header.getValue());
|
||||
}
|
||||
intent.putExtra(Browser.EXTRA_HEADERS, bundleHeaders);
|
||||
}
|
||||
if (referrer != null) {
|
||||
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
|
||||
}
|
||||
activity.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
public static void openCustomTab(Activity activity,
|
||||
CustomTabsIntent customTabsIntent,
|
||||
Uri uri,
|
||||
@Nullable Map<String, String> headers,
|
||||
@Nullable Uri referrer,
|
||||
int requestCode) {
|
||||
CustomTabActivityHelper.openCustomTab(activity, customTabsIntent.intent, uri,
|
||||
headers, referrer, requestCode);
|
||||
}
|
||||
|
||||
public static void openTrustedWebActivity(Activity activity,
|
||||
TrustedWebActivityIntent trustedWebActivityIntent,
|
||||
Uri uri,
|
||||
@Nullable Map<String, String> headers,
|
||||
@Nullable Uri referrer,
|
||||
int requestCode) {
|
||||
CustomTabActivityHelper.openCustomTab(activity, trustedWebActivityIntent.getIntent(), uri,
|
||||
headers, referrer, requestCode);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAvailable(Activity activity) {
|
||||
return CustomTabsHelper.getPackageNameToUse(activity) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbinds the Activity from the Custom Tabs Service.
|
||||
* @param activity the activity that is connected to the service.
|
||||
*/
|
||||
public void unbindCustomTabsService(Activity activity) {
|
||||
if (mConnection == null) return;
|
||||
activity.unbindService(mConnection);
|
||||
mClient = null;
|
||||
mCustomTabsSession = null;
|
||||
mConnection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or retrieves an exiting CustomTabsSession.
|
||||
*
|
||||
* @return a CustomTabsSession.
|
||||
*/
|
||||
@Nullable
|
||||
public CustomTabsSession getSession() {
|
||||
if (mClient == null) {
|
||||
mCustomTabsSession = null;
|
||||
} else if (mCustomTabsSession == null) {
|
||||
mCustomTabsSession = mClient.newSession(mCustomTabsCallback);
|
||||
}
|
||||
return mCustomTabsSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a Callback to be called when connected or disconnected from the Custom Tabs Service.
|
||||
* @param connectionCallback
|
||||
*/
|
||||
public void setConnectionCallback(ConnectionCallback connectionCallback) {
|
||||
this.mConnectionCallback = connectionCallback;
|
||||
}
|
||||
|
||||
public void setCustomTabsCallback(CustomTabsCallback customTabsCallback) {
|
||||
this.mCustomTabsCallback = customTabsCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the Activity to the Custom Tabs Service.
|
||||
* @param activity the activity to be binded to the service.
|
||||
*/
|
||||
public boolean bindCustomTabsService(Activity activity) {
|
||||
if (mClient != null) return true;
|
||||
|
||||
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
|
||||
if (packageName == null) return false;
|
||||
|
||||
mConnection = new ServiceConnection(this);
|
||||
return CustomTabsClient.bindCustomTabsService(activity, packageName, mConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link CustomTabsSession#mayLaunchUrl(Uri, Bundle, List)}.
|
||||
* @return true if call to mayLaunchUrl was accepted.
|
||||
*/
|
||||
public boolean mayLaunchUrl(Uri uri, Bundle extras, List<Bundle> otherLikelyBundles) {
|
||||
if (mClient == null) return false;
|
||||
|
||||
CustomTabsSession session = getSession();
|
||||
if (session == null) return false;
|
||||
|
||||
return session.mayLaunchUrl(uri, extras, otherLikelyBundles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(CustomTabsClient client) {
|
||||
mClient = client;
|
||||
mClient.warmup(0L);
|
||||
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected() {
|
||||
mClient = null;
|
||||
mCustomTabsSession = null;
|
||||
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsDisconnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* A Callback for when the service is connected or disconnected. Use those callbacks to
|
||||
* handle UI changes when the service is connected or disconnected.
|
||||
*/
|
||||
public interface ConnectionCallback {
|
||||
/**
|
||||
* Called when the service is connected.
|
||||
*/
|
||||
void onCustomTabsConnected();
|
||||
|
||||
/**
|
||||
* Called when the service is disconnected.
|
||||
*/
|
||||
void onCustomTabsDisconnected();
|
||||
}
|
||||
}
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.browser.customtabs.CustomTabsService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper class for Custom Tabs.
|
||||
*/
|
||||
public class CustomTabsHelper {
|
||||
protected static final String TAG = "CustomTabsHelper";
|
||||
static final String STABLE_PACKAGE = "com.android.chrome";
|
||||
static final String BETA_PACKAGE = "com.chrome.beta";
|
||||
static final String DEV_PACKAGE = "com.chrome.dev";
|
||||
static final String LOCAL_PACKAGE = "com.google.android.apps.chrome";
|
||||
protected static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE =
|
||||
"android.support.customtabs.extra.KEEP_ALIVE";
|
||||
|
||||
private static String sPackageNameToUse;
|
||||
|
||||
private CustomTabsHelper() {}
|
||||
|
||||
public static void addKeepAliveExtra(Context context, Intent intent) {
|
||||
Intent keepAliveIntent = new Intent().setClassName(
|
||||
context.getPackageName(), KeepAliveService.class.getCanonicalName());
|
||||
intent.putExtra(EXTRA_CUSTOM_TABS_KEEP_ALIVE, keepAliveIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes through all apps that handle VIEW intents and have a warmup service. Picks
|
||||
* the one chosen by the user if there is one, otherwise makes a best effort to return a
|
||||
* valid package name.
|
||||
*
|
||||
* This is <strong>not</strong> threadsafe.
|
||||
*
|
||||
* @param context {@link Context} to use for accessing {@link PackageManager}.
|
||||
* @return The package name recommended to use for connecting to custom tabs related components.
|
||||
*/
|
||||
public static String getPackageNameToUse(Context context) {
|
||||
if (sPackageNameToUse != null) return sPackageNameToUse;
|
||||
|
||||
PackageManager pm = context.getPackageManager();
|
||||
// Get default VIEW intent handler.
|
||||
Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));
|
||||
activityIntent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0);
|
||||
String defaultViewHandlerPackageName = null;
|
||||
if (defaultViewHandlerInfo != null) {
|
||||
defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName;
|
||||
}
|
||||
|
||||
// Get all apps that can handle VIEW intents.
|
||||
int flags = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
flags |= PackageManager.MATCH_ALL;
|
||||
}
|
||||
List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, flags);
|
||||
List<String> packagesSupportingCustomTabs = new ArrayList<>();
|
||||
for (ResolveInfo info : resolvedActivityList) {
|
||||
Intent serviceIntent = new Intent();
|
||||
serviceIntent.setAction(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION);
|
||||
serviceIntent.setPackage(info.activityInfo.packageName);
|
||||
if (pm.resolveService(serviceIntent, 0) != null) {
|
||||
packagesSupportingCustomTabs.add(info.activityInfo.packageName);
|
||||
}
|
||||
}
|
||||
|
||||
// Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents
|
||||
// and service calls.
|
||||
if (packagesSupportingCustomTabs.isEmpty()) {
|
||||
sPackageNameToUse = null;
|
||||
} else if (packagesSupportingCustomTabs.size() == 1) {
|
||||
sPackageNameToUse = packagesSupportingCustomTabs.get(0);
|
||||
} else if (!TextUtils.isEmpty(defaultViewHandlerPackageName)
|
||||
&& !hasSpecializedHandlerIntents(context, activityIntent)
|
||||
&& packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) {
|
||||
sPackageNameToUse = defaultViewHandlerPackageName;
|
||||
} else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) {
|
||||
sPackageNameToUse = STABLE_PACKAGE;
|
||||
} else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) {
|
||||
sPackageNameToUse = BETA_PACKAGE;
|
||||
} else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) {
|
||||
sPackageNameToUse = DEV_PACKAGE;
|
||||
} else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) {
|
||||
sPackageNameToUse = LOCAL_PACKAGE;
|
||||
}
|
||||
return sPackageNameToUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to check whether there is a specialized handler for a given intent.
|
||||
* @param intent The intent to check with.
|
||||
* @return Whether there is a specialized handler for the given intent.
|
||||
*/
|
||||
private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) {
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
List<ResolveInfo> handlers = pm.queryIntentActivities(
|
||||
intent,
|
||||
PackageManager.GET_RESOLVED_FILTER);
|
||||
if (handlers == null || handlers.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
for (ResolveInfo resolveInfo : handlers) {
|
||||
IntentFilter filter = resolveInfo.filter;
|
||||
if (filter == null) continue;
|
||||
if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue;
|
||||
if (resolveInfo.activityInfo == null) continue;
|
||||
return true;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Runtime exception while getting specialized handlers");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return All possible chrome package names that provide custom tabs feature.
|
||||
*/
|
||||
public static String[] getPackages() {
|
||||
return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE};
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
|
||||
/**
|
||||
* Empty service used by the custom tab to bind to, raising the application's importance.
|
||||
*/
|
||||
public class KeepAliveService extends Service {
|
||||
private static final Binder sBinder = new Binder();
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return sBinder;
|
||||
}
|
||||
}
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
|
||||
public class NoHistoryCustomTabsActivityCallbacks implements Disposable {
|
||||
@Nullable
|
||||
public InAppWebViewFlutterPlugin plugin;
|
||||
|
||||
public final Map<String, String> noHistoryBrowserIDs = new HashMap<>();
|
||||
|
||||
public NoHistoryCustomTabsActivityCallbacks(@NonNull final InAppWebViewFlutterPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
|
||||
@Override
|
||||
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResumed(@NonNull Activity activity) {
|
||||
if (activity instanceof FlutterActivity && plugin != null && plugin.chromeSafariBrowserManager != null) {
|
||||
Collection<String> browserIds = noHistoryBrowserIDs.values();
|
||||
for (String browserId : browserIds) {
|
||||
if (browserId != null) {
|
||||
noHistoryBrowserIDs.put(browserId, null);
|
||||
ChromeCustomTabsActivity browser = plugin.chromeSafariBrowserManager.browsers.get(browserId);
|
||||
if (browser != null) {
|
||||
browser.close();
|
||||
browser.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPaused(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(@NonNull Activity activity) {
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
noHistoryBrowserIDs.clear();
|
||||
plugin = null;
|
||||
}
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import androidx.browser.customtabs.CustomTabsClient;
|
||||
import androidx.browser.customtabs.CustomTabsServiceConnection;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Implementation for the CustomTabsServiceConnection that avoids leaking the
|
||||
* ServiceConnectionCallback
|
||||
*/
|
||||
public class ServiceConnection extends CustomTabsServiceConnection {
|
||||
// A weak reference to the ServiceConnectionCallback to avoid leaking it.
|
||||
private WeakReference<ServiceConnectionCallback> mConnectionCallback;
|
||||
|
||||
public ServiceConnection(ServiceConnectionCallback connectionCallback) {
|
||||
mConnectionCallback = new WeakReference<>(connectionCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
|
||||
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
|
||||
if (connectionCallback != null) connectionCallback.onServiceConnected(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
|
||||
if (connectionCallback != null) connectionCallback.onServiceDisconnected();
|
||||
}
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import androidx.browser.customtabs.CustomTabsClient;
|
||||
|
||||
/**
|
||||
* Callback for events when connecting and disconnecting from Custom Tabs Service.
|
||||
*/
|
||||
public interface ServiceConnectionCallback {
|
||||
/**
|
||||
* Called when the service is connected.
|
||||
* @param client a CustomTabsClient
|
||||
*/
|
||||
void onServiceConnected(CustomTabsClient client);
|
||||
|
||||
/**
|
||||
* Called when the service is disconnected.
|
||||
*/
|
||||
void onServiceDisconnected();
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.browser.trusted.TrustedWebActivityIntent;
|
||||
import androidx.browser.trusted.TrustedWebActivityIntentBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TrustedWebActivity extends ChromeCustomTabsActivity {
|
||||
|
||||
protected static final String LOG_TAG = "TrustedWebActivity";
|
||||
|
||||
public TrustedWebActivityIntentBuilder builder;
|
||||
|
||||
@Override
|
||||
public void launchUrl(@NonNull String url,
|
||||
@Nullable Map<String, String> headers,
|
||||
@Nullable String referrer,
|
||||
@Nullable List<String> otherLikelyURLs) {
|
||||
if (customTabsSession == null) {
|
||||
return;
|
||||
}
|
||||
Uri uri = Uri.parse(url);
|
||||
|
||||
mayLaunchUrl(url, otherLikelyURLs);
|
||||
builder = new TrustedWebActivityIntentBuilder(uri);
|
||||
prepareCustomTabs();
|
||||
|
||||
TrustedWebActivityIntent trustedWebActivityIntent = builder.build(customTabsSession);
|
||||
prepareCustomTabsIntent(trustedWebActivityIntent);
|
||||
|
||||
CustomTabActivityHelper.openTrustedWebActivity(this, trustedWebActivityIntent, uri, headers,
|
||||
referrer != null ? Uri.parse(referrer) : null, CHROME_CUSTOM_TAB_REQUEST_CODE);
|
||||
}
|
||||
|
||||
private void prepareCustomTabs() {
|
||||
CustomTabColorSchemeParams.Builder defaultColorSchemeBuilder = new CustomTabColorSchemeParams.Builder();
|
||||
if (customSettings.toolbarBackgroundColor != null && !customSettings.toolbarBackgroundColor.isEmpty()) {
|
||||
defaultColorSchemeBuilder.setToolbarColor(Color.parseColor(customSettings.toolbarBackgroundColor));
|
||||
}
|
||||
if (customSettings.navigationBarColor != null && !customSettings.navigationBarColor.isEmpty()) {
|
||||
defaultColorSchemeBuilder.setNavigationBarColor(Color.parseColor(customSettings.navigationBarColor));
|
||||
}
|
||||
if (customSettings.navigationBarDividerColor != null && !customSettings.navigationBarDividerColor.isEmpty()) {
|
||||
defaultColorSchemeBuilder.setNavigationBarDividerColor(Color.parseColor(customSettings.navigationBarDividerColor));
|
||||
}
|
||||
if (customSettings.secondaryToolbarColor != null && !customSettings.secondaryToolbarColor.isEmpty()) {
|
||||
defaultColorSchemeBuilder.setSecondaryToolbarColor(Color.parseColor(customSettings.secondaryToolbarColor));
|
||||
}
|
||||
builder.setDefaultColorSchemeParams(defaultColorSchemeBuilder.build());
|
||||
|
||||
if (customSettings.additionalTrustedOrigins != null && !customSettings.additionalTrustedOrigins.isEmpty()) {
|
||||
builder.setAdditionalTrustedOrigins(customSettings.additionalTrustedOrigins);
|
||||
}
|
||||
|
||||
if (customSettings.displayMode != null) {
|
||||
builder.setDisplayMode(customSettings.displayMode);
|
||||
}
|
||||
|
||||
builder.setScreenOrientation(customSettings.screenOrientation);
|
||||
}
|
||||
|
||||
private void prepareCustomTabsIntent(TrustedWebActivityIntent trustedWebActivityIntent) {
|
||||
Intent intent = trustedWebActivityIntent.getIntent();
|
||||
if (customSettings.packageName != null)
|
||||
intent.setPackage(customSettings.packageName);
|
||||
else
|
||||
intent.setPackage(CustomTabsHelper.getPackageNameToUse(this));
|
||||
|
||||
if (customSettings.keepAliveEnabled)
|
||||
CustomTabsHelper.addKeepAliveExtra(this, intent);
|
||||
|
||||
if (customSettings.alwaysUseBrowserUI)
|
||||
CustomTabsIntent.setAlwaysUseBrowserUI(intent);
|
||||
}
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.chrome_custom_tabs;
|
||||
|
||||
public class TrustedWebActivitySingleInstance extends TrustedWebActivity {
|
||||
|
||||
protected static final String LOG_TAG = "TrustedWebActivitySingleInstance";
|
||||
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ContentBlocker {
|
||||
@NonNull
|
||||
private ContentBlockerTrigger trigger;
|
||||
@NonNull
|
||||
private ContentBlockerAction action;
|
||||
|
||||
public ContentBlocker (@NonNull ContentBlockerTrigger trigger, @NonNull ContentBlockerAction action) {
|
||||
this.trigger = trigger;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ContentBlockerTrigger getTrigger() {
|
||||
return trigger;
|
||||
}
|
||||
|
||||
public void setTrigger(@NonNull ContentBlockerTrigger trigger) {
|
||||
this.trigger = trigger;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ContentBlockerAction getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(@NonNull ContentBlockerAction action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ContentBlocker that = (ContentBlocker) o;
|
||||
|
||||
if (!trigger.equals(that.trigger)) return false;
|
||||
return action.equals(that.action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = trigger.hashCode();
|
||||
result = 31 * result + action.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ContentBlocker{" +
|
||||
"trigger=" + trigger +
|
||||
", action=" + action +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ContentBlockerAction {
|
||||
@NonNull
|
||||
private ContentBlockerActionType type;
|
||||
|
||||
@Nullable
|
||||
private String selector;
|
||||
|
||||
ContentBlockerAction(@NonNull ContentBlockerActionType type, @Nullable String selector) {
|
||||
this.type = type;
|
||||
if (this.type.equals(ContentBlockerActionType.CSS_DISPLAY_NONE)) {
|
||||
assert(selector != null);
|
||||
}
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
public static ContentBlockerAction fromMap(Map<String, Object> map) {
|
||||
ContentBlockerActionType type = ContentBlockerActionType.fromValue((String) map.get("type"));
|
||||
String selector = (String) map.get("selector");
|
||||
return new ContentBlockerAction(type, selector);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ContentBlockerActionType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(@NonNull ContentBlockerActionType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getSelector() {
|
||||
return selector;
|
||||
}
|
||||
|
||||
public void setSelector(String selector) {
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ContentBlockerAction that = (ContentBlockerAction) o;
|
||||
|
||||
if (type != that.type) return false;
|
||||
return selector != null ? selector.equals(that.selector) : that.selector == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = type.hashCode();
|
||||
result = 31 * result + (selector != null ? selector.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ContentBlockerAction{" +
|
||||
"type=" + type +
|
||||
", selector='" + selector + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
|
||||
|
||||
public enum ContentBlockerActionType {
|
||||
BLOCK ("block"),
|
||||
CSS_DISPLAY_NONE ("css-display-none"),
|
||||
MAKE_HTTPS ("make-https");
|
||||
|
||||
private final String value;
|
||||
|
||||
private ContentBlockerActionType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean equalsValue(String otherValue) {
|
||||
return value.equals(otherValue);
|
||||
}
|
||||
|
||||
public static ContentBlockerActionType fromValue(String value) {
|
||||
for( ContentBlockerActionType type : ContentBlockerActionType.values()) {
|
||||
if(value.equals(type.value))
|
||||
return type;
|
||||
}
|
||||
throw new IllegalArgumentException("No enum constant: " + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
-336
@@ -1,336 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebResourceResponse;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.Util;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.plugin_scripts_js.JavaScriptBridgeJS;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.WebResourceRequestExt;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.InAppWebView;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
public class ContentBlockerHandler {
|
||||
protected static final String LOG_TAG = "ContentBlockerHandler";
|
||||
|
||||
protected List<ContentBlocker> ruleList = new ArrayList<>();
|
||||
|
||||
public ContentBlockerHandler() {}
|
||||
|
||||
public ContentBlockerHandler(List<ContentBlocker> ruleList) {
|
||||
this.ruleList = ruleList;
|
||||
}
|
||||
|
||||
public List<ContentBlocker> getRuleList() {
|
||||
return this.ruleList;
|
||||
}
|
||||
|
||||
public void setRuleList(List<ContentBlocker> newRuleList) {
|
||||
this.ruleList = newRuleList;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public WebResourceResponse checkUrl(final InAppWebView webView, WebResourceRequestExt request,
|
||||
ContentBlockerTriggerResourceType responseResourceType)
|
||||
throws URISyntaxException, InterruptedException, MalformedURLException {
|
||||
if (webView.customSettings.contentBlockers == null)
|
||||
return null;
|
||||
|
||||
String url = request.getUrl();
|
||||
|
||||
URI u;
|
||||
try {
|
||||
u = new URI(url);
|
||||
} catch (URISyntaxException e) {
|
||||
String[] urlSplit = url.split(":");
|
||||
String scheme = urlSplit[0];
|
||||
URL tempUrl = new URL(url.replace(scheme, "https"));
|
||||
u = new URI(scheme, tempUrl.getUserInfo(), tempUrl.getHost(), tempUrl.getPort(), tempUrl.getPath(), tempUrl.getQuery(), tempUrl.getRef());
|
||||
}
|
||||
String host = u.getHost();
|
||||
int port = u.getPort();
|
||||
String scheme = u.getScheme();
|
||||
// thread safe copy list
|
||||
List<ContentBlocker> ruleListCopy = new CopyOnWriteArrayList<ContentBlocker>(ruleList);
|
||||
|
||||
for (ContentBlocker contentBlocker : ruleListCopy) {
|
||||
ContentBlockerTrigger trigger = contentBlocker.getTrigger();
|
||||
List<ContentBlockerTriggerResourceType> resourceTypes = trigger.getResourceType();
|
||||
if (resourceTypes.contains(ContentBlockerTriggerResourceType.IMAGE) && !resourceTypes.contains(ContentBlockerTriggerResourceType.SVG_DOCUMENT)) {
|
||||
resourceTypes.add(ContentBlockerTriggerResourceType.SVG_DOCUMENT);
|
||||
}
|
||||
|
||||
ContentBlockerAction action = contentBlocker.getAction();
|
||||
|
||||
Matcher m = trigger.getUrlFilterPatternCompiled().matcher(url);
|
||||
if (m.matches()) {
|
||||
|
||||
if (!resourceTypes.isEmpty() && !resourceTypes.contains(responseResourceType)) {
|
||||
return null;
|
||||
}
|
||||
if (!trigger.getIfDomain().isEmpty()) {
|
||||
boolean matchFound = false;
|
||||
for (String domain : trigger.getIfDomain()) {
|
||||
if ((domain.startsWith("*") && host.endsWith(domain.replace("*", ""))) || domain.equals(host)) {
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchFound)
|
||||
return null;
|
||||
}
|
||||
if (!trigger.getUnlessDomain().isEmpty()) {
|
||||
for (String domain : trigger.getUnlessDomain())
|
||||
if ((domain.startsWith("*") && host.endsWith(domain.replace("*", ""))) || domain.equals(host))
|
||||
return null;
|
||||
}
|
||||
|
||||
final String[] webViewUrl = new String[1];
|
||||
if (!trigger.getLoadType().isEmpty() || !trigger.getIfTopUrl().isEmpty() || !trigger.getUnlessTopUrl().isEmpty()) {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Handler handler = new Handler(webView.getWebViewLooper());
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
webViewUrl[0] = webView.getUrl();
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
latch.await();
|
||||
}
|
||||
|
||||
if (webViewUrl[0] != null) {
|
||||
if (!trigger.getLoadType().isEmpty()) {
|
||||
URI cUrl = new URI(webViewUrl[0]);
|
||||
String cHost = cUrl.getHost();
|
||||
int cPort = cUrl.getPort();
|
||||
String cScheme = cUrl.getScheme();
|
||||
|
||||
if ( (trigger.getLoadType().contains("first-party") && cHost != null && !(cScheme.equals(scheme) && cHost.equals(host) && cPort == port)) ||
|
||||
(trigger.getLoadType().contains("third-party") && cHost != null && cHost.equals(host)) )
|
||||
return null;
|
||||
}
|
||||
if (!trigger.getIfTopUrl().isEmpty()) {
|
||||
boolean matchFound = false;
|
||||
for (String topUrl : trigger.getIfTopUrl()) {
|
||||
if (webViewUrl[0].startsWith(topUrl)) {
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchFound)
|
||||
return null;
|
||||
}
|
||||
if (!trigger.getUnlessTopUrl().isEmpty()) {
|
||||
for (String topUrl : trigger.getUnlessTopUrl())
|
||||
if (webViewUrl[0].startsWith(topUrl))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
switch (action.getType()) {
|
||||
|
||||
case BLOCK:
|
||||
return new WebResourceResponse("", "", null);
|
||||
|
||||
case CSS_DISPLAY_NONE:
|
||||
final String cssSelector = action.getSelector();
|
||||
final String jsScript = "(function(d) { " +
|
||||
" function hide () { " +
|
||||
" if (d.body != null && !d.getElementById('" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "-css-display-none-style')) { " +
|
||||
" var c = d.createElement('style'); " +
|
||||
" c.id = '" + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + "-css-display-none-style'; " +
|
||||
" c.innerHTML = '" + cssSelector + " { display: none !important; }'; " +
|
||||
" d.body.appendChild(c); " +
|
||||
" }" +
|
||||
" d.querySelectorAll('" + cssSelector + "').forEach(function (item, index) { " +
|
||||
" item.setAttribute('style', 'display: none !important;'); " +
|
||||
" }); " +
|
||||
" }; " +
|
||||
" hide(); " +
|
||||
" d.addEventListener('DOMContentLoaded', function(event) { hide(); }); " +
|
||||
"})(document);";
|
||||
|
||||
final Handler handler = new Handler(webView.getWebViewLooper());
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
webView.evaluateJavascript(jsScript, null);
|
||||
} else {
|
||||
webView.loadUrl("javascript:" + jsScript);
|
||||
}
|
||||
}
|
||||
}, 800);
|
||||
break;
|
||||
|
||||
case MAKE_HTTPS:
|
||||
if (scheme.equals("http") && (port == -1 || port == 80)) {
|
||||
String urlHttps = url.replace("http://", "https://");
|
||||
|
||||
HttpURLConnection urlConnection = Util.makeHttpRequest(urlHttps, request.getMethod(), request.getHeaders());
|
||||
if (urlConnection != null) {
|
||||
try {
|
||||
byte[] dataBytes = Util.readAllBytes(urlConnection.getInputStream());
|
||||
if (dataBytes == null) {
|
||||
return null;
|
||||
}
|
||||
InputStream dataStream = new ByteArrayInputStream(dataBytes);
|
||||
|
||||
String encoding = urlConnection.getContentEncoding();
|
||||
String contentType = urlConnection.getContentType();
|
||||
if (contentType == null) {
|
||||
contentType = "text/plain";
|
||||
} else {
|
||||
String[] contentTypeSplit = contentType.split(";");
|
||||
contentType = contentTypeSplit[0].trim();
|
||||
if (encoding == null) {
|
||||
encoding = (contentTypeSplit.length > 1 && contentTypeSplit[1].contains("charset="))
|
||||
? contentTypeSplit[1].replace("charset=", "").trim()
|
||||
: "utf-8";
|
||||
}
|
||||
}
|
||||
|
||||
String reasonPhrase = urlConnection.getResponseMessage();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && reasonPhrase != null) {
|
||||
Map<String, String> responseHeaders = new HashMap<>();
|
||||
for (Map.Entry<String, List<String>> responseHeader : urlConnection.getHeaderFields().entrySet()) {
|
||||
responseHeaders.put(responseHeader.getKey(), TextUtils.join(",", responseHeader.getValue()));
|
||||
}
|
||||
return new WebResourceResponse(contentType,
|
||||
encoding,
|
||||
urlConnection.getResponseCode(),
|
||||
reasonPhrase,
|
||||
responseHeaders,
|
||||
dataStream);
|
||||
} else {
|
||||
return new WebResourceResponse(contentType,
|
||||
encoding,
|
||||
dataStream);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (!(e instanceof SSLHandshakeException)) {
|
||||
Log.e(LOG_TAG, "", e);
|
||||
}
|
||||
} finally {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// Request mRequest = new Request.Builder().url(urlHttps).build();
|
||||
// Response response = null;
|
||||
//
|
||||
// try {
|
||||
// response = Util.getBasicOkHttpClient().newCall(mRequest).execute();
|
||||
// byte[] dataBytes = response.body().bytes();
|
||||
// InputStream dataStream = new ByteArrayInputStream(dataBytes);
|
||||
//
|
||||
// String[] contentTypeSplit = response.header("content-type", "text/plain").split(";");
|
||||
//
|
||||
// String contentType = contentTypeSplit[0].trim();
|
||||
// String encoding = (contentTypeSplit.length > 1 && contentTypeSplit[1].contains("charset="))
|
||||
// ? contentTypeSplit[1].replace("charset=", "").trim()
|
||||
// : "utf-8";
|
||||
//
|
||||
// response.body().close();
|
||||
// response.close();
|
||||
//
|
||||
// return new WebResourceResponse(contentType, encoding, dataStream);
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// if (response != null) {
|
||||
// response.body().close();
|
||||
// response.close();
|
||||
// }
|
||||
// if (!(e instanceof SSLHandshakeException)) {
|
||||
// Log.e(LOG_TAG, "", e);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public WebResourceResponse checkUrl(final InAppWebView webView, WebResourceRequestExt request) throws URISyntaxException, InterruptedException, MalformedURLException {
|
||||
ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromUrl(request);
|
||||
return checkUrl(webView, request, responseResourceType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public WebResourceResponse checkUrl(final InAppWebView webView, WebResourceRequestExt request, String contentType) throws URISyntaxException, InterruptedException, MalformedURLException {
|
||||
ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromContentType(contentType);
|
||||
return checkUrl(webView, request, responseResourceType);
|
||||
}
|
||||
|
||||
public ContentBlockerTriggerResourceType getResourceTypeFromUrl(WebResourceRequestExt request) {
|
||||
ContentBlockerTriggerResourceType responseResourceType = ContentBlockerTriggerResourceType.RAW;
|
||||
String url = request.getUrl();
|
||||
|
||||
if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||
// make an HTTP "HEAD" request to the server for that URL. This will not return the full content of the URL.
|
||||
HttpURLConnection urlConnection = Util.makeHttpRequest(url, "HEAD", request.getHeaders());
|
||||
if (urlConnection != null) {
|
||||
try {
|
||||
String contentType = urlConnection.getContentType();
|
||||
if (contentType != null) {
|
||||
String[] contentTypeSplit = contentType.split(";");
|
||||
contentType = contentTypeSplit[0].trim();
|
||||
responseResourceType = getResourceTypeFromContentType(contentType);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "", e);
|
||||
} finally {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
return responseResourceType;
|
||||
}
|
||||
|
||||
public ContentBlockerTriggerResourceType getResourceTypeFromContentType(String contentType) {
|
||||
ContentBlockerTriggerResourceType responseResourceType = ContentBlockerTriggerResourceType.RAW;
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
|
||||
if (contentType.equals("text/css")) {
|
||||
responseResourceType = ContentBlockerTriggerResourceType.STYLE_SHEET;
|
||||
} else if (contentType.equals("image/svg+xml")) {
|
||||
responseResourceType = ContentBlockerTriggerResourceType.SVG_DOCUMENT;
|
||||
} else if (contentType.startsWith("image/")) {
|
||||
responseResourceType = ContentBlockerTriggerResourceType.IMAGE;
|
||||
} else if (contentType.startsWith("font/")) {
|
||||
responseResourceType = ContentBlockerTriggerResourceType.FONT;
|
||||
} else if (contentType.startsWith("audio/") || contentType.startsWith("video/") || contentType.equals("application/ogg")) {
|
||||
responseResourceType = ContentBlockerTriggerResourceType.MEDIA;
|
||||
} else if (contentType.endsWith("javascript")) {
|
||||
responseResourceType = ContentBlockerTriggerResourceType.SCRIPT;
|
||||
} else if (contentType.startsWith("text/")) {
|
||||
responseResourceType = ContentBlockerTriggerResourceType.DOCUMENT;
|
||||
}
|
||||
|
||||
return responseResourceType;
|
||||
}
|
||||
}
|
||||
-185
@@ -1,185 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ContentBlockerTrigger {
|
||||
|
||||
@NonNull
|
||||
private String urlFilter;
|
||||
private Pattern urlFilterPatternCompiled;
|
||||
private Boolean urlFilterIsCaseSensitive;
|
||||
private List<ContentBlockerTriggerResourceType> resourceType = new ArrayList<>();
|
||||
private List<String> ifDomain = new ArrayList<>();
|
||||
private List<String> unlessDomain = new ArrayList<>();
|
||||
private List<String> loadType = new ArrayList<>();
|
||||
private List<String> ifTopUrl = new ArrayList<>();
|
||||
private List<String> unlessTopUrl = new ArrayList<>();
|
||||
|
||||
public ContentBlockerTrigger(@NonNull String urlFilter, @Nullable Boolean urlFilterIsCaseSensitive, @Nullable List<ContentBlockerTriggerResourceType> resourceType,
|
||||
@Nullable List<String> ifDomain, @Nullable List<String> unlessDomain, @Nullable List<String> loadType,
|
||||
@Nullable List<String> ifTopUrl, @Nullable List<String> unlessTopUrl) {
|
||||
this.urlFilterIsCaseSensitive = urlFilterIsCaseSensitive != null ? urlFilterIsCaseSensitive : false;
|
||||
|
||||
this.urlFilter = urlFilter;
|
||||
this.urlFilterPatternCompiled = Pattern.compile(this.urlFilter, this.urlFilterIsCaseSensitive ? 0 : Pattern.CASE_INSENSITIVE);
|
||||
|
||||
this.resourceType = resourceType != null ? resourceType : this.resourceType;
|
||||
this.ifDomain = ifDomain != null ? ifDomain : this.ifDomain;
|
||||
this.unlessDomain = unlessDomain != null ? unlessDomain : this.unlessDomain;
|
||||
if ((!(this.ifDomain.isEmpty() || this.unlessDomain.isEmpty()) != false))
|
||||
throw new AssertionError();
|
||||
this.loadType = loadType != null ? loadType : this.loadType;
|
||||
if ((this.loadType.size() > 2)) throw new AssertionError();
|
||||
this.ifTopUrl = ifTopUrl != null ? ifTopUrl : this.ifTopUrl;
|
||||
this.unlessTopUrl = unlessTopUrl != null ? unlessTopUrl : this.unlessTopUrl;
|
||||
if ((!(this.ifTopUrl.isEmpty() || this.unlessTopUrl.isEmpty()) != false))
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static ContentBlockerTrigger fromMap(Map<String, Object> map) {
|
||||
String urlFilter = (String) map.get("url-filter");
|
||||
Boolean urlFilterIsCaseSensitive = (Boolean) map.get("url-filter-is-case-sensitive");
|
||||
List<String> resourceTypeStringList = (List<String>) map.get("resource-type");
|
||||
List<ContentBlockerTriggerResourceType> resourceType = new ArrayList<>();
|
||||
if (resourceTypeStringList != null) {
|
||||
for (String type : resourceTypeStringList) {
|
||||
resourceType.add(ContentBlockerTriggerResourceType.fromValue(type));
|
||||
}
|
||||
} else {
|
||||
resourceType.addAll(Arrays.asList(ContentBlockerTriggerResourceType.values()));
|
||||
}
|
||||
List<String> ifDomain = (List<String>) map.get("if-domain");
|
||||
List<String> unlessDomain = (List<String>) map.get("unless-domain");
|
||||
List<String> loadType = (List<String>) map.get("load-type");
|
||||
List<String> ifTopUrl = (List<String>) map.get("if-top-url");
|
||||
List<String> unlessTopUrl = (List<String>) map.get("unless-top-url");
|
||||
return new ContentBlockerTrigger(urlFilter, urlFilterIsCaseSensitive, resourceType, ifDomain, unlessDomain, loadType, ifTopUrl, unlessTopUrl);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getUrlFilter() {
|
||||
return urlFilter;
|
||||
}
|
||||
|
||||
public void setUrlFilter(@NonNull String urlFilter) {
|
||||
this.urlFilter = urlFilter;
|
||||
}
|
||||
|
||||
public Pattern getUrlFilterPatternCompiled() {
|
||||
return urlFilterPatternCompiled;
|
||||
}
|
||||
|
||||
public void setUrlFilterPatternCompiled(Pattern urlFilterPatternCompiled) {
|
||||
this.urlFilterPatternCompiled = urlFilterPatternCompiled;
|
||||
}
|
||||
|
||||
public Boolean getUrlFilterIsCaseSensitive() {
|
||||
return urlFilterIsCaseSensitive;
|
||||
}
|
||||
|
||||
public void setUrlFilterIsCaseSensitive(Boolean urlFilterIsCaseSensitive) {
|
||||
this.urlFilterIsCaseSensitive = urlFilterIsCaseSensitive;
|
||||
}
|
||||
|
||||
public List<ContentBlockerTriggerResourceType> getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
public void setResourceType(List<ContentBlockerTriggerResourceType> resourceType) {
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
public List<String> getIfDomain() {
|
||||
return ifDomain;
|
||||
}
|
||||
|
||||
public void setIfDomain(List<String> ifDomain) {
|
||||
this.ifDomain = ifDomain;
|
||||
}
|
||||
|
||||
public List<String> getUnlessDomain() {
|
||||
return unlessDomain;
|
||||
}
|
||||
|
||||
public void setUnlessDomain(List<String> unlessDomain) {
|
||||
this.unlessDomain = unlessDomain;
|
||||
}
|
||||
|
||||
public List<String> getLoadType() {
|
||||
return loadType;
|
||||
}
|
||||
|
||||
public void setLoadType(List<String> loadType) {
|
||||
this.loadType = loadType;
|
||||
}
|
||||
|
||||
public List<String> getIfTopUrl() {
|
||||
return ifTopUrl;
|
||||
}
|
||||
|
||||
public void setIfTopUrl(List<String> ifTopUrl) {
|
||||
this.ifTopUrl = ifTopUrl;
|
||||
}
|
||||
|
||||
public List<String> getUnlessTopUrl() {
|
||||
return unlessTopUrl;
|
||||
}
|
||||
|
||||
public void setUnlessTopUrl(List<String> unlessTopUrl) {
|
||||
this.unlessTopUrl = unlessTopUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ContentBlockerTrigger that = (ContentBlockerTrigger) o;
|
||||
|
||||
if (!urlFilter.equals(that.urlFilter)) return false;
|
||||
if (!urlFilterPatternCompiled.equals(that.urlFilterPatternCompiled)) return false;
|
||||
if (!urlFilterIsCaseSensitive.equals(that.urlFilterIsCaseSensitive)) return false;
|
||||
if (!resourceType.equals(that.resourceType)) return false;
|
||||
if (!ifDomain.equals(that.ifDomain)) return false;
|
||||
if (!unlessDomain.equals(that.unlessDomain)) return false;
|
||||
if (!loadType.equals(that.loadType)) return false;
|
||||
if (!ifTopUrl.equals(that.ifTopUrl)) return false;
|
||||
return unlessTopUrl.equals(that.unlessTopUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = urlFilter.hashCode();
|
||||
result = 31 * result + urlFilterPatternCompiled.hashCode();
|
||||
result = 31 * result + urlFilterIsCaseSensitive.hashCode();
|
||||
result = 31 * result + resourceType.hashCode();
|
||||
result = 31 * result + ifDomain.hashCode();
|
||||
result = 31 * result + unlessDomain.hashCode();
|
||||
result = 31 * result + loadType.hashCode();
|
||||
result = 31 * result + ifTopUrl.hashCode();
|
||||
result = 31 * result + unlessTopUrl.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ContentBlockerTrigger{" +
|
||||
"urlFilter='" + urlFilter + '\'' +
|
||||
", urlFilterPatternCompiled=" + urlFilterPatternCompiled +
|
||||
", urlFilterIsCaseSensitive=" + urlFilterIsCaseSensitive +
|
||||
", resourceType=" + resourceType +
|
||||
", ifDomain=" + ifDomain +
|
||||
", unlessDomain=" + unlessDomain +
|
||||
", loadType=" + loadType +
|
||||
", ifTopUrl=" + ifTopUrl +
|
||||
", unlessTopUrl=" + unlessTopUrl +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.content_blocker;
|
||||
|
||||
public enum ContentBlockerTriggerResourceType {
|
||||
DOCUMENT ("document"),
|
||||
IMAGE ("image"),
|
||||
STYLE_SHEET ("style-sheet"),
|
||||
SCRIPT ("script"),
|
||||
FONT ("font"),
|
||||
SVG_DOCUMENT ("svg-document"),
|
||||
MEDIA ("media"),
|
||||
POPUP ("popup"),
|
||||
RAW ("raw");
|
||||
|
||||
private final String value;
|
||||
|
||||
private ContentBlockerTriggerResourceType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean equalsValue(String otherValue) {
|
||||
return value.equals(otherValue);
|
||||
}
|
||||
|
||||
public static ContentBlockerTriggerResourceType fromValue(String value) {
|
||||
for( ContentBlockerTriggerResourceType type : ContentBlockerTriggerResourceType.values()) {
|
||||
if(value.equals(type.value))
|
||||
return type;
|
||||
}
|
||||
throw new IllegalArgumentException("No enum constant: " + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.URLCredential;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.URLProtectionSpace;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CredentialDatabase {
|
||||
|
||||
private static CredentialDatabase instance;
|
||||
static final String LOG_TAG = "CredentialDatabase";
|
||||
|
||||
// If you change the database schema, you must increment the database version.
|
||||
public static final int DATABASE_VERSION = 2;
|
||||
public static final String DATABASE_NAME = "CredentialDatabase.db";
|
||||
|
||||
public URLProtectionSpaceDao protectionSpaceDao;
|
||||
public URLCredentialDao credentialDao;
|
||||
public CredentialDatabaseHelper db;
|
||||
|
||||
private CredentialDatabase() {}
|
||||
|
||||
private CredentialDatabase(CredentialDatabaseHelper db, URLProtectionSpaceDao protectionSpaceDao, URLCredentialDao credentialDao) {
|
||||
this.db = db;
|
||||
this.protectionSpaceDao = protectionSpaceDao;
|
||||
this.credentialDao = credentialDao;
|
||||
}
|
||||
|
||||
public static CredentialDatabase getInstance(Context context) {
|
||||
if (instance != null)
|
||||
return instance;
|
||||
CredentialDatabaseHelper db = new CredentialDatabaseHelper(context);
|
||||
instance = new CredentialDatabase(db, new URLProtectionSpaceDao(db), new URLCredentialDao(db));
|
||||
return instance;
|
||||
}
|
||||
|
||||
public List<URLCredential> getHttpAuthCredentials(String host, String protocol, String realm, Integer port) {
|
||||
List<URLCredential> credentials = new ArrayList<>();
|
||||
URLProtectionSpace protectionSpace = protectionSpaceDao.find(host, protocol, realm, port);
|
||||
if (protectionSpace != null) {
|
||||
credentials = credentialDao.getAllByProtectionSpaceId(protectionSpace.getId());
|
||||
}
|
||||
return credentials;
|
||||
}
|
||||
|
||||
public void clearAllAuthCredentials() {
|
||||
db.clearAllTables(db.getWritableDatabase());
|
||||
}
|
||||
|
||||
public void removeHttpAuthCredentials(String host, String protocol, String realm, Integer port) {
|
||||
URLProtectionSpace URLProtectionSpace = protectionSpaceDao.find(host, protocol, realm, port);
|
||||
if (URLProtectionSpace != null) {
|
||||
protectionSpaceDao.delete(URLProtectionSpace);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeHttpAuthCredential(String host, String protocol, String realm, Integer port, String username, String password) {
|
||||
URLProtectionSpace protectionSpace = protectionSpaceDao.find(host, protocol, realm, port);
|
||||
if (protectionSpace != null) {
|
||||
URLCredential credential = credentialDao.find(username, password, protectionSpace.getId());
|
||||
credentialDao.delete(credential);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHttpAuthCredential(String host, String protocol, String realm, Integer port, String username, String password) {
|
||||
URLProtectionSpace protectionSpace = protectionSpaceDao.find(host, protocol, realm, port);
|
||||
Long protectionSpaceId;
|
||||
if (protectionSpace == null) {
|
||||
protectionSpaceId = protectionSpaceDao.insert(new URLProtectionSpace(null, host, protocol, realm, port));
|
||||
} else {
|
||||
protectionSpaceId = protectionSpace.getId();
|
||||
}
|
||||
|
||||
URLCredential credential = credentialDao.find(username, password, protectionSpaceId);
|
||||
if (credential != null) {
|
||||
boolean needUpdate = false;
|
||||
if (!credential.getUsername().equals(username)) {
|
||||
credential.setUsername(username);
|
||||
needUpdate = true;
|
||||
}
|
||||
if (!credential.getPassword().equals(password)) {
|
||||
credential.setPassword(password);
|
||||
needUpdate = true;
|
||||
}
|
||||
if (needUpdate)
|
||||
credentialDao.update(credential);
|
||||
} else {
|
||||
credential = new URLCredential(null, username, password, protectionSpaceId);
|
||||
credential.setId(credentialDao.insert(credential));
|
||||
}
|
||||
}
|
||||
}
|
||||
-157
@@ -1,157 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
|
||||
|
||||
import android.os.Build;
|
||||
import android.webkit.WebViewDatabase;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.URLCredential;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.URLProtectionSpace;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class CredentialDatabaseHandler extends ChannelDelegateImpl {
|
||||
protected static final String LOG_TAG = "CredentialDatabaseHandler";
|
||||
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_credential_database";
|
||||
|
||||
@Nullable
|
||||
public static CredentialDatabase credentialDatabase;
|
||||
@Nullable
|
||||
public InAppWebViewFlutterPlugin plugin;
|
||||
|
||||
public CredentialDatabaseHandler(@NonNull final InAppWebViewFlutterPlugin plugin) {
|
||||
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public static void init(@NonNull InAppWebViewFlutterPlugin plugin) {
|
||||
if (credentialDatabase == null) {
|
||||
credentialDatabase = CredentialDatabase.getInstance(plugin.applicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
if (plugin != null) {
|
||||
init(plugin);
|
||||
}
|
||||
|
||||
switch (call.method) {
|
||||
case "getAllAuthCredentials":
|
||||
{
|
||||
List<Map<String, Object>> allCredentials = new ArrayList<>();
|
||||
if (credentialDatabase != null) {
|
||||
List<URLProtectionSpace> protectionSpaces = credentialDatabase.protectionSpaceDao.getAll();
|
||||
for (URLProtectionSpace protectionSpace : protectionSpaces) {
|
||||
List<Map<String, Object>> credentials = new ArrayList<>();
|
||||
for (URLCredential credential : credentialDatabase.credentialDao.getAllByProtectionSpaceId(protectionSpace.getId())) {
|
||||
credentials.add(credential.toMap());
|
||||
}
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("protectionSpace", protectionSpace.toMap());
|
||||
obj.put("credentials", credentials);
|
||||
allCredentials.add(obj);
|
||||
}
|
||||
}
|
||||
result.success(allCredentials);
|
||||
}
|
||||
break;
|
||||
case "getHttpAuthCredentials":
|
||||
{
|
||||
List<Map<String, Object>> credentials = new ArrayList<>();
|
||||
if (credentialDatabase != null) {
|
||||
String host = (String) call.argument("host");
|
||||
String protocol = (String) call.argument("protocol");
|
||||
String realm = (String) call.argument("realm");
|
||||
Integer port = (Integer) call.argument("port");
|
||||
|
||||
for (URLCredential credential : credentialDatabase.getHttpAuthCredentials(host, protocol, realm, port)) {
|
||||
credentials.add(credential.toMap());
|
||||
}
|
||||
}
|
||||
result.success(credentials);
|
||||
}
|
||||
break;
|
||||
case "setHttpAuthCredential":
|
||||
{
|
||||
if (credentialDatabase != null) {
|
||||
String host = (String) call.argument("host");
|
||||
String protocol = (String) call.argument("protocol");
|
||||
String realm = (String) call.argument("realm");
|
||||
Integer port = (Integer) call.argument("port");
|
||||
String username = (String) call.argument("username");
|
||||
String password = (String) call.argument("password");
|
||||
|
||||
credentialDatabase.setHttpAuthCredential(host, protocol, realm, port, username, password);
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "removeHttpAuthCredential":
|
||||
{
|
||||
if (credentialDatabase != null) {
|
||||
String host = (String) call.argument("host");
|
||||
String protocol = (String) call.argument("protocol");
|
||||
String realm = (String) call.argument("realm");
|
||||
Integer port = (Integer) call.argument("port");
|
||||
String username = (String) call.argument("username");
|
||||
String password = (String) call.argument("password");
|
||||
|
||||
credentialDatabase.removeHttpAuthCredential(host, protocol, realm, port, username, password);
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "removeHttpAuthCredentials":
|
||||
{
|
||||
if (credentialDatabase != null) {
|
||||
String host = (String) call.argument("host");
|
||||
String protocol = (String) call.argument("protocol");
|
||||
String realm = (String) call.argument("realm");
|
||||
Integer port = (Integer) call.argument("port");
|
||||
|
||||
credentialDatabase.removeHttpAuthCredentials(host, protocol, realm, port);
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "clearAllAuthCredentials":
|
||||
if (credentialDatabase != null) {
|
||||
credentialDatabase.clearAllAuthCredentials();
|
||||
if (plugin != null && plugin.applicationContext != null) {
|
||||
WebViewDatabase.getInstance(plugin.applicationContext).clearHttpAuthUsernamePassword();
|
||||
}
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
plugin = null;
|
||||
credentialDatabase = null;
|
||||
}
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
public class CredentialDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String SQL_CREATE_PROTECTION_SPACE_TABLE =
|
||||
"CREATE TABLE " + URLProtectionSpaceContract.FeedEntry.TABLE_NAME + " (" +
|
||||
URLProtectionSpaceContract.FeedEntry._ID + " INTEGER PRIMARY KEY," +
|
||||
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST + " TEXT NOT NULL," +
|
||||
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL + " TEXT," +
|
||||
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM + " TEXT," +
|
||||
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT + " INTEGER," +
|
||||
"UNIQUE(" + URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST + ", " + URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL + ", " +
|
||||
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM + ", " + URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT +
|
||||
")" +
|
||||
");";
|
||||
|
||||
private static final String SQL_CREATE_CREDENTIAL_TABLE =
|
||||
"CREATE TABLE " + URLCredentialContract.FeedEntry.TABLE_NAME + " (" +
|
||||
URLCredentialContract.FeedEntry._ID + " INTEGER PRIMARY KEY," +
|
||||
URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME + " TEXT NOT NULL," +
|
||||
URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD + " TEXT NOT NULL," +
|
||||
URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID + " INTEGER NOT NULL," +
|
||||
"UNIQUE(" + URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME + ", " + URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD + ", " +
|
||||
URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID +
|
||||
")," +
|
||||
"FOREIGN KEY (" + URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID + ") REFERENCES " +
|
||||
URLProtectionSpaceContract.FeedEntry.TABLE_NAME + " (" + URLProtectionSpaceContract.FeedEntry._ID + ") ON DELETE CASCADE" +
|
||||
");";
|
||||
|
||||
private static final String SQL_DELETE_PROTECTION_SPACE_TABLE =
|
||||
"DROP TABLE IF EXISTS " + URLProtectionSpaceContract.FeedEntry.TABLE_NAME;
|
||||
|
||||
private static final String SQL_DELETE_CREDENTIAL_TABLE =
|
||||
"DROP TABLE IF EXISTS " + URLCredentialContract.FeedEntry.TABLE_NAME;
|
||||
|
||||
public CredentialDatabaseHelper(Context context) {
|
||||
super(context, CredentialDatabase.DATABASE_NAME, null, CredentialDatabase.DATABASE_VERSION);
|
||||
}
|
||||
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(SQL_CREATE_PROTECTION_SPACE_TABLE);
|
||||
db.execSQL(SQL_CREATE_CREDENTIAL_TABLE);
|
||||
}
|
||||
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
// This database is only a cache for online data, so its upgrade policy is
|
||||
// to simply to discard the data and start over
|
||||
db.execSQL(SQL_DELETE_PROTECTION_SPACE_TABLE);
|
||||
db.execSQL(SQL_DELETE_CREDENTIAL_TABLE);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
onUpgrade(db, oldVersion, newVersion);
|
||||
}
|
||||
|
||||
public void clearAllTables(SQLiteDatabase db) {
|
||||
db.execSQL(SQL_DELETE_PROTECTION_SPACE_TABLE);
|
||||
db.execSQL(SQL_DELETE_CREDENTIAL_TABLE);
|
||||
onCreate(db);
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
public class URLCredentialContract {
|
||||
private URLCredentialContract() {}
|
||||
|
||||
/* Inner class that defines the table contents */
|
||||
public static class FeedEntry implements BaseColumns {
|
||||
public static final String TABLE_NAME = "credential";
|
||||
public static final String COLUMN_NAME_USERNAME = "username";
|
||||
public static final String COLUMN_NAME_PASSWORD = "password";
|
||||
public static final String COLUMN_NAME_PROTECTION_SPACE_ID = "protection_space_id";
|
||||
}
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.URLCredential;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class URLCredentialDao {
|
||||
|
||||
CredentialDatabaseHelper credentialDatabaseHelper;
|
||||
String[] projection = {
|
||||
URLCredentialContract.FeedEntry._ID,
|
||||
URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME,
|
||||
URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD,
|
||||
URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID
|
||||
};
|
||||
|
||||
public URLCredentialDao(CredentialDatabaseHelper credentialDatabaseHelper) {
|
||||
this.credentialDatabaseHelper = credentialDatabaseHelper;
|
||||
}
|
||||
|
||||
public List<URLCredential> getAllByProtectionSpaceId(Long protectionSpaceId) {
|
||||
String selection = URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID + " = ?";
|
||||
String[] selectionArgs = {protectionSpaceId.toString()};
|
||||
|
||||
Cursor cursor = credentialDatabaseHelper.getReadableDatabase().query(
|
||||
URLCredentialContract.FeedEntry.TABLE_NAME,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
List<URLCredential> URLCredentials = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
Long id = cursor.getLong(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry._ID));
|
||||
String username = cursor.getString(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME));
|
||||
String password = cursor.getString(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD));
|
||||
URLCredentials.add(new URLCredential(id, username, password, protectionSpaceId));
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
return URLCredentials;
|
||||
}
|
||||
|
||||
public URLCredential find(String username, String password, Long protectionSpaceId) {
|
||||
String selection = URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME + " = ? AND " +
|
||||
URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD + " = ? AND " +
|
||||
URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID + " = ?";
|
||||
String[] selectionArgs = {username, password, protectionSpaceId.toString()};
|
||||
|
||||
Cursor cursor = credentialDatabaseHelper.getReadableDatabase().query(
|
||||
URLCredentialContract.FeedEntry.TABLE_NAME,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
URLCredential URLCredential = null;
|
||||
if (cursor.moveToNext()) {
|
||||
Long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry._ID));
|
||||
String rowUsername = cursor.getString(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME));
|
||||
String rowPassword = cursor.getString(cursor.getColumnIndexOrThrow(URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD));
|
||||
URLCredential = new URLCredential(rowId, rowUsername, rowPassword, protectionSpaceId);
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
return URLCredential;
|
||||
}
|
||||
|
||||
public long insert(URLCredential urlCredential) {
|
||||
ContentValues credentialValues = new ContentValues();
|
||||
credentialValues.put(URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME, urlCredential.getUsername());
|
||||
credentialValues.put(URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD, urlCredential.getPassword());
|
||||
credentialValues.put(URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID, urlCredential.getProtectionSpaceId());
|
||||
|
||||
return credentialDatabaseHelper.getWritableDatabase().insert(URLCredentialContract.FeedEntry.TABLE_NAME, null, credentialValues);
|
||||
}
|
||||
|
||||
public long update(URLCredential urlCredential) {
|
||||
ContentValues credentialValues = new ContentValues();
|
||||
credentialValues.put(URLCredentialContract.FeedEntry.COLUMN_NAME_USERNAME, urlCredential.getUsername());
|
||||
credentialValues.put(URLCredentialContract.FeedEntry.COLUMN_NAME_PASSWORD, urlCredential.getPassword());
|
||||
|
||||
String whereClause = URLCredentialContract.FeedEntry.COLUMN_NAME_PROTECTION_SPACE_ID + " = ?";
|
||||
String[] whereArgs = {urlCredential.getProtectionSpaceId().toString()};
|
||||
|
||||
return credentialDatabaseHelper.getWritableDatabase().update(URLCredentialContract.FeedEntry.TABLE_NAME, credentialValues, whereClause, whereArgs);
|
||||
}
|
||||
|
||||
public long delete(URLCredential urlCredential) {
|
||||
String whereClause = URLCredentialContract.FeedEntry._ID + " = ?";
|
||||
String[] whereArgs = {urlCredential.getId().toString()};
|
||||
|
||||
return credentialDatabaseHelper.getWritableDatabase().delete(URLCredentialContract.FeedEntry.TABLE_NAME, whereClause, whereArgs);
|
||||
}
|
||||
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
public class URLProtectionSpaceContract {
|
||||
private URLProtectionSpaceContract() {}
|
||||
|
||||
/* Inner class that defines the table contents */
|
||||
public static class FeedEntry implements BaseColumns {
|
||||
public static final String TABLE_NAME = "protection_space";
|
||||
public static final String COLUMN_NAME_HOST = "host";
|
||||
public static final String COLUMN_NAME_PROTOCOL = "protocol";
|
||||
public static final String COLUMN_NAME_REALM = "realm";
|
||||
public static final String COLUMN_NAME_PORT = "port";
|
||||
}
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.credential_database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.URLProtectionSpace;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class URLProtectionSpaceDao {
|
||||
CredentialDatabaseHelper credentialDatabaseHelper;
|
||||
String[] projection = {
|
||||
URLProtectionSpaceContract.FeedEntry._ID,
|
||||
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST,
|
||||
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL,
|
||||
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM,
|
||||
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT
|
||||
};
|
||||
|
||||
public URLProtectionSpaceDao(CredentialDatabaseHelper credentialDatabaseHelper) {
|
||||
this.credentialDatabaseHelper = credentialDatabaseHelper;
|
||||
}
|
||||
|
||||
public List<URLProtectionSpace> getAll() {
|
||||
SQLiteDatabase readableDatabase = credentialDatabaseHelper.getReadableDatabase();
|
||||
|
||||
Cursor cursor = readableDatabase.query(
|
||||
URLProtectionSpaceContract.FeedEntry.TABLE_NAME,
|
||||
projection,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
List<URLProtectionSpace> URLProtectionSpaces = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
Long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry._ID));
|
||||
String rowHost = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST));
|
||||
String rowProtocol = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL));
|
||||
String rowRealm = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM));
|
||||
Integer rowPort = cursor.getInt(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT));
|
||||
URLProtectionSpaces.add(new URLProtectionSpace(rowId, rowHost, rowProtocol, rowRealm, rowPort));
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
return URLProtectionSpaces;
|
||||
}
|
||||
|
||||
public URLProtectionSpace find(String host, String protocol, String realm, Integer port) {
|
||||
SQLiteDatabase readableDatabase = credentialDatabaseHelper.getReadableDatabase();
|
||||
|
||||
String selection = URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST + " = ? AND " + URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL + " = ? AND " +
|
||||
URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM + " = ? AND " + URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT + " = ?";
|
||||
String[] selectionArgs = {host, protocol, realm, port.toString()};
|
||||
|
||||
Cursor cursor = readableDatabase.query(
|
||||
URLProtectionSpaceContract.FeedEntry.TABLE_NAME,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
URLProtectionSpace URLProtectionSpace = null;
|
||||
if (cursor.moveToNext()) {
|
||||
Long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry._ID));
|
||||
String rowHost = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST));
|
||||
String rowProtocol = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL));
|
||||
String rowRealm = cursor.getString(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM));
|
||||
Integer rowPort = cursor.getInt(cursor.getColumnIndexOrThrow(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT));
|
||||
URLProtectionSpace = new URLProtectionSpace(rowId, rowHost, rowProtocol, rowRealm, rowPort);
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
return URLProtectionSpace;
|
||||
}
|
||||
|
||||
public long insert(URLProtectionSpace URLProtectionSpace) {
|
||||
ContentValues protectionSpaceValues = new ContentValues();
|
||||
protectionSpaceValues.put(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_HOST, URLProtectionSpace.getHost());
|
||||
protectionSpaceValues.put(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PROTOCOL, URLProtectionSpace.getProtocol());
|
||||
protectionSpaceValues.put(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_REALM, URLProtectionSpace.getRealm());
|
||||
protectionSpaceValues.put(URLProtectionSpaceContract.FeedEntry.COLUMN_NAME_PORT, URLProtectionSpace.getPort());
|
||||
|
||||
return credentialDatabaseHelper.getWritableDatabase().insert(URLProtectionSpaceContract.FeedEntry.TABLE_NAME, null, protectionSpaceValues);
|
||||
};
|
||||
|
||||
public long delete(URLProtectionSpace URLProtectionSpace) {
|
||||
String whereClause = URLProtectionSpaceContract.FeedEntry._ID + " = ?";
|
||||
String[] whereArgs = {URLProtectionSpace.getId().toString()};
|
||||
|
||||
return credentialDatabaseHelper.getWritableDatabase().delete(URLProtectionSpaceContract.FeedEntry.TABLE_NAME, whereClause, whereArgs);
|
||||
}
|
||||
}
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.find_interaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.ChannelDelegateImpl;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.FindSession;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class FindInteractionChannelDelegate extends ChannelDelegateImpl {
|
||||
@Nullable
|
||||
private FindInteractionController findInteractionController;
|
||||
|
||||
public FindInteractionChannelDelegate(@NonNull FindInteractionController findInteractionController, @NonNull MethodChannel channel) {
|
||||
super(channel);
|
||||
this.findInteractionController = findInteractionController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) {
|
||||
switch (call.method) {
|
||||
case "findAll":
|
||||
if (findInteractionController != null) {
|
||||
String find = (String) call.argument("find");
|
||||
findInteractionController.findAll(find);
|
||||
}
|
||||
result.success(true);
|
||||
break;
|
||||
case "findNext":
|
||||
if (findInteractionController != null) {
|
||||
Boolean forward = (Boolean) call.argument("forward");
|
||||
findInteractionController.findNext(forward);
|
||||
}
|
||||
result.success(true);
|
||||
break;
|
||||
case "clearMatches":
|
||||
if (findInteractionController != null) {
|
||||
findInteractionController.clearMatches();
|
||||
}
|
||||
result.success(true);
|
||||
break;
|
||||
case "setSearchText":
|
||||
if (findInteractionController != null) {
|
||||
findInteractionController.searchText = (String) call.argument("searchText");
|
||||
result.success(true);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "getSearchText":
|
||||
if (findInteractionController != null) {
|
||||
result.success(findInteractionController.searchText);
|
||||
} else {
|
||||
result.success(false);
|
||||
}
|
||||
break;
|
||||
case "getActiveFindSession":
|
||||
if (findInteractionController != null && findInteractionController.activeFindSession != null) {
|
||||
result.success(findInteractionController.activeFindSession.toMap());
|
||||
} else {
|
||||
result.success(null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
|
||||
MethodChannel channel = getChannel();
|
||||
if (channel == null) return;
|
||||
|
||||
if (isDoneCounting && findInteractionController != null && findInteractionController.webView != null) {
|
||||
findInteractionController.activeFindSession = new FindSession(numberOfMatches, activeMatchOrdinal);
|
||||
}
|
||||
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
obj.put("activeMatchOrdinal", activeMatchOrdinal);
|
||||
obj.put("numberOfMatches", numberOfMatches);
|
||||
obj.put("isDoneCounting", isDoneCounting);
|
||||
channel.invokeMethod("onFindResultReceived", obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
findInteractionController = null;
|
||||
}
|
||||
}
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.find_interaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.Disposable;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.types.FindSession;
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.webview.InAppWebViewInterface;
|
||||
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class FindInteractionController implements Disposable {
|
||||
static final String LOG_TAG = "FindInteractionController";
|
||||
public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_";
|
||||
|
||||
@Nullable
|
||||
public InAppWebViewInterface webView;
|
||||
@Nullable
|
||||
public FindSession activeFindSession;
|
||||
@Nullable
|
||||
public FindInteractionChannelDelegate channelDelegate;
|
||||
@Nullable
|
||||
public FindInteractionSettings settings;
|
||||
@Nullable
|
||||
public String searchText;
|
||||
|
||||
public FindInteractionController(@NonNull InAppWebViewInterface webView, @NonNull InAppWebViewFlutterPlugin plugin,
|
||||
@NonNull Object id, @Nullable FindInteractionSettings settings) {
|
||||
this.webView = webView;
|
||||
this.settings = settings;
|
||||
final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
|
||||
this.channelDelegate = new FindInteractionChannelDelegate(this, channel);
|
||||
}
|
||||
|
||||
public void prepare() {
|
||||
|
||||
}
|
||||
|
||||
public void findAll(@Nullable String find) {
|
||||
if (find == null) {
|
||||
find = searchText;
|
||||
} else {
|
||||
// updated searchText
|
||||
searchText = find;
|
||||
}
|
||||
if (webView != null && find != null) {
|
||||
webView.findAllAsync(find);
|
||||
}
|
||||
}
|
||||
|
||||
public void findNext(boolean forward) {
|
||||
if (webView != null) {
|
||||
webView.findNext(forward);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearMatches() {
|
||||
if (webView != null) {
|
||||
webView.clearMatches();
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (channelDelegate != null) {
|
||||
channelDelegate.dispose();
|
||||
channelDelegate = null;
|
||||
}
|
||||
webView = null;
|
||||
activeFindSession = null;
|
||||
searchText = null;
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package com.pichillilorenzo.flutter_inappwebview_android.find_interaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.pichillilorenzo.flutter_inappwebview_android.ISettings;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FindInteractionSettings implements ISettings<FindInteractionController> {
|
||||
public static final String LOG_TAG = "FindInteractionSettings";
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FindInteractionSettings parse(@NonNull Map<String, Object> settings) {
|
||||
// for (Map.Entry<String, Object> pair : settings.entrySet()) {
|
||||
// String key = pair.getKey();
|
||||
// Object value = pair.getValue();
|
||||
// if (value == null) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// switch (key) {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> settings = new HashMap<>();
|
||||
return settings;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Map<String, Object> getRealSettings(@NonNull FindInteractionController findInteractionController) {
|
||||
Map<String, Object> realSettings = toMap();
|
||||
return realSettings;
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 50 of 534 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user