JonaNorman vor 1 Jahr
Ursprung
Commit
9ca620f967
72 geänderte Dateien mit 4238 neuen und 0 gelöschten Zeilen
  1. 1 0
      README.md
  2. 87 0
      app/.gitignore
  3. 47 0
      app/build.gradle
  4. 0 0
      app/libs/arm64-v8a/empty.so
  5. 0 0
      app/libs/armeabi-v7a/empty.so
  6. 32 0
      app/proguard-rules.pro
  7. 31 0
      app/src/main/AndroidManifest.xml
  8. 165 0
      app/src/main/assets/aria_config.xml
  9. 250 0
      app/src/main/java/com/norman/webviewup/demo/DownloadSinkImpl.java
  10. 202 0
      app/src/main/java/com/norman/webviewup/demo/MainActivity.java
  11. 14 0
      app/src/main/java/com/norman/webviewup/demo/MyApp.java
  12. 108 0
      app/src/main/java/com/norman/webviewup/demo/WebViewActivity.java
  13. 10 0
      app/src/main/java/com/norman/webviewup/lib/UpgradeCallback.java
  14. 83 0
      app/src/main/java/com/norman/webviewup/lib/UpgradeOptions.java
  15. 457 0
      app/src/main/java/com/norman/webviewup/lib/WebViewUpgrade.java
  16. 38 0
      app/src/main/java/com/norman/webviewup/lib/download/DownloadAction.java
  17. 7 0
      app/src/main/java/com/norman/webviewup/lib/download/DownloaderSink.java
  18. 169 0
      app/src/main/java/com/norman/webviewup/lib/hook/PackageManagerHook.java
  19. 96 0
      app/src/main/java/com/norman/webviewup/lib/hook/WebViewUpdateServiceHook.java
  20. 96 0
      app/src/main/java/com/norman/webviewup/lib/reflect/ReflectConstructor.java
  21. 35 0
      app/src/main/java/com/norman/webviewup/lib/reflect/ReflectException.java
  22. 180 0
      app/src/main/java/com/norman/webviewup/lib/reflect/ReflectField.java
  23. 154 0
      app/src/main/java/com/norman/webviewup/lib/reflect/ReflectMethod.java
  24. 311 0
      app/src/main/java/com/norman/webviewup/lib/reflect/ReflectProxy.java
  25. 221 0
      app/src/main/java/com/norman/webviewup/lib/reflect/RuntimeAccess.java
  26. 122 0
      app/src/main/java/com/norman/webviewup/lib/reflect/RuntimeProxy.java
  27. 15 0
      app/src/main/java/com/norman/webviewup/lib/reflect/annotation/ClassName.java
  28. 15 0
      app/src/main/java/com/norman/webviewup/lib/reflect/annotation/ClassType.java
  29. 12 0
      app/src/main/java/com/norman/webviewup/lib/reflect/annotation/Constructor.java
  30. 19 0
      app/src/main/java/com/norman/webviewup/lib/reflect/annotation/Field.java
  31. 21 0
      app/src/main/java/com/norman/webviewup/lib/reflect/annotation/Method.java
  32. 13 0
      app/src/main/java/com/norman/webviewup/lib/reflect/annotation/ParameterName.java
  33. 13 0
      app/src/main/java/com/norman/webviewup/lib/reflect/annotation/ParameterType.java
  34. 68 0
      app/src/main/java/com/norman/webviewup/lib/service/binder/BinderHook.java
  35. 83 0
      app/src/main/java/com/norman/webviewup/lib/service/binder/ProxyBinder.java
  36. 16 0
      app/src/main/java/com/norman/webviewup/lib/service/interfaces/IActivityThread.java
  37. 12 0
      app/src/main/java/com/norman/webviewup/lib/service/interfaces/IContextImpl.java
  38. 14 0
      app/src/main/java/com/norman/webviewup/lib/service/interfaces/IPackageManager.java
  39. 19 0
      app/src/main/java/com/norman/webviewup/lib/service/interfaces/IServiceManager.java
  40. 16 0
      app/src/main/java/com/norman/webviewup/lib/service/interfaces/IVMRuntime.java
  41. 18 0
      app/src/main/java/com/norman/webviewup/lib/service/interfaces/IWebViewFactory.java
  42. 18 0
      app/src/main/java/com/norman/webviewup/lib/service/interfaces/IWebViewProviderResponse.java
  43. 20 0
      app/src/main/java/com/norman/webviewup/lib/service/interfaces/IWebViewUpdateService.java
  44. 40 0
      app/src/main/java/com/norman/webviewup/lib/service/proxy/PackageManagerProxy.java
  45. 25 0
      app/src/main/java/com/norman/webviewup/lib/service/proxy/WebViewUpdateServiceProxy.java
  46. 116 0
      app/src/main/java/com/norman/webviewup/lib/util/ApkUtils.java
  47. 42 0
      app/src/main/java/com/norman/webviewup/lib/util/FileUtils.java
  48. 26 0
      app/src/main/java/com/norman/webviewup/lib/util/ProcessUtils.java
  49. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  50. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  51. 146 0
      app/src/main/res/layout/activity_main.xml
  52. 12 0
      app/src/main/res/layout/activity_webview.xml
  53. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  54. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  55. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  56. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  57. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  58. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  59. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  60. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  61. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  62. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  63. 10 0
      app/src/main/res/values/colors.xml
  64. 3 0
      app/src/main/res/values/strings.xml
  65. 10 0
      app/src/main/res/values/themes.xml
  66. 20 0
      build.gradle
  67. 17 0
      gradle.properties
  68. BIN
      gradle/wrapper/gradle-wrapper.jar
  69. 6 0
      gradle/wrapper/gradle-wrapper.properties
  70. 172 0
      gradlew
  71. 84 0
      gradlew.bat
  72. 1 0
      settings.gradle

+ 1 - 0
README.md

@@ -0,0 +1 @@
+todo

+ 87 - 0
app/.gitignore

@@ -0,0 +1,87 @@
+# Built application files
+*.apk
+*.aar
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+#  Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+.DS_Store
+*.iml
+.idea/
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+# Android Studio 3 in .gitignore file.
+.idea/caches
+.idea/modules.xml
+# Comment next line if keeping position of elements in Navigation Editor is relevant for you
+.idea/navEditor.xml
+
+# Keystore files
+# Uncomment the following lines if you do not want to check your keystore files in.
+#*.jks
+#*.keystore
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+.cxx/
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+# lint/reports/

+ 47 - 0
app/build.gradle

@@ -0,0 +1,47 @@
+plugins {
+    id 'com.android.application'
+}
+
+android {
+    compileSdkVersion 30
+
+    defaultConfig {
+        applicationId "com.norman.webviewup.demo"
+        minSdkVersion 21
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+
+        ndk {
+            abiFilters "armeabi-v7a"
+//            abiFilters "arm64-v8a", "armeabi-v7a"
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled true
+            shrinkResources true
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+        debug {
+            debuggable true
+            minifyEnabled false
+        }
+    }
+
+    sourceSets {
+        main {
+            jniLibs.srcDirs = ["libs"]
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+
+dependencies {
+    implementation 'androidx.appcompat:appcompat:1.3.0'
+    implementation 'me.laoyuyu.aria:core:3.8.16'
+}

+ 0 - 0
app/libs/arm64-v8a/empty.so


+ 0 - 0
app/libs/armeabi-v7a/empty.so


+ 32 - 0
app/proguard-rules.pro

@@ -0,0 +1,32 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-keep class com.norman.webviewup.lib.reflect.** {*; }
+-keep @com.norman.webviewup.lib.reflect.annotation.ClassName class * {*;}
+-keep @com.norman.webviewup.lib.reflect.annotation.ClassType class * {*;}
+-keepclasseswithmembernames class * {
+    @com.norman.webviewup.lib.reflect.annotation.Constructor.* <methods>;
+    @com.norman.webviewup.lib.reflect.annotation.Field.* <methods>;
+    @com.norman.webviewup.lib.reflect.annotation.Method.* <methods>;
+    @com.norman.webviewup.lib.reflect.annotation.ParameterName.* <methods>;
+    @com.norman.webviewup.lib.reflect.annotation.ParameterType.* <methods>;
+}

+ 31 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.norman.webviewup.demo">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+    <application
+        android:name=".MyApp"
+        android:usesCleartextTraffic="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".MainActivity"
+            android:configChanges="orientation|screenSize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".WebViewActivity"
+            android:configChanges="orientation|screenSize" />
+    </application>
+
+
+</manifest>

+ 165 - 0
app/src/main/assets/aria_config.xml

@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="utf-8"?>
+<aria>
+  <!--注意,修改该配置文件中的属性会覆盖代码中所设置的属性-->
+
+  <!--Aria框架配置-->
+  <app>
+    <!--是否使用AriaCrashHandler来捕获异常,异常日志保存在:/mnt/sdcard/Android/data/{package_name}/files/log/-->
+    <useAriaCrashHandler value="false"/>
+    <!--设置Aria的日志级别,{@link ALog#LOG_LEVEL_VERBOSE}-->
+    <logLevel value="2"/>
+    <!-- 是否检查网络 true: 检查网络,false: 不检查网络-->
+    <netCheck value="true"/>
+    <!--除非无法使用注解,否则不建议使用广播来接受任务状态,true:使用广播接收任务状态,false:不适用广播接收状态 -->
+    <!-- http://aria.laoyuyu.me/aria_doc/api/use_broadcast.html -->
+    <useBroadcast value="false"/>
+    <!--断网的时候是否重试,true:断网也重试;false:断网不重试,直接走失败的回调-->
+    <notNetRetry value="true"/>
+  </app>
+
+
+  <!--普通下载任务-->
+  <download>
+    <!--设置http下载获取文件大小是否使用Head请求。true:使用head请求,false:使用默认的get请求。默认为false-->
+    <!--只适用于3.8.11以上的版本-->
+    <useHeadRequest value="false"/>
+
+    <!--设置任务最大下载速度,0表示不限速,单位为:kb-->
+    <maxSpeed value="0"/>
+
+    <!--
+      多线程下载是否使用块下载模式,{@code true}使用,{@code false}不使用
+      注意:
+        1、使用分块模式,在读写性能底下的手机上,合并文件需要的时间会更加长;
+        2、优点是使用多线程的块下载,初始化时,文件初始化时将不会预占用对应长度的空间;
+        3、只对新的多线程下载任务有效
+        4、只对多线程的任务有效
+    -->
+    <useBlock value="true"/>
+
+    <!--设置下载线程数,下载线程数不能小于1
+      注意:
+      1、线程下载数改变后,新的下载任务才会生效;
+      2、如果任务大小小于1m,该设置不会生效;
+      3、从3.4.1开始,如果线程数为1,文件初始化时将不再预占用对应长度的空间,下载多少byte,则占多大的空间;
+         对于采用多线程的任务或旧任务,依然采用原来的文件空间占用方式;
+    -->
+    <threadNum value="5"/>
+
+    <!--设置下载队列最大任务数, 默认为2-->
+    <maxTaskNum value="1"/>
+
+    <!--设置下载失败,重试次数,默认为10-->
+    <reTryNum value="5"/>
+
+    <!--设置重试间隔,单位为毫秒,默认2000毫秒-->
+    <reTryInterval value="5000"/>
+
+    <!--设置url连接超时时间,单位为毫秒,默认5000毫秒-->
+    <connectTimeOut value="5000"/>
+
+    <!--设置IO流读取时间,单位为毫秒,默认20000毫秒,该时间不能少于10000毫秒-->
+    <iOTimeOut value="10000"/>
+
+    <!--设置写文件buff大小,该数值大小不能小于2048,数值变小,下载速度会变慢-->
+    <buffSize value="8192"/>
+
+    <!--设置https ca 证书信息;path 为assets目录下的CA证书完整路径,name 为CA证书名-->
+    <ca name="" path=""/>
+
+    <!--是否需要转换速度单位,转换完成后为:1b/s、1kb/s、1mb/s、1gb/s、1tb/s,如果不需要将返回byte长度-->
+    <convertSpeed value="true"/>
+
+    <!--执行队列类型,见com.arialyy.aria.core.QueueMod,默认类型为wait-->
+    <queueMod value="now"/>
+
+    <!--进度更新更新间隔,默认1000毫秒-->
+    <updateInterval value="1000"/>
+
+  </download>
+
+  <!--普通上传任务-->
+  <upload>
+    <!--设置任务最大上传速度,0表示不限速,单位为:kb-->
+    <maxSpeed value="0"/>
+
+    <!--设置IO流读取时间,单位为毫秒,默认20000毫秒,该时间不能少于10000毫秒-->
+    <iOTimeOut value="10000"/>
+
+    <!--设置写文件buff大小,该数值大小不能小于2048,数值变小,速度会变慢-->
+    <buffSize value="8192"/>
+
+    <!--是否需要转换速度单位,转换完成后为:1b/s、1kb/s、1mb/s、1gb/s、1tb/s,如果不需要将返回byte长度-->
+    <convertSpeed value="true"/>
+
+    <!--设置上传队列最大任务数, 默认为2-->
+    <maxTaskNum value="2"/>
+
+    <!--设置上传失败,重试次数,默认为10-->
+    <reTryNum value="3"/>
+
+    <!--设置重试间隔,单位为毫秒-->
+    <reTryInterval value="2000"/>
+
+    <!--设置url连接超时时间,单位为毫秒,默认5000毫秒-->
+    <connectTimeOut value="5000"/>
+
+    <!--执行队列类型,见com.arialyy.aria.core.QueueMod,默认类型为wait-->
+    <queueMod value="wait"/>
+
+    <!--进度更新更新间隔,默认1000毫秒-->
+    <updateInterval value="1000"/>
+
+  </upload>
+
+  <!-- 下载类组合任务 -->
+  <dGroup>
+
+    <!--组合任务下载队列最大任务数, 默认为2-->
+    <maxTaskNum value="3"/>
+
+    <!--设置下载失败,重试次数,默认为10-->
+    <reTryNum value="5"/>
+
+    <!--设置重试间隔,单位为毫秒,默认2000毫秒-->
+    <reTryInterval value="5000"/>
+
+    <!--执行队列类型,见com.arialyy.aria.core.QueueMod,默认类型为wait-->
+    <queueMod value="wait"/>
+
+    <!--进度更新更新间隔,默认1000毫秒-->
+    <updateInterval value="1000"/>
+    <!--子任务失败时组任务回调stop,默认true false将回调fail-->
+    <subFailAsStop value="true"/>
+
+    <!-- =============================以下为子任务的配置====================================-->
+
+    <!--能同时下载的子任务最大任务数,默认3-->
+    <subMaxTaskNum value="5"/>
+
+    <!--子任务下载失败时的重试次数,默认为5-->
+    <subReTryNum value="5"/>
+
+    <!--子任务下载失败时的重试间隔,单位为毫秒,默认2000毫秒-->
+    <subReTryInterval value="5000"/>
+
+    <!--子任务url连接超时时间,单位为毫秒,默认5000毫秒-->
+    <connectTimeOut value="5000"/>
+
+    <!--子任务IO流读取时间,单位为毫秒,默认20000毫秒,该时间不能少于10000毫秒-->
+    <iOTimeOut value="10000"/>
+
+    <!--子任务写文件buff大小,该数值大小不能小于2048,数值变小,下载速度会变慢-->
+    <buffSize value="8192"/>
+
+    <!--子任务 https ca 证书信息;path 为assets目录下的CA证书完整路径,name 为CA证书名-->
+    <ca name="" path=""/>
+
+    <!--子任务是否需要转换速度单位,转换完成后为:1b/s、1kb/s、1mb/s、1gb/s、1tb/s,如果不需要将返回byte长度-->
+    <convertSpeed value="true"/>
+
+    <!--子任务的最大下载速度,0表示不限速,单位为:kb; -->
+    <maxSpeed value="0"/>
+
+  </dGroup>
+</aria>

+ 250 - 0
app/src/main/java/com/norman/webviewup/demo/DownloadSinkImpl.java

@@ -0,0 +1,250 @@
+package com.norman.webviewup.demo;
+
+import android.util.Log;
+
+import com.arialyy.aria.core.Aria;
+import com.arialyy.aria.core.download.DownloadEntity;
+import com.arialyy.aria.core.download.DownloadReceiver;
+import com.arialyy.aria.core.download.DownloadTaskListener;
+import com.arialyy.aria.core.inf.IEntity;
+import com.arialyy.aria.core.task.DownloadTask;
+import com.norman.webviewup.lib.download.DownloadAction;
+import com.norman.webviewup.lib.download.DownloaderSink;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class DownloadSinkImpl implements DownloaderSink {
+
+    private final List<DownloadActionImpl> downloadActionList = new ArrayList<>();
+
+    @Override
+    public synchronized DownloadAction createDownload(String url, String path) {
+        DownloadActionImpl action = null;
+        for (DownloadActionImpl downloadAction : downloadActionList) {
+            if (Objects.equals(downloadAction.url, url)) {
+                action = downloadAction;
+                break;
+            }
+        }
+        if (action != null) {
+            return action;
+        }
+        action = new DownloadActionImpl(url, path);
+        downloadActionList.add(action);
+        return action;
+    }
+
+
+    static class DownloadActionImpl implements DownloadAction, DownloadTaskListener {
+
+        private final String url;
+        private final DownloadReceiver downloadReceiver;
+
+        private final List<DownloadAction.Callback> callbackList = new ArrayList<>();
+
+        private final String path;
+
+        private long taskId;
+
+        private DownloadEntity downloadEntity;
+
+        private boolean restart;
+
+
+        public DownloadActionImpl(String url, String path) {
+            this.url = url;
+            this.path = path;
+            this.downloadReceiver = Aria.download(this);
+            this.downloadReceiver.register();
+            List<DownloadEntity> downloadEntityList = this.downloadReceiver.getDownloadEntity(url);
+            if (downloadEntityList != null) {
+                for (DownloadEntity entity : downloadEntityList) {
+                    if (Objects.equals(entity.getFilePath(), path)) {
+                        downloadEntity = entity;
+                        break;
+                    }
+                }
+            }
+            if (downloadEntity != null) {
+                taskId = downloadEntity.getId();
+            }
+        }
+
+
+        @Override
+        public String getUrl() {
+            return url;
+        }
+
+        @Override
+        public synchronized void start() {
+            if (downloadEntity != null && !Objects.equals(downloadEntity.getUrl(), url)) {
+                downloadReceiver
+                        .load(downloadEntity.getId())
+                        .cancel(true);
+                downloadEntity = null;
+                taskId = 0;
+            }
+            if (downloadEntity == null ||
+                    downloadEntity.getState() == IEntity.STATE_CANCEL) {
+                taskId = downloadReceiver
+                        .load(url)
+                        .setFilePath(path)
+                        .ignoreCheckPermissions()
+                        .ignoreFilePathOccupy()
+                        .create();
+                downloadEntity = downloadReceiver.getDownloadEntity(taskId);
+            } else if (downloadEntity.getState() == IEntity.STATE_WAIT
+                    ||downloadEntity.getState() == IEntity.STATE_OTHER
+                    || downloadEntity.getState() == IEntity.STATE_FAIL
+                    || downloadEntity.getState() == IEntity.STATE_STOP) {
+                downloadReceiver
+                        .load(taskId)
+                        .ignoreCheckPermissions()
+                        .resume();
+            } else if (downloadEntity.getState() == IEntity.STATE_COMPLETE) {
+                downloadReceiver
+                        .load(taskId)
+                        .ignoreCheckPermissions()
+                        .cancel(true);
+                downloadEntity = null;
+                restart = true;
+            }
+        }
+
+        @Override
+        public synchronized void stop() {
+            if (downloadEntity == null) {
+                return;
+            }
+            int state = downloadEntity.getState();
+            if (state == IEntity.STATE_PRE
+                    || state == IEntity.STATE_POST_PRE
+                    || state == IEntity.STATE_RUNNING) {
+                downloadReceiver
+                        .load(taskId)
+                        .stop();
+            }
+        }
+
+        @Override
+        public synchronized boolean isCompleted() {
+            if (downloadEntity == null) {
+                return false;
+            }
+            int state = downloadEntity.getState();
+            return Objects.equals(downloadEntity.getUrl(), url)
+                    && state == IEntity.STATE_COMPLETE;
+        }
+
+        @Override
+        public synchronized boolean isProcessing() {
+            if (downloadEntity == null) {
+                return false;
+            }
+            int state = downloadEntity.getState();
+            return Objects.equals(downloadEntity.getUrl(), url)
+                    && (state == IEntity.STATE_WAIT
+                    || state == IEntity.STATE_PRE
+                    || state == IEntity.STATE_POST_PRE
+                    || state == IEntity.STATE_RUNNING);
+        }
+
+        @Override
+        public synchronized void addCallback(Callback callback) {
+            if (callbackList.contains(callback)){
+                return;
+            }
+            callbackList.add(callback);
+        }
+
+        @Override
+        public synchronized void removeCallback(Callback callback) {
+            if (!callbackList.contains(callback)){
+                return;
+            }
+            callbackList.remove(callback);
+        }
+
+        @Override
+        public void onWait(DownloadTask task) {
+        }
+
+        @Override
+        public synchronized void onPre(DownloadTask task) {
+            downloadEntity = task.getDownloadEntity();
+
+        }
+
+        @Override
+        public void onTaskPre(DownloadTask task) {
+            Log.v("aa", "a");
+        }
+
+        @Override
+        public synchronized void onTaskResume(DownloadTask task) {
+            for (Callback callback : callbackList) {
+                callback.onStart();;
+            }
+        }
+
+        @Override
+        public synchronized void onTaskStart(DownloadTask task) {
+            for (Callback callback : callbackList) {
+                callback.onStart();;
+            }
+        }
+
+        @Override
+        public synchronized void onTaskStop(DownloadTask task) {
+            downloadEntity = task.getDownloadEntity();
+        }
+
+        @Override
+        public synchronized void onTaskCancel(DownloadTask task) {
+            downloadEntity = task.getDownloadEntity();
+            if (restart){
+                taskId = downloadReceiver
+                        .load(url)
+                        .setFilePath(path)
+                        .ignoreCheckPermissions()
+                        .ignoreFilePathOccupy()
+                        .create();
+                downloadEntity = downloadReceiver.getDownloadEntity(taskId);
+                restart = false;
+            }
+
+        }
+
+        @Override
+        public synchronized void onTaskFail(DownloadTask task, Exception e) {
+            for (Callback callback : callbackList) {
+                callback.onFail(e);;
+            }
+        }
+
+        @Override
+        public synchronized void onTaskComplete(DownloadTask task) {
+
+            for (Callback callback : callbackList) {
+                callback.onComplete(task.getFilePath());
+            }
+        }
+
+        @Override
+        public synchronized void onTaskRunning(DownloadTask task) {
+            float percent = task.getPercent() / 100.0f;
+            for (Callback callback : callbackList) {
+                callback.onProcess(percent);
+            }
+        }
+
+        @Override
+        public void onNoSupportBreakPoint(DownloadTask task) {
+
+
+        }
+    }
+}

+ 202 - 0
app/src/main/java/com/norman/webviewup/demo/MainActivity.java

@@ -0,0 +1,202 @@
+package com.norman.webviewup.demo;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.norman.webviewup.lib.UpgradeCallback;
+import com.norman.webviewup.lib.UpgradeOptions;
+import com.norman.webviewup.lib.WebViewUpgrade;
+
+import java.util.Arrays;
+import java.util.List;
+
+
+public class MainActivity extends Activity implements UpgradeCallback {
+
+    private static final List<PackageInfo> UPGRADE_PACKAGE_LIST = Arrays.asList(new PackageInfo("com.google.android.webview",
+            "122.0.6261.43",
+            "https://d-01.aabstatic.com/0224/android_system_webview_122.0.6261.43_androidapksbox.apk"
+    ));
+
+    ProgressBar progressBar;
+    TextView systemWebViewPackageTextView;
+    TextView upgradeWebViewPackageTextView;
+
+    TextView upgradeStatusTextView;
+    TextView upgradeErrorTextView;
+
+    PackageInfo selectPackageInfo;
+
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        WebViewUpgrade.addUpgradeCallback(this);
+        progressBar = findViewById(R.id.upgradeProgressBar);
+        systemWebViewPackageTextView = findViewById(R.id.systemWebViewPackageTextView);
+        upgradeWebViewPackageTextView = findViewById(R.id.upgradeWebViewPackageTextView);
+        upgradeStatusTextView = findViewById(R.id.upgradeStatusTextView);
+        upgradeErrorTextView = findViewById(R.id.upgradeErrorTextView);
+        updateSystemWebViewPackageInfo();
+        updateSystemWebViewPackageInfo();
+        updateUpgradeWebViewStatus();
+
+
+        findViewById(R.id.upgradeButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (WebViewUpgrade.isProcessing()) {
+                    Toast.makeText(getApplicationContext(), "webView is being upgraded, please wait", Toast.LENGTH_SHORT).show();
+                } else if (WebViewUpgrade.isFailed()) {
+                    Toast.makeText(getApplicationContext(), "webView upgrade failed, please check the reason", Toast.LENGTH_SHORT).show();
+                } else if (WebViewUpgrade.isCompleted()) {
+                    Toast.makeText(getApplicationContext(), "WebView is already upgrade success", Toast.LENGTH_SHORT).show();
+                } else {
+                    showChooseWebViewDialog();
+                }
+            }
+        });
+
+        findViewById(R.id.webViewButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                startActivity(new Intent(MainActivity.this, WebViewActivity.class));
+            }
+        });
+
+
+    }
+
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        WebViewUpgrade.removeUpgradeCallback(this);
+    }
+
+    @Override
+    public void onUpgradeProcess(float percent) {
+        updateUpgradeWebViewStatus();
+    }
+
+    @Override
+    public void onUpgradeComplete() {
+        updateUpgradeWebViewStatus();
+        Toast.makeText(getApplicationContext(), "webView upgrade success", Toast.LENGTH_SHORT).show();
+    }
+
+    @Override
+    public void onUpgradeError(Throwable throwable) {
+        Toast.makeText(getApplicationContext(), "webView upgrade fail", Toast.LENGTH_SHORT).show();
+        updateUpgradeWebViewStatus();
+    }
+
+    private void showChooseWebViewDialog() {
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle("Choose WebView");
+
+        String[] items = new String[UPGRADE_PACKAGE_LIST.size()];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = UPGRADE_PACKAGE_LIST.get(i).title;
+        }
+        builder.setItems(items, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                PackageInfo packageInfo = UPGRADE_PACKAGE_LIST.get(which);
+                selectPackageInfo = packageInfo;
+                UpgradeOptions upgradeOptions = new UpgradeOptions
+                        .Builder(getApplicationContext(),
+                        packageInfo.packageName,
+                        packageInfo.url,
+                        packageInfo.versionName,
+                        new DownloadSinkImpl())
+                        .build();
+                WebViewUpgrade.init(upgradeOptions);
+                WebViewUpgrade.upgrade();
+                updateUpgradeWebViewPackageInfo();
+                updateUpgradeWebViewStatus();
+            }
+        });
+        AlertDialog dialog = builder.create();
+        dialog.show();
+    }
+
+
+    private void updateSystemWebViewPackageInfo() {
+        String systemWebViewPackageName = WebViewUpgrade.getSystemWebViewPackageName();
+        String systemWebViewPackageVersion = WebViewUpgrade.getSystemWebViewPackageVersion();
+
+        String systemWebViewPackageInfo = "unknown";
+        if (!TextUtils.isEmpty(systemWebViewPackageName)
+                || !TextUtils.isEmpty(systemWebViewPackageVersion)) {
+            systemWebViewPackageInfo = (!TextUtils.isEmpty(systemWebViewPackageName) ? systemWebViewPackageName : "unknown")
+                    + ":" + (!TextUtils.isEmpty(systemWebViewPackageVersion) ? systemWebViewPackageVersion : "unknown");
+        }
+        systemWebViewPackageTextView.setText(systemWebViewPackageInfo);
+    }
+
+    private void updateUpgradeWebViewPackageInfo() {
+        String upgradeWebViewPackageName = WebViewUpgrade.getUpgradeWebViewPackageName();
+        String upgradeWebViewPackageVersion = WebViewUpgrade.getUpgradeWebViewVersion();
+
+        String upgradeWebViewPackageInfo = "";
+        if (!TextUtils.isEmpty(upgradeWebViewPackageName)
+                || !TextUtils.isEmpty(upgradeWebViewPackageVersion)) {
+            upgradeWebViewPackageInfo = (!TextUtils.isEmpty(upgradeWebViewPackageName) ? upgradeWebViewPackageName : "unknown")
+                    + ":" + (!TextUtils.isEmpty(upgradeWebViewPackageVersion) ? upgradeWebViewPackageVersion : "unknown");
+        }
+        upgradeWebViewPackageTextView.setText(upgradeWebViewPackageInfo);
+    }
+
+
+    private void updateUpgradeWebViewStatus() {
+        if (WebViewUpgrade.isProcessing()) {
+            upgradeStatusTextView.setText("runing...");
+        } else if (WebViewUpgrade.isFailed()) {
+            upgradeStatusTextView.setText("fail");
+        } else if (WebViewUpgrade.isCompleted()) {
+            upgradeStatusTextView.setText("complete");
+        } else if (WebViewUpgrade.isInited()) {
+            upgradeStatusTextView.setText("init");
+        } else {
+            upgradeStatusTextView.setText("uninit");
+        }
+        progressBar.setProgress((int) (WebViewUpgrade.getUpgradeProcess() * 100));
+        Throwable throwable = WebViewUpgrade.getUpgradeError();
+        if (throwable == null) {
+            upgradeErrorTextView.setText("");
+        } else {
+            upgradeErrorTextView.setText("message:" + throwable.getMessage() + "\nstackTrace:" + Log.getStackTraceString(throwable));
+        }
+    }
+
+    static class PackageInfo {
+
+        public PackageInfo(String packageName, String versionName, String url) {
+            this.title = packageName + "\n" + versionName;
+            this.url = url;
+            this.packageName = packageName;
+            this.versionName = versionName;
+        }
+
+        String title;
+        String url;
+        String packageName;
+
+        String versionName;
+    }
+
+
+}

+ 14 - 0
app/src/main/java/com/norman/webviewup/demo/MyApp.java

@@ -0,0 +1,14 @@
+package com.norman.webviewup.demo;
+
+import android.app.Application;
+
+import com.arialyy.aria.core.Aria;
+
+public class MyApp extends Application {
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Aria.init(this);
+    }
+}

+ 108 - 0
app/src/main/java/com/norman/webviewup/demo/WebViewActivity.java

@@ -0,0 +1,108 @@
+package com.norman.webviewup.demo;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.DownloadManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.webkit.CookieManager;
+import android.webkit.URLUtil;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.Toast;
+
+import androidx.annotation.RequiresApi;
+import androidx.core.app.ActivityCompat;
+
+
+
+public class WebViewActivity extends Activity {
+
+    private final int STORAGE_PERMISSION_CODE = 1;
+    private WebView mWebView;
+    private void requestStoragePermission() {
+        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
+            new AlertDialog.Builder(this)
+                    .setTitle("Permission needed")
+                    .setMessage("This permission is needed to download files")
+                    .setPositiveButton("ok", (dialog, which) -> ActivityCompat.requestPermissions(WebViewActivity.this,
+                            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE))
+                    .setNegativeButton("cancel", (dialog, which) -> dialog.dismiss())
+                    .create().show();
+        } else {
+            ActivityCompat.requestPermissions(this,
+                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE);
+        }
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.Q)
+    @Override
+    @SuppressLint("SetJavaScriptEnabled")
+    protected void onCreate(Bundle savedInstanceState) {
+        requestStoragePermission();
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_webview);
+        mWebView = findViewById(R.id.webview);
+//        mWebView.setWebViewRenderProcessClient();
+//        mWebView.getWebViewLooper()
+        WebSettings webSettings = mWebView.getSettings();
+        String userAgentString = webSettings.getUserAgentString();
+        Log.v("webview", "userAgentString" + userAgentString);
+        webSettings.setJavaScriptEnabled(true);
+        mWebView.setWebViewClient(new HelloWebViewClient());
+        mWebView.setDownloadListener((url, userAgent, contentDisposition, mimeType, contentLength) -> {
+            Uri source = Uri.parse(url);
+            DownloadManager.Request request = new DownloadManager.Request(source);
+            String cookies = CookieManager.getInstance().getCookie(url);
+            request.addRequestHeader("cookie", cookies);
+            request.addRequestHeader("User-Agent", userAgent);
+            request.setDescription("Downloading File...");
+            request.setTitle(URLUtil.guessFileName(url, contentDisposition, mimeType));
+            request.allowScanningByMediaScanner();
+            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(url, contentDisposition, mimeType));
+            DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
+            dm.enqueue(request);
+            Toast.makeText(getApplicationContext(), "Downloading File", Toast.LENGTH_LONG).show();
+        });
+        mWebView.loadUrl("https://test-videos.co.uk/bigbuckbunny/mp4-h265"); //Replace The Link Here
+
+
+    }
+
+    private static class HelloWebViewClient extends WebViewClient {
+        @Override
+        public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
+//            view.loadUrl(url);
+            return false;
+        }
+
+        @Override
+        public void onPageStarted(WebView view, String url, Bitmap favicon) {
+            super.onPageStarted(view, url, favicon);
+        }
+
+        @Override
+        public void onPageFinished(WebView view, String url) {
+            super.onPageFinished(view, url);
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (mWebView.canGoBack()) {
+            mWebView.goBack();
+        } else {
+            super.onBackPressed();
+        }
+    }
+
+}

+ 10 - 0
app/src/main/java/com/norman/webviewup/lib/UpgradeCallback.java

@@ -0,0 +1,10 @@
+package com.norman.webviewup.lib;
+
+public interface UpgradeCallback {
+
+    void onUpgradeProcess(float percent);
+
+    void onUpgradeComplete();
+
+    void onUpgradeError(Throwable throwable);
+}

+ 83 - 0
app/src/main/java/com/norman/webviewup/lib/UpgradeOptions.java

@@ -0,0 +1,83 @@
+package com.norman.webviewup.lib;
+
+import android.app.Application;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.norman.webviewup.lib.download.DownloaderSink;
+
+public class UpgradeOptions {
+
+    public final DownloaderSink downloaderSink;
+    public final Application context;
+    public final String packageName;
+    public final String url;
+    public final String versionName;
+
+
+    private UpgradeOptions(UpgradeOptions.Builder builder) {
+        this.downloaderSink = builder.downloaderSink;
+        this.context = builder.context;
+        this.packageName = builder.packageName;
+        this.url = builder.url;
+        this.versionName = builder.versionName;
+    }
+
+    public static class Builder {
+        public DownloaderSink downloaderSink;
+        public Application context;
+        public String packageName;
+        public String url;
+        public String versionName;
+
+
+        public Builder(@NonNull Context context, @NonNull String packageName, @NonNull String url, @NonNull String version, @NonNull DownloaderSink downloaderSink) {
+            this.downloaderSink = downloaderSink;
+            this.context = (Application) context.getApplicationContext();
+            this.packageName = packageName;
+            this.url = url;
+            this.versionName = version;
+        }
+
+        private Builder(UpgradeOptions options) {
+            downloaderSink = options.downloaderSink;
+            context = options.context;
+            packageName = options.packageName;
+            url = options.url;
+            versionName = options.versionName;
+        }
+
+
+        public Builder setDownloaderSink(DownloaderSink downloaderSink) {
+            this.downloaderSink = downloaderSink;
+            return this;
+        }
+
+        public Builder setContext(Context context) {
+            this.context = (Application) context.getApplicationContext();
+            return this;
+        }
+
+        public Builder setPackageName(String packageName) {
+            this.packageName = packageName;
+            return this;
+        }
+
+        public Builder setUrl(String url) {
+            this.url = url;
+            return this;
+        }
+
+        public Builder setVersionName(String versionName) {
+            this.versionName = versionName;
+            return this;
+        }
+
+        public UpgradeOptions build() {
+            return new UpgradeOptions(this);
+        }
+    }
+
+
+}

+ 457 - 0
app/src/main/java/com/norman/webviewup/lib/WebViewUpgrade.java

@@ -0,0 +1,457 @@
+package com.norman.webviewup.lib;
+
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Looper;
+import android.webkit.WebView;
+
+import com.norman.webviewup.lib.download.DownloadAction;
+import com.norman.webviewup.lib.download.DownloaderSink;
+import com.norman.webviewup.lib.hook.PackageManagerHook;
+import com.norman.webviewup.lib.hook.WebViewUpdateServiceHook;
+import com.norman.webviewup.lib.reflect.RuntimeAccess;
+import com.norman.webviewup.lib.service.interfaces.IServiceManager;
+import com.norman.webviewup.lib.service.interfaces.IWebViewFactory;
+import com.norman.webviewup.lib.service.interfaces.IWebViewUpdateService;
+import com.norman.webviewup.lib.util.ApkUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class WebViewUpgrade {
+
+    private static final List<UpgradeCallback> UPGRADE_CALLBACK_LIST = new ArrayList<>();
+    private static final String UPGRADE_DIRECTORY = "WebViewUpgrade";
+
+
+    private static final int STATUS_UNINIT = 0;
+
+    private static final int STATUS_INIT = 1;
+
+    private static final int STATUS_RUNNING = 2;
+
+    private static final int STATUS_FAIL = 3;
+
+    private static final int STATUS_COMPLETE = 4;
+
+    private static UpgradeOptions UPGRADE_OPTIONS;
+
+    private static int UPGRADE_STATUS = STATUS_UNINIT;
+
+
+    private static float UPGRADE_PROCESS;
+
+    private static String SYSTEM_WEB_VIEW_PACKAGE_NAME;
+
+    private static String SYSTEM_WEB_VIEW_PACKAGE_VERSION;
+
+    private static String UPGRADE_WEB_VIEW_PACKAGE_NAME;
+
+    private static String UPGRADE_WEB_VIEW_PACKAGE_VERSION;
+
+
+    private static Throwable UPGRADE_THROWABLE;
+
+    private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
+
+
+
+
+
+    public synchronized static void init(UpgradeOptions options) {
+        if (UPGRADE_STATUS != STATUS_UNINIT) {
+            throw new IllegalStateException("WebViewUpgrade is already init");
+        }
+        if (options == null) {
+            throw new NullPointerException("options is  null");
+        }
+        UPGRADE_OPTIONS = options;
+        UPGRADE_STATUS = STATUS_INIT;
+    }
+
+
+    public synchronized static void addUpgradeCallback(UpgradeCallback upgradeCallback) {
+        if (upgradeCallback == null) return;
+        if (UPGRADE_CALLBACK_LIST.contains(upgradeCallback)) return;
+        UPGRADE_CALLBACK_LIST.add(upgradeCallback);
+    }
+
+    public synchronized static void removeUpgradeCallback(UpgradeCallback upgradeCallback) {
+        if (upgradeCallback == null) return;
+        if (!UPGRADE_CALLBACK_LIST.contains(upgradeCallback)) return;
+        UPGRADE_CALLBACK_LIST.remove(upgradeCallback);
+    }
+
+
+    public synchronized static boolean isProcessing() {
+        return UPGRADE_STATUS == STATUS_RUNNING;
+    }
+
+    public synchronized static boolean isCompleted() {
+        return UPGRADE_STATUS == STATUS_COMPLETE;
+    }
+
+    public synchronized static boolean isFailed() {
+        return UPGRADE_STATUS == STATUS_FAIL;
+    }
+
+    public synchronized static boolean isInited() {
+        return UPGRADE_STATUS != STATUS_UNINIT;
+    }
+
+    public synchronized static Throwable getUpgradeError(){
+        return UPGRADE_THROWABLE;
+    }
+
+    public synchronized static float getUpgradeProcess(){
+        return UPGRADE_PROCESS;
+    }
+
+    public synchronized static void upgrade() {
+        try {
+            if (UPGRADE_STATUS == STATUS_UNINIT) {
+                throw new IllegalStateException("please first init");
+            }
+            if (UPGRADE_STATUS != STATUS_INIT) {
+                return;
+            }
+            UPGRADE_STATUS = STATUS_RUNNING;
+            HandlerThread upgradeThread = new HandlerThread("WebViewUpgrade");
+            upgradeThread.start();
+            Handler upgradeHandler = new Handler(upgradeThread.getLooper());
+            upgradeHandler.post(new UPGRADE_ACTION(upgradeHandler));
+        } catch (Throwable throwable) {
+            callErrorCallback(throwable);
+        }
+    }
+
+    static class UPGRADE_ACTION implements Runnable {
+
+        private final Handler handler;
+        private Context context;
+        private String apkUrl;
+        private String packageName;
+        private String versionName;
+        private String apkPath;
+        private String soLibDir;
+
+        private String soLibInstallCompleteKey;
+
+        private SharedPreferences sharedPreferences;
+
+        public UPGRADE_ACTION(Handler handler) {
+            this.handler = handler;
+        }
+
+        @Override
+        public void run() {
+            try {
+                UpgradeOptions options = UPGRADE_OPTIONS;
+                DownloaderSink downloaderSink = options.downloaderSink;
+                context = options.context;
+                apkUrl = options.url;
+                packageName = options.packageName;
+                versionName = options.versionName;
+                apkPath = new File(context.getFilesDir(),
+                        UPGRADE_DIRECTORY
+                                + "/" + packageName
+                                + "/" + versionName
+                                + "/base.apk").getAbsolutePath();
+
+                soLibDir = new File(context.getFilesDir(),
+                        UPGRADE_DIRECTORY
+                                + "/" + packageName
+                                + "/" + versionName
+                                + "/libs").getAbsolutePath();
+
+                sharedPreferences = context
+                        .getSharedPreferences(
+                                UPGRADE_DIRECTORY,
+                                Context.MODE_PRIVATE);
+
+                soLibInstallCompleteKey = packageName + ":" + versionName;
+
+                File soDir = new File(soLibDir);
+                if (!soDir.exists()) {
+                    soDir.mkdirs();
+                }
+
+                boolean installComplete = sharedPreferences
+                        .getBoolean(soLibInstallCompleteKey, false);
+                if (installComplete) {
+                    upgradeWebView();
+                } else {
+                    DownloadAction downloadAction = downloaderSink.createDownload(apkUrl, apkPath);
+                    if (downloadAction.isCompleted()) {
+                        extractNativeLibrary();
+                        upgradeWebView();
+                    } else {
+                        downloadAction.addCallback(new DownloadAction.Callback() {
+
+                            @Override
+                            public void onComplete(String path) {
+                                handler.post(() -> {
+                                    try {
+                                        extractNativeLibrary();
+                                        upgradeWebView();
+                                    }catch (Throwable throwable){
+                                        callErrorCallback(throwable);
+                                        handler.getLooper().quit();
+                                    }
+                                });
+                            }
+
+                            @Override
+                            public void onFail(Throwable throwable) {
+                                callErrorCallback(throwable);
+                                handler.getLooper().quit();
+                            }
+
+                            @Override
+                            public void onProcess(float percent) {
+                                callProcessCallback(percent * 0.90f);
+                            }
+                        });
+                    }
+                    downloadAction.start();
+                }
+            } catch (Throwable throwable) {
+                callErrorCallback(throwable);
+                handler.getLooper().quit();
+            }
+        }
+
+        private void extractNativeLibrary() {
+            callProcessCallback(0.92f);
+            ApkUtils.extractNativeLibrary(apkPath, soLibDir);
+            callProcessCallback(0.94f);
+            sharedPreferences
+                    .edit()
+                    .putBoolean(soLibInstallCompleteKey, true)
+                    .commit();
+        }
+
+        private void upgradeWebView() {
+            callProcessCallback(0.95f);
+            replaceWebViewProvider(context,
+                    packageName,
+                    versionName,
+                    apkPath,
+                    soLibDir);
+            handler.getLooper().quit();
+        }
+    }
+
+    private static void replaceWebViewProvider(Context context,
+                                               String packageName,
+                                               String versionName,
+                                               String apkPath,
+                                               String soLibDir) {
+        PackageManagerHook managerHook = null;
+        WebViewUpdateServiceHook updateServiceHook = null;
+        try {
+            callProcessCallback(0.96f);
+            PackageInfo packageInfo = context.getPackageManager()
+                    .getPackageArchiveInfo(apkPath, 0);
+
+
+            if (packageInfo == null) {
+                throw new NullPointerException("path: " + apkPath + " is not apk");
+            }
+            if (!Objects.equals(packageInfo.packageName, packageName)) {
+                throw new IllegalArgumentException("packageName:"
+                        + packageInfo.packageName
+                        + " in the options is different from packageName:"
+                        + packageName + " in the apk");
+            }
+            if (!Objects.equals(packageInfo.versionName, versionName)) {
+                throw new IllegalArgumentException("versionName:"
+                        + packageInfo.versionName
+                        + " in the options is different from versionName:"
+                        + versionName + " in the apk");
+            }
+
+            int sdkVersion = Build.VERSION.SDK_INT;
+            ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                if (sdkVersion < applicationInfo.minSdkVersion) {
+                    throw new RuntimeException("The current system version " + sdkVersion + " is smaller than the minimum version " + applicationInfo.minSdkVersion + "required by the apk  " + apkPath);
+                }
+            }
+
+            checkWebView();
+            managerHook = new PackageManagerHook(context, packageName, apkPath, soLibDir);
+            updateServiceHook = new WebViewUpdateServiceHook(context, packageName);
+            managerHook.hook();
+            callProcessCallback(0.97f);
+            updateServiceHook.hook();
+            callProcessCallback(0.98f);
+
+            Object lock = new Object();
+            AtomicBoolean loadOver = new AtomicBoolean();
+            AtomicReference<Throwable> throwableReference = new AtomicReference<>(null);
+            MAIN_HANDLER.post(() -> {
+                try {
+                    loadSystemWebViewPackage();
+                    checkWebView();
+                    new WebView(context);
+                } catch (Throwable throwable) {
+                    throwableReference.set(throwable);
+                } finally {
+                    synchronized (lock) {
+                        loadOver.set(true);
+                        lock.notifyAll();
+                    }
+                }
+            });
+            synchronized (lock) {
+                long startTime = System.currentTimeMillis();
+                while (!loadOver.get()) {
+                    try {
+                        lock.wait(100);
+                    } catch (InterruptedException ignore) {
+                    }
+                    if ((System.currentTimeMillis() - startTime) > 5000) {
+                        throwableReference.set(new RuntimeException("webView load timeOut"));
+                        break;
+                    }
+                }
+            }
+            Throwable throwable = throwableReference.get();
+            if (throwable != null) {
+                throw new RuntimeException(throwable);
+            }
+            callProcessCallback(1.0f);
+            callCompleteCallback();
+        } finally {
+//            if (managerHook != null) {
+//                managerHook.restore();
+//            }
+//            if (updateServiceHook != null) {
+//                updateServiceHook.restore();
+//            }
+
+        }
+    }
+
+    private static void checkWebView() {
+        IWebViewFactory webViewFactory = RuntimeAccess.staticAccess(IWebViewFactory.class);
+        Object providerInstance = webViewFactory.getProviderInstance();
+        if (providerInstance != null) {
+            throw new IllegalStateException("WebViewProvider has been created, and the upgrade function can only be used before the webview is instantiated");
+        }
+    }
+
+
+    private static void callErrorCallback(Throwable throwable) {
+        synchronized (WebViewUpgrade.class) {
+            UPGRADE_STATUS = STATUS_FAIL;
+            UPGRADE_THROWABLE = throwable;
+        }
+        runInMainThread(() -> {
+            synchronized (WebViewUpgrade.class) {
+                for (UpgradeCallback upgradeCallback : UPGRADE_CALLBACK_LIST) {
+                    upgradeCallback.onUpgradeError(throwable);
+                }
+            }
+        });
+    }
+
+    private static void callProcessCallback(float percent) {
+        synchronized (WebViewUpgrade.class){
+            UPGRADE_PROCESS = percent;
+        }
+        runInMainThread(() -> {
+            synchronized (WebViewUpgrade.class) {
+                for (UpgradeCallback upgradeCallback : UPGRADE_CALLBACK_LIST) {
+                    upgradeCallback.onUpgradeProcess(percent);
+                }
+            }
+        });
+    }
+
+    private static void callCompleteCallback() {
+        synchronized (WebViewUpgrade.class) {
+            UPGRADE_STATUS = STATUS_COMPLETE;
+        }
+        runInMainThread(() -> {
+            synchronized (WebViewUpgrade.class) {
+                for (UpgradeCallback upgradeCallback : UPGRADE_CALLBACK_LIST) {
+                    upgradeCallback.onUpgradeComplete();
+                }
+            }
+        });
+    }
+
+
+    private static void runInMainThread(Runnable runnable) {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            runnable.run();
+        } else {
+            MAIN_HANDLER.post(runnable);
+        }
+    }
+
+    public synchronized static String getSystemWebViewPackageName() {
+        if (SYSTEM_WEB_VIEW_PACKAGE_NAME != null) {
+            return SYSTEM_WEB_VIEW_PACKAGE_NAME;
+        }
+        loadSystemWebViewPackage();
+        return SYSTEM_WEB_VIEW_PACKAGE_NAME;
+    }
+
+    public synchronized static String getSystemWebViewPackageVersion() {
+        if (SYSTEM_WEB_VIEW_PACKAGE_VERSION != null) {
+            return SYSTEM_WEB_VIEW_PACKAGE_VERSION;
+        }
+        loadSystemWebViewPackage();
+        return SYSTEM_WEB_VIEW_PACKAGE_VERSION;
+    }
+
+    public synchronized static String getUpgradeWebViewPackageName() {
+        return UPGRADE_OPTIONS != null ? UPGRADE_OPTIONS.packageName : null;
+    }
+
+    public synchronized static String getUpgradeWebViewVersion() {
+        return UPGRADE_OPTIONS != null ? UPGRADE_OPTIONS.versionName : null;
+    }
+
+    private static void loadSystemWebViewPackage() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            try {
+                PackageInfo packageInfo = WebView.getCurrentWebViewPackage();
+                SYSTEM_WEB_VIEW_PACKAGE_NAME = packageInfo.packageName;
+                SYSTEM_WEB_VIEW_PACKAGE_VERSION = packageInfo.versionName;
+            } catch (Throwable ignore) {
+
+            }
+        }
+        if (SYSTEM_WEB_VIEW_PACKAGE_NAME == null) {
+            try {
+                IServiceManager serviceManager = RuntimeAccess.staticAccess(IServiceManager.class);
+                IBinder binder = serviceManager.getService(IWebViewUpdateService.SERVICE);
+                IWebViewUpdateService service = RuntimeAccess.staticAccess(IWebViewUpdateService.class);
+                IInterface iInterface = service.asInterface(binder);
+                service = RuntimeAccess.objectAccess(IWebViewUpdateService.class, iInterface);
+                PackageInfo packageInfo = service.getCurrentWebViewPackage();
+                SYSTEM_WEB_VIEW_PACKAGE_NAME = packageInfo.packageName;
+                SYSTEM_WEB_VIEW_PACKAGE_VERSION = packageInfo.versionName;
+            } catch (Throwable ignore) {
+            }
+        }
+    }
+
+}

+ 38 - 0
app/src/main/java/com/norman/webviewup/lib/download/DownloadAction.java

@@ -0,0 +1,38 @@
+package com.norman.webviewup.lib.download;
+
+public interface DownloadAction {
+
+    String getUrl();
+
+    void start();
+
+    void stop();
+
+    boolean isCompleted();
+
+    boolean isProcessing();
+
+
+    void addCallback(Callback callback);
+
+    void removeCallback(Callback callback);
+
+    interface Callback {
+
+        default void onStart() {
+
+        }
+
+        default void onProcess(float percent) {
+
+        }
+
+        default void onComplete(String path) {
+
+        }
+
+        default void onFail(Throwable throwable) {
+
+        }
+    }
+}

+ 7 - 0
app/src/main/java/com/norman/webviewup/lib/download/DownloaderSink.java

@@ -0,0 +1,7 @@
+package com.norman.webviewup.lib.download;
+
+public interface DownloaderSink {
+
+    DownloadAction createDownload(String url,
+                                  String path);
+}

+ 169 - 0
app/src/main/java/com/norman/webviewup/lib/hook/PackageManagerHook.java

@@ -0,0 +1,169 @@
+package com.norman.webviewup.lib.hook;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+
+import androidx.annotation.NonNull;
+
+import com.norman.webviewup.lib.reflect.RuntimeAccess;
+import com.norman.webviewup.lib.service.binder.BinderHook;
+import com.norman.webviewup.lib.service.interfaces.IActivityThread;
+import com.norman.webviewup.lib.service.interfaces.IContextImpl;
+import com.norman.webviewup.lib.service.interfaces.IPackageManager;
+import com.norman.webviewup.lib.service.interfaces.IServiceManager;
+import com.norman.webviewup.lib.service.binder.ProxyBinder;
+import com.norman.webviewup.lib.service.proxy.PackageManagerProxy;
+import com.norman.webviewup.lib.util.ProcessUtils;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+public class PackageManagerHook extends BinderHook {
+
+    private final Context context;
+
+    private final String webViewPackageName;
+
+    private final String apkPath;
+
+    private final String soPath;
+
+    private Map<String, IBinder> binderCacheMap;
+
+
+    public PackageManagerHook(@NonNull Context context,
+                              @NonNull String packageName,
+                              @NonNull String apkPath,
+                              @NonNull String soPath) {
+        this.context = context;
+        this.webViewPackageName = packageName;
+        this.apkPath = apkPath;
+        this.soPath = soPath;
+    }
+
+    private final PackageManagerProxy proxy = new PackageManagerProxy() {
+
+        @Override
+        protected PackageInfo getPackageInfo(String packageName, long flags, int userId) {
+            return getPackageInfo(packageName, (int) flags);
+        }
+
+        @Override
+        protected PackageInfo getPackageInfo(String packageName, int flags, int userId) {
+            return getPackageInfo(packageName, flags);
+        }
+
+        @Override
+        protected int getComponentEnabledSetting(ComponentName componentName, int userId) {
+            return getComponentEnabledSetting(componentName);
+        }
+
+        @Override
+        protected PackageInfo getPackageInfo(String packageName, int flags) {
+            if (packageName.equals(webViewPackageName)) {
+                PackageInfo packageInfo = context.getPackageManager().getPackageArchiveInfo(apkPath, flags);
+                boolean is64Bit = ProcessUtils.is64Bit();
+                String[] supportBitAbis = is64Bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS;
+                Arrays.sort(supportBitAbis, Collections.reverseOrder());
+                File nativeLibraryDir = null;
+                for (String supportBitAbi : supportBitAbis) {
+                    File file = new File(soPath + "/" + supportBitAbi);
+                    File[] childFile = file.listFiles();
+                    if (childFile != null && childFile.length > 0) {
+                        nativeLibraryDir = file;
+                        break;
+                    }
+                }
+                if (nativeLibraryDir == null) {
+                    throw new NullPointerException("unable to find supported abis "
+                            + Arrays.toString(supportBitAbis)
+                            + " in apk " + apkPath);
+                }
+                packageInfo.applicationInfo.nativeLibraryDir = nativeLibraryDir.getAbsolutePath();
+                return packageInfo;
+            }
+            return (PackageInfo) invoke();
+        }
+
+        @Override
+        protected int getComponentEnabledSetting(ComponentName componentName) {
+            if (componentName.getPackageName().equals(webViewPackageName)) {
+                return PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+            } else {
+                return (int) invoke();
+            }
+        }
+
+        @Override
+        protected IBinder asBinder() {
+            IBinder proxyBinder = getProxyBinder();
+            return proxyBinder != null ? proxyBinder : (IBinder) invoke();
+        }
+    };
+
+
+    @Override
+    protected IBinder onTargetBinderObtain() {
+        IServiceManager serviceManager = RuntimeAccess.staticAccess(IServiceManager.class);
+        return serviceManager.getService(IPackageManager.SERVICE);
+    }
+
+    @Override
+    protected ProxyBinder onProxyBinderCreate(IBinder binder) {
+        IPackageManager service = RuntimeAccess.staticAccess(IPackageManager.class);
+        IServiceManager serviceManager = RuntimeAccess.staticAccess(IServiceManager.class);
+
+        IInterface targetInterface = service.asInterface(binder);
+        proxy.setTarget(targetInterface);
+        IInterface proxyInterface = (IInterface) proxy.get();
+        ProxyBinder proxyBinder = new ProxyBinder(targetInterface, proxyInterface);
+
+        binderCacheMap = serviceManager.getServiceCache();
+        return proxyBinder;
+    }
+
+    @Override
+    protected void onTargetBinderRestore(IBinder binder) {
+        IInterface targetInterface;
+        try {
+            targetInterface = binder.queryLocalInterface(binder.getInterfaceDescriptor());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+        binderCacheMap.put(IPackageManager.SERVICE, binder);
+        updateActivityThreadPackageManager(targetInterface);
+        flushContextImplPackageManager();
+    }
+
+    @Override
+    protected void onProxyBinderReplace(ProxyBinder binder) {
+        binderCacheMap.put(IPackageManager.SERVICE, binder);
+        updateActivityThreadPackageManager(binder.getProxyIInterface());
+        flushContextImplPackageManager();
+    }
+
+    private static void updateActivityThreadPackageManager(IInterface iInterface) {
+        IActivityThread activityThread = RuntimeAccess.staticAccess(IActivityThread.class);
+        activityThread.setPackageManager(iInterface);
+    }
+
+    private void flushContextImplPackageManager() {
+        Context baseContext = context.getApplicationContext();
+        while (baseContext instanceof ContextWrapper) {
+            baseContext = ((ContextWrapper) context).getBaseContext();
+        }
+        IContextImpl contextImpl = RuntimeAccess.objectAccess(IContextImpl.class, baseContext);
+        contextImpl.setPackageManager(null);
+    }
+
+
+}

+ 96 - 0
app/src/main/java/com/norman/webviewup/lib/hook/WebViewUpdateServiceHook.java

@@ -0,0 +1,96 @@
+package com.norman.webviewup.lib.hook;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.IInterface;
+
+import com.norman.webviewup.lib.reflect.RuntimeAccess;
+import com.norman.webviewup.lib.service.binder.BinderHook;
+import com.norman.webviewup.lib.service.interfaces.IServiceManager;
+import com.norman.webviewup.lib.service.interfaces.IWebViewProviderResponse;
+import com.norman.webviewup.lib.service.interfaces.IWebViewUpdateService;
+import com.norman.webviewup.lib.service.binder.ProxyBinder;
+import com.norman.webviewup.lib.service.proxy.WebViewUpdateServiceProxy;
+
+import java.util.Map;
+
+
+public class WebViewUpdateServiceHook extends BinderHook {
+
+
+    private final Context context;
+
+    private final String webViewPackageName;
+
+    public WebViewUpdateServiceHook(Context context, String packageName) {
+        this.context = context;
+        this.webViewPackageName =packageName;
+    }
+
+    private final WebViewUpdateServiceProxy proxy = new WebViewUpdateServiceProxy() {
+        @Override
+        protected Object waitForAndGetProvider() {
+            Object result = invoke();
+            PackageInfo packageInfo;
+            try {
+                packageInfo = context.getPackageManager()
+                        .getPackageInfo(webViewPackageName,
+                                PackageManager.GET_SHARED_LIBRARY_FILES
+                                        | PackageManager.GET_SIGNATURES
+                                        | PackageManager.GET_META_DATA);
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+            IWebViewProviderResponse webViewProviderResponse = RuntimeAccess.objectAccess(IWebViewProviderResponse.class, result);
+            webViewProviderResponse.setPackageInfo(packageInfo);
+            return result;
+        }
+
+        @Override
+        protected IBinder asBinder() {
+            IBinder proxyBinder = getProxyBinder();
+            return  proxyBinder != null?proxyBinder: (IBinder) invoke();
+        }
+
+        @Override
+        protected boolean isMultiProcessEnabled() {
+            return false;
+        }
+    };
+
+
+    private Map<String, IBinder> binderCacheMap;
+
+    @Override
+    protected IBinder onTargetBinderObtain() {
+        IServiceManager serviceManager = RuntimeAccess.staticAccess(IServiceManager.class);
+        return serviceManager.getService(IWebViewUpdateService.SERVICE);
+    }
+
+    @Override
+    protected ProxyBinder onProxyBinderCreate(IBinder binder) {
+        IWebViewUpdateService service = RuntimeAccess.staticAccess(IWebViewUpdateService.class);
+        IServiceManager serviceManager = RuntimeAccess.staticAccess(IServiceManager.class);
+
+        IInterface oldInterface = service.asInterface(binder);
+        proxy.setTarget(oldInterface);
+        IInterface proxyInterface = (IInterface) proxy.get();
+        ProxyBinder proxyBinder = new ProxyBinder(oldInterface,proxyInterface);
+
+        binderCacheMap = serviceManager.getServiceCache();
+        return proxyBinder;
+    }
+
+    @Override
+    protected void onTargetBinderRestore(IBinder binder) {
+        binderCacheMap.put(IWebViewUpdateService.SERVICE, binder);
+    }
+
+    @Override
+    protected void onProxyBinderReplace(ProxyBinder binder) {
+        binderCacheMap.put(IWebViewUpdateService.SERVICE, binder);
+    }
+
+}

+ 96 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/ReflectConstructor.java

@@ -0,0 +1,96 @@
+package com.norman.webviewup.lib.reflect;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+
+public class ReflectConstructor {
+
+    private final Object mConstructorLock = new Object();
+
+    private String mClassName;
+
+    private Class<?> mClass;
+
+    private Constructor<?> mConstructor;
+
+    private Class<?>[] mParameterTypes;
+
+
+    private Constructor<?> assessConstructor;
+
+    public ReflectConstructor(Constructor<?> constructor) {
+        this.mConstructor = constructor;
+        this.mParameterTypes = constructor.getParameterTypes();
+    }
+
+    public ReflectConstructor(String className, Class<?>... parameterTypes) {
+        this.mClassName = className;
+        this.mParameterTypes = parameterTypes == null ? new Class<?>[0] : parameterTypes;
+    }
+
+    public ReflectConstructor(Class<?> cls, Class<?>... parameterTypes) {
+        this.mClass = cls;
+        this.mParameterTypes = parameterTypes == null ? new Class<?>[0] : parameterTypes;
+    }
+
+    public ReflectConstructor(Object obj, Class<?>... parameterTypes) {
+        if (obj != null) {
+            if (obj instanceof Class<?>) {
+                this.mClass = (Class<?>) obj;
+            } else {
+                this.mClass = obj.getClass();
+            }
+        }
+        this.mParameterTypes = parameterTypes == null ? new Class<?>[0] : parameterTypes;
+    }
+
+
+    public Object newInstance(Object... args) throws ReflectException {
+        synchronized (mConstructorLock) {
+            prepareConstructor();
+            try {
+                return assessConstructor.newInstance(args);
+            } catch (Throwable throwable) {
+                throw new ReflectException(throwable);
+            }
+        }
+    }
+
+
+    private void prepareConstructor() throws ReflectException {
+        synchronized (mConstructorLock) {
+            try {
+                if (assessConstructor != null) {
+                    return;
+                }
+                Constructor<?> findConstructor = mConstructor;
+                if (findConstructor == null) {
+                    Class<?> findClass = null;
+                    if (mClass != null) {
+                        findClass = mClass;
+                    } else if (mClassName != null) {
+                        findClass = Class.forName(mClassName);
+                    }
+                    if (findClass != null) {
+                        findConstructor = findClass.getDeclaredConstructor(mParameterTypes);
+                    }
+                }
+                if (findConstructor == null) {
+                    throw new NoSuchMethodException("can not find constructor");
+                }
+                int modifiers = findConstructor.getModifiers();
+                if (!Modifier.isPublic(modifiers) ||
+                        !Modifier.isPublic(findConstructor
+                                .getDeclaringClass()
+                                .getModifiers())) {
+                    if (!findConstructor.isAccessible()) {
+                        findConstructor.setAccessible(true);
+                    }
+                }
+                assessConstructor = findConstructor;
+            } catch (Throwable throwable) {
+                throw new ReflectException(throwable);
+            }
+        }
+    }
+}

+ 35 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/ReflectException.java

@@ -0,0 +1,35 @@
+package com.norman.webviewup.lib.reflect;
+
+
+import java.lang.reflect.InvocationTargetException;
+
+public class ReflectException extends RuntimeException {
+
+
+    public ReflectException(String message, Throwable cause) {
+        super(message, getCause(cause));
+        setStackTrace(cause.getStackTrace());
+    }
+
+    public ReflectException(Throwable cause) {
+        super(getCause(cause));
+        setStackTrace(cause.getStackTrace());
+    }
+
+    public ReflectException(String message) {
+        super(message);
+    }
+
+    private static Throwable getCause(Throwable throwable) {
+        if (throwable instanceof InvocationTargetException) {
+            InvocationTargetException invocationTargetException = (InvocationTargetException) throwable;
+            Throwable cause = invocationTargetException.getCause();
+            if ( cause!= null) {
+                return cause;
+            } else {
+                return invocationTargetException;
+            }
+        }
+        return throwable instanceof ReflectException ? throwable.getCause() : throwable;
+    }
+}

+ 180 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/ReflectField.java

@@ -0,0 +1,180 @@
+package com.norman.webviewup.lib.reflect;
+
+import android.text.TextUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+class ReflectField {
+
+    private final Object mFiledLock = new Object();
+    private String mName;
+
+    private String mClassName;
+    private Class<?> mClass;
+
+    private Object mObject;
+
+    private Field mFiled;
+    private Field assessfield;
+
+    private int staticType;
+
+    public ReflectField(Field field) {
+        this(null,field);
+    }
+
+    public ReflectField(Object object, Field field) {
+        this.mFiled = field;
+        if (object != null) {
+            if (object instanceof Class<?>) {
+                this.mClass = (Class<?>) object;
+            } else {
+                this.mObject = object;
+                this.mClass = object.getClass();
+            }
+        }
+    }
+
+    public ReflectField(String className, String name) {
+        this.mName = name;
+        this.mClassName = className;
+    }
+
+    public ReflectField(Class<?> cls, String name) {
+        this.mName = name;
+        this.mClass = cls;
+    }
+
+    public ReflectField(Object object, String name) {
+        this.mName = name;
+        if (object != null) {
+            if (object instanceof Class<?>) {
+                this.mClass = (Class<?>) object;
+            } else {
+                this.mObject = object;
+                this.mClass = object.getClass();
+            }
+        }
+    }
+
+
+    public void setStaticType(int staticValue) {
+        this.staticType = staticValue;
+    }
+
+    public void set(Object value) throws ReflectException {
+        synchronized (mFiledLock) {
+            prepareFiled();
+            try {
+                assessfield.set(mObject, value);
+            } catch (Throwable throwable) {
+                throw new ReflectException(throwable);
+            }
+        }
+    }
+
+    public Object get() throws ReflectException {
+        synchronized (mFiledLock) {
+            prepareFiled();
+            try {
+                return assessfield.get(mObject);
+            } catch (Throwable throwable) {
+                throw new ReflectException(throwable);
+            }
+        }
+    }
+
+
+    private void prepareFiled() throws ReflectException {
+        synchronized (mFiledLock) {
+            try {
+                if (assessfield != null) {
+                    return;
+                }
+                Field findFiled = mFiled;
+
+                if (findFiled == null) {
+                    String className = null;
+                    String filedName = mName;
+                    if (!TextUtils.isEmpty(mName)) {
+                        int lastDotIndex = mName.lastIndexOf(".");
+                        if (lastDotIndex >= 0 && lastDotIndex < mName.length() - 1) {
+                            className = mName.substring(0, lastDotIndex);
+                            filedName = mName.substring(lastDotIndex + 1);
+                        }
+                    }
+                    if (className != null){
+                        try {
+                            Class<?> relfectClass = Class.forName(className);
+                            findFiled = findField(relfectClass,filedName);
+                        }catch (Throwable ignore){
+
+                        }
+                    }
+                    if (findFiled == null){
+                        Class<?> findClass = null;
+                        if (mClass != null) {
+                            findClass = mClass;
+                        } else if (mClassName != null) {
+                            findClass = Class.forName(mClassName);
+                        }
+                        findFiled = findField(findClass, filedName);
+                    }
+                }
+                if (findFiled == null) {
+                    throw new NoSuchFieldException("can not find filed: " + mName);
+                }
+                int modifiers = findFiled.getModifiers();
+                if (!Modifier.isPublic(modifiers) ||
+                        !Modifier.isPublic(findFiled
+                                .getDeclaringClass()
+                                .getModifiers())) {
+                    if (!findFiled.isAccessible()) {
+                        findFiled.setAccessible(true);
+                    }
+                }
+
+                if (Modifier.isFinal(modifiers)) {
+                    try {
+                        Field modifiersField = Field.class.getDeclaredField("accessFlags");
+                        if (!modifiersField.isAccessible()) {
+                            modifiersField.setAccessible(true);
+                        }
+                        modifiersField.setInt(findFiled, modifiers & ~Modifier.FINAL);
+                    } catch (Throwable ignore) {
+
+                    }
+                    try {
+                        Field modifiersField = Field.class.getDeclaredField("modifiers");
+                        if (!modifiersField.isAccessible()) {
+                            modifiersField.setAccessible(true);
+                        }
+                        modifiersField.setInt(findFiled, modifiers & ~Modifier.FINAL);
+                    } catch (Throwable ignore) {
+
+                    }
+                }
+                assessfield = findFiled;
+            } catch (Throwable throwable) {
+                throw new ReflectException(throwable);
+            }
+
+        }
+    }
+
+    private Field findField(Class<?> findClass, String filedName) {
+        for (Class<?> clazz = findClass; clazz != null; clazz = clazz.getSuperclass()) {
+            try {
+                Field field = clazz.getDeclaredField(filedName);
+                boolean staticModifiers = Modifier.isStatic(field.getModifiers());
+                if ((staticType == 0 && staticModifiers) || (staticType == 1 && !staticModifiers)) {
+                    continue;
+                }
+                return field;
+            } catch (Exception ignore) {
+            }
+        }
+        return null;
+    }
+}

+ 154 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/ReflectMethod.java

@@ -0,0 +1,154 @@
+package com.norman.webviewup.lib.reflect;
+
+import android.text.TextUtils;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+class ReflectMethod {
+
+    private final Object mMethodLock = new Object();
+    private String mName;
+
+    private Class<?> mClass;
+    private String mClassName;
+
+    private Object mObject;
+
+    private Class<?>[] mParameterTypes;
+
+    private Method mMethod;
+
+    private Method assessMethod;
+
+    private int staticType;
+
+
+    public ReflectMethod(Method method) {
+        this(null,method);
+    }
+
+    public ReflectMethod(Object object, Method method) {
+        this.mMethod = method;
+        this.mParameterTypes = method.getParameterTypes();
+        if (object != null) {
+            if (object instanceof Class<?>) {
+                this.mClass = (Class<?>) object;
+            } else {
+                this.mObject = object;
+                this.mClass = object.getClass();
+            }
+        }
+    }
+
+    public ReflectMethod(String className, String name, Class<?>... parameterTypes) {
+        this.mName = name;
+        this.mClassName = className;
+        this.mParameterTypes = parameterTypes == null ? new Class<?>[0] : parameterTypes;
+    }
+
+    public ReflectMethod(Class<?> cls, String name, Class<?>... parameterTypes) {
+        this.mName = name;
+        this.mClass = cls;
+        this.mParameterTypes = parameterTypes == null ? new Class<?>[0] : parameterTypes;
+    }
+
+    public ReflectMethod(Object object, String name, Class<?>... parameterTypes) {
+        this.mName = name;
+        this.mParameterTypes = parameterTypes == null ? new Class<?>[0] : parameterTypes;
+        if (object != null) {
+            if (object instanceof Class<?>) {
+                this.mClass = (Class<?>) object;
+            } else {
+                this.mObject = object;
+                this.mClass = object.getClass();
+            }
+        }
+    }
+
+    public Object invoke(Object... args) throws ReflectException {
+        synchronized (mMethodLock) {
+            prepareMethod();
+            try {
+                return assessMethod.invoke(mObject, args);
+            } catch (Throwable throwable) {
+                throw new ReflectException(throwable);
+            }
+        }
+    }
+
+    public void setStaticType(int staticValue) {
+        this.staticType = staticValue;
+    }
+
+    private void prepareMethod() throws ReflectException {
+        synchronized (mMethodLock) {
+            try {
+                if (assessMethod != null) {
+                    return;
+                }
+                Method findMethod = mMethod;
+                if (findMethod == null) {
+                    String className = null;
+                    String filedName = mName;
+                    if (!TextUtils.isEmpty(mName)) {
+                        int lastDotIndex = mName.lastIndexOf(".");
+                        if (lastDotIndex >= 0 && lastDotIndex < mName.length() - 1) {
+                            className = mName.substring(0, lastDotIndex);
+                            filedName = mName.substring(lastDotIndex + 1);
+                        }
+                    }
+                    if (className != null){
+                        try {
+                            Class<?> relfectClass = Class.forName(className);
+                            findMethod = findMethod(relfectClass, filedName);
+                        }catch (Throwable ignore){
+
+                        }
+                    }
+                    if (findMethod == null){
+                        Class<?> findClass = null;
+                        if (mClass != null) {
+                            findClass = mClass;
+                        } else if (mClassName != null) {
+                            findClass = Class.forName(mClassName);
+                        }
+                        findMethod = findMethod(findClass, filedName);
+                    }
+                }
+                if (findMethod == null) {
+                    throw new NoSuchMethodException("can not find method: " + mName);
+                }
+                int modifiers = findMethod.getModifiers();
+                if (!Modifier.isPublic(modifiers) ||
+                        !Modifier.isPublic(findMethod
+                                .getDeclaringClass()
+                                .getModifiers())) {
+                    if (!findMethod.isAccessible()) {
+                        findMethod.setAccessible(true);
+                    }
+                }
+                assessMethod = findMethod;
+            } catch (Throwable throwable) {
+                throw new ReflectException(throwable);
+            }
+
+        }
+    }
+
+    private Method findMethod(Class<?> findClass, String filedName) {
+        for (Class<?> clazz = findClass; clazz != null; clazz = clazz.getSuperclass()) {
+            try {
+                Method method = clazz.getDeclaredMethod(filedName, mParameterTypes);
+                boolean staticModifiers = Modifier.isStatic(method.getModifiers());
+                if ((staticType == 0 && staticModifiers) || (staticType == 1 && !staticModifiers)) {
+                    continue;
+                }
+                return  method;
+            } catch (Throwable ignore) {
+            }
+        }
+        return null;
+    }
+
+}

+ 311 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/ReflectProxy.java

@@ -0,0 +1,311 @@
+package com.norman.webviewup.lib.reflect;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+
+class ReflectProxy {
+
+    private final Object mProxyLock = new Object();
+
+
+
+    private final Map<MethodSignature,Invoke> mInvokeMap = new HashMap<>();
+
+    private Class<?>[] mAllInterfaces;
+
+    private Class<?> mProxyClass;
+    private Class<?> mClass;
+
+    private String mClassName;
+
+
+    private Object mTargetObject;
+
+
+    public ReflectProxy(Class<?> clazz) {
+        this.mClass = clazz;
+    }
+
+    public ReflectProxy(String className) {
+        this.mClassName = className;
+    }
+
+    public void addInvoke(Invoke invoke) throws ReflectException {
+       addInvoke(invoke,true);
+    }
+
+    public void addInvoke(Invoke invoke,boolean override) throws ReflectException {
+        if (invoke == null) return;
+        synchronized (mProxyLock) {
+            prepareAllInterface();
+            try {
+                if (!override && mInvokeMap.containsKey(invoke.signature)){
+                    return;
+                }
+                mInvokeMap.put(invoke.signature,invoke);
+            } catch (Throwable throwable) {
+                throw new ReflectException(throwable);
+            }
+        }
+    }
+
+    public void setTarget(Object target) {
+        this.mTargetObject = target;
+    }
+
+    public Object newProxyInstance() throws ReflectException {
+        synchronized (mProxyLock) {
+            try {
+                prepareAllInterface();
+                return Proxy.newProxyInstance(
+                        mProxyClass.getClassLoader(),
+                        mAllInterfaces,
+                        new InvocationHandler() {
+                            @Override
+                            public Object invoke(Object proxyObject, Method method, Object[] args)  {
+                                InvokeContext invokeContext = new InvokeContext(
+                                        mTargetObject,
+                                        proxyObject,
+                                        mProxyClass,
+                                        args,
+                                        method);
+
+                                Invoke invoke = mInvokeMap.get(invokeContext.signature);
+                                if (invoke != null) {
+                                    invoke.onInvoke(invokeContext);
+                                }
+                                return invokeContext.replace ?
+                                        invokeContext.replaceResult :
+                                        invokeContext.invoke();
+
+                            }
+                        });
+            } catch (Throwable throwable) {
+
+                throw new ReflectException(throwable);
+            }
+
+        }
+
+    }
+
+    private void prepareAllInterface() throws ReflectException {
+        synchronized (mProxyLock) {
+            try {
+                if (mAllInterfaces != null && mAllInterfaces.length !=0) {
+                    return;
+                }
+                if (mProxyClass == null) {
+                    mProxyClass = mClass;
+                }
+                if (mProxyClass == null) {
+                    mProxyClass = Class.forName(mClassName);
+                }
+                List<Class<?>> interfaceList = new ArrayList<>();
+                Queue<Class<?>> findClassQueue = new LinkedList<>();
+                findClassQueue.add(mProxyClass);
+                while (!findClassQueue.isEmpty()) {
+                    Class<?> findClass = findClassQueue.remove();
+                    if (findClass.isInterface()) {
+                        if (!interfaceList.contains(findClass)) {
+                            interfaceList.add(findClass);
+                        }
+                    }
+                    Class<?> superClass = findClass != Object.class ?
+                            findClass.getSuperclass() :
+                            null;
+                    if (superClass != null) {
+                        findClassQueue.add(superClass);
+                    }
+                    Class<?>[] interfaces = findClass.getInterfaces();
+                    findClassQueue.addAll(Arrays.asList(interfaces));
+                }
+
+                if (interfaceList.size() == 0) {
+                    throw new IllegalArgumentException(mProxyClass + " not exist interfaces");
+                }
+                mAllInterfaces = new Class[interfaceList.size()];
+                mAllInterfaces = interfaceList.toArray(mAllInterfaces);
+            } catch (Throwable throwable) {
+                throw new ReflectException(throwable);
+            }
+        }
+
+
+    }
+
+    public static abstract class Invoke {
+
+        private final MethodSignature signature;
+
+        public Invoke(String name, Class<?>... paramTypes) {
+            signature = new MethodSignature(name, paramTypes);
+        }
+
+        protected abstract void onInvoke(InvokeContext invokeContext);
+
+
+
+        public String getName() {
+            return signature.name;
+        }
+
+        public Class<?>[] getParamTypes() {
+            return signature.paramTypes;
+        }
+
+        MethodSignature getSignature() {
+            return signature;
+        }
+    }
+
+    static class MethodSignature {
+        private final String name;
+
+        private final Class<?>[] paramTypes;
+
+        public MethodSignature(String name, Class<?>[] paramTypes) {
+            this.name = name;
+            this.paramTypes = paramTypes == null ? new Class<?>[0] : paramTypes;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            MethodSignature that = (MethodSignature) o;
+            return Objects.equals(name, that.name) && Arrays.equals(paramTypes, that.paramTypes);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = Objects.hash(name);
+            result = 31 * result + Arrays.hashCode(paramTypes);
+            return result;
+        }
+    }
+
+    public static class InvokeContext {
+        public final Object target;
+
+        public final Object proxy;
+
+        public final Class<?> proxyClass;
+
+        public final Object[] args;
+
+        private final Method method;
+
+        private Object replaceResult;
+
+        private Object invokeResult;
+        private boolean invokeCalled;
+
+        private boolean replace;
+
+        private static final Method HASH_CODE;
+        private static final Method EQUALS;
+        private static final Method TO_STRING;
+
+        private final MethodSignature signature;
+
+        static {
+            Class<Object> object = Object.class;
+            try {
+                HASH_CODE = object.getDeclaredMethod("hashCode");
+                EQUALS = object.getDeclaredMethod("equals", object);
+                TO_STRING = object.getDeclaredMethod("toString");
+            } catch (NoSuchMethodException e) {
+                // Never happens.
+                throw new Error(e);
+            }
+        }
+
+        public InvokeContext(Object target,
+                             Object proxy,
+                             Class<?> proxyClass,
+                             Object[] args,
+                             Method method) {
+            this.target = target;
+            this.proxy = proxy;
+            this.proxyClass = proxyClass;
+            this.args = args;
+            this.method = method;
+            this.signature = new MethodSignature(method.getName(), method.getParameterTypes());
+        }
+
+        public final void setResult(Object replaceResult) {
+            this.replaceResult = replaceResult;
+            this.replace = true;
+        }
+
+        public final Object invoke() throws ReflectException {
+            try {
+                if (invokeCalled) {
+                    return invokeResult;
+                }
+                if (method.equals(HASH_CODE)) {
+                    invokeResult = System.identityHashCode(proxy);
+                } else if (method.equals(EQUALS)) {
+                    invokeResult = proxy == args[0];
+                } else if (method.equals(TO_STRING)) {
+                    invokeResult = proxyClass.getName();
+                } else if (target != null) {
+                    invokeResult = method.invoke(target, args);
+                } else {
+                    Class<?> returnType = method.getReturnType();
+                    if (returnType.isPrimitive()) {
+                        if (boolean.class == returnType) {
+                           invokeResult = false;
+                        } else if (int.class == returnType) {
+                            invokeResult = 0;
+                        } else if (long.class == returnType) {
+                            invokeResult = 0;
+                        } else if (short.class == returnType) {
+                            invokeResult = 0;
+                        } else if (byte.class == returnType) {
+                            invokeResult =0;
+                        } else if (double.class == returnType) {
+                            invokeResult =0.0d;
+                        } else if (float.class == returnType) {
+                            invokeResult =0.0f;
+                        } else if (char.class == returnType) {
+                            invokeResult = '\u0000';
+                        } else  {
+                            invokeResult = null;
+                        }
+                    } else {
+                        invokeResult = null;
+                    }
+                }
+                invokeCalled = true;
+                return invokeResult;
+            } catch (Throwable e) {
+                throw new ReflectException(e);
+            }
+        }
+
+        public final String getName() {
+            return method.getName();
+        }
+
+        public final Class<?>[] getParameterTypes() {
+            return method.getParameterTypes();
+        }
+
+        public final Annotation[][] getParameterAnnotations() {
+            return method.getParameterAnnotations();
+        }
+    }
+
+}

+ 221 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/RuntimeAccess.java

@@ -0,0 +1,221 @@
+package com.norman.webviewup.lib.reflect;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.ClassType;
+import com.norman.webviewup.lib.reflect.annotation.Constructor;
+import com.norman.webviewup.lib.reflect.annotation.Field;
+import com.norman.webviewup.lib.reflect.annotation.Method;
+import com.norman.webviewup.lib.reflect.annotation.ParameterName;
+import com.norman.webviewup.lib.reflect.annotation.ParameterType;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class RuntimeAccess<T> {
+
+    private final static Map<Class<?>, Object> STATIC_ACCESS_MAP = new ConcurrentHashMap<>();
+
+    private volatile Object assessObject;
+
+    private final Class<T> buildClass;
+
+    private T proxy;
+
+
+    private boolean staticAccess;
+
+    public RuntimeAccess(Class<T> buildClass) {
+        this.buildClass = buildClass;
+    }
+
+    public RuntimeAccess(Class<T> buildClass, Object obj) {
+        this.buildClass = buildClass;
+        this.assessObject = obj;
+    }
+
+    public void setAssessObject(Object assessObject) {
+        this.assessObject = assessObject;
+    }
+
+
+    public static <T> T staticAccess(Class<T> buildClass) {
+        Object access = STATIC_ACCESS_MAP.get(buildClass);
+        if (access != null) {
+            return (T) access;
+        }
+        RuntimeAccess<T> runtimeAccess = new RuntimeAccess<>(buildClass);
+        access = runtimeAccess.get();
+        STATIC_ACCESS_MAP.put(buildClass, access);
+        return (T) access;
+    }
+
+    public static <T> T objectAccess(Class<T> buildClass, Object object) {
+        RuntimeAccess<T> runtimeAccess = new RuntimeAccess<>(buildClass, object);
+        return runtimeAccess.get();
+    }
+
+    boolean isStaticAccess() {
+        return staticAccess;
+    }
+
+    public T get() throws ReflectException {
+        try {
+            if (proxy != null) {
+                return proxy;
+            }
+            if (!buildClass.isInterface()) {
+                throw new IllegalArgumentException("build class must be interface");
+            }
+            ClassType classTypeAnnotation = buildClass.getAnnotation(ClassType.class);
+            ClassName classNameAnnotation = buildClass.getAnnotation(ClassName.class);
+            Class<?> annotationClass = null;
+            if (classNameAnnotation != null) {
+                annotationClass = Class.forName(classNameAnnotation.value());
+            } else if (classTypeAnnotation != null) {
+                annotationClass = classTypeAnnotation.value();
+            }
+            final Class<?> reflectClass = annotationClass;
+            if (reflectClass == null) {
+                throw new NullPointerException(buildClass + " must use " + ClassType.class + " or" + ClassName.class);
+            }
+            ReflectProxy reflectProxy = new ReflectProxy(buildClass);
+            boolean allStaticAnnotation = true;
+            for (java.lang.reflect.Method method : buildClass.getMethods()) {
+                Method methodAnnotation = method.getAnnotation(Method.class);
+                Constructor constructorAnnotation = method.getAnnotation(Constructor.class);
+                Field filedAnnotation = method.getAnnotation(Field.class);
+                ReflectProxy.Invoke invoke = null;
+                if (methodAnnotation != null) {
+                    if (methodAnnotation.type() != Method.STATIC) {
+                        allStaticAnnotation = false;
+                    }
+                    invoke = new ReflectProxy.Invoke(method.getName(), method.getParameterTypes()) {
+                        @Override
+                        protected void onInvoke(ReflectProxy.InvokeContext invokeContext) {
+                            Class<?>[] methodParameterTypes = findParameterTypes(invokeContext);
+                            String methodName = methodAnnotation.value();
+                            if (TextUtils.isEmpty(methodName)) {
+                                methodName = invokeContext.getName();
+                            }
+                            Object assessObj = checkAssessObject(reflectClass);
+                            ReflectMethod reflectMethod = new ReflectMethod(reflectClass, methodName, methodParameterTypes);
+                            if (assessObj != null) {
+                                reflectMethod = new ReflectMethod(assessObj, methodName, methodParameterTypes);
+                            }
+                            reflectMethod.setStaticType(methodAnnotation.type());
+                            Object result = reflectMethod.invoke(invokeContext.args);
+                            invokeContext.setResult(result);
+                        }
+                    };
+                } else if (constructorAnnotation != null) {
+                    invoke = new ReflectProxy.Invoke(method.getName(), method.getParameterTypes()) {
+                        @Override
+                        protected void onInvoke(ReflectProxy.InvokeContext invokeContext) {
+                            Class<?>[] constructorParameterTypes = findParameterTypes(invokeContext);
+                            ReflectConstructor reflectMethod = new ReflectConstructor(reflectClass, constructorParameterTypes);
+                            Object result = reflectMethod.newInstance(invokeContext.args);
+                            invokeContext.setResult(result);
+                        }
+                    };
+                } else if (filedAnnotation != null) {
+                    if (filedAnnotation.type() != Field.STATIC) {
+                        allStaticAnnotation = false;
+                    }
+                    Class<?> returnType = method.getReturnType();
+                    if (returnType == Void.TYPE && method.getParameterTypes().length == 0) {
+                        throw new IllegalArgumentException("method of set filed  must set parameter");
+                    }
+                    invoke = new ReflectProxy.Invoke(method.getName(), method.getParameterTypes()) {
+                        @Override
+                        protected void onInvoke(ReflectProxy.InvokeContext invokeContext) {
+                            String filedName = filedAnnotation.value();
+                            if (TextUtils.isEmpty(filedName)) {
+                                filedName = invokeContext.getName();
+                            }
+                            Object assessObj = checkAssessObject(reflectClass);
+                            ReflectField reflectField = new ReflectField(reflectClass, filedName);
+                            if (assessObj != null) {
+                                reflectField = new ReflectField(assessObj, filedName);
+                            }
+                            reflectField.setStaticType(filedAnnotation.type());
+                            Class<?> returnType = method.getReturnType();
+                            if (returnType == Void.TYPE) {
+                                reflectField.set(invokeContext.args[0]);
+                                invokeContext.setResult(null);
+                            } else {
+                                Object kkk = reflectField.get();
+                                invokeContext.setResult(kkk);
+                            }
+                        }
+                    };
+                }
+                if (invoke != null) {
+                    reflectProxy.addInvoke(invoke);
+                }
+            }
+            proxy = (T) reflectProxy.newProxyInstance();
+            staticAccess = allStaticAnnotation;
+            return proxy;
+        } catch (
+                Throwable throwable) {
+            throw new ReflectException(throwable);
+        }
+    }
+
+    private Object checkAssessObject(Class<?> reflectCls) {
+        Object obj = assessObject;
+        if (obj != null && !Objects.equals(obj.getClass(), reflectCls)) {
+            throw new IllegalArgumentException(reflectCls + " is not same as " + assessObject.getClass());
+        }
+        return obj;
+    }
+
+    @NonNull
+    private static Class<?>[] findParameterTypes(ReflectProxy.InvokeContext invokeContext) {
+        Annotation[][] invokeAnnotations = invokeContext.getParameterAnnotations();
+        Class<?>[] invokeTypes = invokeContext.getParameterTypes();
+        Class<?>[] methodParameterTypes = new Class<?>[invokeTypes.length];
+        for (int i = 0; i < methodParameterTypes.length; i++) {
+            Annotation[] annotations = invokeAnnotations[i];
+            ParameterName parameterNameAnnotation = null;
+            ParameterType parameterTypeAnnotation = null;
+            if (annotations != null) {
+                for (Annotation annotation : annotations) {
+                    if (annotation instanceof ParameterName) {
+                        parameterNameAnnotation = (ParameterName) annotation;
+                        break;
+                    } else if (annotation instanceof ParameterType) {
+                        parameterTypeAnnotation = (ParameterType) annotation;
+                        break;
+                    }
+                }
+            }
+            Class<?> parameterClass = null;
+            if (parameterNameAnnotation != null) {
+                String className = parameterNameAnnotation.value();
+                if (!TextUtils.isEmpty(className)) {
+                    try {
+                        parameterClass = Class.forName(className);
+                    } catch (Throwable throwable) {
+                        throw new ReflectException(throwable);
+                    }
+                }
+            } else if (parameterTypeAnnotation != null) {
+                parameterClass = parameterTypeAnnotation.value();
+            }
+            if (parameterClass == null) {
+                parameterClass = invokeTypes[i];
+            }
+            methodParameterTypes[i] = parameterClass;
+        }
+        return methodParameterTypes;
+    }
+
+
+}

+ 122 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/RuntimeProxy.java

@@ -0,0 +1,122 @@
+package com.norman.webviewup.lib.reflect;
+
+import android.text.TextUtils;
+
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.ClassType;
+import com.norman.webviewup.lib.reflect.annotation.Method;
+
+
+public abstract class RuntimeProxy {
+
+
+    private final Object mProxyLock = new Object();
+
+    private final ThreadLocal<ReflectProxy.InvokeContext> mInvokeContextThreadLocal = new ThreadLocal<>();
+
+
+    private volatile Object target;
+
+    private Object proxy;
+
+    private ReflectProxy reflectProxy;
+
+    private final Class<?> proxyClass;
+
+    public RuntimeProxy() {
+        this(null);
+    }
+
+    public RuntimeProxy(Class<?> proxyClass) {
+        this.proxyClass = proxyClass;
+    }
+
+    public void setTarget(Object target) {
+        synchronized (mProxyLock){
+            this.target = target;
+            if (reflectProxy != null){
+                reflectProxy.setTarget(target);
+            }
+        }
+    }
+
+    public Object get() throws ReflectException {
+        try {
+            synchronized (mProxyLock) {
+                if (proxy != null) {
+                    return  proxy;
+                }
+
+                Class<?> reflectClass = proxyClass;
+                if (reflectClass == null){
+                    ClassType classTypeAnnotation = getClass().getAnnotation(ClassType.class);
+                    ClassName classNameAnnotation = getClass().getAnnotation(ClassName.class);
+                    Class<?> annotationClass = null;
+                    if (classNameAnnotation != null) {
+                        annotationClass = Class.forName(classNameAnnotation.value());
+                    } else if (classTypeAnnotation != null) {
+                        annotationClass = classTypeAnnotation.value();
+                    }
+                    reflectClass = annotationClass;
+                }
+                ReflectProxy reflectProxy = new ReflectProxy(reflectClass);
+                for (Class<?> clazz = getClass(); clazz != null; clazz = clazz.getSuperclass()) {
+                    java.lang.reflect.Method[] methods = clazz.getDeclaredMethods();
+                    for (java.lang.reflect.Method method : methods) {
+                        Method methodAnnotation = method.getAnnotation(Method.class);
+                        if (methodAnnotation != null) {
+                            String methodName = methodAnnotation.value();
+                            if (TextUtils.isEmpty(methodName)) {
+                                methodName = method.getName();
+                            }
+                            ReflectProxy.Invoke invoke = new ReflectProxy.Invoke(
+                                    methodName,
+                                    method.getParameterTypes()) {
+                                @Override
+                                protected void onInvoke(ReflectProxy.InvokeContext invokeContext) {
+                                    try {
+                                        if (invokeContext.target == null) return;
+                                        if (!method.isAccessible()) {
+                                            method.setAccessible(true);
+                                        }
+                                        mInvokeContextThreadLocal.set(invokeContext);
+                                        Object result = method.invoke(RuntimeProxy.this, invokeContext.args);
+                                        invokeContext.setResult(result);
+                                    } catch (Throwable e) {
+                                        throw new ReflectException(e);
+                                    } finally {
+                                        mInvokeContextThreadLocal.set(null);
+                                    }
+                                }
+                            };
+                            reflectProxy.addInvoke(invoke,false);
+                        }
+                    }
+                }
+                reflectProxy.setTarget(target);
+                proxy = reflectProxy.newProxyInstance();
+                this.reflectProxy = reflectProxy;
+                return  proxy;
+            }
+        } catch (Throwable throwable) {
+            throw new ReflectException(throwable);
+        }
+    }
+
+
+    protected Object invoke() {
+        ReflectProxy.InvokeContext invokeContext = mInvokeContextThreadLocal.get();
+        if (invokeContext == null) {
+            return null;
+        }
+        return invokeContext.invoke();
+    }
+
+    protected Object getTarget() {
+        ReflectProxy.InvokeContext invokeContext = mInvokeContextThreadLocal.get();
+        if (invokeContext == null) {
+            return null;
+        }
+        return invokeContext.target;
+    }
+}

+ 15 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/annotation/ClassName.java

@@ -0,0 +1,15 @@
+package com.norman.webviewup.lib.reflect.annotation;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({TYPE})
+@Inherited
+public @interface ClassName {
+    String value();
+}

+ 15 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/annotation/ClassType.java

@@ -0,0 +1,15 @@
+package com.norman.webviewup.lib.reflect.annotation;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({TYPE})
+@Inherited
+public @interface ClassType {
+    Class<?> value();
+}

+ 12 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/annotation/Constructor.java

@@ -0,0 +1,12 @@
+package com.norman.webviewup.lib.reflect.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({METHOD})
+public @interface Constructor {
+}

+ 19 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/annotation/Field.java

@@ -0,0 +1,19 @@
+package com.norman.webviewup.lib.reflect.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({METHOD})
+public @interface Field {
+    int STATIC = 1;
+    int OBJECT = 0;
+    int ANY = -1;
+
+    String value() default "";
+
+    int type() default OBJECT;
+}

+ 21 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/annotation/Method.java

@@ -0,0 +1,21 @@
+package com.norman.webviewup.lib.reflect.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({METHOD})
+@Inherited
+public @interface Method {
+    int STATIC = 1;
+    int OBJECT = 0;
+    int ANY = -1;
+
+    String value() default "";
+
+    int type() default OBJECT;
+}

+ 13 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/annotation/ParameterName.java

@@ -0,0 +1,13 @@
+package com.norman.webviewup.lib.reflect.annotation;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({PARAMETER})
+public @interface ParameterName {
+    String value();
+}

+ 13 - 0
app/src/main/java/com/norman/webviewup/lib/reflect/annotation/ParameterType.java

@@ -0,0 +1,13 @@
+package com.norman.webviewup.lib.reflect.annotation;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({PARAMETER})
+public @interface ParameterType {
+    Class<?> value();
+}

+ 68 - 0
app/src/main/java/com/norman/webviewup/lib/service/binder/BinderHook.java

@@ -0,0 +1,68 @@
+package com.norman.webviewup.lib.service.binder;
+
+import android.os.IBinder;
+
+
+public abstract class BinderHook {
+
+    private final Object sync = new Object();
+
+    private IBinder originalBinder;
+
+    private ProxyBinder proxyBinder;
+
+    private boolean currentHook;
+    private boolean recentHook;
+
+
+    public BinderHook() {
+    }
+
+    public final void hook() {
+        synchronized (sync) {
+            if (currentHook) {
+                return;
+            }
+            if (recentHook) {
+                onProxyBinderReplace(proxyBinder);
+            } else {
+                IBinder original = onTargetBinderObtain();
+                ProxyBinder proxy = onProxyBinderCreate(original);
+                onProxyBinderReplace(proxy);
+                this.originalBinder = original;
+                this.proxyBinder = proxy;
+                this.recentHook = true;
+            }
+            currentHook = true;
+        }
+    }
+
+    public boolean isHook() {
+        synchronized (sync) {
+            return currentHook;
+        }
+    }
+
+    public final boolean restore() {
+        synchronized (sync) {
+            if (!currentHook) {
+                return false;
+            }
+            onTargetBinderRestore(originalBinder);
+            currentHook = false;
+            return true;
+        }
+    }
+
+    protected ProxyBinder getProxyBinder() {
+        return proxyBinder;
+    }
+
+    protected abstract IBinder onTargetBinderObtain();
+
+    protected abstract ProxyBinder onProxyBinderCreate(IBinder binder);
+
+    protected abstract void onTargetBinderRestore(IBinder binder);
+
+    protected abstract void onProxyBinderReplace(ProxyBinder binder);
+}

+ 83 - 0
app/src/main/java/com/norman/webviewup/lib/service/binder/ProxyBinder.java

@@ -0,0 +1,83 @@
+package com.norman.webviewup.lib.service.binder;
+
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+import java.io.FileDescriptor;
+
+
+public class ProxyBinder implements IBinder {
+
+    private final IBinder mRemoteBinder;
+
+    private final IInterface mStubIInterface;
+
+    private IInterface mProxyIInterface;
+
+
+    public ProxyBinder(IInterface stubIInterface) {
+        this.mStubIInterface = stubIInterface;
+        this.mRemoteBinder = stubIInterface.asBinder();
+    }
+
+    public ProxyBinder(IInterface stubIInterface,IInterface proxyIInterface) {
+        this(stubIInterface);
+        setProxyInterface(proxyIInterface);
+    }
+
+    public synchronized void setProxyInterface(IInterface proxyIInterface) {
+        this.mProxyIInterface = proxyIInterface;
+    }
+
+    public synchronized IInterface getProxyIInterface() {
+        return mProxyIInterface;
+    }
+
+    @Override
+    public String getInterfaceDescriptor() throws RemoteException {
+        return mRemoteBinder.getInterfaceDescriptor();
+    }
+
+    @Override
+    public boolean pingBinder() {
+        return mRemoteBinder.pingBinder();
+    }
+
+    @Override
+    public boolean isBinderAlive() {
+        return mRemoteBinder.isBinderAlive();
+    }
+
+    @Override
+    public synchronized IInterface queryLocalInterface(String descriptor) {
+        return mProxyIInterface != null? mProxyIInterface :mStubIInterface;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, String[] args) throws RemoteException {
+        mRemoteBinder.dump(fd, args);
+    }
+
+    @Override
+    public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
+        mRemoteBinder.dumpAsync(fd, args);
+    }
+
+    @Override
+    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
+        return mRemoteBinder.transact(code, data, reply, flags);
+    }
+
+    @Override
+    public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException {
+        mRemoteBinder.linkToDeath(recipient, flags);
+    }
+
+    @Override
+    public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
+        return mRemoteBinder.unlinkToDeath(recipient, flags);
+    }
+}
+

+ 16 - 0
app/src/main/java/com/norman/webviewup/lib/service/interfaces/IActivityThread.java

@@ -0,0 +1,16 @@
+package com.norman.webviewup.lib.service.interfaces;
+
+
+import android.os.IInterface;
+
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.Field;
+
+@ClassName("android.app.ActivityThread")
+public interface IActivityThread {
+    @Field(value = "sPackageManager", type = Field.STATIC)
+    void setPackageManager(IInterface iInterface);
+
+    @Field(value = "sPackageManager", type = Field.STATIC)
+    IInterface getPackageManager();
+}

+ 12 - 0
app/src/main/java/com/norman/webviewup/lib/service/interfaces/IContextImpl.java

@@ -0,0 +1,12 @@
+package com.norman.webviewup.lib.service.interfaces;
+
+import android.content.pm.PackageManager;
+
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.Field;
+
+@ClassName("android.app.ContextImpl")
+public interface IContextImpl {
+    @Field("mPackageManager")
+    void setPackageManager(PackageManager packageManager);
+}

+ 14 - 0
app/src/main/java/com/norman/webviewup/lib/service/interfaces/IPackageManager.java

@@ -0,0 +1,14 @@
+package com.norman.webviewup.lib.service.interfaces;
+
+import android.os.IInterface;
+
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.Method;
+
+@ClassName("android.content.pm.IPackageManager$Stub")
+public interface IPackageManager {
+
+    String SERVICE = "package";
+    @Method(value = "asInterface", type = Method.STATIC)
+    IInterface asInterface(android.os.IBinder obj);
+}

+ 19 - 0
app/src/main/java/com/norman/webviewup/lib/service/interfaces/IServiceManager.java

@@ -0,0 +1,19 @@
+package com.norman.webviewup.lib.service.interfaces;
+
+import android.os.IBinder;
+
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.Field;
+import com.norman.webviewup.lib.reflect.annotation.Method;
+
+import java.util.Map;
+
+@ClassName(value = "android.os.ServiceManager")
+public interface IServiceManager {
+
+    @Method(value = "getService", type = Method.STATIC)
+    IBinder getService(String name);
+
+    @Field(value = "sCache", type = Field.STATIC)
+    Map<String, IBinder> getServiceCache();
+}

+ 16 - 0
app/src/main/java/com/norman/webviewup/lib/service/interfaces/IVMRuntime.java

@@ -0,0 +1,16 @@
+package com.norman.webviewup.lib.service.interfaces;
+
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.Field;
+import com.norman.webviewup.lib.reflect.annotation.Method;
+
+@ClassName("dalvik.system.VMRuntime")
+public interface IVMRuntime {
+    @Method(value = "getRuntime", type = Field.STATIC)
+    Object getRuntime();
+
+    @Method(value = "is64Bit")
+    boolean is64Bit();
+
+
+}

+ 18 - 0
app/src/main/java/com/norman/webviewup/lib/service/interfaces/IWebViewFactory.java

@@ -0,0 +1,18 @@
+package com.norman.webviewup.lib.service.interfaces;
+
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.Field;
+
+@ClassName("android.webkit.WebViewFactory")
+public interface IWebViewFactory {
+
+
+    @Field(value = "sProviderLock", type = Field.STATIC)
+    Object getProviderLock();
+
+    @Field(value = "sProviderInstance", type = Field.STATIC)
+    Object getProviderInstance();
+
+    @Field(value = "sProviderInstance", type = Field.STATIC)
+    void setProviderInstance(Object instance);
+}

+ 18 - 0
app/src/main/java/com/norman/webviewup/lib/service/interfaces/IWebViewProviderResponse.java

@@ -0,0 +1,18 @@
+package com.norman.webviewup.lib.service.interfaces;
+
+import android.content.pm.PackageInfo;
+
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.Field;
+
+
+@ClassName(value = "android.webkit.WebViewProviderResponse")
+public interface IWebViewProviderResponse {
+
+    @Field(value = "packageInfo")
+    void setPackageInfo(PackageInfo packageInfo);
+
+    @Field(value = "packageInfo")
+    PackageInfo getPackageInfo();
+
+}

+ 20 - 0
app/src/main/java/com/norman/webviewup/lib/service/interfaces/IWebViewUpdateService.java

@@ -0,0 +1,20 @@
+package com.norman.webviewup.lib.service.interfaces;
+
+import android.content.pm.PackageInfo;
+import android.os.IInterface;
+
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.Method;
+
+@ClassName(value = "android.webkit.IWebViewUpdateService$Stub")
+public interface IWebViewUpdateService {
+    String SERVICE = "webviewupdate";
+
+    @Method(value = "asInterface", type = Method.STATIC)
+    IInterface asInterface(android.os.IBinder obj);
+
+    @Method(value = "getCurrentWebViewPackage")
+    PackageInfo getCurrentWebViewPackage();
+
+}
+

+ 40 - 0
app/src/main/java/com/norman/webviewup/lib/service/proxy/PackageManagerProxy.java

@@ -0,0 +1,40 @@
+package com.norman.webviewup.lib.service.proxy;
+
+import android.content.ComponentName;
+import android.content.pm.PackageInfo;
+
+import com.norman.webviewup.lib.reflect.RuntimeProxy;
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.Method;
+
+
+@ClassName("android.content.pm.IPackageManager$Stub")
+public abstract class PackageManagerProxy extends RuntimeProxy {
+
+    public PackageManagerProxy() {
+        super();
+    }
+
+
+    @Method("getPackageInfo")
+    protected abstract PackageInfo getPackageInfo(String packageName, long flags, int userId);
+
+    @Method("getPackageInfo")
+    protected abstract  PackageInfo getPackageInfo(String packageName, int flags, int userId);
+
+
+    @Method("getPackageInfo")
+    protected abstract PackageInfo getPackageInfo(String packageName, int flags);
+
+
+    @Method("getComponentEnabledSetting")
+    protected abstract int getComponentEnabledSetting(ComponentName componentName, int userId);
+
+    @Method("getComponentEnabledSetting")
+    protected abstract int getComponentEnabledSetting(ComponentName componentName);
+
+
+    @Method("asBinder")
+    protected abstract android.os.IBinder asBinder();
+
+}

+ 25 - 0
app/src/main/java/com/norman/webviewup/lib/service/proxy/WebViewUpdateServiceProxy.java

@@ -0,0 +1,25 @@
+package com.norman.webviewup.lib.service.proxy;
+
+import com.norman.webviewup.lib.reflect.RuntimeProxy;
+import com.norman.webviewup.lib.reflect.annotation.ClassName;
+import com.norman.webviewup.lib.reflect.annotation.Method;
+
+
+@ClassName(value = "android.webkit.IWebViewUpdateService$Stub")
+public abstract class WebViewUpdateServiceProxy extends RuntimeProxy {
+
+    public WebViewUpdateServiceProxy() {
+        super();
+    }
+
+    @Method("waitForAndGetProvider")
+    protected abstract Object waitForAndGetProvider();
+
+    @Method("asBinder")
+    protected abstract android.os.IBinder asBinder();
+
+    @Method("isMultiProcessEnabled")
+    protected abstract boolean isMultiProcessEnabled();
+
+
+}

+ 116 - 0
app/src/main/java/com/norman/webviewup/lib/util/ApkUtils.java

@@ -0,0 +1,116 @@
+package com.norman.webviewup.lib.util;
+
+import android.os.Build;
+import android.text.TextUtils;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class ApkUtils {
+
+
+    public static void extractNativeLibrary(String apkPath, String soDir) {
+        ZipFile zipFile = null;
+        try {
+            if (TextUtils.isEmpty(apkPath)) {
+                throw new NullPointerException("apkPath is empty");
+            }
+            if (TextUtils.isEmpty(soDir)) {
+                throw new NullPointerException("apkPath is empty");
+            }
+            zipFile = new ZipFile(new File(apkPath));
+            Enumeration<? extends ZipEntry> entries = zipFile.entries();
+            Map<String, List<ZipEntry>> soLibEntryMap = new HashMap<>();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = entries.nextElement();
+                String entryName = entry.getName();
+                if (entryName.contains("../") || entry.isDirectory()) {
+                    continue;
+                }
+                if (!entryName.startsWith("lib/") && !entryName.endsWith(".so")) {
+                    continue;
+                }
+                String[] split = entry.getName().split("/");
+                if (split.length >= 3) {
+                    String abi = split[1];
+                    List<ZipEntry> list = soLibEntryMap.get(abi);
+                    if (list == null) {
+                        list = new ArrayList<>();
+                        soLibEntryMap.put(abi, list);
+                    }
+                    list.add(entry);
+                }
+            }
+            String[] supportedAbis = Build.SUPPORTED_ABIS;
+            for (String abi : supportedAbis) {
+                List<ZipEntry> entryList = soLibEntryMap.get(abi);
+                if (entryList == null) continue;
+                for (ZipEntry entry : entryList) {
+                    byte[] buffer = new byte[8192];
+                    InputStream inputStream = null;
+                    OutputStream outputStream = null;
+                    BufferedInputStream bufferedInput = null;
+                    BufferedOutputStream bufferedOutput = null;
+                    try {
+                        inputStream = zipFile.getInputStream(entry);
+                        String[] split = entry.getName().split("/");
+                        File targetFile = new File(soDir, abi + "/" + split[split.length - 1]);
+                        if (!targetFile.exists()) {
+                            File fileParentDir = targetFile.getParentFile();
+                            if (fileParentDir != null && !fileParentDir.exists()) {
+                                fileParentDir.mkdirs();
+                            }
+                            targetFile.createNewFile();
+                        }
+                        outputStream = new FileOutputStream(targetFile);
+                        bufferedInput = new BufferedInputStream(inputStream);
+                        bufferedOutput = new BufferedOutputStream(outputStream);
+                        int count;
+                        while ((count = bufferedInput.read(buffer)) > 0) {
+                            bufferedOutput.write(buffer, 0, count);
+                        }
+                        bufferedOutput.flush();
+                    } finally {
+                        if (bufferedOutput != null) {
+                            bufferedOutput.close();
+                        }
+                        if (outputStream != null) {
+                            outputStream.close();
+                        }
+                        if (bufferedInput != null) {
+                            bufferedInput.close();
+                        }
+                        if (inputStream != null) {
+                            inputStream.close();
+                        }
+                    }
+
+                }
+            }
+
+        } catch (IOException ioException) {
+            FileUtils.cleanDirectory(soDir);
+            throw new RuntimeException(ioException);
+        } finally {
+            if (zipFile != null) {
+                try {
+                    zipFile.close();
+                } catch (IOException ignore) {
+
+                }
+            }
+        }
+    }
+}

+ 42 - 0
app/src/main/java/com/norman/webviewup/lib/util/FileUtils.java

@@ -0,0 +1,42 @@
+package com.norman.webviewup.lib.util;
+
+import java.io.File;
+
+public class FileUtils {
+
+
+    public static void delete(File file) {
+        if (file.isFile()) {
+            file.delete();
+            return;
+        }
+        if (file.isDirectory()) {
+            File[] childFile = file.listFiles();
+            if (childFile == null || childFile.length == 0) {
+                file.delete();
+                return;
+            }
+            for (File f : childFile) {
+                delete(f);
+            }
+            file.delete();
+        }
+    }
+
+
+    public static void cleanDirectory(String path) {
+        cleanDirectory(new File(path));
+    }
+
+    public static void cleanDirectory(File file) {
+        if (file.isDirectory()) {
+            File[] childFile = file.listFiles();
+            if (childFile == null || childFile.length == 0) {
+                return;
+            }
+            for (File f : childFile) {
+                delete(f);
+            }
+        }
+    }
+}

+ 26 - 0
app/src/main/java/com/norman/webviewup/lib/util/ProcessUtils.java

@@ -0,0 +1,26 @@
+package com.norman.webviewup.lib.util;
+
+import android.os.Build;
+import android.os.Process;
+
+import com.norman.webviewup.lib.reflect.RuntimeAccess;
+import com.norman.webviewup.lib.service.interfaces.IVMRuntime;
+
+public class ProcessUtils {
+
+    public static boolean is64Bit() {
+        boolean process64bit = false;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            process64bit = Process.is64Bit();
+        } else {
+            try {
+                IVMRuntime vmRuntime = RuntimeAccess.staticAccess(IVMRuntime.class);
+                vmRuntime = RuntimeAccess.objectAccess(IVMRuntime.class, vmRuntime.getRuntime());
+                process64bit = vmRuntime.is64Bit();
+            } catch (Throwable ignore) {
+
+            }
+        }
+        return process64bit;
+    }
+}

+ 30 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 146 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,146 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="20dp"
+    tools:context="com.norman.webviewup.demo.MainActivity">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="System WebView:"
+            android:textSize="16dp"
+            android:textStyle="bold">
+
+        </TextView>
+
+        <TextView
+            android:id="@+id/systemWebViewPackageTextView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text=""
+            android:textSize="14dp">
+
+        </TextView>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Upgrade WebView:"
+            android:textSize="16dp"
+            android:textStyle="bold">
+
+        </TextView>
+
+        <TextView
+            android:id="@+id/upgradeWebViewPackageTextView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text=""
+            android:textSize="14dp">
+
+        </TextView>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Upgrade Status:"
+            android:textSize="16dp"
+            android:textStyle="bold">
+
+        </TextView>
+
+        <TextView
+            android:id="@+id/upgradeStatusTextView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text=""
+            android:textSize="14dp">
+
+        </TextView>
+
+    </LinearLayout>
+
+    <ProgressBar
+        android:id="@+id/upgradeProgressBar"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:max="100" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="30dp"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+
+        <Button
+            android:id="@+id/upgradeButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="upgrade"
+            android:textSize="20dp">
+
+        </Button>
+
+        <Button
+            android:id="@+id/webViewButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="test"
+            android:textSize="20dp">
+
+        </Button>
+
+    </LinearLayout>
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:id="@+id/upgradeErrorTextView"
+                android:textSize="14dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+
+            </TextView>
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</LinearLayout>
+
+
+

+ 12 - 0
app/src/main/res/layout/activity_webview.xml

@@ -0,0 +1,12 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.norman.webviewup.demo.WebViewActivity">
+
+    <WebView
+        android:id="@+id/webview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</FrameLayout>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 10 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">WebViewUpgradeDemo</string>
+</resources>

+ 10 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,10 @@
+<resources>
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">#6200EE</item>
+        <item name="colorPrimaryDark">#3700B3</item>
+        <item name="colorAccent">#03DAC5</item>
+    </style>
+
+</resources>

+ 20 - 0
build.gradle

@@ -0,0 +1,20 @@
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+    }
+    dependencies {
+        classpath "com.android.tools.build:gradle:4.1.3"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 17 - 0
gradle.properties

@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Sat May 22 18:51:13 IST 2021
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME

+ 172 - 0
gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 1 - 0
settings.gradle

@@ -0,0 +1 @@
+include ':app'