diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..b73537336
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,35 @@
+---
+name: Bug report
+about: Create a report to help us improve
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/bug_report_cn.md b/.github/ISSUE_TEMPLATE/bug_report_cn.md
new file mode 100644
index 000000000..74bfdf050
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report_cn.md
@@ -0,0 +1,29 @@
+---
+name: BUG反馈
+about: 中文BUG反馈
+
+---
+
+**反馈BUG之前,先issue里面搜看看有没有别人已经反馈过,重复的不予处理!!**
+
+## 问题描述
+
+(请尽量详细地描述你遇到的问题)
+
+## 复现步骤
+
+(请分步骤描述如何复现这个BUG,非毕现BUG请给出如何能大概率复现的步骤)
+
+## 环境
+
+机型:
+系统版本:
+ROM版本:(请区分内测版和开发版稳定版,除稳定版本外不予修复)
+Xposed 插件以及插件版本:
+VirtualXposed版本:
+
+## 补充
+
+(别的需要描述的内容)
+
+**写完之后,请自己再读一遍自己写的,如果你自己都读不懂,就不用说修复了**
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..2fb7441da
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,21 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+
+Add any other context or screenshots about the feature request here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request_cn.md b/.github/ISSUE_TEMPLATE/feature_request_cn.md
new file mode 100644
index 000000000..27cd8d15a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request_cn.md
@@ -0,0 +1,19 @@
+---
+name: 意见和建议
+about: Feature中文版
+
+---
+
+**BUG反馈请不要用这个模版,否则直接关闭!!**
+
+## 场景描述
+
+(请详细和精确地表述你的使用场景)
+
+## 希望的解决方案
+
+(你希望如何解决这个问题?)
+
+## 其他信息
+
+(其他你认为有用的信息)
\ No newline at end of file
diff --git a/.github/issue-close-app.yml b/.github/issue-close-app.yml
new file mode 100644
index 000000000..a504b93b9
--- /dev/null
+++ b/.github/issue-close-app.yml
@@ -0,0 +1,19 @@
+# Comment that will be sent if an issue is judged to be closed
+comment: "This issue is closed because it does not meet our issue template/为方便解决问题,请使用 issue 模版提交问题。"
+issueConfigs:
+# There can be several configs for different kind of issues.
+- content:
+# Example 1: bug report
+  - "Expected behavior"
+  - "To Reproduce"
+  - "Describe the bug"
+- content:
+# Example 2: feature request
+  - "Describe the solution you'd like"
+- content:
+  - "问题描述"
+  - "复现步骤"
+  - "环境"
+- content:
+  - "场景描述"
+  - "希望的解决方案"
\ No newline at end of file
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 000000000..1b547d34c
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,17 @@
+# Number of days of inactivity before an issue becomes stale
+daysUntilStale: 20
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 7
+# Issues with these labels will never be considered stale
+exemptLabels:
+  - bug
+  - security
+# Label to use when marking an issue as stale
+staleLabel: wontfix
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+  This issue has been automatically marked as stale because it has not had
+  recent activity. It will be closed if no further activity occurs. Thank you
+  for your contributions.
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: false
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..d1c4983af
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "VirtualApp/launcher"]
+	path = VirtualApp/launcher
+	url = https://github.com/android-hacker/Launcher3.git
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..a546f4d35
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,21 @@
+language: android
+android:
+  components:
+    - tools
+    - build-tools-28.0.3
+    - android-27
+    - android-28
+    - extra-android-m2repository
+    - extra-android-support
+
+install:
+  - echo y | sdkmanager 'ndk-bundle'
+  - echo y | sdkmanager 'cmake;3.6.4111459'
+  - echo y | sdkmanager 'lldb;3.0'
+
+before_install:
+  - chmod +x ./VirtualApp/gradlew
+
+script:
+  - cd VirtualApp
+  - ./gradlew assembleRelease
diff --git a/CHINESE.md b/CHINESE.md
index f285cbcb5..8a214fe75 100644
--- a/CHINESE.md
+++ b/CHINESE.md
@@ -1,80 +1,120 @@
-[![VA banner](https://raw.githubusercontent.com/asLody/VirtualApp/master/Logo.png)](https://github.com/asLody/VirtualApp)
+[![Build Status](https://travis-ci.org/android-hacker/VirtualXposed.svg?branch=exposed)](https://travis-ci.org/android-hacker/VirtualXposed)
 
 简介
----
-**VirtualApp**是一个**App虚拟化引擎**(简称`VA`)。
+-----
+**VirtualXposed** 是基于[VirtualApp](https://github.com/asLody/VirtualApp) 和 [epic](https://github.com/tiann/epic) 在**非ROOT**环境下运行Xposed模块的实现(支持5.0~9.0)。
 
-**VirtualApp已兼容Android 0(8.0 Preview)。**
+与 Xposed 相比,目前 VirtualXposed 有两个限制:
 
-VirtualApp在你的App内创建一个`虚拟空间`,你可以在虚拟空间内任意的`安装`、`启动`和`卸载`APK,这一切都与外部隔离,如同一个`沙盒`。
+1. 不支持修改系统(可以修改普通APP中对系统API的调用),因此重力工具箱,应用控制器等无法使用。
+2. 暂不支持资源HOOK,因此资源钩子不会起任何作用;使用资源HOOK的模块,相应的功能不会生效。
 
-运行在`VA`中的APK无需在外部安装,即VA支持**免安装运行APK**。
 
-VA目前被广泛应用于双开/多开,但它决不仅限于此,Android本身就是一个极其开放的平台,免安装运行APK这一Feature打开了太多太多的可能--------这都取决于你的想象力。
+警告
+-------
+本项目使用的 VirtualApp 不允许用于商业用途,如果有这个需求,请联系 Lody (imlody@foxmail.com)。
 
-申明
----
-**您没有权利将VirtualApp的app模块作为您自己的app上架到软件市场,一经发现,后果你懂的。**
+使用
+----------
 
-**您需要授权才可以使用lib的代码,VirtualApp已申请国家专利, 并获得软件著作权保护, 当你的行为对项目或是项目作者构成利益冲突时,我们将追究法律责任。若需使用本项目,请与作者联系。**
+## 准备
 
-谁在使用本项目
--------------
-* 地铁跑酷
-* 骑士助手
-* X-Phone
-* Dual app
-* 机友精灵
-* 隐秘(PrivateMe)
+首先在 [发布页面](https://github.com/android-hacker/VirtualXposed/releases) 下载最新的VAExposed安装包安装到手机。
 
-已支持的加固
-----------
-* 360加固
-* 腾讯加固
-* 梆梆加固
-* 爱加密
-* 百度加固
-* 娜迦加固
-* (非VMP的加固都可以通过VA来脱壳,但目前本技术尚不公开)
+## 安装模块
 
+打开 VirtualXposed,在里面安装要使用的APP,以及相应的Xposed模块即可。
 
-使用说明
-----------
+注意:**所有的工作(安装Xposed模块,安装APP)必须在 VirtualXposed中**进行,否则Xposed模块不会有任何作用!比如,将微信直接安装在系统上(而非VirtualXposed中),防撤回安装在VirtualXposed中;或者把微信安装在VirtualXposed上,防撤回插件直接安装在系统上;或者两者都直接安装在系统上,**均不会起任何作用**。
+
+在VirtualXposed中安装App有两种方式:
+
+1. 直接复制已经在系统中安装好的APP,比如如果你系统中装了微信,那么可以直接复制一份。
+2. 通过外置存储直接安装APK文件;点主界面的底部按钮-添加应用,然后选择后面两个TAB即可。
+
+在VirtualXposed中安装Xposed模块,可以跟安装正常的APK一样,以上两种安装App的方式也适用于安装Xposed模块。不过,你也可以通过VirtualXposed中内置的XposedInstaller来安装和管理模块,跟通常的XposedInstaller使用方式一样;去下载页面,下载安装即可。 
 
-**前往你的Application并添加如下代码:**
-```java
-    @Override
-    protected void attachBaseContext(Context base) {
-        super.attachBaseContext(base);
-        try {
-            VirtualCore.getCore().startup(base);
-        } catch (Throwable e) {
-            e.printStackTrace();
-        }
-    }
-```
-**安装App:**
-```java
-    VirtualCore.getCore().installApp({APK PATH}, flags);
-```
-**启动App:**
-```java
-    VirtualCore.getCore().launchApp({PackageName});
-```
-**移除App:**
-```java
-    VirtualCore.getCore().uninstallApp({PackageName});
-```
-**该App的基本信息:**
-```java
-    VirtualCore.getCore().findApp({PackageName});
-```
-
-License
+## 亲测可用的模块
+
+- [XPrivacyLua][xpl]: Really simple to use privacy manager for Android 6.0 Marshmallow and later.
+- [XInsta][xinsta]: Instagram module(Feed downing, stories downloading, etc).
+- [Minminguard][minminguard]: Completely remove both the ads inside apps and the empty space caused by those ads.
+- [YouTube AdAway][yta]:  Get rid of ads on the official YouTube App.
+- [微X模块][wx]: 微信模块,功能强大。
+- [畅玩微信][cwwx]: 微信模块新秀,功能丰富。
+- [微信巫师][wxws]: 微信模块,项目开源,代码优秀。
+- [MDWechat][mdwechat]: 微信美化模块,可以把微信整成MD风格。
+- [应用变量][yybl]: 可以用来进行机型修改,比如王者荣耀高帧率;QQ空间修改小尾巴等。
+- [音量增强器][ylzqq]: 网易云音乐模块,非常好用,低调。
+- [微信学英语][wxxyy]: 自动把微信消息翻译为英语,非常实用。
+- [情迁抢包][qqqb]: 微信QQ抢红包模块。
+- [微信跳一跳助手][ttzs]: 微信跳一跳游戏辅助模块。
+- [步数修改器][bsxg]: 运动步数修改模块。
+- [模拟位置][mnwz]: 虚拟定位模块,稳定好用。
+- [指纹支付][zwzf]: 对不支持指纹支付但系统本身有指纹的手机开启指纹支付的模块。
+- [QQ精简模块 2.0][qqjj]: QQ模块,不仅可以精简QQ,还能防撤回,防闪照。
+- [微信增强插件][wxzqcj]: 微信模块,VXP内最稳定的微信模块;如无特殊需求建议用这个。
+- [QX模块][qx]: QQ模块,防撤回抢红包斗图一应俱全。
+- [QQ斗图神器][qqdtsq]: 各种表情,斗图神器。
+- [微信斗图神器][wxdtsq]: 斗图神器,微信用的。
+- [大圣净化][dsjh]: 去广告神器,推荐使用。
+
+真正能用的模块远不止这么多,要用的话可以自己测试;如果你发现某些模块可以用但不在上面的列表中,欢迎给我发个PR。
+
+其他
 -------
-GPL 3.0
 
-技术支持
+### GameGuardian
+
+VirtualXposed也支持GG修改器,如果你需要用GG,那么请使用GG专版(可以在发布页面下载,带 For_GameGuardian后缀)。
+
+[GG修改器使用视频教程](https://gameguardian.net/forum/gallery/image/437-no-root-via-virtualxposed-without-error-105-gameguardian/)
+
+### VirusTotal
+
+VirusTotal 还有一些其他的杀毒引擎检测到VirtualXposed有病毒,这一点我该不承认,而且我觉得这些愚蠢的杀毒引擎是在胡扯。请看[我的说明](https://github.com/android-hacker/VirtualXposed/issues/10).
+
+而且,VirtualXposed是开源的,你可以直接查看代码;我可以打包票,VirtualXposed本身没有做任何有害的事情(但是它确实有这个能力,所以请不要下载不明来源的Xposed插件)。
+
+如果你还是不放心,那么你可以使用 [0.8.7版本](https://github.com/android-hacker/VirtualXposed/releases/tag/0.8.7), 这个版本杀毒引擎的检测结果是安全的(简直就是扯淡)。
+
+
+支持和加入
 ------------
-Lody (imlody@foxmail.com)
-QQ/WeChat (382816028)
+
+目前VirtualXposed 还不完善,如果你对非ROOT下实现Xposed感兴趣;欢迎加入!你可以通过如下方式来支持:
+
+1. 直接贡献代码,提供Feature,修复BUG!
+2. 使用你拥有的手机,安装你常用的Xposed模块,反馈不可用情况;协助帮忙解决兼容性问题!
+3. 提出体验上,功能上的建议,帮助完善VirtualXposed!
+
+致谢
+------
+
+1. [VirtualApp](https://github.com/asLody/VirtualApp)
+2. [Xposed](https://github.com/rovo89/Xposed)
+
+[wx]: http://repo.xposed.info/module/com.fkzhang.wechatxposed
+[qx]: http://repo.xposed.info/module/com.fkzhang.qqxposed
+[wxws]: https://github.com/Gh0u1L5/WechatMagician/releases
+[yybl]: https://www.coolapk.com/apk/com.sollyu.xposed.hook.model
+[ylzqq]: https://github.com/bin456789/Unblock163MusicClient-Xposed/releases
+[wxxyy]: https://www.coolapk.com/apk/com.hiwechart.translate
+[qqqb]: http://repo.xposed.info/module/cn.qssq666.redpacket
+[ttzs]: http://repo.xposed.info/module/com.emily.mmjumphelper
+[mnwz]: https://www.coolapk.com/apk/com.rong.xposed.fakelocation
+[zwzf]: https://github.com/android-hacker/Xposed-Fingerprint-pay/releases
+[bsxg]: https://www.coolapk.com/apk/com.specher.sm
+[mdwechat]: https://github.com/Blankeer/MDWechat
+[wxzqcj]:https://github.com/firesunCN/WechatEnhancement
+[qqjj]: https://www.coolapk.com/apk/me.zpp0196.qqsimple
+[qqdtsq]: https://www.coolapk.com/apk/x.hook.qqemoji
+[wxdtsq]: https://www.coolapk.com/apk/x.hook.emojihook
+[dsjh]: https://wiki.ad-gone.com/archives/32
+[xpl]: https://github.com/android-hacker/VirtualXposed/wiki/Privacy-control(XPrivacyLua)
+[minminguard]: http://repo.xposed.info/module/tw.fatminmin.xposed.minminguard
+[yta]: http://repo.xposed.info/module/ma.wanam.youtubeadaway
+[xinsta]: http://repo.xposed.info/module/com.ihelp101.instagram
+[cwwx]: http://repo.xposed.info/module/com.example.wx_plug_in3
+
+
diff --git a/LICENSE.txt b/LICENSE.txt
deleted file mode 100644
index 94a9ed024..000000000
--- a/LICENSE.txt
+++ /dev/null
@@ -1,674 +0,0 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
-  The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works.  By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.  We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors.  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
-  To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights.  Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received.  You must make sure that they, too, receive
-or can get the source code.  And you must show them these terms so they
-know their rights.
-
-  Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
-  For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software.  For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
-  Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so.  This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software.  The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable.  Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products.  If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
-  Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary.  To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                       TERMS AND CONDITIONS
-
-  0. Definitions.
-
-  "This License" refers to version 3 of the GNU General Public License.
-
-  "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
-  "The Program" refers to any copyrightable work licensed under this
-License.  Each licensee is addressed as "you".  "Licensees" and
-"recipients" may be individuals or organizations.
-
-  To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy.  The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
-  A "covered work" means either the unmodified Program or a work based
-on the Program.
-
-  To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy.  Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
-  To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies.  Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
-  An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License.  If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
-  1. Source Code.
-
-  The "source code" for a work means the preferred form of the work
-for making modifications to it.  "Object code" means any non-source
-form of a work.
-
-  A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
-  The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form.  A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
-  The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities.  However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work.  For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
-  The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
-  The Corresponding Source for a work in source code form is that
-same work.
-
-  2. Basic Permissions.
-
-  All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met.  This License explicitly affirms your unlimited
-permission to run the unmodified Program.  The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work.  This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
-  You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force.  You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright.  Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
-  Conveying under any other circumstances is permitted solely under
-the conditions stated below.  Sublicensing is not allowed; section 10
-makes it unnecessary.
-
-  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-  No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
-  When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
-  4. Conveying Verbatim Copies.
-
-  You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
-  You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
-  5. Conveying Modified Source Versions.
-
-  You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
-    a) The work must carry prominent notices stating that you modified
-    it, and giving a relevant date.
-
-    b) The work must carry prominent notices stating that it is
-    released under this License and any conditions added under section
-    7.  This requirement modifies the requirement in section 4 to
-    "keep intact all notices".
-
-    c) You must license the entire work, as a whole, under this
-    License to anyone who comes into possession of a copy.  This
-    License will therefore apply, along with any applicable section 7
-    additional terms, to the whole of the work, and all its parts,
-    regardless of how they are packaged.  This License gives no
-    permission to license the work in any other way, but it does not
-    invalidate such permission if you have separately received it.
-
-    d) If the work has interactive user interfaces, each must display
-    Appropriate Legal Notices; however, if the Program has interactive
-    interfaces that do not display Appropriate Legal Notices, your
-    work need not make them do so.
-
-  A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit.  Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
-  6. Conveying Non-Source Forms.
-
-  You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
-    a) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by the
-    Corresponding Source fixed on a durable physical medium
-    customarily used for software interchange.
-
-    b) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by a
-    written offer, valid for at least three years and valid for as
-    long as you offer spare parts or customer support for that product
-    model, to give anyone who possesses the object code either (1) a
-    copy of the Corresponding Source for all the software in the
-    product that is covered by this License, on a durable physical
-    medium customarily used for software interchange, for a price no
-    more than your reasonable cost of physically performing this
-    conveying of source, or (2) access to copy the
-    Corresponding Source from a network server at no charge.
-
-    c) Convey individual copies of the object code with a copy of the
-    written offer to provide the Corresponding Source.  This
-    alternative is allowed only occasionally and noncommercially, and
-    only if you received the object code with such an offer, in accord
-    with subsection 6b.
-
-    d) Convey the object code by offering access from a designated
-    place (gratis or for a charge), and offer equivalent access to the
-    Corresponding Source in the same way through the same place at no
-    further charge.  You need not require recipients to copy the
-    Corresponding Source along with the object code.  If the place to
-    copy the object code is a network server, the Corresponding Source
-    may be on a different server (operated by you or a third party)
-    that supports equivalent copying facilities, provided you maintain
-    clear directions next to the object code saying where to find the
-    Corresponding Source.  Regardless of what server hosts the
-    Corresponding Source, you remain obligated to ensure that it is
-    available for as long as needed to satisfy these requirements.
-
-    e) Convey the object code using peer-to-peer transmission, provided
-    you inform other peers where the object code and Corresponding
-    Source of the work are being offered to the general public at no
-    charge under subsection 6d.
-
-  A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
-  A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling.  In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage.  For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product.  A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
-  "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source.  The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
-  If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information.  But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
-  The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed.  Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
-  Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
-  7. Additional Terms.
-
-  "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law.  If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
-  When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it.  (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.)  You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
-  Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
-    a) Disclaiming warranty or limiting liability differently from the
-    terms of sections 15 and 16 of this License; or
-
-    b) Requiring preservation of specified reasonable legal notices or
-    author attributions in that material or in the Appropriate Legal
-    Notices displayed by works containing it; or
-
-    c) Prohibiting misrepresentation of the origin of that material, or
-    requiring that modified versions of such material be marked in
-    reasonable ways as different from the original version; or
-
-    d) Limiting the use for publicity purposes of names of licensors or
-    authors of the material; or
-
-    e) Declining to grant rights under trademark law for use of some
-    trade names, trademarks, or service marks; or
-
-    f) Requiring indemnification of licensors and authors of that
-    material by anyone who conveys the material (or modified versions of
-    it) with contractual assumptions of liability to the recipient, for
-    any liability that these contractual assumptions directly impose on
-    those licensors and authors.
-
-  All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10.  If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term.  If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
-  If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
-  Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
-  8. Termination.
-
-  You may not propagate or modify a covered work except as expressly
-provided under this License.  Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
-  However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
-  Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
-  Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License.  If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
-  9. Acceptance Not Required for Having Copies.
-
-  You are not required to accept this License in order to receive or
-run a copy of the Program.  Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance.  However,
-nothing other than this License grants you permission to propagate or
-modify any covered work.  These actions infringe copyright if you do
-not accept this License.  Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
-  10. Automatic Licensing of Downstream Recipients.
-
-  Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License.  You are not responsible
-for enforcing compliance by third parties with this License.
-
-  An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations.  If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
-  You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License.  For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
-  11. Patents.
-
-  A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based.  The
-work thus licensed is called the contributor's "contributor version".
-
-  A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version.  For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
-  Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
-  In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement).  To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
-  If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients.  "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
-  If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
-  A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License.  You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
-  Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
-  12. No Surrender of Others' Freedom.
-
-  If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all.  For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
-  13. Use with the GNU Affero General Public License.
-
-  Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work.  The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
-  14. Revised Versions of this License.
-
-  The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-  Each version is given a distinguishing version number.  If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation.  If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
-  If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
-  Later license versions may give you additional or different
-permissions.  However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
-  15. Disclaimer of Warranty.
-
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. Limitation of Liability.
-
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-  17. Interpretation of Sections 15 and 16.
-
-  If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
-  If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
-    <program>  Copyright (C) <year>  <name of author>
-    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
-  You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-<http://www.gnu.org/licenses/>.
-
-  The GNU General Public License does not permit incorporating your program
-into proprietary programs.  If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library.  If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.  But first, please read
-<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/README.md b/README.md
index 8ee3bf35d..4ec36cdcb 100644
--- a/README.md
+++ b/README.md
@@ -1,80 +1,137 @@
-[![VA banner](https://raw.githubusercontent.com/asLody/VirtualApp/master/Logo.png)](https://github.com/asLody/VirtualApp)
+[![Build Status](https://travis-ci.org/android-hacker/VirtualXposed.svg?branch=exposed)](https://travis-ci.org/android-hacker/VirtualXposed)
 
-[中国人猛戳这里](CHINESE.md "中文")
+[中文文档](CHINESE.md "中文")
 
-About
------
-**VirtualApp** is an open platform for Android that allows you to create a `Virtual Space`,
-you can install and run apk inside. Beyond that, VirtualApp is also a `Plugin Framework`,
-the plugins running on VirtualApp does not require any constraints.
-VirtualApp does **not** require root, it is running on the `local process`.
+Introduction
+------------
+**VirtualXposed** is a simple APP based on [VirtualApp](https://github.com/asLody/VirtualApp) and [epic](https://github.com/tiann/epic) that allows you to use an Xposed Module without needing to root, unlock the bootloader, or flash a custom system image. (Supports Android 5.0~9.0) 
+
+The only two restriction of VirtualXposed are:
+
+1. Unable to modify system, so any Module which modifies system won't be able to work properly.
+2. Currently resource hooks are not supported. (Theming modules use Resource Hooks).
+
+Warning
+-----------
 
-NOTICE
+Usage for Commercial Purposes are not allowed!!!  Please refer to VirtualApp's [declaration](https://github.com/asLody/VirtualApp).
+
+Usage
 -------
-**This project has been authorized by the business.**
 
-**You are not allowed to modify the app module and put to the software market, if you do that, The consequences you know :)**
+### Preparation
 
-**VirtualApp is not free, If you need to use the lib code, please send email to me :)**
+Download the latest APK from the [release page](https://github.com/android-hacker/VirtualXposed/releases), and install it on your Android device.
 
-Background
-----------
+### Install APP and Xposed Module
 
-VirtualApp was born in early 2015, Originally, it is just a simple plugin framework, 
-But as time goes on,
-the compatibility of it is getting better and better.
-in the end, it evolved into a `Virtual Container`.
+Open VirtualXposed, Click on the **Drawer Button** at the bottom of home page(Or long click the screen), add your desired APP and Xposed Module to VirtualXposed's virtual environment.
 
+Note: **All operations(installation of Xposed Module, APP)must be done in VirtualXposed**, otherwise the Xposed Module installed won't take effect. For example, if you install the YouTube app on your system (Your phone's original system, not in VirtualXposed), and then install YouTube AdAway (A YouTube Xposed Module) in VirtualXposed; or you install YouTube in VirtualXposed, and install YouTube AdAway on original system; or both of them are installed on original system, **neither of these three cases will work!**
 
-Get started
------------
-If you use latest android studio (version 2.0 or above), please disable `Instant Run`.
-Open `Setting | Build,Exception,Deployment`, and disable `Enable Instant Run to hot swap...`
-
-**Goto your Application and insert the following code:**
-```java
-    @Override
-    protected void attachBaseContext(Context base) {
-        super.attachBaseContext(base);
-        try {
-            VirtualCore.get().startup(base);
-        } catch (Throwable e) {
-            e.printStackTrace();
-        }
-    }
-```
-
-**Install a virtual App:**
-```java
-    VirtualCore.get().installPackage({APK PATH}, flags);
-    
-```
-
-**Launch a virtual App:**
-```java
-    //VirtualApp support multi-user-mode which can run multiple instances of a same app.
-    //if you don't need this feature, just set `{userId}` to 0.
-    Intent intent = VirtualCore.get().getLaunchIntent({PackageName}, {userId});
-    VActivityManager.get().startActivity(intent, {userId});
-```
-
-**Uninstall a virtual App:**
-```java
-    VirtualCore.get().uninstallPackage({PackageName});
-```
-
-More details, please read the source code of demo app, :-)
-
-Documentation
--------------
-
-VirtualApp currently has **no documentation**, If you are interested in VirtualApp, please send email to me.
-
-License
+![How to install](https://raw.githubusercontent.com/tiann/arts/master/vxp_install.gif)
+
+There are three ways to install an APP or Xposed Module to VirtualXposed:
+
+1. **Clone an installed app from your original system.** (Click Button at bottom of home page, then click Add App, the first page shows a list of installed apps.)
+2. **Install via an APK file.** (Click Button at bottom of home page, then click Add App, the second page shows APKs found in your sdcard)
+3. **Install via an external file chooser.** (Click Button at bottom of home page, then click Add App, use the floating action button to choose an APK file to install)
+
+For Xposed Module, You can install it from Xposed Installer, too.
+
+### Activate the Xposed Module
+
+Open Xposed Installer in VirtualXposed, go to the module fragment, check the module you want to use:
+
+![How to activate module](https://raw.githubusercontent.com/tiann/arts/master/vxp_activate.gif)
+
+### Reboot
+
+You only need to reboot VirtualXposed, **There's no need to reboot your phone**; Just click Settings in home page of VirtualXposed, click `Reboot` button, and VirtualXposed will reboot in a blink. 
+
+![How to reboot](https://raw.githubusercontent.com/tiann/arts/master/vxp_reboot.gif)
+
+Modules tested by myself
+-------------------------
+
+- [XPrivacyLua][xpl]: Really simple to use privacy manager for Android 6.0 Marshmallow and later.
+- [XInsta][xinsta]: Instagram module(Feed downing, stories downloading, etc).
+- [Minminguard][minminguard]: Completely remove both the ads inside apps and the empty space caused by those ads.
+- [YouTube AdAway][yta]:  Get rid of ads on the official YouTube App.
+- [微X模块][wx]: 微信模块,功能强大。
+- [畅玩微信][cwwx]: 微信模块新秀,功能丰富。
+- [微信巫师][wxws]: 微信模块,项目开源,代码优秀。
+- [MDWechat][mdwechat]: 微信美化模块,可以把微信整成MD风格。
+- [应用变量][yybl]: 可以用来进行机型修改,比如王者荣耀高帧率;QQ空间修改小尾巴等。
+- [音量增强器][ylzqq]: 网易云音乐模块,非常好用,低调。
+- [微信学英语][wxxyy]: 自动把微信消息翻译为英语,非常实用。
+- [情迁抢包][qqqb]: 微信QQ抢红包模块。
+- [微信跳一跳助手][ttzs]: 微信跳一跳游戏辅助模块。
+- [步数修改器][bsxg]: 运动步数修改模块。
+- [模拟位置][mnwz]: 虚拟定位模块,稳定好用。
+- [指纹支付][zwzf]: 对不支持指纹支付但系统本身有指纹的手机开启指纹支付的模块。
+- [QQ精简模块 2.0][qqjj]: QQ模块,不仅可以精简QQ,还能防撤回,防闪照。
+- [微信增强插件][wxzqcj]: 微信模块,VXP内最稳定的微信模块;如无特殊需求建议用这个。
+- [QX模块][qx]: QQ模块,防撤回抢红包斗图一应俱全。
+- [QQ斗图神器][qqdtsq]: 各种表情,斗图神器。
+- [微信斗图神器][wxdtsq]: 斗图神器,微信用的。
+- [大圣净化][dsjh]: 去广告神器,推荐使用。
+
+Supported modules are far more than above, you should test it by yourself, and welcome to send me a PR for the list above.
+
+Others
 -------
-GPL 3.0
 
-About Author
-------------
+### GameGuardian
+
+VirtualXposed also supports GameGuardian, **you should use the separate version for GameGuardian**.(Download it in release page).
+
+[Video Tutorial](https://gameguardian.net/forum/gallery/image/437-no-root-via-virtualxposed-without-error-105-gameguardian/)
+
+### VirusTotal
+
+VirusTotal might report VirtualXposed as a malware, it is stupid, you can refer to my [explanation](https://github.com/android-hacker/VirtualXposed/issues/10).
+
+And obviously, VirtualXposed is open source, so you can refer to the source code. I am sure that it is safe to use.
+
+If you still couldn't believe in me, you can install version [0.8.7](https://github.com/android-hacker/VirtualXposed/releases/tag/0.8.7); VirusTotal reports this version as safe.
+
+Support
+-----------
+
+Contributions to VirtualXposed are always welcomed!!
+
+For Developers
+--------------
+
+- [File a bug](https://github.com/android-hacker/exposed/issues)
+- [Wiki](https://github.com/android-hacker/VirtualXposed/wiki)
+
+Credits
+-------
 
-    Lody (imlody@foxmail.com)
+1. [VirtualApp](https://github.com/asLody/VirtualApp)
+2. [Xposed](https://github.com/rovo89/Xposed)
+
+[wx]: http://repo.xposed.info/module/com.fkzhang.wechatxposed
+[qx]: http://repo.xposed.info/module/com.fkzhang.qqxposed
+[wxws]: https://github.com/Gh0u1L5/WechatMagician/releases
+[yybl]: https://www.coolapk.com/apk/com.sollyu.xposed.hook.model
+[ylzqq]: https://github.com/bin456789/Unblock163MusicClient-Xposed/releases
+[wxxyy]: https://www.coolapk.com/apk/com.hiwechart.translate
+[qqqb]: http://repo.xposed.info/module/cn.qssq666.redpacket
+[ttzs]: http://repo.xposed.info/module/com.emily.mmjumphelper
+[mnwz]: https://www.coolapk.com/apk/com.rong.xposed.fakelocation
+[zwzf]: https://github.com/android-hacker/Xposed-Fingerprint-pay/releases
+[bsxg]: https://www.coolapk.com/apk/com.specher.sm
+[mdwechat]: https://github.com/Blankeer/MDWechat
+[wxzqcj]:https://github.com/firesunCN/WechatEnhancement
+[qqjj]: https://www.coolapk.com/apk/me.zpp0196.qqsimple
+[qqdtsq]: https://www.coolapk.com/apk/x.hook.qqemoji
+[wxdtsq]: https://www.coolapk.com/apk/x.hook.emojihook
+[dsjh]: https://wiki.ad-gone.com/archives/32
+[xpl]: https://github.com/android-hacker/VirtualXposed/wiki/Privacy-control(XPrivacyLua)
+[minminguard]: http://repo.xposed.info/module/tw.fatminmin.xposed.minminguard
+[yta]: http://repo.xposed.info/module/ma.wanam.youtubeadaway
+[xinsta]: http://repo.xposed.info/module/com.ihelp101.instagram
+[cwwx]: http://repo.xposed.info/module/com.example.wx_plug_in3
diff --git a/VirtualApp/.gitignore b/VirtualApp/.gitignore
index c07e41de4..6f0efc106 100644
--- a/VirtualApp/.gitignore
+++ b/VirtualApp/.gitignore
@@ -7,3 +7,4 @@
 .DS_Store
 /build
 /captures
+!*.apk
diff --git a/VirtualApp/app/build.gradle b/VirtualApp/app/build.gradle
index d7bc65045..6c4ae80c4 100644
--- a/VirtualApp/app/build.gradle
+++ b/VirtualApp/app/build.gradle
@@ -1,51 +1,103 @@
 apply plugin: 'com.android.application'
-apply plugin: 'me.tatarka.retrolambda'
+apply plugin: 'io.fabric'
 
+repositories {
+    maven { url 'https://maven.fabric.io/public' }
+}
+
+Properties properties = new Properties()
+def localProp = file(project.rootProject.file('local.properties'))
+if (localProp.exists()) {
+    properties.load(localProp.newDataInputStream())
+}
+def keyFile = file(properties.getProperty("keystore.path") ?: "/tmp/does_not_exist")
 
 android {
-    compileSdkVersion 25
-    buildToolsVersion "25.0.2"
+    signingConfigs {
+        config {
+            keyAlias properties.getProperty("keystore.alias")
+            keyPassword properties.getProperty("keystore.pwd")
+            storeFile keyFile
+            storePassword properties.getProperty("keystore.alias_pwd")
+        }
+    }
+
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
     defaultConfig {
-        applicationId "io.virtualapp"
-        minSdkVersion 15
-        targetSdkVersion 21
-        versionCode 23
-        versionName "1.2.4"
+        applicationId "io.va.exposed"
+        minSdkVersion 21
+        targetSdkVersion 23
+        versionCode 170
+        versionName "0.17.0"
+        multiDexEnabled false
         android {
             defaultConfig {
                 ndk {
-                    abiFilters "armeabi", "armeabi-v7a", "x86"
+                    abiFilters "armeabi-v7a", "x86"
                 }
             }
         }
     }
+    // https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html?utm_source=android-studio
+    flavorDimensions 'main'
+    productFlavors {
+        aosp {
+            dimension 'main'
+            //matchingFallbacks ['aosp']
+            if (keyFile.exists()) {
+                signingConfig signingConfigs.config
+            }
+        }
+    }
+    sourceSets {
+        main {
+            jniLibs.srcDirs = ['libs']
+        }
+    }
     buildTypes {
         release {
             minifyEnabled false
-            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+            debuggable
+            debuggable false
         }
     }
-
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_8
         targetCompatibility JavaVersion.VERSION_1_8
     }
+    lintOptions {
+        checkReleaseBuilds false
+        // Or, if you prefer, you can continue to check for errors in release builds,
+        // but continue the build even when errors are found:
+        abortOnError false
+    }
 }
 
 dependencies {
-    compile fileTree(include: ['*.jar'], dir: 'libs')
-    compile project(':lib')
+    implementation fileTree(include: ['*.jar'], dir: 'libs')
+    implementation project(':lib')
+    implementation project(':launcher')
     //Android Lib
-    compile 'com.android.support:appcompat-v7:25.0.1'
-    compile 'com.melnykov:floatingactionbutton:1.3.0'
-    compile 'com.android.support:recyclerview-v7:25.0.1'
-    compile 'com.android.support:percent:25.0.1'
-    compile 'com.android.support:design:25.0.1'
-    compile 'com.android.support:cardview-v7:25.0.1'
+    implementation 'com.android.support:multidex:1.0.3'
+    implementation 'com.android.support:appcompat-v7:27.1.1'
+    implementation 'com.android.support:recyclerview-v7:27.1.1'
+    implementation 'com.android.support:design:27.1.1'
     //Promise Support
-    compile 'org.jdeferred:jdeferred-android-aar:1.2.4'
+    implementation 'org.jdeferred:jdeferred-android-aar:1.2.4'
     // ThirdParty
-    compile 'com.jonathanfinerty.once:once:1.0.3'
+    implementation 'com.jonathanfinerty.once:once:1.0.3'
+    implementation('com.crashlytics.sdk.android:crashlytics:2.9.0@aar') {
+        transitive = true
+    }
+    implementation 'com.kyleduo.switchbutton:library:1.4.6'
+    implementation 'com.allenliu.versionchecklib:library:1.8.3'
+    implementation 'com.github.medyo:android-about-page:1.2.2'
+    implementation 'moe.feng:AlipayZeroSdk:1.1'
 
-    compile 'com.flurry.android:analytics:6.9.2'
+    //Glide
+    implementation ('com.github.bumptech.glide:glide:4.8.0') {
+        exclude(group: "com.android.support")
+    }
+    annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
 }
diff --git a/VirtualApp/app/proguard-rules.pro b/VirtualApp/app/proguard-rules.pro
index 93c6c7ca0..b592419bc 100644
--- a/VirtualApp/app/proguard-rules.pro
+++ b/VirtualApp/app/proguard-rules.pro
@@ -15,3 +15,31 @@
 #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
 #   public *;
 #}
+-keep   class com.amap.api.maps.**{*;}
+-keep   class com.autonavi.**{*;}
+-keep   class com.amap.api.trace.**{*;}
+
+#定位
+-keep class com.amap.api.location.**{*;}
+-keep class com.amap.api.fence.**{*;}
+-keep class com.autonavi.aps.amapapi.model.**{*;}
+
+#搜索
+-keep   class com.amap.api.services.**{*;}
+
+#2D地图
+-keep class com.amap.api.maps2d.**{*;}
+-keep class com.amap.api.mapcore2d.**{*;}
+
+#导航
+-keep class com.amap.api.navi.**{*;}
+-keep class com.autonavi.**{*;}
+
+##--Glide--
+-keep class com.bumptech.glide.**{*;}
+-keep public class * implements com.bumptech.glide.module.GlideModule
+-keep public class * extends com.bumptech.glide.module.AppGlideModule
+-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
+  **[] $VALUES;
+  public *;
+}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/AndroidManifest.xml b/VirtualApp/app/src/main/AndroidManifest.xml
index 0f906711e..774e06b3c 100644
--- a/VirtualApp/app/src/main/AndroidManifest.xml
+++ b/VirtualApp/app/src/main/AndroidManifest.xml
@@ -1,16 +1,37 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="io.virtualapp">
 
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
+
+    <uses-sdk tools:overrideLibrary="com.android.launcher3,android.support.dynamicanimation" />
+
     <application
-        android:name=".VApp"
+        android:name=".XApp"
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
-        android:label="@string/app_name"
-        android:theme="@style/AppTheme">
+        android:label="@string/vxp"
+        android:theme="@style/LauncherTheme"
+        tools:replace="android:icon,android:label">
+
+        <meta-data
+            android:name="android.max_aspect"
+            android:value="2.1" />
+
+        <meta-data
+            android:name="android.support.VERSION"
+            android:value="com.android.support:design:25.4.0"
+            tools:replace="android:value" />
+
+        <meta-data
+            android:name="io.fabric.ApiKey"
+            android:value="797c6ad1f87c908a4d7f6e8091e6466848b1611d" />
 
         <activity
             android:name=".splash.SplashActivity"
+            android:screenOrientation="portrait"
             android:theme="@style/AppTheme">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -18,21 +39,130 @@
             </intent-filter>
         </activity>
 
-        <activity
-            android:name=".home.HomeActivity"
-            android:theme="@style/UITheme" />
-
         <activity
             android:name=".home.ListAppActivity"
-            android:theme="@style/UITheme" />
+            android:screenOrientation="portrait"
+            android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
 
         <activity
             android:name=".home.LoadingActivity"
             android:excludeFromRecents="true"
             android:noHistory="true"
+            android:screenOrientation="portrait"
             android:taskAffinity="va.task.loading"
             android:theme="@style/TransparentTheme" />
 
+        <activity
+            android:name=".settings.AboutActivity"
+            android:screenOrientation="portrait"
+            android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
+
+        <activity android:name=".home.NewHomeActivity" />
+
+        <activity
+            android:name=".settings.SettingsActivity"
+            android:label="@string/settings_title"
+            android:screenOrientation="portrait"
+            android:theme="@android:style/Theme.Material.Light" />
+
+
+        <activity
+            android:name=".settings.RecommendPluginActivity"
+            android:label="@string/settings_plugin_recommend"
+            android:screenOrientation="portrait"
+            android:theme="@style/Theme.AppCompat.Light" />
+
+        <activity
+            android:name=".settings.AppManageActivity"
+            android:label="@string/settings_app_manage_text"
+            android:screenOrientation="portrait"
+            android:theme="@style/Theme.AppCompat.Light" />
+
+        <activity
+            android:name=".settings.TaskManageActivity"
+            android:label="@string/settings_task_manage_text"
+            android:screenOrientation="portrait"
+            android:theme="@style/Theme.AppCompat.Light" />
+
+        <activity
+            android:name=".sys.ShareBridgeActivity"
+            android:configChanges="keyboard|keyboardHidden|orientation"
+            android:excludeFromRecents="true"
+            android:label="@string/shared_to_vxp"
+            android:screenOrientation="portrait"
+            android:taskAffinity="io.va.exposed.share"
+            android:theme="@style/Theme.AppCompat.Light.Dialog">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".sys.InstallerActivity"
+            android:allowTaskReparenting="true"
+            android:configChanges="keyboard|keyboardHidden|orientation"
+            android:excludeFromRecents="true"
+            android:exported="false"
+            android:icon="@mipmap/ic_launcher"
+            android:label="@string/app_installer_label"
+            android:noHistory="true"
+            android:taskAffinity="io.va.exposed.installer"
+            android:theme="@style/Theme.AppCompat.Light" />
+
+        <activity-alias
+            android:name="vxp.installer"
+            android:enabled="true"
+            android:exported="true"
+            android:targetActivity=".sys.InstallerActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:scheme="file" />
+                <data android:mimeType="application/vnd.android.package-archive" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:scheme="content" />
+                <data android:mimeType="application/vnd.android.package-archive" />
+            </intent-filter>
+        </activity-alias>
+
+        <activity-alias
+            android:name="vxp.launcher"
+            android:enabled="false"
+            android:exported="true"
+            android:targetActivity=".home.NewHomeActivity">
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.HOME" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.MONKEY" />
+                <category android:name="android.intent.category.LAUNCHER_APP" />
+            </intent-filter>
+        </activity-alias>
+
+
+        <service
+            android:name=".update.VAVersionService"
+            android:enabled="true"
+            android:exported="true" />
+
+        <receiver
+            android:name=".dev.CmdReceiver"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="io.va.exposed.CMD" />
+            </intent-filter>
+        </receiver>
     </application>
 
 
diff --git a/VirtualApp/app/src/main/assets/OaceOaT8w5Xda6wa b/VirtualApp/app/src/main/assets/OaceOaT8w5Xda6wa
deleted file mode 100644
index 0f6cebfa8..000000000
Binary files a/VirtualApp/app/src/main/assets/OaceOaT8w5Xda6wa and /dev/null differ
diff --git a/VirtualApp/app/src/main/assets/XposedInstaller_3.1.5.apk_ b/VirtualApp/app/src/main/assets/XposedInstaller_3.1.5.apk_
new file mode 100644
index 000000000..26f4a17bb
Binary files /dev/null and b/VirtualApp/app/src/main/assets/XposedInstaller_3.1.5.apk_ differ
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/VCommends.java b/VirtualApp/app/src/main/java/io/virtualapp/VCommends.java
index a095e92d3..f9c4e5fe3 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/VCommends.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/VCommends.java
@@ -1,5 +1,13 @@
 package io.virtualapp;
 
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
 /**
  * @author Lody
  */
@@ -12,4 +20,51 @@ public class VCommends {
 
 	public static final String EXTRA_APP_INFO_LIST = "va.extra.APP_INFO_LIST";
 
+	public static final String TAG_ASK_INSTALL_GMS = "va.extra.ASK_INSTALL_GMS";
+
+	static String getSig(Context context) {
+		try {
+			PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
+			Signature[] signs = packageInfo.signatures;
+			Signature sign = signs[0];
+			String signStr = md(sign.toByteArray());
+			return signStr;
+		} catch (PackageManager.NameNotFoundException e) {
+			e.printStackTrace();
+		}
+		return "";
+	}
+
+	static String md(byte[] byteStr) {
+		MessageDigest messageDigest = null;
+		StringBuffer md5StrBuff = new StringBuffer();
+		try {
+			messageDigest = MessageDigest.getInstance("MD5");
+			messageDigest.reset();
+			messageDigest.update(byteStr);
+			byte[] byteArray = messageDigest.digest();
+			for (int i = 0; i < byteArray.length; i++) {
+				if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) {
+					md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i]));
+				} else {
+					md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
+				}
+			}
+		} catch (NoSuchAlgorithmException e) {
+			e.printStackTrace();
+		}
+		return md5StrBuff.toString();
+	}
+
+	/**
+	 * 君子坦荡荡,小人常戚戚
+	 * @param context the context
+	 */
+	public static void c(Context context) {
+		String sig = getSig(context);
+		if (!"99A244F52F40581B48E4BA61E3435B6C".equalsIgnoreCase(sig)) {
+			System.exit(10);
+			android.os.Process.killProcess(android.os.Process.myPid());
+		}
+	}
 }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/VApp.java b/VirtualApp/app/src/main/java/io/virtualapp/XApp.java
similarity index 57%
rename from VirtualApp/app/src/main/java/io/virtualapp/VApp.java
rename to VirtualApp/app/src/main/java/io/virtualapp/XApp.java
index c7756175c..d06d96489 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/VApp.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/XApp.java
@@ -2,34 +2,50 @@
 
 import android.app.Application;
 import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
 
-import com.flurry.android.FlurryAgent;
+import com.crashlytics.android.Crashlytics;
+import com.lody.virtual.client.NativeEngine;
 import com.lody.virtual.client.core.VirtualCore;
-import com.lody.virtual.client.stub.StubManifest;
+import com.lody.virtual.client.stub.VASettings;
+import com.lody.virtual.os.VEnvironment;
 
+import io.fabric.sdk.android.Fabric;
 import io.virtualapp.delegate.MyAppRequestListener;
 import io.virtualapp.delegate.MyComponentDelegate;
+import io.virtualapp.delegate.MyCrashHandler;
 import io.virtualapp.delegate.MyPhoneInfoDelegate;
-import io.virtualapp.delegate.MyTaskDescriptionDelegate;
+import io.virtualapp.delegate.MyTaskDescDelegate;
 import jonathanfinerty.once.Once;
+import me.weishu.exposed.LogcatService;
 
 /**
  * @author Lody
  */
-public class VApp extends Application {
+public class XApp extends Application {
 
+    private static final String TAG = "XApp";
 
-    private static VApp gApp;
+    public static final String XPOSED_INSTALLER_PACKAGE = "de.robv.android.xposed.installer";
 
-    public static VApp getApp() {
+    private static XApp gApp;
+    private SharedPreferences mPreferences;
+
+    public static XApp getApp() {
         return gApp;
     }
 
     @Override
     protected void attachBaseContext(Context base) {
+        gApp = this;
         super.attachBaseContext(base);
-        StubManifest.ENABLE_IO_REDIRECT = true;
-        StubManifest.ENABLE_INNER_SHORTCUT = false;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            NativeEngine.disableJit(Build.VERSION.SDK_INT);
+        }
+        mPreferences = base.getSharedPreferences("va", Context.MODE_MULTI_PROCESS);
+        VASettings.ENABLE_IO_REDIRECT = true;
+        VASettings.ENABLE_INNER_SHORTCUT = false;
         try {
             VirtualCore.get().startup(base);
         } catch (Throwable e) {
@@ -39,35 +55,35 @@ protected void attachBaseContext(Context base) {
 
     @Override
     public void onCreate() {
-        gApp = this;
         super.onCreate();
         VirtualCore virtualCore = VirtualCore.get();
         virtualCore.initialize(new VirtualCore.VirtualInitializer() {
 
             @Override
             public void onMainProcess() {
-                Once.initialise(VApp.this);
-                new FlurryAgent.Builder()
-                        .withLogEnabled(true)
-                        .withListener(() -> {
-                            // nothing
-                        })
-                        .build(VApp.this, "48RJJP7ZCZZBB6KMMWW5");
+                Once.initialise(XApp.this);
+                Fabric.with(XApp.this, new Crashlytics());
             }
 
             @Override
             public void onVirtualProcess() {
+                Fabric.with(XApp.this, new Crashlytics());
+
                 //listener components
                 virtualCore.setComponentDelegate(new MyComponentDelegate());
                 //fake phone imei,macAddress,BluetoothAddress
                 virtualCore.setPhoneInfoDelegate(new MyPhoneInfoDelegate());
                 //fake task description's icon and title
-                virtualCore.setTaskDescriptionDelegate(new MyTaskDescriptionDelegate());
+                virtualCore.setTaskDescriptionDelegate(new MyTaskDescDelegate());
+                virtualCore.setCrashHandler(new MyCrashHandler());
+
+                // ensure the logcat service alive when every virtual process start.
+                LogcatService.start(XApp.this, VEnvironment.getDataUserPackageDirectory(0, XPOSED_INSTALLER_PACKAGE));
             }
 
             @Override
             public void onServerProcess() {
-                virtualCore.setAppRequestListener(new MyAppRequestListener(VApp.this));
+                virtualCore.setAppRequestListener(new MyAppRequestListener(XApp.this));
                 virtualCore.addVisibleOutsidePackage("com.tencent.mobileqq");
                 virtualCore.addVisibleOutsidePackage("com.tencent.mobileqqi");
                 virtualCore.addVisibleOutsidePackage("com.tencent.minihd.qq");
@@ -80,4 +96,8 @@ public void onServerProcess() {
         });
     }
 
+    public static SharedPreferences getPreferences() {
+        return getApp().mPreferences;
+    }
+
 }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/abs/Value.java b/VirtualApp/app/src/main/java/io/virtualapp/abs/Value.java
deleted file mode 100644
index 193e58ae8..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/abs/Value.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.virtualapp.abs;
-
-/**
- * @author Lody
- */
-
-public class Value<T> {
-    public T val;
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/abs/nestedadapter/RecyclerViewAdapterWrapper.java b/VirtualApp/app/src/main/java/io/virtualapp/abs/nestedadapter/RecyclerViewAdapterWrapper.java
deleted file mode 100644
index 540395474..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/abs/nestedadapter/RecyclerViewAdapterWrapper.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package io.virtualapp.abs.nestedadapter;
-
-import android.support.v7.widget.RecyclerView;
-import android.view.ViewGroup;
-
-public class RecyclerViewAdapterWrapper extends RecyclerView.Adapter {
-
-    protected final RecyclerView.Adapter wrapped;
-
-    public RecyclerViewAdapterWrapper(RecyclerView.Adapter wrapped) {
-        super();
-        this.wrapped = wrapped;
-        this.wrapped.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
-            public void onChanged() {
-                notifyDataSetChanged();
-            }
-
-            public void onItemRangeChanged(int positionStart, int itemCount) {
-                notifyItemRangeChanged(positionStart, itemCount);
-            }
-
-            public void onItemRangeInserted(int positionStart, int itemCount) {
-                notifyItemRangeInserted(positionStart, itemCount);
-            }
-
-            public void onItemRangeRemoved(int positionStart, int itemCount) {
-                notifyItemRangeRemoved(positionStart, itemCount);
-            }
-
-            public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
-                notifyItemMoved(fromPosition, toPosition);
-            }
-        });
-    }
-
-    @Override
-    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        return wrapped.onCreateViewHolder(parent, viewType);
-    }
-
-
-    @Override
-    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
-        wrapped.onBindViewHolder(holder, position);
-    }
-
-    @Override
-    public int getItemCount() {
-        return wrapped.getItemCount();
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        return wrapped.getItemViewType(position);
-    }
-
-    @Override
-    public void setHasStableIds(boolean hasStableIds) {
-        wrapped.setHasStableIds(hasStableIds);
-    }
-
-    @Override
-    public long getItemId(int position) {
-        return wrapped.getItemId(position);
-    }
-
-    @Override
-    public void onViewRecycled(RecyclerView.ViewHolder holder) {
-        wrapped.onViewRecycled(holder);
-    }
-
-    @Override
-    public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
-        return wrapped.onFailedToRecycleView(holder);
-    }
-
-    @Override
-    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
-        wrapped.onViewAttachedToWindow(holder);
-    }
-
-    @Override
-    public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
-        wrapped.onViewDetachedFromWindow(holder);
-    }
-
-    @Override
-    public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
-        wrapped.registerAdapterDataObserver(observer);
-    }
-
-    @Override
-    public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
-        wrapped.unregisterAdapterDataObserver(observer);
-    }
-
-    @Override
-    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
-        wrapped.onAttachedToRecyclerView(recyclerView);
-    }
-
-    @Override
-    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
-        wrapped.onDetachedFromRecyclerView(recyclerView);
-    }
-
-    public RecyclerView.Adapter getWrappedAdapter() {
-        return wrapped;
-    }
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/abs/nestedadapter/SmartRecyclerAdapter.java b/VirtualApp/app/src/main/java/io/virtualapp/abs/nestedadapter/SmartRecyclerAdapter.java
deleted file mode 100644
index ef8ee75ca..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/abs/nestedadapter/SmartRecyclerAdapter.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package io.virtualapp.abs.nestedadapter;
-
-import android.support.annotation.NonNull;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.StaggeredGridLayoutManager;
-import android.view.View;
-import android.view.ViewGroup;
-
-public class SmartRecyclerAdapter extends RecyclerViewAdapterWrapper {
-    public static final int TYPE_HEADER = -1;
-    public static final int TYPE_FOOTER = -2;
-
-    private RecyclerView.LayoutManager layoutManager;
-
-    private View headerView, footerView;
-
-    public SmartRecyclerAdapter(@NonNull RecyclerView.Adapter targetAdapter) {
-        super(targetAdapter);
-    }
-
-    public void setHeaderView(View view) {
-        headerView = view;
-        getWrappedAdapter().notifyDataSetChanged();
-    }
-
-    public void removeHeaderView() {
-        headerView = null;
-        getWrappedAdapter().notifyDataSetChanged();
-    }
-
-    public void setFooterView(View view) {
-        footerView = view;
-        getWrappedAdapter().notifyDataSetChanged();
-    }
-
-    public void removeFooterView() {
-        footerView = null;
-        getWrappedAdapter().notifyDataSetChanged();
-    }
-
-    private void setGridHeaderFooter(RecyclerView.LayoutManager layoutManager) {
-        if (layoutManager instanceof GridLayoutManager) {
-            final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
-            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
-                @Override
-                public int getSpanSize(int position) {
-                    boolean isShowHeader = (position == 0 && hasHeader());
-                    boolean isShowFooter = (position == getItemCount() - 1 && hasFooter());
-                    if (isShowFooter || isShowHeader) {
-                        return gridLayoutManager.getSpanCount();
-                    }
-                    return 1;
-                }
-            });
-        }
-    }
-
-    private boolean hasHeader() {
-        return headerView != null;
-    }
-
-    private boolean hasFooter() {
-        return footerView != null;
-    }
-
-    @Override
-    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
-        super.onAttachedToRecyclerView(recyclerView);
-        layoutManager = recyclerView.getLayoutManager();
-        setGridHeaderFooter(layoutManager);
-    }
-
-    @Override
-    public int getItemCount() {
-        return super.getItemCount() + (hasHeader() ? 1 : 0) + (hasFooter() ? 1 : 0);
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        if (hasHeader() && position == 0) {
-            return TYPE_HEADER;
-        }
-
-        if (hasFooter() && position == getItemCount() - 1) {
-            return TYPE_FOOTER;
-        }
-        return super.getItemViewType(hasHeader() ? position - 1 : position);
-    }
-
-    @Override
-    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View itemView = null;
-        if (viewType == TYPE_HEADER) {
-            itemView = headerView;
-        } else if (viewType == TYPE_FOOTER) {
-            itemView = footerView;
-        }
-        if (itemView != null) {
-            //set StaggeredGridLayoutManager header & footer view
-            if (layoutManager instanceof StaggeredGridLayoutManager) {
-                ViewGroup.LayoutParams targetParams = itemView.getLayoutParams();
-                StaggeredGridLayoutManager.LayoutParams StaggerLayoutParams;
-                if (targetParams != null) {
-                    StaggerLayoutParams = new StaggeredGridLayoutManager.LayoutParams(targetParams.width, targetParams.height);
-                } else {
-                    StaggerLayoutParams = new StaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-                }
-                StaggerLayoutParams.setFullSpan(true);
-                itemView.setLayoutParams(StaggerLayoutParams);
-            }
-            return new RecyclerView.ViewHolder(itemView) {
-            };
-        }
-        return super.onCreateViewHolder(parent, viewType);
-    }
-
-    @Override
-    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
-        if (getItemViewType(position) == TYPE_HEADER || getItemViewType(position) == TYPE_FOOTER) {
-            //if you need get header & footer state , do here
-            return;
-        }
-        super.onBindViewHolder(holder, hasHeader() ? position - 1 : position);
-    }
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/abs/percent/PercentLinearLayout.java b/VirtualApp/app/src/main/java/io/virtualapp/abs/percent/PercentLinearLayout.java
deleted file mode 100644
index 1e0f0e851..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/abs/percent/PercentLinearLayout.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package io.virtualapp.abs.percent;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.support.percent.PercentLayoutHelper;
-import android.util.AttributeSet;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-/**
- * @author Lody
- */
-public class PercentLinearLayout extends LinearLayout {
-
-	private PercentLayoutHelper mPercentLayoutHelper;
-
-	public PercentLinearLayout(Context context, AttributeSet attrs) {
-		super(context, attrs);
-
-		mPercentLayoutHelper = new PercentLayoutHelper(this);
-	}
-
-	@Override
-	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-		mPercentLayoutHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
-		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-		if (mPercentLayoutHelper.handleMeasuredStateTooSmall()) {
-			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-		}
-	}
-
-	@Override
-	protected void onLayout(boolean changed, int l, int t, int r, int b) {
-		super.onLayout(changed, l, t, r, b);
-		mPercentLayoutHelper.restoreOriginalParams();
-	}
-
-	@Override
-	public LayoutParams generateLayoutParams(AttributeSet attrs) {
-		return new LayoutParams(getContext(), attrs);
-	}
-
-	public static class LayoutParams extends LinearLayout.LayoutParams
-			implements
-				PercentLayoutHelper.PercentLayoutParams {
-		private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;
-
-		public LayoutParams(Context c, AttributeSet attrs) {
-			super(c, attrs);
-			mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs);
-		}
-
-		public LayoutParams(int width, int height) {
-			super(width, height);
-		}
-
-		public LayoutParams(ViewGroup.LayoutParams source) {
-			super(source);
-		}
-
-		public LayoutParams(MarginLayoutParams source) {
-			super(source);
-		}
-
-		@Override
-		public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() {
-			return mPercentLayoutInfo;
-		}
-
-		@Override
-		protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
-			PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr);
-		}
-
-	}
-
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/abs/reflect/ReflectException.java b/VirtualApp/app/src/main/java/io/virtualapp/abs/reflect/ReflectException.java
deleted file mode 100644
index 3b6e57352..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/abs/reflect/ReflectException.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package io.virtualapp.abs.reflect;
-
-/**
- * @author Lody
- */
-public class ReflectException extends RuntimeException {
-
-	private static final long serialVersionUID = 663038727503637969L;
-
-	public ReflectException(Throwable cause) {
-		super(cause);
-	}
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/abs/ui/VActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/abs/ui/VActivity.java
index 7f091e00f..b968cbead 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/abs/ui/VActivity.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/abs/ui/VActivity.java
@@ -6,8 +6,6 @@
 import android.support.v4.app.Fragment;
 import android.support.v7.app.AppCompatActivity;
 
-import com.flurry.android.FlurryAgent;
-
 import org.jdeferred.android.AndroidDeferredManager;
 
 import io.virtualapp.abs.BaseView;
@@ -46,12 +44,10 @@ public void replaceFragment(@IdRes int id, Fragment fragment) {
     @Override
     protected void onStart() {
         super.onStart();
-        FlurryAgent.onStartSession(this);
     }
 
     @Override
     protected void onStop() {
         super.onStop();
-        FlurryAgent.onEndSession(this);
     }
 }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyAppRequestListener.java b/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyAppRequestListener.java
index 707d3fa42..358d61f1f 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyAppRequestListener.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyAppRequestListener.java
@@ -1,13 +1,15 @@
 package io.virtualapp.delegate;
 
 import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
 import android.widget.Toast;
 
-import com.lody.virtual.client.core.InstallStrategy;
 import com.lody.virtual.client.core.VirtualCore;
-import com.lody.virtual.remote.InstallResult;
 
-import java.io.IOException;
+import java.io.File;
+
+import io.virtualapp.sys.InstallerActivity;
 
 /**
  * @author Lody
@@ -23,27 +25,18 @@ public MyAppRequestListener(Context context) {
 
     @Override
     public void onRequestInstall(String path) {
-        Toast.makeText(context, "Installing: " + path, Toast.LENGTH_SHORT).show();
-        InstallResult res = VirtualCore.get().installPackage(path, InstallStrategy.UPDATE_IF_EXIST);
-        if (res.isSuccess) {
-            try {
-                VirtualCore.get().preOpt(res.packageName);
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-            if (res.isUpdate) {
-                Toast.makeText(context, "Update: " + res.packageName + " success!", Toast.LENGTH_SHORT).show();
-            } else {
-                Toast.makeText(context, "Install: " + res.packageName + " success!", Toast.LENGTH_SHORT).show();
-            }
-        } else {
-            Toast.makeText(context, "Install failed: " + res.error, Toast.LENGTH_SHORT).show();
+        try {
+            Intent t = new Intent(context, InstallerActivity.class);
+            t.setDataAndType(Uri.fromFile(new File(path)), "application/vnd.android.package-archive");
+            t.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            context.startActivity(t);
+        } catch (Throwable e) {
+            e.printStackTrace();
         }
     }
 
     @Override
     public void onRequestUninstall(String pkg) {
         Toast.makeText(context, "Uninstall: " + pkg, Toast.LENGTH_SHORT).show();
-
     }
 }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyComponentDelegate.java b/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyComponentDelegate.java
index 5a4a3af61..06a861292 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyComponentDelegate.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyComponentDelegate.java
@@ -1,6 +1,7 @@
 package io.virtualapp.delegate;
 
 import android.app.Activity;
+import android.app.Application;
 import android.content.Intent;
 
 import com.lody.virtual.client.hook.delegate.ComponentDelegate;
@@ -10,6 +11,17 @@
 
 
 public class MyComponentDelegate implements ComponentDelegate {
+
+    @Override
+    public void beforeApplicationCreate(Application application) {
+
+    }
+
+    @Override
+    public void afterApplicationCreate(Application application) {
+
+    }
+
     @Override
     public void beforeActivityCreate(Activity activity) {
 
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyCrashHandler.java b/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyCrashHandler.java
new file mode 100644
index 000000000..4eca7e8c8
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyCrashHandler.java
@@ -0,0 +1,86 @@
+package io.virtualapp.delegate;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.os.Looper;
+import android.util.Log;
+
+import com.crashlytics.android.Crashlytics;
+import com.lody.virtual.client.VClientImpl;
+import com.lody.virtual.client.core.CrashHandler;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.os.VUserHandle;
+import com.lody.virtual.remote.InstalledAppInfo;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * author: weishu on 18/3/10.
+ */
+public class MyCrashHandler implements CrashHandler {
+
+    private static final String TAG = "XApp";
+    private static final String CRASH_SP = "vxp_crash";
+    private static final String KEY_LAST_CRASH_TIME = "last_crash_time";
+    private static final String KEY_LAST_CRASH_TYPE = "last_crash_type";
+
+    @SuppressLint("ApplySharedPref")
+    @Override
+    public void handleUncaughtException(Thread t, Throwable e) {
+        SharedPreferences sp = VirtualCore.get().getContext().getSharedPreferences(CRASH_SP, Context.MODE_MULTI_PROCESS);
+
+        try {
+            ApplicationInfo currentApplicationInfo = VClientImpl.get().getCurrentApplicationInfo();
+            if (currentApplicationInfo != null) {
+                String packageName = currentApplicationInfo.packageName;
+                String processName = currentApplicationInfo.processName;
+
+                Crashlytics.setString("process", processName);
+                Crashlytics.setString("package", packageName);
+
+                int userId = VUserHandle.myUserId();
+
+                Crashlytics.setInt("uid", userId);
+
+                InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(packageName, 0);
+                if (installedAppInfo != null) {
+                    PackageInfo packageInfo = installedAppInfo.getPackageInfo(userId);
+                    if (packageInfo != null) {
+                        String versionName = packageInfo.versionName;
+                        int versionCode = packageInfo.versionCode;
+
+                        Crashlytics.setString("versionName", versionName);
+                        Crashlytics.setInt("versionCode", versionCode);
+
+                    }
+                }
+            }
+        } catch (Throwable ignored) {
+        }
+        final String exceptionType = e.getClass().getName();
+        final long now = System.currentTimeMillis();
+
+        final long lastCrash = sp.getLong(KEY_LAST_CRASH_TIME, 0);
+        final String lastCrashType = sp.getString(KEY_LAST_CRASH_TYPE, null);
+
+        if (exceptionType.equals(lastCrashType) && (now - lastCrash) < TimeUnit.MINUTES.toMillis(1)) {
+            // continues crash, do not upload
+        } else {
+            Crashlytics.logException(e);
+        }
+
+        Log.i(TAG, "uncaught :" + t, e);
+
+        // must commit.
+        sp.edit().putLong(KEY_LAST_CRASH_TIME, now).putString(KEY_LAST_CRASH_TYPE, exceptionType).commit();
+
+        if (t == Looper.getMainLooper().getThread()) {
+            System.exit(0);
+        } else {
+            Log.e(TAG, "ignore uncaught exception of sub thread: " + t);
+        }
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyTaskDescriptionDelegate.java b/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyTaskDescDelegate.java
similarity index 73%
rename from VirtualApp/app/src/main/java/io/virtualapp/delegate/MyTaskDescriptionDelegate.java
rename to VirtualApp/app/src/main/java/io/virtualapp/delegate/MyTaskDescDelegate.java
index 6c8e733c4..49b1469f9 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyTaskDescriptionDelegate.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/delegate/MyTaskDescDelegate.java
@@ -12,12 +12,16 @@
  * Patch the task description with the (Virtual) user name
  */
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class MyTaskDescriptionDelegate implements TaskDescriptionDelegate {
+public class MyTaskDescDelegate implements TaskDescriptionDelegate {
     @Override
     public ActivityManager.TaskDescription getTaskDescription(ActivityManager.TaskDescription oldTaskDescription) {
+        if (oldTaskDescription == null) {
+            return null;
+        }
         String labelPrefix = "[" + VUserManager.get().getUserName() + "] ";
+        String oldLabel = oldTaskDescription.getLabel() != null ? oldTaskDescription.getLabel() : "";
 
-        if (!oldTaskDescription.getLabel().startsWith(labelPrefix)) {
+        if (!oldLabel.startsWith(labelPrefix)) {
             // Is it really necessary?
             return new ActivityManager.TaskDescription(labelPrefix + oldTaskDescription.getLabel(), oldTaskDescription.getIcon(), oldTaskDescription.getPrimaryColor());
         } else {
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/dev/CmdReceiver.java b/VirtualApp/app/src/main/java/io/virtualapp/dev/CmdReceiver.java
new file mode 100644
index 000000000..fc704a8ae
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/dev/CmdReceiver.java
@@ -0,0 +1,103 @@
+package io.virtualapp.dev;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import com.lody.virtual.client.core.InstallStrategy;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.remote.InstallResult;
+
+import io.virtualapp.home.LoadingActivity;
+
+/**
+ * author: weishu on 18/2/23.
+ */
+
+public class CmdReceiver extends BroadcastReceiver {
+
+    private static final String ACTION = "io.va.exposed.CMD";
+    private static final String KEY_CMD = "cmd";
+    private static final String KEY_PKG = "pkg";
+    private static final String KEY_UID = "uid";
+
+    private static final String CMD_UPDATE = "update";
+    private static final String CMD_REBOOT = "reboot";
+    private static final String CMD_LAUNCH = "launch";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (!ACTION.equalsIgnoreCase(action)) {
+            return;
+        }
+
+        String cmd = intent.getStringExtra(KEY_CMD);
+        if (TextUtils.isEmpty(cmd)) {
+            showTips(context, "No cmd found!");
+            return;
+        }
+
+        if (CMD_REBOOT.equalsIgnoreCase(cmd)) {
+            VirtualCore.get().killAllApps();
+            showTips(context, "Reboot Success!!");
+            return;
+        }
+
+        if (CMD_UPDATE.equalsIgnoreCase(cmd)) {
+            String pkg = intent.getStringExtra(KEY_PKG);
+            if (TextUtils.isEmpty(pkg)) {
+                showTips(context, "Please tell me the update package!!");
+                return;
+            }
+
+            PackageManager packageManager = context.getPackageManager();
+            if (packageManager == null) {
+                showTips(context, "system error, update failed!");
+                return;
+            }
+
+            try {
+                ApplicationInfo applicationInfo = packageManager.getApplicationInfo(pkg, 0);
+                String apkPath = applicationInfo.sourceDir;
+                InstallResult installResult = VirtualCore.get().installPackage(apkPath, InstallStrategy.UPDATE_IF_EXIST);
+                if (installResult.isSuccess) {
+                    if (installResult.isUpdate) {
+                        showTips(context, "Update " + pkg + " Success!!");
+                    }
+                } else {
+                    showTips(context, "Update " + pkg + " failed: " + installResult.error);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                showTips(context, "Can not found " + pkg + " outside!");
+            }
+        } else if (CMD_LAUNCH.equalsIgnoreCase(cmd)) {
+            String pkg = intent.getStringExtra(KEY_PKG);
+            if (TextUtils.isEmpty(pkg)) {
+                showTips(context, "Please tell me the launch package!!");
+                return;
+            }
+            String uid = intent.getStringExtra(KEY_UID);
+            int userId = 0;
+            if (!TextUtils.isEmpty(uid)){
+                try {
+                    userId = Integer.parseInt(uid);
+                }catch (NumberFormatException e){
+                    e.printStackTrace();
+                }
+            }
+            LoadingActivity.launch(context, pkg, userId);
+
+
+        }
+    }
+
+    private void showTips(Context context, String tips) {
+        Toast.makeText(context, tips, Toast.LENGTH_SHORT).show();
+
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/effects/ExplosionAnimator.java b/VirtualApp/app/src/main/java/io/virtualapp/effects/ExplosionAnimator.java
deleted file mode 100644
index 90fc09b44..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/effects/ExplosionAnimator.java
+++ /dev/null
@@ -1,139 +0,0 @@
-package io.virtualapp.effects;
-
-import java.util.Random;
-
-import android.animation.ValueAnimator;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.view.View;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
-
-import io.virtualapp.VApp;
-import io.virtualapp.abs.ui.VUiKit;
-
-public class ExplosionAnimator extends ValueAnimator {
-
-	private static final Interpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator(0.6f);
-	private static final float END_VALUE = 1.4f;
-	private static final float X = VUiKit.dpToPx(VApp.getApp(), 5);
-	private static final float Y = VUiKit.dpToPx(VApp.getApp(), 20);
-	private static final float V = VUiKit.dpToPx(VApp.getApp(), 2);
-	private static final float W = VUiKit.dpToPx(VApp.getApp(), 1);
-	static long DEFAULT_DURATION = 0x450;
-	private Paint mPaint;
-	private Particle[] mParticles;
-	private Rect mBound;
-	private View mContainer;
-
-	public ExplosionAnimator(View container, Bitmap bitmap, Rect bound) {
-		mPaint = new Paint();
-		mBound = new Rect(bound);
-		int partLen = 15;
-		mParticles = new Particle[partLen * partLen];
-		Random random = new Random(System.currentTimeMillis());
-		int w = bitmap.getWidth() / (partLen + 2);
-		int h = bitmap.getHeight() / (partLen + 2);
-		for (int i = 0; i < partLen; i++) {
-			for (int j = 0; j < partLen; j++) {
-				mParticles[(i * partLen) + j] = generateParticle(bitmap.getPixel((j + 1) * w, (i + 1) * h), random);
-			}
-		}
-		mContainer = container;
-		setFloatValues(0f, END_VALUE);
-		setInterpolator(DEFAULT_INTERPOLATOR);
-		setDuration(DEFAULT_DURATION);
-	}
-
-	private Particle generateParticle(int color, Random random) {
-		Particle particle = new Particle();
-		particle.color = color;
-		particle.radius = V;
-		if (random.nextFloat() < 0.2f) {
-			particle.baseRadius = V + ((X - V) * random.nextFloat());
-		} else {
-			particle.baseRadius = W + ((V - W) * random.nextFloat());
-		}
-		float nextFloat = random.nextFloat();
-		particle.top = mBound.height() * ((0.18f * random.nextFloat()) + 0.2f);
-		particle.top = nextFloat < 0.2f ? particle.top : particle.top + ((particle.top * 0.2f) * random.nextFloat());
-		particle.bottom = (mBound.height() * (random.nextFloat() - 0.5f)) * 1.8f;
-		float f = nextFloat < 0.2f
-				? particle.bottom
-				: nextFloat < 0.8f ? particle.bottom * 0.6f : particle.bottom * 0.3f;
-		particle.bottom = f;
-		particle.mag = 4.0f * particle.top / particle.bottom;
-		particle.neg = (-particle.mag) / particle.bottom;
-		f = mBound.centerX() + (Y * (random.nextFloat() - 0.5f));
-		particle.baseCx = f;
-		particle.cx = f;
-		f = mBound.centerY() + (Y * (random.nextFloat() - 0.5f));
-		particle.baseCy = f;
-		particle.cy = f;
-		particle.life = END_VALUE / 10 * random.nextFloat();
-		particle.overflow = 0.4f * random.nextFloat();
-		particle.alpha = 1f;
-		return particle;
-	}
-
-	public boolean draw(Canvas canvas) {
-		if (!isStarted()) {
-			return false;
-		}
-		for (Particle particle : mParticles) {
-			particle.advance((float) getAnimatedValue());
-			if (particle.alpha > 0f) {
-				mPaint.setColor(particle.color);
-				mPaint.setAlpha((int) (Color.alpha(particle.color) * particle.alpha));
-				canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint);
-			}
-		}
-		mContainer.invalidate();
-		return true;
-	}
-
-	@Override
-	public void start() {
-		super.start();
-		mContainer.invalidate(mBound);
-	}
-
-	private class Particle {
-		float alpha;
-		int color;
-		float cx;
-		float cy;
-		float radius;
-		float baseCx;
-		float baseCy;
-		float baseRadius;
-		float top;
-		float bottom;
-		float mag;
-		float neg;
-		float life;
-		float overflow;
-
-		public void advance(float factor) {
-			float f = 0f;
-			float normalization = factor / END_VALUE;
-			if (normalization < life || normalization > 1f - overflow) {
-				alpha = 0f;
-				return;
-			}
-			normalization = (normalization - life) / (1f - life - overflow);
-			float f2 = normalization * END_VALUE;
-			if (normalization >= 0.7f) {
-				f = (normalization - 0.7f) / 0.3f;
-			}
-			alpha = 1f - f;
-			f = bottom * f2;
-			cx = baseCx + f;
-			cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag;
-			radius = V + (baseRadius - V) * f2;
-		}
-	}
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/effects/ExplosionField.java b/VirtualApp/app/src/main/java/io/virtualapp/effects/ExplosionField.java
deleted file mode 100644
index 3171091f0..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/effects/ExplosionField.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package io.virtualapp.effects;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Random;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.app.Activity;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.widget.ImageView;
-
-import io.virtualapp.VApp;
-import io.virtualapp.abs.ui.VUiKit;
-
-public class ExplosionField extends View {
-
-	private static final Canvas sCanvas = new Canvas();
-	private List<ExplosionAnimator> mExplosions = new ArrayList<>();
-	private int[] mExpandInset = new int[2];
-
-	public ExplosionField(Context context) {
-		super(context);
-		init();
-	}
-
-	public ExplosionField(Context context, AttributeSet attrs) {
-		super(context, attrs);
-		init();
-	}
-
-	public ExplosionField(Context context, AttributeSet attrs, int defStyleAttr) {
-		super(context, attrs, defStyleAttr);
-		init();
-	}
-
-	public static Bitmap createBitmapFromView(View view) {
-		if (view instanceof ImageView) {
-			Drawable drawable = ((ImageView) view).getDrawable();
-			if (drawable != null && drawable instanceof BitmapDrawable) {
-				return ((BitmapDrawable) drawable).getBitmap();
-			}
-		}
-		view.clearFocus();
-		Bitmap bitmap = createBitmapSafely(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888, 1);
-		if (bitmap != null) {
-			synchronized (sCanvas) {
-				Canvas canvas = sCanvas;
-				canvas.setBitmap(bitmap);
-				view.draw(canvas);
-				canvas.setBitmap(null);
-			}
-		}
-		return bitmap;
-	}
-
-	public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) {
-		try {
-			return Bitmap.createBitmap(width, height, config);
-		} catch (OutOfMemoryError e) {
-			e.printStackTrace();
-			if (retryCount > 0) {
-				System.gc();
-				return createBitmapSafely(width, height, config, retryCount - 1);
-			}
-			return null;
-		}
-	}
-
-	public static ExplosionField attachToWindow(Activity activity) {
-		ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
-		ExplosionField explosionField = new ExplosionField(activity);
-		rootView.addView(explosionField,
-				new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-		return explosionField;
-	}
-
-	public static ExplosionField attachToWindow(ViewGroup rootView, Activity activity) {
-		ExplosionField explosionField = new ExplosionField(activity);
-		rootView.addView(explosionField,
-				new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-		return explosionField;
-	}
-
-	private void init() {
-		Arrays.fill(mExpandInset, VUiKit.dpToPx(VApp.getApp(), 32));
-	}
-
-	@Override
-	protected void onDraw(Canvas canvas) {
-		super.onDraw(canvas);
-		for (ExplosionAnimator explosion : mExplosions) {
-			explosion.draw(canvas);
-		}
-	}
-
-	public void expandExplosionBound(int dx, int dy) {
-		mExpandInset[0] = dx;
-		mExpandInset[1] = dy;
-	}
-
-	public void explode(Bitmap bitmap, Rect bound, long startDelay, long duration) {
-		final ExplosionAnimator explosion = new ExplosionAnimator(this, bitmap, bound);
-		explosion.addListener(new AnimatorListenerAdapter() {
-			@Override
-			public void onAnimationEnd(Animator animation) {
-				mExplosions.remove(animation);
-			}
-		});
-		explosion.setStartDelay(startDelay);
-		explosion.setDuration(duration);
-		mExplosions.add(explosion);
-		explosion.start();
-	}
-
-	public void explode(final View view) {
-		explode(view, null);
-	}
-
-	public void explode(final View view, OnExplodeFinishListener listener) {
-		Rect r = new Rect();
-		view.getGlobalVisibleRect(r);
-		int[] location = new int[2];
-		getLocationOnScreen(location);
-		r.offset(-location[0], -location[1]);
-		r.inset(-mExpandInset[0], -mExpandInset[1]);
-		int startDelay = 100;
-		ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150);
-		animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-			Random random = new Random();
-
-			@Override
-			public void onAnimationUpdate(ValueAnimator animation) {
-				view.setTranslationX((random.nextFloat() - 0.5f) * view.getWidth() * 0.05f);
-				view.setTranslationY((random.nextFloat() - 0.5f) * view.getHeight() * 0.05f);
-			}
-
-		});
-		animator.addListener(new AnimatorListenerAdapter() {
-			@Override
-			public void onAnimationEnd(Animator animation) {
-				if (listener != null) {
-					listener.onExplodeFinish(view);
-				}
-			}
-		});
-		animator.start();
-		view.animate().setDuration(150).setStartDelay(startDelay).scaleX(0f).scaleY(0f).alpha(0f).start();
-		explode(createBitmapFromView(view), r, startDelay, ExplosionAnimator.DEFAULT_DURATION);
-	}
-
-	public void clear() {
-		mExplosions.clear();
-		invalidate();
-	}
-
-	public interface OnExplodeFinishListener {
-		void onExplodeFinish(View v);
-	}
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/glide/GlideUtils.java b/VirtualApp/app/src/main/java/io/virtualapp/glide/GlideUtils.java
new file mode 100644
index 000000000..ad7b85ab3
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/glide/GlideUtils.java
@@ -0,0 +1,33 @@
+package io.virtualapp.glide;
+
+import android.content.Context;
+import android.support.annotation.DrawableRes;
+import android.widget.ImageView;
+
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+
+import static io.virtualapp.glide.PackageIconResourceLoader.DATA_PACKAGE_FILE_PATH_PREFIX;
+import static io.virtualapp.glide.PackageIconResourceLoader.DATA_PACKAGE_PREFIX;
+
+/**
+ * Created by Windy on 2018/10/25
+ */
+public class GlideUtils {
+
+    public static void loadInstalledPackageIcon(Context context, String packageName, ImageView target, @DrawableRes int placeHolder) {
+        GlideApp.with(context)
+                .load(DATA_PACKAGE_PREFIX + packageName)
+                .placeholder(placeHolder)
+                .diskCacheStrategy(DiskCacheStrategy.NONE)
+                .into(target);
+    }
+
+    public static void loadPackageIconFromApkFile(Context context, String apkFilePath, ImageView target, @DrawableRes int placeHolder) {
+        GlideApp.with(context)
+                .load(DATA_PACKAGE_FILE_PATH_PREFIX + apkFilePath)
+                .placeholder(placeHolder)
+                .diskCacheStrategy(DiskCacheStrategy.NONE)
+                .into(target);
+    }
+
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/glide/MyGlideModule.java b/VirtualApp/app/src/main/java/io/virtualapp/glide/MyGlideModule.java
new file mode 100644
index 000000000..2b6ad9f81
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/glide/MyGlideModule.java
@@ -0,0 +1,40 @@
+package io.virtualapp.glide;
+
+import android.content.Context;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.GlideBuilder;
+import com.bumptech.glide.Registry;
+import com.bumptech.glide.annotation.GlideModule;
+import com.bumptech.glide.load.engine.cache.LruResourceCache;
+import com.bumptech.glide.load.engine.cache.MemorySizeCalculator;
+import com.bumptech.glide.module.AppGlideModule;
+import com.lody.virtual.helper.utils.VLog;
+
+import java.io.InputStream;
+
+/**
+ * Created by Windy on 2018/10/25
+ */
+@GlideModule
+public class MyGlideModule extends AppGlideModule {
+    @Override
+    public void applyOptions(Context context, GlideBuilder builder) {
+        MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
+                .build();
+        builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize() / 2));
+
+        VLog.i("MyGlideModule", "applyOptions");
+    }
+
+    @Override
+    public boolean isManifestParsingEnabled() {
+        return false;
+    }
+
+    @Override
+    public void registerComponents(Context context, Glide glide, Registry registry) {
+        super.registerComponents(context, glide, registry);
+        registry.prepend(String.class, InputStream.class, new PackageIconResourceLoaderFactory(context));
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/glide/PackageIconResourceDataFetcher.java b/VirtualApp/app/src/main/java/io/virtualapp/glide/PackageIconResourceDataFetcher.java
new file mode 100644
index 000000000..312f60b4d
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/glide/PackageIconResourceDataFetcher.java
@@ -0,0 +1,141 @@
+package io.virtualapp.glide;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.data.DataFetcher;
+import com.lody.virtual.helper.utils.VLog;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static io.virtualapp.glide.PackageIconResourceLoader.DATA_PACKAGE_FILE_PATH_PREFIX;
+import static io.virtualapp.glide.PackageIconResourceLoader.DATA_PACKAGE_PREFIX;
+
+/**
+ * Created by Windy on 2018/10/25
+ */
+public class PackageIconResourceDataFetcher implements DataFetcher<InputStream> {
+
+    private static final String TAG = PackageIconResourceDataFetcher.class.getSimpleName();
+
+    private Context context;
+    private String packageModel;
+
+    private InputStream data;
+
+    public PackageIconResourceDataFetcher(Context context, String packageName) {
+        this.context = context.getApplicationContext();
+        this.packageModel = packageName;
+    }
+
+    @Override
+    public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
+        try {
+            data = loadResource();
+        } catch (Exception e) {
+            VLog.e(TAG, "Failed to load data from asset manager", e);
+            callback.onLoadFailed(e);
+            return;
+        }
+        callback.onDataReady(data);
+    }
+
+    @Override
+    public void cleanup() {
+        if (data == null) {
+            return;
+        }
+        try {
+            data.close();
+        } catch (IOException e) {
+            // Ignored.
+        }
+    }
+
+    @Override
+    public void cancel() {
+
+    }
+
+    @NonNull
+    @Override
+    public Class<InputStream> getDataClass() {
+        return InputStream.class;
+    }
+
+    @NonNull
+    @Override
+    public DataSource getDataSource() {
+        return DataSource.LOCAL;
+    }
+
+    //load icon res accord to package name, or apk path
+    private InputStream loadResource() {
+        PackageInfo packageInfo = null;
+        Drawable drawable = null;
+        try {
+            packageInfo = getPackageInfo();
+            if (packageInfo == null) {
+                return null;
+            }
+
+            drawable = packageInfo.applicationInfo.loadIcon(context.getPackageManager());
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        if (drawable == null) {
+            return null;
+        }
+        return drawableToInputStream(drawable);
+    }
+
+    private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
+        if (packageModel.startsWith(DATA_PACKAGE_PREFIX)) {
+            return context.getPackageManager().getPackageInfo(getPackageTrueModel(DATA_PACKAGE_PREFIX), 0);
+        } else if (packageModel.startsWith(DATA_PACKAGE_FILE_PATH_PREFIX)) {
+            return context.getPackageManager().getPackageArchiveInfo(getPackageTrueModel(DATA_PACKAGE_FILE_PATH_PREFIX), 0);
+        }
+        return null;
+    }
+
+    private String getPackageTrueModel(String prefix) {
+        return packageModel.replaceAll(prefix, "");
+    }
+
+    private InputStream drawableToInputStream(Drawable drawable) {
+        Bitmap bitmap = drawableToBitmap(drawable);
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); //use the compression format of your need
+        return new ByteArrayInputStream(stream.toByteArray());
+    }
+
+    private static Bitmap drawableToBitmap(Drawable drawable) {
+        if (drawable instanceof BitmapDrawable) {
+            return ((BitmapDrawable) drawable).getBitmap();
+        }
+
+        int width = drawable.getIntrinsicWidth();
+        width = width > 0 ? width : 1;
+        int height = drawable.getIntrinsicHeight();
+        height = height > 0 ? height : 1;
+
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+
+        return bitmap;
+    }
+
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/glide/PackageIconResourceLoader.java b/VirtualApp/app/src/main/java/io/virtualapp/glide/PackageIconResourceLoader.java
new file mode 100644
index 000000000..23e6e2e71
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/glide/PackageIconResourceLoader.java
@@ -0,0 +1,38 @@
+package io.virtualapp.glide;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.bumptech.glide.load.Options;
+import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.signature.ObjectKey;
+
+import java.io.InputStream;
+
+/**
+ * Created by Windy on 2018/10/25
+ */
+public class PackageIconResourceLoader implements ModelLoader<String, InputStream> {
+
+    public static final String DATA_PACKAGE_PREFIX = "data:packageName/";
+    public static final String DATA_PACKAGE_FILE_PATH_PREFIX = "data:packageFilePath/";
+
+    private Context context;
+
+
+    public PackageIconResourceLoader(Context context) {
+        this.context = context;
+    }
+
+    @Nullable
+    @Override
+    public LoadData<InputStream> buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) {
+        return new LoadData<>(new ObjectKey(model), new PackageIconResourceDataFetcher(context, model));
+    }
+
+    @Override
+    public boolean handles(@NonNull String model) {
+        return model.startsWith(DATA_PACKAGE_PREFIX) || model.startsWith(DATA_PACKAGE_FILE_PATH_PREFIX);
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/glide/PackageIconResourceLoaderFactory.java b/VirtualApp/app/src/main/java/io/virtualapp/glide/PackageIconResourceLoaderFactory.java
new file mode 100644
index 000000000..a96734d6d
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/glide/PackageIconResourceLoaderFactory.java
@@ -0,0 +1,33 @@
+package io.virtualapp.glide;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.model.ModelLoaderFactory;
+import com.bumptech.glide.load.model.MultiModelLoaderFactory;
+
+import java.io.InputStream;
+
+/**
+ * Created by Windy on 2018/10/25
+ */
+public class PackageIconResourceLoaderFactory implements ModelLoaderFactory<String, InputStream> {
+
+    private Context context;
+
+    public PackageIconResourceLoaderFactory(Context context) {
+        this.context = context;
+    }
+
+    @NonNull
+    @Override
+    public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
+        return new PackageIconResourceLoader(context);
+    }
+
+    @Override
+    public void teardown() {
+
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/gms/FakeGms.java b/VirtualApp/app/src/main/java/io/virtualapp/gms/FakeGms.java
new file mode 100644
index 000000000..f54e61d98
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/gms/FakeGms.java
@@ -0,0 +1,422 @@
+package io.virtualapp.gms;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.lody.virtual.client.core.InstallStrategy;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.os.VEnvironment;
+import com.lody.virtual.remote.InstallResult;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import io.virtualapp.R;
+import io.virtualapp.abs.ui.VUiKit;
+import io.virtualapp.utils.DialogUtil;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+/**
+ * @author weishu
+ * @date 2018/6/9.
+ */
+public class FakeGms {
+
+    private static final String TAG = "FakeGms";
+
+    private static final String GMS_CONFIG_URL = "http://vaexposed.weishu.me/gms.json";
+
+    private static final String GMS_PKG = "com.google.android.gms";
+    private static final String GSF_PKG = "com.google.android.gsf";
+    private static final String STORE_PKG = "com.android.vending";
+    private static final String FAKE_GAPPS_PKG = "com.thermatk.android.xf.fakegapps";
+
+    private static ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+    public static void uninstallGms(Activity activity) {
+        if (activity == null) {
+            return;
+        }
+
+        AlertDialog failDialog = new AlertDialog.Builder(activity, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                .setTitle(R.string.uninstall_gms_title)
+                .setMessage(R.string.uninstall_gms_content)
+                .setPositiveButton(R.string.uninstall_gms_ok, ((dialog1, which1) -> {
+                    ProgressDialog dialog = new ProgressDialog(activity);
+                    dialog.show();
+                    VUiKit.defer().when(() -> {
+                        VirtualCore.get().uninstallPackage(GMS_PKG);
+                        VirtualCore.get().uninstallPackage(GSF_PKG);
+                        VirtualCore.get().uninstallPackage(STORE_PKG);
+                        VirtualCore.get().uninstallPackage(FAKE_GAPPS_PKG);
+                    }).then((v) -> {
+                        dialog.dismiss();
+                        AlertDialog hits = new AlertDialog.Builder(activity, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                                .setTitle(R.string.uninstall_gms_title)
+                                .setMessage(R.string.uninstall_gms_success)
+                                .setPositiveButton(android.R.string.ok, null)
+                                .create();
+                        DialogUtil.showDialog(hits);
+
+                    }).fail((v) -> {
+                        dialog.dismiss();
+                    });
+
+                }))
+                .setNegativeButton(android.R.string.cancel, null)
+                .create();
+        DialogUtil.showDialog(failDialog);
+    }
+
+    public static boolean isAlreadyInstalled(Context context) {
+        if (context == null) {
+            return false;
+        }
+
+        boolean alreadyInstalled = true;
+        if (!VirtualCore.get().isAppInstalled(GMS_PKG)) {
+            alreadyInstalled = false;
+        }
+        if (!VirtualCore.get().isAppInstalled(GSF_PKG)) {
+            alreadyInstalled = false;
+        }
+        if (!VirtualCore.get().isAppInstalled(STORE_PKG)) {
+            alreadyInstalled = false;
+        }
+        if (!VirtualCore.get().isAppInstalled(FAKE_GAPPS_PKG)) {
+            alreadyInstalled = false;
+        }
+        return alreadyInstalled;
+    }
+
+    public static void installGms(Activity activity) {
+
+        if (activity == null) {
+            return;
+        }
+
+        AlertDialog alertDialog = new AlertDialog.Builder(activity, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                .setTitle(R.string.install_gms_title)
+                .setMessage(R.string.install_gms_content)
+                .setPositiveButton(android.R.string.ok, ((dialog, which) -> {
+                    // show a loading dialog and start install gms.
+
+                    ProgressDialog progressDialog = new ProgressDialog(activity);
+                    progressDialog.setCancelable(false);
+                    progressDialog.show();
+
+                    executorService.submit(() -> {
+                        String failMsg = installGmsInternal(activity, progressDialog);
+                        Log.i(TAG, "install gms result: " + failMsg);
+                        try {
+                            progressDialog.dismiss();
+                        } catch (Throwable e) {
+                            e.printStackTrace();
+                        }
+
+                        if (failMsg == null) {
+                            activity.runOnUiThread(() -> {
+                                AlertDialog failDialog = new AlertDialog.Builder(activity, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                                        .setTitle(R.string.install_gms_title)
+                                        .setMessage(R.string.install_gms_success)
+                                        .setPositiveButton(android.R.string.ok, null)
+                                        .create();
+                                DialogUtil.showDialog(failDialog);
+                            });
+                        } else {
+                            activity.runOnUiThread(() -> {
+                                AlertDialog failDialog = new AlertDialog.Builder(activity, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                                        .setTitle(R.string.install_gms_fail_title)
+                                        .setMessage(R.string.install_gms_fail_content)
+                                        .setPositiveButton(R.string.install_gms_fail_ok, ((dialog1, which1) -> {
+                                            try {
+                                                Intent t = new Intent(Intent.ACTION_VIEW);
+                                                t.setData(Uri.parse("https://github.com/android-hacker/VirtualXposed/wiki/Google-service-support"));
+                                                activity.startActivity(t);
+                                            } catch (Throwable ignored) {
+                                                ignored.printStackTrace();
+                                            }
+                                        }))
+                                        .setNegativeButton(android.R.string.cancel, null)
+                                        .create();
+                                DialogUtil.showDialog(failDialog);
+                            });
+
+                        }
+                    });
+                }))
+                .setNegativeButton(android.R.string.cancel, null)
+                .create();
+
+        DialogUtil.showDialog(alertDialog);
+    }
+
+
+    private static String installGmsInternal(Activity activity, ProgressDialog dialog) {
+        File cacheDir = activity.getCacheDir();
+
+        // 下载配置文件,得到各自的URL
+        OkHttpClient client = new OkHttpClient.Builder()
+                .connectTimeout(30, TimeUnit.SECONDS)
+                .readTimeout(30, TimeUnit.SECONDS)
+                .writeTimeout(30, TimeUnit.SECONDS)
+                .build();
+
+        Request request = new Request.Builder()
+                .url(GMS_CONFIG_URL)
+                .build();
+
+        updateMessage(activity, dialog, "Fetching gms config...");
+        Response response;
+        try {
+            response = client.newCall(request).execute();
+        } catch (IOException e) {
+            return "Download gms config failed, please check your network, error: 0";
+        }
+
+        if (!response.isSuccessful()) {
+            return "Download gms config failed, please check your network, error: 1";
+        }
+
+        Log.i(TAG, "response success: " + response.code());
+        if (200 != response.code()) {
+            return "Download gms config failed, please check your network, error: 2";
+        }
+
+        updateMessage(activity, dialog, "Parsing gms config...");
+        ResponseBody body = response.body();
+        if (body == null) {
+            return "Download gms config failed, please check your network, error: 3";
+        }
+
+        String string = null;
+        try {
+            string = body.string();
+        } catch (IOException e) {
+            return "Download gms config failed, please check your network, error: 4";
+        }
+
+        JSONObject jsonObject = null;
+        try {
+            jsonObject = new JSONObject(string);
+        } catch (JSONException e) {
+            return "Download gms config failed, please check your network, error: 5";
+        }
+        String gmsCoreUrl = null;
+        try {
+            gmsCoreUrl = jsonObject.getString("gms");
+        } catch (JSONException e) {
+            return "Download gms config failed, please check your network, error: 6";
+        }
+        String gmsServiceUrl = null;
+        try {
+            gmsServiceUrl = jsonObject.getString("gsf");
+        } catch (JSONException e) {
+            return "Download gms config failed, please check your network, error: 7";
+        }
+        String storeUrl = null;
+        try {
+            storeUrl = jsonObject.getString("store");
+        } catch (JSONException e) {
+            return "Download gms config failed, please check your network, error: 8";
+        }
+        String fakeGappsUrl = null;
+        try {
+            fakeGappsUrl = jsonObject.getString("fakegapps");
+        } catch (JSONException e) {
+            return "Download gms config failed, please check your network, error: 9";
+        }
+
+        String yalpStoreUrl = null;
+        try {
+            yalpStoreUrl = jsonObject.getString("yalp");
+        } catch (JSONException e) {
+            // ignore.
+            Log.i(TAG, "Download gms config failed, please check your network");
+        }
+
+        updateMessage(activity, dialog, "config parse success!");
+
+        File gmsCoreFile = new File(cacheDir, "gms.apk");
+        File gmsServiceFile = new File(cacheDir, "gsf.apk");
+        File storeFile = new File(cacheDir, "store.apk");
+        File fakeGappsFile = new File(cacheDir, "fakegapps.apk");
+        File yalpStoreFile = new File(cacheDir, "yalpStore.apk");
+
+        // clear old files.
+        if (gmsCoreFile.exists()) {
+            gmsCoreFile.delete();
+        }
+        if (gmsServiceFile.exists()) {
+            gmsServiceFile.delete();
+        }
+        if (storeFile.exists()) {
+            storeFile.delete();
+        }
+        if (fakeGappsFile.exists()) {
+            fakeGappsFile.delete();
+        }
+
+        boolean downloadResult = downloadFile(gmsCoreUrl, gmsCoreFile,
+                (progress) -> updateMessage(activity, dialog, "download gms core..." + progress + "%"));
+        if (!downloadResult) {
+            return "Download gms config failed, please check your network, error: 10";
+        }
+
+        downloadResult = downloadFile(gmsServiceUrl, gmsServiceFile,
+                (progress -> updateMessage(activity, dialog, "download gms service framework proxy.." + progress + "%")));
+
+        if (!downloadResult) {
+            return "Download gms config failed, please check your network, error: 11";
+        }
+
+        updateMessage(activity, dialog, "download gms store...");
+
+        downloadResult = downloadFile(storeUrl, storeFile,
+                (progress -> updateMessage(activity, dialog, "download gms store.." + progress + "%")));
+        if (!downloadResult) {
+            return "Download gms config failed, please check your network, error: 12";
+        }
+
+        downloadResult = downloadFile(fakeGappsUrl, fakeGappsFile,
+                (progress -> updateMessage(activity, dialog, "download gms Xposed module.." + progress + "%")));
+        if (!downloadResult) {
+            return "Download gms config failed, please check your network, error: 13";
+        }
+
+        if (yalpStoreUrl != null) {
+            downloadFile(yalpStoreUrl,yalpStoreFile,
+                    (progress -> updateMessage(activity, dialog, "download yalp store.." + progress + "%")));
+        }
+
+        updateMessage(activity, dialog, "installing gms core");
+        InstallResult installResult = VirtualCore.get().installPackage(gmsCoreFile.getAbsolutePath(), InstallStrategy.UPDATE_IF_EXIST);
+
+        if (!installResult.isSuccess) {
+            return "install gms core failed: " + installResult.error;
+        }
+
+        updateMessage(activity, dialog, "installing gms service framework...");
+        installResult = VirtualCore.get().installPackage(gmsServiceFile.getAbsolutePath(), InstallStrategy.UPDATE_IF_EXIST);
+        if (!installResult.isSuccess) {
+            return "install gms service framework failed: " + installResult.error;
+        }
+
+        updateMessage(activity, dialog, "installing gms store...");
+        installResult = VirtualCore.get().installPackage(storeFile.getAbsolutePath(), InstallStrategy.UPDATE_IF_EXIST);
+        if (!installResult.isSuccess) {
+            return "install gms store failed: " + installResult.error;
+        }
+
+        updateMessage(activity, dialog, "installing gms Xposed module...");
+        installResult = VirtualCore.get().installPackage(fakeGappsFile.getAbsolutePath(), InstallStrategy.UPDATE_IF_EXIST);
+        if (!installResult.isSuccess) {
+            return "install gms xposed module failed: " + installResult.error;
+        }
+
+        if (yalpStoreFile.exists()) {
+            updateMessage(activity, dialog, "installing yalp store...");
+            VirtualCore.get().installPackage(yalpStoreFile.getAbsolutePath(), InstallStrategy.UPDATE_IF_EXIST);
+        }
+
+        // Enable the Xposed module.
+        File dataDir = VEnvironment.getDataUserPackageDirectory(0, "de.robv.android.xposed.installer");
+        File modulePath = VEnvironment.getPackageResourcePath(FAKE_GAPPS_PKG);
+        File configDir = new File(dataDir, "exposed_conf" + File.separator + "modules.list");
+        FileWriter writer = null;
+        try {
+            writer = new FileWriter(configDir, true);
+            writer.append(modulePath.getAbsolutePath());
+            writer.flush();
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        // success!!!
+        return null;
+    }
+
+    private static void updateMessage(Activity activity, ProgressDialog dialog, String msg) {
+        if (activity == null || dialog == null || TextUtils.isEmpty(msg)) {
+            return;
+        }
+        Log.i(TAG, "update dialog message: " + msg);
+        activity.runOnUiThread(() -> {
+            dialog.setMessage(msg);
+        });
+    }
+
+    public interface DownloadListener {
+        void onProgress(int progress);
+    }
+
+    public static boolean downloadFile(String url, File outFile, DownloadListener listener) {
+        OkHttpClient client = new OkHttpClient();
+        Request request = new Request.Builder().url(url).build();
+        FileOutputStream fos = null;
+        try {
+            Response response = client.newCall(request).execute();
+            if (response.code() != 200) {
+                return false;
+            }
+            ResponseBody body = response.body();
+            if (body == null) {
+                return false;
+            }
+            long toal = body.contentLength();
+            long sum = 0;
+
+            InputStream inputStream = body.byteStream();
+            fos = new FileOutputStream(outFile);
+            byte[] buffer = new byte[1024];
+            int count = 0;
+            while ((count = inputStream.read(buffer)) >= 0) {
+                fos.write(buffer, 0, count);
+                sum += count;
+                int progress = (int) ((sum * 1.0) / toal * 100);
+                if (listener != null) {
+                    listener.onProgress(progress);
+                }
+            }
+            fos.flush();
+            return true;
+        } catch (IOException e) {
+            return false;
+        } finally {
+            if (fos != null) {
+                try {
+                    fos.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/FlurryROMCollector.java b/VirtualApp/app/src/main/java/io/virtualapp/home/FlurryROMCollector.java
index d184ffe12..70dfdf8bc 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/FlurryROMCollector.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/FlurryROMCollector.java
@@ -4,8 +4,8 @@
 import android.os.Build;
 import android.util.Log;
 
-import com.flurry.android.FlurryAgent;
-import com.flurry.android.FlurryEventRecordStatus;
+import com.crashlytics.android.answers.Answers;
+import com.crashlytics.android.answers.CustomEvent;
 import com.lody.virtual.client.natives.NativeMethods;
 import com.lody.virtual.helper.utils.Reflect;
 
@@ -21,21 +21,33 @@ public class FlurryROMCollector {
     private static final String TAG = FlurryROMCollector.class.getSimpleName();
 
     public static void startCollect() {
-        Log.d(TAG, "start collect...");
+        Log.i(TAG, "start collect...");
         NativeMethods.init();
         if (NativeMethods.gCameraNativeSetup == null) {
             reportCameraNativeSetup();
         }
-        Log.d(TAG, "end collect...");
+        Log.i(TAG, "end collect...");
     }
 
+    private static void reportOffsetInfo(Map<String, Integer> info) {
+        Map<String, String> toReport = new HashMap<>();
+        CustomEvent methodOffset = new CustomEvent("methodOffset");
+        for (String key : info.keySet()) {
+            toReport.put(key, String.valueOf(info.get(key)));
+        }
+        addRomInfo(toReport);
+        Answers.getInstance().logCustom(methodOffset);
+    }
 
     private static void reportCameraNativeSetup() {
         for (Method method : Camera.class.getDeclaredMethods()) {
             if ("native_setup".equals(method.getName())) {
-                FlurryEventRecordStatus status =
-                        FlurryAgent.logEvent("camera::native_setup", createLogContent("method_details", Reflect.getMethodDetails(method)));
-                Log.d(TAG, "report CNS: " + status);
+                CustomEvent cameraSetup = new CustomEvent("camera::native_setup");
+                Map<String, String> methodDetails = createLogContent("method_details", Reflect.getMethodDetails(method));
+                for (String key : methodDetails.keySet()) {
+                    cameraSetup.putCustomAttribute(key, methodDetails.get(key));
+                }
+                Answers.getInstance().logCustom(cameraSetup);
                 break;
             }
         }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/HomeActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/home/HomeActivity.java
deleted file mode 100644
index 626cbb282..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/HomeActivity.java
+++ /dev/null
@@ -1,417 +0,0 @@
-package io.virtualapp.home;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AlertDialog;
-import android.support.v7.widget.OrientationHelper;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.StaggeredGridLayoutManager;
-import android.support.v7.widget.helper.ItemTouchHelper;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.melnykov.fab.FloatingActionButton;
-
-import java.util.List;
-
-import io.virtualapp.R;
-import io.virtualapp.VCommends;
-import io.virtualapp.abs.nestedadapter.SmartRecyclerAdapter;
-import io.virtualapp.abs.ui.VActivity;
-import io.virtualapp.abs.ui.VUiKit;
-import io.virtualapp.home.adapters.LaunchpadAdapter;
-import io.virtualapp.home.adapters.decorations.ItemOffsetDecoration;
-import io.virtualapp.home.models.AppData;
-import io.virtualapp.home.models.AppInfoLite;
-import io.virtualapp.home.models.EmptyAppData;
-import io.virtualapp.home.models.PackageAppData;
-import io.virtualapp.widgets.CircularAnim;
-import io.virtualapp.widgets.TwoGearsView;
-
-import static android.support.v7.widget.helper.ItemTouchHelper.ACTION_STATE_DRAG;
-import static android.support.v7.widget.helper.ItemTouchHelper.DOWN;
-import static android.support.v7.widget.helper.ItemTouchHelper.END;
-import static android.support.v7.widget.helper.ItemTouchHelper.LEFT;
-import static android.support.v7.widget.helper.ItemTouchHelper.RIGHT;
-import static android.support.v7.widget.helper.ItemTouchHelper.START;
-import static android.support.v7.widget.helper.ItemTouchHelper.UP;
-
-/**
- * @author Lody
- */
-public class HomeActivity extends VActivity implements HomeContract.HomeView {
-
-    private static final String TAG = HomeActivity.class.getSimpleName();
-
-    private HomeContract.HomePresenter mPresenter;
-    private TwoGearsView mLoadingView;
-    private RecyclerView mLauncherView;
-    private FloatingActionButton mFloatingButton;
-    private View bottomArea;
-    private View createShortcutArea;
-    private View deleteAppArea;
-    private LaunchpadAdapter mLaunchpadAdapter;
-    private Handler mUiHandler;
-
-
-    public static void goHome(Context context) {
-        Intent intent = new Intent(context, HomeActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        context.startActivity(intent);
-    }
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        overridePendingTransition(0, 0);
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_home);
-        mUiHandler = new Handler(Looper.getMainLooper());
-        bindViews();
-        initLaunchpad();
-        initFab();
-        new HomePresenterImpl(this).start();
-    }
-
-    private void bindViews() {
-        mLoadingView = (TwoGearsView) findViewById(R.id.pb_loading_app);
-        mLauncherView = (RecyclerView) findViewById(R.id.home_launcher);
-        mFloatingButton = (FloatingActionButton) findViewById(R.id.home_fab);
-        bottomArea = findViewById(R.id.bottom_area);
-        createShortcutArea = findViewById(R.id.create_shortcut_area);
-        deleteAppArea = findViewById(R.id.delete_app_area);
-    }
-
-    private void initLaunchpad() {
-        mLauncherView.setHasFixedSize(true);
-        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, OrientationHelper.VERTICAL);
-        mLauncherView.setLayoutManager(layoutManager);
-        mLaunchpadAdapter = new LaunchpadAdapter(this);
-        SmartRecyclerAdapter wrap = new SmartRecyclerAdapter(mLaunchpadAdapter);
-        View footer = new View(this);
-        footer.setLayoutParams(new StaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, VUiKit.dpToPx(this, 60)));
-        wrap.setFooterView(footer);
-        mLauncherView.setAdapter(wrap);
-        mLauncherView.addItemDecoration(new ItemOffsetDecoration(this, R.dimen.desktop_divider));
-        ItemTouchHelper touchHelper = new ItemTouchHelper(new LauncherTouchCallback());
-        touchHelper.attachToRecyclerView(mLauncherView);
-        mLaunchpadAdapter.setAppClickListener((pos, data) -> {
-            if (!data.isLoading()) {
-                mLaunchpadAdapter.notifyItemChanged(pos);
-                mPresenter.launchApp(data);
-            }
-        });
-    }
-
-    private void initFab() {
-        mFloatingButton.setOnClickListener(v -> {
-            CircularAnim.fullActivity(this, mFloatingButton)
-                    .colorOrImageRes(R.color.colorPrimaryRavel)
-                    .go(() -> ListAppActivity.gotoListApp(this));
-        });
-    }
-
-    private void deleteApp(int position) {
-        AppData data = mLaunchpadAdapter.getList().get(position);
-        new AlertDialog.Builder(this)
-                .setTitle("Delete app")
-                .setMessage("Do you want to delete " + data.getName() + "?")
-                .setPositiveButton(android.R.string.yes, (dialog, which) -> {
-                    mPresenter.deleteApp(data);
-                })
-                .setNegativeButton(android.R.string.no, null)
-                .show();
-    }
-
-    private void createShortcut(int position) {
-        AppData model = mLaunchpadAdapter.getList().get(position);
-        if (model instanceof PackageAppData) {
-            mPresenter.createShortcut(model);
-        }
-    }
-
-    @Override
-    public void setPresenter(HomeContract.HomePresenter presenter) {
-        mPresenter = presenter;
-    }
-
-    @Override
-    public void showBottomAction() {
-        hideFab();
-        bottomArea.setTranslationY(bottomArea.getHeight());
-        bottomArea.setVisibility(View.VISIBLE);
-        bottomArea.animate().translationY(0).setDuration(500L).start();
-    }
-
-    @Override
-    public void hideBottomAction() {
-        bottomArea.setTranslationY(0);
-        ObjectAnimator transAnim = ObjectAnimator.ofFloat(bottomArea, "translationY", 0, bottomArea.getHeight());
-        transAnim.addListener(new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animator) {
-
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                bottomArea.setVisibility(View.GONE);
-                showFab();
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animator) {
-                bottomArea.setVisibility(View.GONE);
-                showFab();
-            }
-
-            @Override
-            public void onAnimationRepeat(Animator animator) {
-
-            }
-        });
-        transAnim.setDuration(500L);
-        transAnim.start();
-    }
-
-    @Override
-    public void showLoading() {
-        mFloatingButton.hide(false);
-        mLoadingView.setVisibility(View.VISIBLE);
-        mLoadingView.startAnim();
-    }
-
-    @Override
-    public void hideLoading() {
-        mFloatingButton.show();
-        mLoadingView.setVisibility(View.GONE);
-        mLoadingView.stopAnim();
-    }
-
-    @Override
-    public void loadFinish(List<AppData> list) {
-        while (list.size() < 9) {
-            list.add(new EmptyAppData());
-        }
-        mLaunchpadAdapter.setList(list);
-        hideLoading();
-    }
-
-    @Override
-    public void loadError(Throwable err) {
-        err.printStackTrace();
-        hideLoading();
-    }
-
-    @Override
-    public void showGuide() {
-
-    }
-
-    @Override
-    public void addAppToLauncher(AppData model) {
-        List<AppData> dataList = mLaunchpadAdapter.getList();
-        boolean replaced = false;
-        for (int i = 0; i < dataList.size(); i++) {
-            AppData data = dataList.get(i);
-            if (data instanceof EmptyAppData) {
-                mLaunchpadAdapter.replace(i, model);
-                replaced = true;
-                break;
-            }
-        }
-        if (!replaced) {
-            mLaunchpadAdapter.add(model);
-            mLauncherView.smoothScrollToPosition(mLaunchpadAdapter.getItemCount() - 1);
-        }
-    }
-
-
-    @Override
-    public void removeAppToLauncher(AppData model) {
-        mLaunchpadAdapter.remove(model);
-    }
-
-    @Override
-    public void refreshLauncherItem(AppData model) {
-        mLaunchpadAdapter.refresh(model);
-    }
-
-    @Override
-    public void showFab() {
-        mFloatingButton.show();
-    }
-
-    @Override
-    public void hideFab() {
-        mFloatingButton.hide();
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        if (resultCode == RESULT_OK && data != null) {
-            List<AppInfoLite> appList = data.getParcelableArrayListExtra(VCommends.EXTRA_APP_INFO_LIST);
-            if (appList != null) {
-                for (AppInfoLite info : appList) {
-                    mPresenter.addApp(info);
-                }
-            }
-        }
-    }
-
-    private class LauncherTouchCallback extends ItemTouchHelper.SimpleCallback {
-
-        int[] location = new int[2];
-        boolean upAtDeleteAppArea;
-        boolean upAtCreateShortcutArea;
-        RecyclerView.ViewHolder dragHolder;
-
-        LauncherTouchCallback() {
-            super(UP | DOWN | LEFT | RIGHT | START | END, 0);
-        }
-
-        @Override
-        public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) {
-            return 0;
-        }
-
-        @Override
-        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
-            try {
-                AppData data = mLaunchpadAdapter.getList().get(viewHolder.getAdapterPosition());
-                if (!data.canReorder()) {
-                    return makeMovementFlags(0, 0);
-                }
-            } catch (IndexOutOfBoundsException e) {
-                e.printStackTrace();
-            }
-            return super.getMovementFlags(recyclerView, viewHolder);
-        }
-
-        @Override
-        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
-            int pos = viewHolder.getAdapterPosition();
-            int targetPos = target.getAdapterPosition();
-            mLaunchpadAdapter.moveItem(pos, targetPos);
-            return true;
-        }
-
-        @Override
-        public boolean isLongPressDragEnabled() {
-            return true;
-        }
-
-        @Override
-        public boolean isItemViewSwipeEnabled() {
-            return false;
-        }
-
-        @Override
-        public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
-            if (viewHolder instanceof LaunchpadAdapter.ViewHolder) {
-                if (actionState == ACTION_STATE_DRAG) {
-                    if (dragHolder != viewHolder) {
-                        dragHolder = viewHolder;
-                        viewHolder.itemView.setScaleX(1.2f);
-                        viewHolder.itemView.setScaleY(1.2f);
-                        if (bottomArea.getVisibility() == View.GONE) {
-                            showBottomAction();
-                        }
-                    }
-                }
-            }
-            super.onSelectedChanged(viewHolder, actionState);
-        }
-
-        @Override
-        public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) {
-            if (upAtCreateShortcutArea || upAtDeleteAppArea) {
-                return false;
-            }
-            try {
-                AppData data = mLaunchpadAdapter.getList().get(target.getAdapterPosition());
-                return data.canReorder();
-            } catch (IndexOutOfBoundsException e) {
-                e.printStackTrace();
-            }
-            return false;
-        }
-
-        @Override
-        public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
-            if (viewHolder instanceof LaunchpadAdapter.ViewHolder) {
-                LaunchpadAdapter.ViewHolder holder = (LaunchpadAdapter.ViewHolder) viewHolder;
-                viewHolder.itemView.setScaleX(1f);
-                viewHolder.itemView.setScaleY(1f);
-                viewHolder.itemView.setBackgroundColor(holder.color);
-            }
-            super.clearView(recyclerView, viewHolder);
-            if (dragHolder == viewHolder) {
-                if (bottomArea.getVisibility() == View.VISIBLE) {
-                    mUiHandler.postDelayed(HomeActivity.this::hideBottomAction, 200L);
-                    if (upAtCreateShortcutArea) {
-                        createShortcut(viewHolder.getAdapterPosition());
-                    } else if (upAtDeleteAppArea) {
-                        deleteApp(viewHolder.getAdapterPosition());
-                    }
-                }
-                dragHolder = null;
-            }
-        }
-
-
-        @Override
-        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
-        }
-
-        @Override
-        public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
-            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
-            if (actionState != ACTION_STATE_DRAG || !isCurrentlyActive) {
-                return;
-            }
-            View itemView = viewHolder.itemView;
-            itemView.getLocationInWindow(location);
-            int x = (int) (location[0] + dX);
-            int y = (int) (location[1] + dY);
-
-            bottomArea.getLocationInWindow(location);
-            int baseLine = location[1] - bottomArea.getHeight();
-            if (y >= baseLine) {
-                deleteAppArea.getLocationInWindow(location);
-                int deleteAppAreaStartX = location[0];
-                if (x < deleteAppAreaStartX) {
-                    upAtCreateShortcutArea = true;
-                    upAtDeleteAppArea = false;
-                    createShortcutArea.setBackgroundColor(Color.parseColor("#0099cc"));
-                    deleteAppArea.setBackgroundColor(Color.TRANSPARENT);
-                    createShortcutArea.setAlpha(0.7f);
-                    deleteAppArea.setAlpha(1f);
-                } else {
-                    upAtDeleteAppArea = true;
-                    upAtCreateShortcutArea = false;
-                    deleteAppArea.setBackgroundColor(Color.RED);
-                    createShortcutArea.setBackgroundColor(Color.TRANSPARENT);
-                    deleteAppArea.setAlpha(0.7f);
-                    createShortcutArea.setAlpha(1f);
-                }
-            } else {
-                upAtCreateShortcutArea = false;
-                upAtDeleteAppArea = false;
-                createShortcutArea.setBackgroundColor(Color.TRANSPARENT);
-                deleteAppArea.setBackgroundColor(Color.TRANSPARENT);
-                createShortcutArea.setAlpha(1f);
-                deleteAppArea.setAlpha(1f);
-            }
-        }
-    }
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/HomeContract.java b/VirtualApp/app/src/main/java/io/virtualapp/home/HomeContract.java
deleted file mode 100644
index 2665c73d7..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/HomeContract.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package io.virtualapp.home;
-
-
-import java.util.List;
-
-import io.virtualapp.abs.BasePresenter;
-import io.virtualapp.abs.BaseView;
-import io.virtualapp.home.models.AppData;
-import io.virtualapp.home.models.AppInfoLite;
-
-/**
- * @author Lody
- */
-/* package */ class HomeContract {
-
-	/* package */ interface HomeView extends BaseView<HomePresenter> {
-
-        void showBottomAction();
-
-        void hideBottomAction();
-
-		void showLoading();
-
-		void hideLoading();
-
-		void loadFinish(List<AppData> appModels);
-
-		void loadError(Throwable err);
-
-		void showGuide();
-
-		void addAppToLauncher(AppData model);
-
-        void showFab();
-
-        void hideFab();
-
-        void removeAppToLauncher(AppData model);
-
-		void refreshLauncherItem(AppData model);
-	}
-
-	/* package */ interface HomePresenter extends BasePresenter {
-
-		void launchApp(AppData data);
-
-		void dataChanged();
-
-		void addApp(AppInfoLite info);
-
-		void deleteApp(AppData data);
-
-        void createShortcut(AppData data);
-    }
-
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/HomePresenterImpl.java b/VirtualApp/app/src/main/java/io/virtualapp/home/HomePresenterImpl.java
deleted file mode 100644
index 899581f0e..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/HomePresenterImpl.java
+++ /dev/null
@@ -1,202 +0,0 @@
-package io.virtualapp.home;
-
-import android.app.Activity;
-import android.graphics.Bitmap;
-
-import com.lody.virtual.client.core.VirtualCore;
-import com.lody.virtual.os.VUserInfo;
-import com.lody.virtual.os.VUserManager;
-import com.lody.virtual.remote.InstallResult;
-import com.lody.virtual.remote.InstalledAppInfo;
-
-import java.io.IOException;
-
-import io.virtualapp.VCommends;
-import io.virtualapp.abs.ui.VUiKit;
-import io.virtualapp.home.models.AppData;
-import io.virtualapp.home.models.AppInfoLite;
-import io.virtualapp.home.models.MultiplePackageAppData;
-import io.virtualapp.home.models.PackageAppData;
-import io.virtualapp.home.repo.AppRepository;
-import io.virtualapp.home.repo.PackageAppDataStorage;
-import jonathanfinerty.once.Once;
-
-/**
- * @author Lody
- */
-class HomePresenterImpl implements HomeContract.HomePresenter {
-
-    private HomeContract.HomeView mView;
-    private Activity mActivity;
-    private AppRepository mRepo;
-
-    HomePresenterImpl(HomeContract.HomeView view) {
-        mView = view;
-        mActivity = view.getActivity();
-        mRepo = new AppRepository(mActivity);
-        mView.setPresenter(this);
-    }
-
-    @Override
-    public void start() {
-        dataChanged();
-        if (!Once.beenDone(VCommends.TAG_SHOW_ADD_APP_GUIDE)) {
-            mView.showGuide();
-            Once.markDone(VCommends.TAG_SHOW_ADD_APP_GUIDE);
-        }
-    }
-
-    @Override
-    public void launchApp(AppData data) {
-        try {
-            if (data instanceof PackageAppData) {
-                PackageAppData appData = (PackageAppData) data;
-                appData.isFirstOpen = false;
-                LoadingActivity.launch(mActivity, appData.packageName, 0);
-            } else if (data instanceof MultiplePackageAppData) {
-                MultiplePackageAppData multipleData = (MultiplePackageAppData) data;
-                multipleData.isFirstOpen = false;
-                LoadingActivity.launch(mActivity, multipleData.appInfo.packageName, ((MultiplePackageAppData) data).userId);
-            }
-        } catch (Throwable e) {
-            e.printStackTrace();
-        }
-    }
-
-    @Override
-    public void dataChanged() {
-        mView.showLoading();
-        mRepo.getVirtualApps().done(mView::loadFinish).fail(mView::loadError);
-    }
-
-
-    @Override
-    public void addApp(AppInfoLite info) {
-        class AddResult {
-            private PackageAppData appData;
-            private int userId;
-            private boolean justEnableHidden;
-        }
-        AddResult addResult = new AddResult();
-        VUiKit.defer().when(() -> {
-            InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(info.packageName, 0);
-            addResult.justEnableHidden = installedAppInfo != null;
-            if (addResult.justEnableHidden) {
-                int[] userIds = installedAppInfo.getInstalledUsers();
-                int nextUserId = userIds.length;
-                /*
-                  Input : userIds = {0, 1, 3}
-                  Output: nextUserId = 2
-                 */
-                for (int i = 0; i < userIds.length; i++) {
-                    if (userIds[i] != i) {
-                        nextUserId = i;
-                        break;
-                    }
-                }
-                addResult.userId = nextUserId;
-
-                if (VUserManager.get().getUserInfo(nextUserId) == null) {
-                    // user not exist, create it automatically.
-                    String nextUserName = "Space " + (nextUserId + 1);
-                    VUserInfo newUserInfo = VUserManager.get().createUser(nextUserName, VUserInfo.FLAG_ADMIN);
-                    if (newUserInfo == null) {
-                        throw new IllegalStateException();
-                    }
-                }
-                boolean success = VirtualCore.get().installPackageAsUser(nextUserId, info.packageName);
-                if (!success) {
-                    throw new IllegalStateException();
-                }
-            } else {
-                InstallResult res = mRepo.addVirtualApp(info);
-                if (!res.isSuccess) {
-                    throw new IllegalStateException();
-                }
-            }
-        }).then((res) -> {
-            addResult.appData = PackageAppDataStorage.get().acquire(info.packageName);
-        }).done(res -> {
-            boolean multipleVersion = addResult.justEnableHidden && addResult.userId != 0;
-            if (!multipleVersion) {
-                PackageAppData data = addResult.appData;
-                data.isLoading = true;
-                mView.addAppToLauncher(data);
-                handleOptApp(data, info.packageName, true);
-            } else {
-                MultiplePackageAppData data = new MultiplePackageAppData(addResult.appData, addResult.userId);
-                data.isLoading = true;
-                mView.addAppToLauncher(data);
-                handleOptApp(data, info.packageName, false);
-            }
-        });
-    }
-
-
-    private void handleOptApp(AppData data, String packageName, boolean needOpt) {
-        VUiKit.defer().when(() -> {
-            long time = System.currentTimeMillis();
-            if (needOpt) {
-                try {
-                    VirtualCore.get().preOpt(packageName);
-                } catch (IOException e) {
-                    e.printStackTrace();
-                }
-            }
-            time = System.currentTimeMillis() - time;
-            if (time < 1500L) {
-                try {
-                    Thread.sleep(1500L - time);
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
-                }
-            }
-        }).done((res) -> {
-            if (data instanceof PackageAppData) {
-                ((PackageAppData) data).isLoading = false;
-                ((PackageAppData) data).isFirstOpen = true;
-            } else if (data instanceof MultiplePackageAppData) {
-                ((MultiplePackageAppData) data).isLoading = false;
-                ((MultiplePackageAppData) data).isFirstOpen = true;
-            }
-            mView.refreshLauncherItem(data);
-        });
-    }
-
-    @Override
-    public void deleteApp(AppData data) {
-        try {
-            mView.removeAppToLauncher(data);
-            if (data instanceof PackageAppData) {
-                mRepo.removeVirtualApp(((PackageAppData) data).packageName, 0);
-            } else {
-                MultiplePackageAppData appData = (MultiplePackageAppData) data;
-                mRepo.removeVirtualApp(appData.appInfo.packageName, appData.userId);
-            }
-        } catch (Throwable e) {
-            e.printStackTrace();
-        }
-    }
-
-    @Override
-    public void createShortcut(AppData data) {
-        VirtualCore.OnEmitShortcutListener listener = new VirtualCore.OnEmitShortcutListener() {
-            @Override
-            public Bitmap getIcon(Bitmap originIcon) {
-                return originIcon;
-            }
-
-            @Override
-            public String getName(String originName) {
-                return originName + "(VA)";
-            }
-        };
-        if (data instanceof PackageAppData) {
-            VirtualCore.get().createShortcut(0, ((PackageAppData) data).packageName, listener);
-        } else if (data instanceof MultiplePackageAppData) {
-            MultiplePackageAppData appData = (MultiplePackageAppData) data;
-            VirtualCore.get().createShortcut(appData.userId, appData.appInfo.packageName, listener);
-        }
-    }
-
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/ListAppActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/home/ListAppActivity.java
index 6d1acc6ef..34b2cf0c7 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/ListAppActivity.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/ListAppActivity.java
@@ -4,15 +4,13 @@
 import android.app.Activity;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.design.widget.TabLayout;
 import android.support.v4.app.ActivityCompat;
 import android.support.v4.view.ViewPager;
-import android.support.v7.app.ActionBar;
+import android.support.v7.app.AlertDialog;
 import android.support.v7.widget.Toolbar;
 import android.view.MenuItem;
 
@@ -38,31 +36,29 @@ public static void gotoListApp(Activity activity) {
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        getWindow().setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.colorPrimaryDark)));
         setContentView(R.layout.activity_clone_app);
         mToolBar = (Toolbar) findViewById(R.id.clone_app_tool_bar);
         mTabLayout = (TabLayout) mToolBar.findViewById(R.id.clone_app_tab_layout);
         mViewPager = (ViewPager) findViewById(R.id.clone_app_view_pager);
-        setupToolBar();
         mViewPager.setAdapter(new AppPagerAdapter(getSupportFragmentManager()));
         mTabLayout.setupWithViewPager(mViewPager);
+
         // Request permission to access external storage
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
-                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 0);
+        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+            AlertDialog alertDialog = new AlertDialog.Builder(this, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                    .setMessage(R.string.list_app_access_external_storage)
+                    .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+                        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 0);
+                    })
+                    .create();
+            try {
+                alertDialog.show();
+            } catch (Throwable ignored) {
+                // BadTokenException.
             }
         }
     }
 
-    private void setupToolBar() {
-        setSupportActionBar(mToolBar);
-        ActionBar actionBar = getSupportActionBar();
-        if (actionBar != null) {
-            actionBar.setDisplayHomeAsUpEnabled(true);
-        }
-    }
-
-
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (item.getItemId() == android.R.id.home) {
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/ListAppFragment.java b/VirtualApp/app/src/main/java/io/virtualapp/home/ListAppFragment.java
index 1d953d932..c05499f7d 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/ListAppFragment.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/ListAppFragment.java
@@ -1,9 +1,17 @@
 package io.virtualapp.home;
 
 import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.support.v7.widget.DividerItemDecoration;
 import android.support.v7.widget.OrientationHelper;
 import android.support.v7.widget.StaggeredGridLayoutManager;
 import android.view.LayoutInflater;
@@ -19,24 +27,27 @@
 import java.util.Locale;
 
 import io.virtualapp.R;
-import io.virtualapp.VCommends;
+import io.virtualapp.XApp;
 import io.virtualapp.abs.ui.VFragment;
-import io.virtualapp.abs.ui.VUiKit;
 import io.virtualapp.home.adapters.CloneAppListAdapter;
-import io.virtualapp.home.adapters.decorations.ItemOffsetDecoration;
 import io.virtualapp.home.models.AppInfo;
 import io.virtualapp.home.models.AppInfoLite;
+import io.virtualapp.sys.Installd;
 import io.virtualapp.widgets.DragSelectRecyclerView;
 
+
 /**
  * @author Lody
  */
 public class ListAppFragment extends VFragment<ListAppContract.ListAppPresenter> implements ListAppContract.ListAppView {
     private static final String KEY_SELECT_FROM = "key_select_from";
+    private static final int REQUEST_GET_FILE = 1;
+
     private DragSelectRecyclerView mRecyclerView;
     private ProgressBar mProgressBar;
     private Button mInstallButton;
     private CloneAppListAdapter mAdapter;
+    private View mSelectFromExternal;
 
     public static ListAppFragment newInstance(File selectFrom) {
         Bundle args = new Bundle();
@@ -70,14 +81,88 @@ public void onSaveInstanceState(Bundle outState) {
         mAdapter.saveInstanceState(outState);
     }
 
+    private void whatIsTaiChi() {
+        AlertDialog alertDialog = new AlertDialog.Builder(getContext())
+                .setTitle(R.string.what_is_exp)
+                .setMessage(R.string.exp_tips)
+                .setPositiveButton(R.string.exp_introduce_title, (dialog, which) -> {
+                    Intent t = new Intent(Intent.ACTION_VIEW);
+                    t.setData(Uri.parse("https://www.coolapk.com/apk/me.weishu.exp"));
+                    startActivity(t);
+                }).setNegativeButton(R.string.about_donate_title, (dialog, which) -> {
+                    Intent t = new Intent(Intent.ACTION_VIEW);
+                    t.setData(Uri.parse("https://vxposed.com/donate.html"));
+                    startActivity(t);
+                })
+                .create();
+        try {
+            alertDialog.show();
+        } catch (Throwable ignored) {
+        }
+    }
+
+    private void chooseInstallWay(Runnable runnable, String path) {
+        AlertDialog alertDialog = new AlertDialog.Builder(getContext())
+                .setTitle(R.string.install_choose_way)
+                .setMessage(R.string.install_choose_content)
+                .setPositiveButton(R.string.install_choose_taichi, (dialog, which) -> {
+                    PackageManager packageManager = getActivity().getPackageManager();
+                    try {
+                        packageManager.getPackageInfo("me.weishu.exp", 0);
+                        Intent intent = new Intent();
+                        intent.setComponent(new ComponentName("me.weishu.exp", "me.weishu.exp.ui.MainActivity"));
+                        intent.putExtra("path", path);
+                        startActivity(intent);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        AlertDialog showInstallDialog = new AlertDialog.Builder(getContext())
+                                .setTitle(android.R.string.dialog_alert_title)
+                                .setMessage(R.string.install_taichi_not_exist)
+                                .setPositiveButton(R.string.install_go_to_install_exp, (dialog1, which1) -> {
+                                    Intent t = new Intent(Intent.ACTION_VIEW);
+                                    t.setData(Uri.parse("https://www.coolapk.com/apk/me.weishu.exp"));
+                                    startActivity(t);
+                                })
+                                .create();
+                        showInstallDialog.show();
+                    } catch (Throwable e) {
+                        AlertDialog showInstallDialog = new AlertDialog.Builder(getContext())
+                                .setTitle(android.R.string.dialog_alert_title)
+                                .setMessage(R.string.install_taichi_while_old_version)
+                                .setPositiveButton(R.string.install_go_latest_exp, (dialog1, which1) -> {
+                                    Intent t = new Intent(Intent.ACTION_VIEW);
+                                    t.setData(Uri.parse("https://www.coolapk.com/apk/me.weishu.exp"));
+                                    startActivity(t);
+                                })
+                                .create();
+                        showInstallDialog.show();
+                    }
+                    finishActivity();
+                }).setNegativeButton("VirtualXposed", (dialog, which) -> {
+                    if (runnable != null) {
+                        runnable.run();
+                    }
+                    finishActivity();
+                }).setNeutralButton(R.string.what_is_exp, ((dialog, which) -> {
+                    whatIsTaiChi();
+                }))
+                .create();
+        try {
+            alertDialog.show();
+        } catch (Throwable ignored) {
+        }
+    }
+
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         mRecyclerView = (DragSelectRecyclerView) view.findViewById(R.id.select_app_recycler_view);
         mProgressBar = (ProgressBar) view.findViewById(R.id.select_app_progress_bar);
         mInstallButton = (Button) view.findViewById(R.id.select_app_install_btn);
-        mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, OrientationHelper.VERTICAL));
-        mRecyclerView.addItemDecoration(new ItemOffsetDecoration(VUiKit.dpToPx(getContext(), 2)));
-        mAdapter = new CloneAppListAdapter(getActivity());
+        mSelectFromExternal = view.findViewById(R.id.select_app_from_external);
+        mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(1, OrientationHelper.VERTICAL));
+        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL);
+        dividerItemDecoration.setDrawable(new ColorDrawable(0x1f000000));
+        mRecyclerView.addItemDecoration(dividerItemDecoration);
+        mAdapter = new CloneAppListAdapter(getActivity(), getSelectFrom());
         mRecyclerView.setAdapter(mAdapter);
         mAdapter.setOnItemClickListener(new CloneAppListAdapter.ItemEventListener() {
             @Override
@@ -85,7 +170,7 @@ public void onItemClick(AppInfo info, int position) {
                 int count = mAdapter.getSelectedCount();
                 if (!mAdapter.isIndexSelected(position)) {
                     if (count >= 9) {
-                        Toast.makeText(getContext(), "No more then 9 apps can be chosen at a time!", Toast.LENGTH_SHORT).show();
+                        Toast.makeText(getContext(), R.string.install_too_much_once_time, Toast.LENGTH_SHORT).show();
                         return;
                     }
                 }
@@ -99,19 +184,30 @@ public boolean isSelectable(int position) {
         });
         mAdapter.setSelectionListener(count -> {
             mInstallButton.setEnabled(count > 0);
-            mInstallButton.setText(String.format(Locale.ENGLISH, "Install to SandBox (%d)", count));
+            mInstallButton.setText(String.format(Locale.ENGLISH, XApp.getApp().getResources().getString(R.string.install_d), count));
         });
         mInstallButton.setOnClickListener(v -> {
             Integer[] selectedIndices = mAdapter.getSelectedIndices();
             ArrayList<AppInfoLite> dataList = new ArrayList<AppInfoLite>(selectedIndices.length);
             for (int index : selectedIndices) {
                 AppInfo info = mAdapter.getItem(index);
-                dataList.add(new AppInfoLite(info.packageName, info.path, info.fastOpen));
+                dataList.add(new AppInfoLite(info.packageName, info.path, info.fastOpen, info.disableMultiVersion));
+            }
+
+            if (dataList.size() > 0) {
+                String path = dataList.get(0).path;
+                chooseInstallWay(() -> Installd.startInstallerActivity(getActivity(), dataList), path);
+            }
+        });
+        mSelectFromExternal.setOnClickListener(v -> {
+            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+            intent.setType("application/vnd.android.package-archive"); // apk file
+            intent.addCategory(Intent.CATEGORY_OPENABLE);
+            try {
+                startActivityForResult(intent, REQUEST_GET_FILE);
+            } catch (Throwable ignored) {
+                Toast.makeText(getActivity(), "Error", Toast.LENGTH_SHORT).show();
             }
-            Intent data = new Intent();
-            data.putParcelableArrayListExtra(VCommends.EXTRA_APP_INFO_LIST, dataList);
-            getActivity().setResult(Activity.RESULT_OK, data);
-            getActivity().finish();
         });
         new ListAppPresenterImpl(getActivity(), this, getSelectFrom()).start();
     }
@@ -125,7 +221,7 @@ public void startLoading() {
     @Override
     public void loadFinish(List<AppInfo> infoList) {
         mAdapter.setList(infoList);
-        mRecyclerView.setDragSelectActive(true, 0);
+        mRecyclerView.setDragSelectActive(false, 0);
         mAdapter.setSelected(0, false);
         mProgressBar.setVisibility(View.GONE);
         mRecyclerView.setVisibility(View.VISIBLE);
@@ -136,4 +232,49 @@ public void setPresenter(ListAppContract.ListAppPresenter presenter) {
         this.mPresenter = presenter;
     }
 
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (!(requestCode == REQUEST_GET_FILE && resultCode == Activity.RESULT_OK)) {
+            return;
+        }
+        Activity current = getActivity();
+        if (current == null) {
+            return;
+        }
+
+        Uri uri = data.getData();
+        String path = getPath(getActivity(), uri);
+        if (path == null) {
+            return;
+        }
+
+        chooseInstallWay(() -> Installd.handleRequestFromFile(getActivity(), path), path);
+    }
+
+    public static String getPath(Context context, Uri uri) {
+        if (uri == null) {
+            return null;
+        }
+        if ("content".equalsIgnoreCase(uri.getScheme())) {
+            String[] projection = {"_data"};
+            Cursor cursor = null;
+            try {
+                cursor = context.getContentResolver().query(uri, projection, null, null, null);
+                int column_index = cursor.getColumnIndexOrThrow("_data");
+                if (cursor.moveToFirst()) {
+                    return cursor.getString(column_index);
+                }
+            } catch (Exception e) {
+                // Eat it  Or Log it.
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
+            return uri.getPath();
+        }
+        return null;
+    }
 }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/LoadingActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/home/LoadingActivity.java
index 9d7547ff8..3c68a5704 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/LoadingActivity.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/LoadingActivity.java
@@ -1,16 +1,35 @@
 package io.virtualapp.home;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
+import android.util.Log;
 import android.widget.ImageView;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.env.Constants;
 import com.lody.virtual.client.ipc.VActivityManager;
+import com.lody.virtual.client.ipc.VPackageManager;
+import com.lody.virtual.helper.utils.VLog;
+import com.lody.virtual.server.pm.parser.VPackage;
 
+import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 
 import io.virtualapp.R;
 import io.virtualapp.abs.ui.VActivity;
@@ -18,6 +37,7 @@
 import io.virtualapp.home.models.PackageAppData;
 import io.virtualapp.home.repo.PackageAppDataStorage;
 import io.virtualapp.widgets.EatBeansView;
+import jonathanfinerty.once.Once;
 
 /**
  * @author Lody
@@ -25,61 +45,234 @@
 
 public class LoadingActivity extends VActivity {
 
-    private static final String PKG_NAME_ARGUMENT = "MODEL_ARGUMENT";
-    private static final String KEY_INTENT = "KEY_INTENT";
-    private static final String KEY_USER = "KEY_USER";
+    private static final String TAG = "LoadingActivity";
+
     private PackageAppData appModel;
     private EatBeansView loadingView;
 
-    public static void launch(Context context, String packageName, int userId) {
+    private static final int REQUEST_PERMISSION_CODE = 100;
+
+    private Intent intentToLaunch;
+    private int userToLaunch;
+
+    private long start;
+
+    public static boolean launch(Context context, String packageName, int userId) {
         Intent intent = VirtualCore.get().getLaunchIntent(packageName, userId);
         if (intent != null) {
             Intent loadingPageIntent = new Intent(context, LoadingActivity.class);
-            loadingPageIntent.putExtra(PKG_NAME_ARGUMENT, packageName);
+            loadingPageIntent.putExtra(Constants.PASS_PKG_NAME_ARGUMENT, packageName);
             loadingPageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            loadingPageIntent.putExtra(KEY_INTENT, intent);
-            loadingPageIntent.putExtra(KEY_USER, userId);
+            loadingPageIntent.putExtra(Constants.PASS_KEY_INTENT, intent);
+            loadingPageIntent.putExtra(Constants.PASS_KEY_USER, userId);
             context.startActivity(loadingPageIntent);
+            return true;
+        } else {
+            return false;
         }
     }
 
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        start = SystemClock.elapsedRealtime();
+
         setContentView(R.layout.activity_loading);
         loadingView = (EatBeansView) findViewById(R.id.loading_anim);
-        int userId = getIntent().getIntExtra(KEY_USER, -1);
-        String pkg = getIntent().getStringExtra(PKG_NAME_ARGUMENT);
+        int userId = getIntent().getIntExtra(Constants.PASS_KEY_USER, -1);
+        String pkg = getIntent().getStringExtra(Constants.PASS_PKG_NAME_ARGUMENT);
         appModel = PackageAppDataStorage.get().acquire(pkg);
+        if (appModel == null) {
+            Toast.makeText(getApplicationContext(), "Open App:" + pkg + " failed.", Toast.LENGTH_SHORT).show();
+            finish();
+            return;
+        }
+
         ImageView iconView = (ImageView) findViewById(R.id.app_icon);
         iconView.setImageDrawable(appModel.icon);
         TextView nameView = (TextView) findViewById(R.id.app_name);
         nameView.setText(String.format(Locale.ENGLISH, "Opening %s...", appModel.name));
-        Intent intent = getIntent().getParcelableExtra(KEY_INTENT);
+        Intent intent = getIntent().getParcelableExtra(Constants.PASS_KEY_INTENT);
         if (intent == null) {
+            finish();
             return;
         }
         VirtualCore.get().setUiCallback(intent, mUiCallback);
-        VUiKit.defer().when(() -> {
-            long startTime = System.currentTimeMillis();
-            if (!appModel.fastOpen) {
-                try {
-                    VirtualCore.get().preOpt(appModel.packageName);
-                } catch (Exception e) {
-                    e.printStackTrace();
+
+        try {
+            // 如果已经在运行了,那么直接拉起,不做任何检测。
+            boolean uiRunning = false;
+            ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+            if (am != null) {
+                List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();
+                for (ActivityManager.RunningAppProcessInfo runningAppProcess : runningAppProcesses) {
+                    String appProcessName = VActivityManager.get().getAppProcessName(runningAppProcess.pid);
+                    if (TextUtils.equals(appProcessName, pkg)) {
+                        uiRunning = true;
+                        break;
+                    }
                 }
             }
-            long spend = System.currentTimeMillis() - startTime;
-            if (spend < 500) {
-                try {
-                    Thread.sleep(500 - spend);
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
+
+            VLog.i(TAG, pkg + "is running: " + uiRunning);
+            if (uiRunning) {
+                launchActivity(intent, userId);
+                return;
+            }
+        } catch (Throwable ignored) {
+            ignored.printStackTrace();
+        }
+
+        checkAndLaunch(intent, userId);
+    }
+
+    private void checkAndLaunch(Intent intent, int userId) {
+        final int RUNTIME_PERMISSION_API_LEVEL = android.os.Build.VERSION_CODES.M;
+
+        if (android.os.Build.VERSION.SDK_INT < RUNTIME_PERMISSION_API_LEVEL) {
+            // the device is below Android M, the permissions are granted when install, start directly
+            Log.i(TAG, "device's api level below Android M, do not need runtime permission.");
+            launchActivityWithDelay(intent, userId);
+            return;
+        }
+
+        // The device is above android M, support runtime permission.
+        String packageName = appModel.packageName;
+        String name = appModel.name;
+
+        // analyze permission
+        try {
+            ApplicationInfo applicationInfo = VPackageManager.get().getApplicationInfo(packageName, 0, 0);
+            int targetSdkVersion = applicationInfo.targetSdkVersion;
+            Log.i(TAG, "target package: " + packageName + " targetSdkVersion: " + targetSdkVersion);
+
+            if (targetSdkVersion >= RUNTIME_PERMISSION_API_LEVEL) {
+                Log.i(TAG, "target package support runtime permission, launch directly.");
+                launchActivityWithDelay(intent, userId);
+            } else {
+
+                intentToLaunch = intent;
+                userToLaunch = userId;
+
+                PackageInfo packageInfo = VPackageManager.get().getPackageInfo(packageName, PackageManager.GET_PERMISSIONS, 0);
+                String[] requestedPermissions = packageInfo.requestedPermissions;
+
+                Set<String> dangerousPermissions = new HashSet<>();
+                for (String requestedPermission : requestedPermissions) {
+                    if (VPackage.PermissionComponent.DANGEROUS_PERMISSION.contains(requestedPermission)) {
+                        // dangerous permission, check it
+                        if (ContextCompat.checkSelfPermission(this, requestedPermission) != PackageManager.PERMISSION_GRANTED) {
+                            dangerousPermissions.add(requestedPermission);
+                        } else {
+                            Log.i(TAG, "permission: " + requestedPermission + " is granted, ignore.");
+                        }
+                    }
+                }
+
+                if (dangerousPermissions.isEmpty()) {
+                    Log.i(TAG, "all permission are granted, launch directly.");
+                    // all permission are granted, launch directly.
+                    launchActivityWithDelay(intent, userId);
+                } else {
+                    // tell user that this app need that permission
+                    Log.i(TAG, "request permission: " + dangerousPermissions);
+
+                    AlertDialog alertDialog = new AlertDialog.Builder(this, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                            .setTitle(R.string.permission_tip_title)
+                            .setMessage(getResources().getString(R.string.permission_tips_content, name))
+                            .setPositiveButton(R.string.permission_tips_confirm, (dialog, which) -> {
+                                String[] permissionToRequest = dangerousPermissions.toArray(new String[dangerousPermissions.size()]);
+                                try {
+                                    ActivityCompat.requestPermissions(this, permissionToRequest, REQUEST_PERMISSION_CODE);
+                                } catch (Throwable ignored) {
+                                }
+                            })
+                            .create();
+                    try {
+                        alertDialog.show();
+                    } catch (Throwable ignored) {
+                        // BadTokenException.
+                        finish();
+                        Toast.makeText(this, getResources().getString(R.string.start_app_failed, appModel.name), Toast.LENGTH_SHORT).show();
+                    }
                 }
             }
-        }).done((res) ->
-                VActivityManager.get().startActivity(intent, userId));
+        } catch (Throwable e) {
+            Log.e(TAG, "check permission failed: ", e);
+            launchActivityWithDelay(intent, userId);
+        }
+    }
+
+    private void launchActivityWithDelay(Intent intent, int userId) {
+        final int MAX_WAIT = 1000;
+        long delta = SystemClock.elapsedRealtime() - start;
+        long waitTime = MAX_WAIT - delta;
+
+        if (waitTime <= 0) {
+            launchActivity(intent, userId);
+        } else {
+            loadingView.postDelayed(() -> launchActivity(intent, userId), waitTime);
+        }
+    }
 
+    private void launchActivity(Intent intent, int userId) {
+        try {
+            VActivityManager.get().startActivity(intent, userId);
+        } catch (Throwable e) {
+            VLog.e(TAG, "start activity failed:", e);
+            Toast.makeText(getApplicationContext(), getResources().getString(R.string.start_app_failed, appModel.name), Toast.LENGTH_SHORT).show();
+            finish();
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        if (requestCode == REQUEST_PERMISSION_CODE) {
+            boolean allGranted = true;
+            for (int grantResult : grantResults) {
+                if (grantResult == PackageManager.PERMISSION_DENIED) {
+                    allGranted = false;
+                    break;
+                }
+            }
+
+            if (allGranted) {
+                if (intentToLaunch == null) {
+                    Toast.makeText(this, getResources().getString(R.string.start_app_failed, appModel.name), Toast.LENGTH_SHORT).show();
+                    finish();
+                } else {
+                    launchActivityWithDelay(intentToLaunch, userToLaunch);
+                }
+            } else {
+                // 提示用户,targetSdkVersion < 23 无法使用运行时权限
+                Log.i(TAG, "can not use runtime permission, you must grant all permission, otherwise the app may not work!");
+
+                final String tag = "permission_tips_" + appModel.packageName.replaceAll("\\.", "_");
+                // TODO find a device figuring out why some permissions are not detected.
+                if (!Once.beenDone(tag)) {
+                    AlertDialog alertDialog = new AlertDialog.Builder(this, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                            .setTitle(android.R.string.dialog_alert_title)
+                            .setMessage(getResources().getString(R.string.permission_denied_tips_content, appModel.name))
+                            .setPositiveButton(R.string.permission_tips_confirm, (dialog, which) -> {
+                                finish();
+                                Once.markDone(tag);
+                                launchActivityWithDelay(intentToLaunch, userToLaunch);
+                            })
+                            .create();
+                    try {
+                        alertDialog.show();
+                    } catch (Throwable ignored) {
+                        // BadTokenException.
+                        Toast.makeText(this, getResources().getString(R.string.start_app_failed, appModel.name), Toast.LENGTH_SHORT).show();
+                    }
+                } else {
+                    launchActivityWithDelay(intentToLaunch, userToLaunch);
+                    finish();
+                }
+            }
+        }
     }
 
     private final VirtualCore.UiCallback mUiCallback = new VirtualCore.UiCallback() {
@@ -88,17 +281,38 @@ protected void onCreate(Bundle savedInstanceState) {
         public void onAppOpened(String packageName, int userId) throws RemoteException {
             finish();
         }
+
+        @Override
+        public void onOpenFailed(String packageName, int userId) throws RemoteException {
+            VUiKit.defer().when(() -> {
+            }).done((v) -> {
+                if (!isFinishing()) {
+                    Toast.makeText(getApplicationContext(), getResources().getString(R.string.start_app_failed, packageName),
+                            Toast.LENGTH_SHORT).show();
+                }
+            });
+
+            finish();
+        }
     };
 
     @Override
     protected void onResume() {
         super.onResume();
-        loadingView.startAnim();
+        startAnim();
+    }
+
+    private void startAnim() {
+        if (loadingView != null) {
+            loadingView.startAnim();
+        }
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
-        loadingView.stopAnim();
+    protected void onStop() {
+        super.onStop();
+        if (loadingView != null) {
+            loadingView.stopAnim();
+        }
     }
 }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/NewHomeActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/home/NewHomeActivity.java
new file mode 100644
index 000000000..657fc58bc
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/NewHomeActivity.java
@@ -0,0 +1,341 @@
+package io.virtualapp.home;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.launcher3.LauncherFiles;
+import com.google.android.apps.nexuslauncher.NexusLauncherActivity;
+import com.lody.virtual.client.core.InstallStrategy;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.helper.utils.DeviceUtil;
+import com.lody.virtual.helper.utils.FileUtils;
+import com.lody.virtual.helper.utils.MD5Utils;
+import com.lody.virtual.helper.utils.VLog;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+
+import io.virtualapp.R;
+import io.virtualapp.abs.ui.VUiKit;
+import io.virtualapp.settings.SettingsActivity;
+import io.virtualapp.update.VAVersionService;
+
+import static io.virtualapp.XApp.XPOSED_INSTALLER_PACKAGE;
+
+/**
+ * @author weishu
+ * @date 18/2/9.
+ */
+
+public class NewHomeActivity extends NexusLauncherActivity {
+
+    private static final String SHOW_DOZE_ALERT_KEY = "SHOW_DOZE_ALERT_KEY";
+    private static final String WALLPAPER_FILE_NAME = "wallpaper.png";
+
+    private Handler mUiHandler;
+    private boolean mDirectlyBack = false;
+    private boolean checkXposedInstaller = true;
+
+    public static void goHome(Context context) {
+        Intent intent = new Intent(context, NewHomeActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        SharedPreferences sharedPreferences = getSharedPreferences(LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
+        super.onCreate(savedInstanceState);
+        showMenuKey();
+        mUiHandler = new Handler(getMainLooper());
+        alertForMeizu();
+        alertForDoze();
+        mDirectlyBack = sharedPreferences.getBoolean(SettingsActivity.DIRECTLY_BACK_KEY, false);
+    }
+
+    private void installXposed() {
+        boolean isXposedInstalled = false;
+        try {
+            isXposedInstalled = VirtualCore.get().isAppInstalled(XPOSED_INSTALLER_PACKAGE);
+            File oldXposedInstallerApk = getFileStreamPath("XposedInstaller_1_31.apk");
+            if (oldXposedInstallerApk.exists()) {
+                VirtualCore.get().uninstallPackage(XPOSED_INSTALLER_PACKAGE);
+                oldXposedInstallerApk.delete();
+                isXposedInstalled = false;
+                Log.d(TAG, "remove xposed installer success!");
+            }
+        } catch (Throwable e) {
+            VLog.d(TAG, "remove xposed install failed.", e);
+        }
+
+        if (!isXposedInstalled) {
+            ProgressDialog dialog = new ProgressDialog(this);
+            dialog.setCancelable(false);
+            dialog.setMessage(getResources().getString(R.string.prepare_xposed_installer));
+            dialog.show();
+
+            VUiKit.defer().when(() -> {
+                File xposedInstallerApk = getFileStreamPath("XposedInstaller_5_8.apk");
+                if (!xposedInstallerApk.exists()) {
+                    InputStream input = null;
+                    OutputStream output = null;
+                    try {
+                        input = getApplicationContext().getAssets().open("XposedInstaller_3.1.5.apk_");
+                        output = new FileOutputStream(xposedInstallerApk);
+                        byte[] buffer = new byte[1024];
+                        int length;
+                        while ((length = input.read(buffer)) > 0) {
+                            output.write(buffer, 0, length);
+                        }
+                    } catch (Throwable e) {
+                        VLog.e(TAG, "copy file error", e);
+                    } finally {
+                        FileUtils.closeQuietly(input);
+                        FileUtils.closeQuietly(output);
+                    }
+                }
+
+                if (xposedInstallerApk.isFile() && !DeviceUtil.isMeizuBelowN()) {
+                    try {
+                        if ("8537fb219128ead3436cc19ff35cfb2e".equals(MD5Utils.getFileMD5String(xposedInstallerApk))) {
+                            VirtualCore.get().installPackage(xposedInstallerApk.getPath(), InstallStrategy.TERMINATE_IF_EXIST);
+                        } else {
+                            VLog.w(TAG, "unknown Xposed installer, ignore!");
+                        }
+                    } catch (Throwable ignored) {
+                    }
+                }
+            }).then((v) -> {
+                dismissDialog(dialog);
+            }).fail((err) -> {
+                dismissDialog(dialog);
+            });
+        }
+    }
+
+    private static void dismissDialog(ProgressDialog dialog) {
+        if (dialog == null) {
+            return;
+        }
+        try {
+            dialog.dismiss();
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (checkXposedInstaller) {
+            checkXposedInstaller = false;
+            installXposed();
+        }
+        // check for update
+        new Handler().postDelayed(() ->
+                VAVersionService.checkUpdate(getApplicationContext(), false), 1000);
+
+        // check for wallpaper
+        setWallpaper();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_MENU) {
+            onSettingsClicked();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    public Activity getActivity() {
+        return this;
+    }
+
+    public Context getContext() {
+        return this;
+    }
+
+    @Override
+    public void onClickAddWidgetButton(View view) {
+        onAddAppClicked();
+    }
+
+    private void onAddAppClicked() {
+        ListAppActivity.gotoListApp(this);
+    }
+
+    private void onSettingsClicked() {
+        startActivity(new Intent(NewHomeActivity.this, SettingsActivity.class));
+    }
+
+    @Override
+    public void onClickSettingsButton(View v) {
+        onSettingsClicked();
+    }
+
+    @Override
+    protected void onClickAllAppsButton(View v) {
+        onSettingsClicked();
+    }
+
+    @Override
+    public void startVirtualActivity(Intent intent, Bundle options, int usedId) {
+        String packageName = intent.getPackage();
+        if (TextUtils.isEmpty(packageName)) {
+            ComponentName component = intent.getComponent();
+            if (component != null) {
+                packageName = component.getPackageName();
+            }
+        }
+        if (packageName == null) {
+            try {
+                startActivity(intent);
+                return;
+            } catch (Throwable ignored) {
+                // ignore
+            }
+        }
+        boolean result = LoadingActivity.launch(this, packageName, usedId);
+        if (!result) {
+            throw new ActivityNotFoundException("can not launch activity for :" + intent);
+        }
+        if (mDirectlyBack) {
+            finish();
+        }
+    }
+
+    private void alertForMeizu() {
+        if (!DeviceUtil.isMeizuBelowN()) {
+            return;
+        }
+        boolean isXposedInstalled = VirtualCore.get().isAppInstalled(XPOSED_INSTALLER_PACKAGE);
+        if (isXposedInstalled) {
+            return;
+        }
+        mUiHandler.postDelayed(() -> {
+            AlertDialog alertDialog = new AlertDialog.Builder(getContext())
+                    .setTitle(R.string.meizu_device_tips_title)
+                    .setMessage(R.string.meizu_device_tips_content)
+                    .setPositiveButton(android.R.string.yes, (dialog, which) -> {
+                    })
+                    .create();
+            try {
+                alertDialog.show();
+            } catch (Throwable ignored) {
+            }
+        }, 2000);
+    }
+
+    private void alertForDoze() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            return;
+        }
+        PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
+        if (powerManager == null) {
+            return;
+        }
+        boolean showAlert = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(SHOW_DOZE_ALERT_KEY, true);
+        if (!showAlert) {
+            return;
+        }
+        String packageName = getPackageName();
+        boolean ignoringBatteryOptimizations = powerManager.isIgnoringBatteryOptimizations(packageName);
+        if (!ignoringBatteryOptimizations) {
+
+            mUiHandler.postDelayed(() -> {
+                AlertDialog alertDialog = new AlertDialog.Builder(getContext())
+                        .setTitle(R.string.alert_for_doze_mode_title)
+                        .setMessage(R.string.alert_for_doze_mode_content)
+                        .setPositiveButton(R.string.alert_for_doze_mode_yes, (dialog, which) -> {
+                            try {
+                                startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse("package:" + getPackageName())));
+                            } catch (ActivityNotFoundException ignored) {
+                                // ActivityNotFoundException on some devices.
+                                try {
+                                    startActivity(new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS));
+                                } catch (Throwable e) {
+                                    PreferenceManager.getDefaultSharedPreferences(getActivity())
+                                            .edit().putBoolean(SHOW_DOZE_ALERT_KEY, false).apply();
+                                }
+                            } catch (Throwable e) {
+                                PreferenceManager.getDefaultSharedPreferences(getActivity())
+                                        .edit().putBoolean(SHOW_DOZE_ALERT_KEY, false).apply();
+                            }
+                        })
+                        .setNegativeButton(R.string.alert_for_doze_mode_no, (dialog, which) ->
+                                PreferenceManager.getDefaultSharedPreferences(getActivity())
+                                        .edit().putBoolean(SHOW_DOZE_ALERT_KEY, false).apply())
+                        .create();
+                try {
+                    alertDialog.show();
+                } catch (Throwable ignored) {
+                    ignored.printStackTrace();
+                }
+            }, 1000);
+        }
+    }
+
+    private void setWallpaper() {
+        File wallpaper = getFileStreamPath(WALLPAPER_FILE_NAME);
+        if (wallpaper == null || !wallpaper.exists() || wallpaper.isDirectory()) {
+            setOurWallpaper(getResources().getDrawable(R.drawable.home_bg));
+        } else {
+            long start = SystemClock.elapsedRealtime();
+            Drawable d;
+            try {
+                d = BitmapDrawable.createFromPath(wallpaper.getPath());
+            } catch (Throwable e) {
+                Toast.makeText(getApplicationContext(), R.string.wallpaper_too_big_tips, Toast.LENGTH_SHORT).show();
+                return;
+            }
+            long cost = SystemClock.elapsedRealtime() - start;
+            if (cost > 200) {
+                Toast.makeText(getApplicationContext(), R.string.wallpaper_too_big_tips, Toast.LENGTH_SHORT).show();
+            }
+            if (d == null) {
+                setOurWallpaper(getResources().getDrawable(R.drawable.home_bg));
+            } else {
+                setOurWallpaper(d);
+            }
+        }
+    }
+
+    private void showMenuKey() {
+        try {
+            Method setNeedsMenuKey = Window.class.getDeclaredMethod("setNeedsMenuKey", int.class);
+            setNeedsMenuKey.setAccessible(true);
+            int value = WindowManager.LayoutParams.class.getField("NEEDS_MENU_SET_TRUE").getInt(null);
+            setNeedsMenuKey.invoke(getWindow(), value);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/AppPagerAdapter.java b/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/AppPagerAdapter.java
index 1bfffd67d..ad152d634 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/AppPagerAdapter.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/AppPagerAdapter.java
@@ -9,13 +9,15 @@
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentPagerAdapter;
 
+import com.lody.virtual.helper.utils.DeviceUtil;
 import com.lody.virtual.helper.utils.Reflect;
 
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
-import io.virtualapp.VApp;
+import io.virtualapp.XApp;
+import io.virtualapp.R;
 import io.virtualapp.home.ListAppFragment;
 
 /**
@@ -27,10 +29,10 @@ public class AppPagerAdapter extends FragmentPagerAdapter {
 
     public AppPagerAdapter(FragmentManager fm) {
         super(fm);
-        titles.add("Clone Apps");
+        titles.add(XApp.getApp().getResources().getString(R.string.clone_apps));
         dirs.add(null);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            Context ctx = VApp.getApp();
+            Context ctx = XApp.getApp();
             StorageManager storage = (StorageManager) ctx.getSystemService(Context.STORAGE_SERVICE);
             for (StorageVolume volume : storage.getStorageVolumes()) {
                 //Why the fuck are getPathFile and getUserLabel hidden?!
@@ -44,10 +46,12 @@ public AppPagerAdapter(FragmentManager fm) {
             }
         } else {
             // Fallback: only support the default storage sources
-            File storageFir = Environment.getExternalStorageDirectory();
-            if (storageFir.list() != null) {
-                titles.add("Ghost Installation");
-                dirs.add(storageFir);
+            if (!DeviceUtil.isMeizuBelowN()) {
+                File storageFir = Environment.getExternalStorageDirectory();
+                if (storageFir != null && storageFir.isDirectory()) {
+                    titles.add(XApp.getApp().getResources().getString(R.string.external_storage));
+                    dirs.add(storageFir);
+                }
             }
         }
     }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/CloneAppListAdapter.java b/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/CloneAppListAdapter.java
index 696ae2e5a..a527ad471 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/CloneAppListAdapter.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/CloneAppListAdapter.java
@@ -1,6 +1,7 @@
 package io.virtualapp.home.adapters;
 
 import android.content.Context;
+import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.StaggeredGridLayoutManager;
 import android.view.LayoutInflater;
@@ -9,10 +10,12 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import java.io.File;
 import java.util.List;
 
 import io.virtualapp.R;
 import io.virtualapp.abs.ui.VUiKit;
+import io.virtualapp.glide.GlideUtils;
 import io.virtualapp.home.models.AppInfo;
 import io.virtualapp.widgets.DragSelectRecyclerViewAdapter;
 import io.virtualapp.widgets.LabelView;
@@ -28,7 +31,13 @@ public class CloneAppListAdapter extends DragSelectRecyclerViewAdapter<CloneAppL
     private List<AppInfo> mAppList;
     private ItemEventListener mItemEventListener;
 
-    public CloneAppListAdapter(Context context) {
+    private Context mContext;
+    private File mFrom;
+
+
+    public CloneAppListAdapter(Context context, @Nullable File from) {
+        mContext = context;
+        mFrom = from;
         this.mInflater = LayoutInflater.from(context);
         mFooterView = new View(context);
         StaggeredGridLayoutManager.LayoutParams params = new StaggeredGridLayoutManager.LayoutParams(
@@ -67,8 +76,14 @@ public void onBindViewHolder(ViewHolder holder, int position) {
         }
         super.onBindViewHolder(holder, position);
         AppInfo info = mAppList.get(position);
-        holder.iconView.setImageDrawable(info.icon);
-        holder.nameView.setText(info.name);
+
+        if (mFrom == null) {
+            GlideUtils.loadInstalledPackageIcon(mContext, info.packageName, holder.iconView, android.R.drawable.sym_def_app_icon);
+        } else {
+            GlideUtils.loadPackageIconFromApkFile(mContext, info.path, holder.iconView, android.R.drawable.sym_def_app_icon);
+        }
+
+        holder.nameView.setText(String.format("%s: %s", info.name, info.version));
         if (isIndexSelected(position)) {
             holder.iconView.setAlpha(1f);
             holder.appCheckView.setImageResource(R.drawable.ic_check);
@@ -83,6 +98,12 @@ public void onBindViewHolder(ViewHolder holder, int position) {
             holder.labelView.setVisibility(View.INVISIBLE);
         }
 
+        if (info.path == null) {
+            holder.summaryView.setVisibility(View.GONE);
+        } else {
+            holder.summaryView.setVisibility(View.VISIBLE);
+            holder.summaryView.setText(info.path);
+        }
         holder.itemView.setOnClickListener(v -> {
             mItemEventListener.onItemClick(info, position);
         });
@@ -127,6 +148,7 @@ class ViewHolder extends RecyclerView.ViewHolder {
         private TextView nameView;
         private ImageView appCheckView;
         private LabelView labelView;
+        private TextView summaryView;
 
         ViewHolder(View itemView) {
             super(itemView);
@@ -135,6 +157,7 @@ class ViewHolder extends RecyclerView.ViewHolder {
                 nameView = (TextView) itemView.findViewById(R.id.item_app_name);
                 appCheckView = (ImageView) itemView.findViewById(R.id.item_app_checked);
                 labelView = (LabelView) itemView.findViewById(R.id.item_app_clone_count);
+                summaryView = (TextView) itemView.findViewById(R.id.item_app_summary);
             }
         }
     }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/LaunchpadAdapter.java b/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/LaunchpadAdapter.java
deleted file mode 100644
index 3a825d7ea..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/LaunchpadAdapter.java
+++ /dev/null
@@ -1,183 +0,0 @@
-package io.virtualapp.home.adapters;
-
-import android.content.Context;
-import android.support.v7.widget.RecyclerView;
-import android.util.SparseIntArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import java.util.List;
-
-import io.virtualapp.R;
-import io.virtualapp.abs.ui.VUiKit;
-import io.virtualapp.home.models.AppData;
-import io.virtualapp.home.models.MultiplePackageAppData;
-import io.virtualapp.widgets.LabelView;
-import io.virtualapp.widgets.LauncherIconView;
-
-/**
- * @author Lody
- */
-public class LaunchpadAdapter extends RecyclerView.Adapter<LaunchpadAdapter.ViewHolder> {
-
-    private LayoutInflater mInflater;
-    private List<AppData> mList;
-    private SparseIntArray mColorArray = new SparseIntArray();
-    private OnAppClickListener mAppClickListener;
-
-    public LaunchpadAdapter(Context context) {
-        mInflater = LayoutInflater.from(context);
-    }
-
-    public void add(AppData model) {
-        mList.add(model);
-        notifyItemInserted(mList.size() - 1);
-    }
-
-    public void replace(int index, AppData data) {
-        mList.set(index, data);
-        notifyItemChanged(index);
-    }
-
-    public void remove(AppData data) {
-        if (mList.remove(data)) {
-            notifyDataSetChanged();
-        }
-    }
-
-    @Override
-    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        return new ViewHolder(mInflater.inflate(R.layout.item_launcher_app, null));
-    }
-
-    @Override
-    public void onBindViewHolder(ViewHolder holder, int position) {
-        AppData data = mList.get(position);
-        holder.color = getColor(position);
-        holder.iconView.setImageDrawable(data.getIcon());
-        holder.nameView.setText(data.getName());
-        if (data.isFirstOpen() && !data.isLoading()) {
-            holder.firstOpenDot.setVisibility(View.VISIBLE);
-        } else {
-            holder.firstOpenDot.setVisibility(View.INVISIBLE);
-        }
-        holder.itemView.setBackgroundColor(holder.color);
-        holder.itemView.setOnClickListener(v -> {
-            if (mAppClickListener != null) {
-                mAppClickListener.onAppClick(position, data);
-            }
-        });
-        if (data instanceof MultiplePackageAppData) {
-            MultiplePackageAppData multipleData = (MultiplePackageAppData) data;
-            holder.spaceLabelView.setVisibility(View.VISIBLE);
-            holder.spaceLabelView.setText(multipleData.userId + 1 + "");
-        } else {
-            holder.spaceLabelView.setVisibility(View.INVISIBLE);
-        }
-        if (data.isLoading()) {
-            startLoadingAnimation(holder.iconView);
-        } else {
-            holder.iconView.setProgress(100, false);
-        }
-    }
-
-    private void startLoadingAnimation(LauncherIconView iconView) {
-        iconView.setProgress(40, true);
-        VUiKit.defer().when(() -> {
-            try {
-                Thread.sleep(900L);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }).done((res) -> iconView.setProgress(80, true));
-    }
-
-    private int getColor(int position) {
-        int color = mColorArray.get(position);
-        if (color == 0) {
-            int type = position % 3;
-            int row = position / 3;
-            int rowType = row % 3;
-            if (rowType == 0) {
-                if (type == 0) {
-                    color = mInflater.getContext().getResources().getColor(R.color.desktopColorA);
-                } else if (type == 1) {
-                    color = mInflater.getContext().getResources().getColor(R.color.desktopColorB);
-                } else {
-                    color = mInflater.getContext().getResources().getColor(R.color.desktopColorC);
-                }
-            } else if (rowType == 1) {
-                if (type == 0) {
-                    color = mInflater.getContext().getResources().getColor(R.color.desktopColorB);
-                } else if (type == 1) {
-                    color = mInflater.getContext().getResources().getColor(R.color.desktopColorC);
-                } else {
-                    color = mInflater.getContext().getResources().getColor(R.color.desktopColorA);
-                }
-            } else {
-                if (type == 0) {
-                    color = mInflater.getContext().getResources().getColor(R.color.desktopColorC);
-                } else if (type == 1) {
-                    color = mInflater.getContext().getResources().getColor(R.color.desktopColorA);
-                } else {
-                    color = mInflater.getContext().getResources().getColor(R.color.desktopColorB);
-                }
-            }
-            mColorArray.put(position, color);
-        }
-        return color;
-    }
-
-    @Override
-    public int getItemCount() {
-        return mList == null ? 0 : mList.size();
-    }
-
-    public List<AppData> getList() {
-        return mList;
-    }
-
-    public void setList(List<AppData> list) {
-        this.mList = list;
-        notifyDataSetChanged();
-    }
-
-    public void setAppClickListener(OnAppClickListener mAppClickListener) {
-        this.mAppClickListener = mAppClickListener;
-    }
-
-    public void moveItem(int pos, int targetPos) {
-        AppData model = mList.remove(pos);
-        mList.add(targetPos, model);
-        notifyItemMoved(pos, targetPos);
-    }
-
-    public void refresh(AppData model) {
-        int index = mList.indexOf(model);
-        if (index >= 0) {
-            notifyItemChanged(index);
-        }
-    }
-
-    public interface OnAppClickListener {
-        void onAppClick(int position, AppData model);
-    }
-
-    public class ViewHolder extends RecyclerView.ViewHolder {
-        public int color;
-        LauncherIconView iconView;
-        TextView nameView;
-        LabelView spaceLabelView;
-        View firstOpenDot;
-
-        ViewHolder(View itemView) {
-            super(itemView);
-            iconView = (LauncherIconView) itemView.findViewById(R.id.item_app_icon);
-            nameView = (TextView) itemView.findViewById(R.id.item_app_name);
-            spaceLabelView = (LabelView) itemView.findViewById(R.id.item_app_space_idx);
-            firstOpenDot = itemView.findViewById(R.id.item_first_open_dot);
-        }
-    }
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/decorations/ItemOffsetDecoration.java b/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/decorations/ItemOffsetDecoration.java
deleted file mode 100644
index 533a38827..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/adapters/decorations/ItemOffsetDecoration.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package io.virtualapp.home.adapters.decorations;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.support.annotation.DimenRes;
-import android.support.annotation.NonNull;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-
-public class ItemOffsetDecoration extends RecyclerView.ItemDecoration {
-
-    private int mItemOffset;
-
-    public ItemOffsetDecoration(int itemOffset) {
-        mItemOffset = itemOffset;
-    }
-
-    public ItemOffsetDecoration(@NonNull Context context, @DimenRes int itemOffsetId) {
-        this(context.getResources().getDimensionPixelSize(itemOffsetId));
-    }
-
-    @Override
-    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
-                               RecyclerView.State state) {
-        outRect.set(mItemOffset, mItemOffset, mItemOffset, mItemOffset);
-    }
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppData.java b/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppData.java
index b2759951f..01ceb30b2 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppData.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppData.java
@@ -8,6 +8,8 @@
 
 public interface AppData {
 
+    boolean isInstalling();
+
     boolean isLoading();
 
     boolean isFirstOpen();
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppInfo.java b/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppInfo.java
index bde5666c9..65bffbe2b 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppInfo.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppInfo.java
@@ -12,5 +12,7 @@ public class AppInfo {
     public boolean fastOpen;
     public Drawable icon;
     public CharSequence name;
+    public CharSequence version;
     public int cloneCount;
+    public boolean disableMultiVersion;
 }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppInfoLite.java b/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppInfoLite.java
index b2ad022a6..69ae476ab 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppInfoLite.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/models/AppInfoLite.java
@@ -23,17 +23,20 @@ public AppInfoLite[] newArray(int size) {
     public String packageName;
     public String path;
     public boolean fastOpen;
+    public boolean disableMultiVersion;
 
-    public AppInfoLite(String packageName, String path, boolean fastOpen) {
+    public AppInfoLite(String packageName, String path, boolean fastOpen, boolean disableMultiVersion) {
         this.packageName = packageName;
         this.path = path;
         this.fastOpen = fastOpen;
+        this.disableMultiVersion = disableMultiVersion;
     }
 
     protected AppInfoLite(Parcel in) {
         this.packageName = in.readString();
         this.path = in.readString();
         this.fastOpen = in.readByte() != 0;
+        this.disableMultiVersion = in.readByte() != 0;
     }
 
     @Override
@@ -46,5 +49,6 @@ public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(this.packageName);
         dest.writeString(this.path);
         dest.writeByte(this.fastOpen ? (byte) 1 : (byte) 0);
+        dest.writeByte(this.disableMultiVersion ? (byte) 1 : (byte) 0);
     }
 }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/models/EmptyAppData.java b/VirtualApp/app/src/main/java/io/virtualapp/home/models/EmptyAppData.java
deleted file mode 100644
index 6a3b05311..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/models/EmptyAppData.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package io.virtualapp.home.models;
-
-import android.graphics.drawable.Drawable;
-
-/**
- * @author Lody
- */
-
-public class EmptyAppData implements AppData {
-
-    @Override
-    public boolean isLoading() {
-        return false;
-    }
-
-    @Override
-    public boolean isFirstOpen() {
-        return false;
-    }
-
-    @Override
-    public Drawable getIcon() {
-        return null;
-    }
-
-    @Override
-    public String getName() {
-        return null;
-    }
-
-    @Override
-    public boolean canReorder() {
-        return false;
-    }
-
-    @Override
-    public boolean canLaunch() {
-        return false;
-    }
-
-    @Override
-    public boolean canDelete() {
-        return false;
-    }
-
-    @Override
-    public boolean canCreateShortcut() {
-        return false;
-    }
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/models/MultiplePackageAppData.java b/VirtualApp/app/src/main/java/io/virtualapp/home/models/MultiplePackageAppData.java
index 9785be668..f3af71db0 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/models/MultiplePackageAppData.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/models/MultiplePackageAppData.java
@@ -14,6 +14,7 @@ public class MultiplePackageAppData implements AppData {
     public InstalledAppInfo appInfo;
     public int userId;
     public boolean isFirstOpen;
+    public boolean isInstalling;
     public boolean isLoading;
     public Drawable icon;
     public String name;
@@ -31,6 +32,11 @@ public MultiplePackageAppData(PackageAppData target, int userId) {
         name = target.name;
     }
 
+    @Override
+    public boolean isInstalling() {
+        return isInstalling;
+    }
+
     @Override
     public boolean isLoading() {
         return isLoading;
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/models/PackageAppData.java b/VirtualApp/app/src/main/java/io/virtualapp/home/models/PackageAppData.java
index d65acf3c7..fa06ad426 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/models/PackageAppData.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/models/PackageAppData.java
@@ -18,6 +18,7 @@ public class PackageAppData implements AppData {
     public boolean fastOpen;
     public boolean isFirstOpen;
     public boolean isLoading;
+    public boolean isInstalling;
 
     public PackageAppData(Context context, InstalledAppInfo installedAppInfo) {
         this.packageName = installedAppInfo.packageName;
@@ -25,6 +26,11 @@ public PackageAppData(Context context, InstalledAppInfo installedAppInfo) {
         loadData(context, installedAppInfo.getApplicationInfo(installedAppInfo.getInstalledUsers()[0]));
     }
 
+    public PackageAppData(Context context, ApplicationInfo appInfo) {
+        this.packageName = appInfo.packageName;
+        loadData(context, appInfo);
+    }
+
     private void loadData(Context context, ApplicationInfo appInfo) {
         if (appInfo == null) {
             return;
@@ -41,6 +47,11 @@ private void loadData(Context context, ApplicationInfo appInfo) {
         }
     }
 
+    @Override
+    public boolean isInstalling() {
+        return isInstalling;
+    }
+
     @Override
     public boolean isLoading() {
         return isLoading;
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/platform/PlatformInfo.java b/VirtualApp/app/src/main/java/io/virtualapp/home/platform/PlatformInfo.java
deleted file mode 100644
index 44d1417bb..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/platform/PlatformInfo.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package io.virtualapp.home.platform;
-
-import android.content.pm.PackageInfo;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * @author Lody
- */
-public abstract class PlatformInfo {
-
-    private final Set<String> platformPkgs = new HashSet<>();
-
-    public PlatformInfo(String... pkgs) {
-        Collections.addAll(platformPkgs, pkgs);
-    }
-
-    public abstract boolean relyOnPlatform(PackageInfo info);
-
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/platform/WechatPlatformInfo.java b/VirtualApp/app/src/main/java/io/virtualapp/home/platform/WechatPlatformInfo.java
deleted file mode 100644
index fab6c2c08..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/platform/WechatPlatformInfo.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package io.virtualapp.home.platform;
-
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
-
-/**
- * @author Lody
- */
-
-public class WechatPlatformInfo extends PlatformInfo {
-
-    public WechatPlatformInfo() {
-        super("com.tencent.mm");
-    }
-
-    @Override
-    public boolean relyOnPlatform(PackageInfo info) {
-        if (info.activities == null) {
-            return false;
-        }
-        for (ActivityInfo activityInfo : info.activities) {
-            if (activityInfo.name.endsWith("WXEntryActivity")) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/repo/AppRepository.java b/VirtualApp/app/src/main/java/io/virtualapp/home/repo/AppRepository.java
index 383891a81..89063ca2e 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/repo/AppRepository.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/repo/AppRepository.java
@@ -5,8 +5,10 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 
+import com.lody.virtual.GmsSupport;
 import com.lody.virtual.client.core.InstallStrategy;
 import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.helper.utils.DeviceUtil;
 import com.lody.virtual.remote.InstallResult;
 import com.lody.virtual.remote.InstalledAppInfo;
 
@@ -16,6 +18,7 @@
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 
@@ -25,6 +28,7 @@
 import io.virtualapp.home.models.AppInfoLite;
 import io.virtualapp.home.models.MultiplePackageAppData;
 import io.virtualapp.home.models.PackageAppData;
+import io.virtualapp.utils.HanziToPinyin;
 
 /**
  * @author Lody
@@ -42,6 +46,8 @@ public class AppRepository implements AppDataSource {
             "pp/downloader/apk",
             "pp/downloader/silent/apk");
 
+    private static final int MAX_SCAN_DEPTH = 2;
+
     private Context mContext;
 
     public AppRepository(Context context) {
@@ -49,7 +55,8 @@ public AppRepository(Context context) {
     }
 
     private static boolean isSystemApplication(PackageInfo packageInfo) {
-        return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+        return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+                && !GmsSupport.isGmsFamilyPackage(packageInfo.packageName);
     }
 
     @Override
@@ -78,12 +85,53 @@ public Promise<List<AppData>, Throwable, Void> getVirtualApps() {
 
     @Override
     public Promise<List<AppInfo>, Throwable, Void> getInstalledApps(Context context) {
-        return VUiKit.defer().when(() -> convertPackageInfoToAppData(context, context.getPackageManager().getInstalledPackages(0), true));
+        return VUiKit.defer().when(() -> convertPackageInfoToAppData(context, context.getPackageManager().getInstalledPackages(PackageManager.GET_META_DATA), true));
     }
 
     @Override
     public Promise<List<AppInfo>, Throwable, Void> getStorageApps(Context context, File rootDir) {
-        return VUiKit.defer().when(() -> convertPackageInfoToAppData(context, findAndParseAPKs(context, rootDir, SCAN_PATH_LIST), false));
+        // return VUiKit.defer().when(() -> convertPackageInfoToAppData(context, findAndParseAPKs(context, rootDir, SCAN_PATH_LIST), false));
+        return VUiKit.defer().when(() -> convertPackageInfoToAppData(context, findAndParseApkRecursively(context, rootDir,null, 0), false));
+    }
+
+    private List<PackageInfo> findAndParseApkRecursively(Context context, File rootDir, List<PackageInfo> result, int depth) {
+        if (result == null) {
+            result = new ArrayList<>();
+        }
+
+        if (depth > MAX_SCAN_DEPTH) {
+            return result;
+        }
+
+        File[] dirFiles = rootDir.listFiles();
+
+        if (dirFiles == null) {
+            return Collections.emptyList();
+        }
+
+        for (File f: dirFiles) {
+            if (f.isDirectory()) {
+                List<PackageInfo> andParseApkRecursively = findAndParseApkRecursively(context, f, new ArrayList<>(), depth + 1);
+                result.addAll(andParseApkRecursively);
+            }
+
+            if (!(f.isFile() && f.getName().toLowerCase().endsWith(".apk"))) {
+                continue;
+            }
+
+            PackageInfo pkgInfo = null;
+            try {
+                pkgInfo = context.getPackageManager().getPackageArchiveInfo(f.getAbsolutePath(), PackageManager.GET_META_DATA);
+                pkgInfo.applicationInfo.sourceDir = f.getAbsolutePath();
+                pkgInfo.applicationInfo.publicSourceDir = f.getAbsolutePath();
+            } catch (Exception e) {
+                // Ignore
+            }
+            if (pkgInfo != null) {
+                result.add(pkgInfo);
+            }
+        }
+        return result;
     }
 
     private List<PackageInfo> findAndParseAPKs(Context context, File rootDir, List<String> paths) {
@@ -121,6 +169,12 @@ private List<AppInfo> convertPackageInfoToAppData(Context context, List<PackageI
             if (hostPkg.equals(pkg.packageName)) {
                 continue;
             }
+
+            // ignore taichi package
+            if (VirtualCore.TAICHI_PACKAGE.equals(pkg.packageName)) {
+                continue;
+            }
+
             // ignore the System package
             if (isSystemApplication(pkg)) {
                 continue;
@@ -134,23 +188,43 @@ private List<AppInfo> convertPackageInfoToAppData(Context context, List<PackageI
             info.packageName = pkg.packageName;
             info.fastOpen = fastOpen;
             info.path = path;
-            info.icon = ai.loadIcon(pm);
+//            info.icon = ai.loadIcon(pm);
+            info.icon = null;  // Use Glide to load the icon async
             info.name = ai.loadLabel(pm);
+            info.version = pkg.versionName;
             InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(pkg.packageName, 0);
             if (installedAppInfo != null) {
                 info.cloneCount = installedAppInfo.getInstalledUsers().length;
             }
+            if (ai.metaData != null && ai.metaData.containsKey("xposedmodule")) {
+                info.disableMultiVersion = true;
+                info.cloneCount = 0;
+            }
             list.add(info);
         }
+        // sort by name
+        Collections.sort(list, (o1, o2) -> {
+            HanziToPinyin hanziToPinyin = HanziToPinyin.getInstance();
+            String pinyin1 = hanziToPinyin.toPinyinString(o1.name.toString().trim());
+            String pinyin2 = hanziToPinyin.toPinyinString(o2.name.toString().trim());
+            return pinyin1.compareTo(pinyin2);
+        });
         return list;
     }
 
     @Override
     public InstallResult addVirtualApp(AppInfoLite info) {
-        int flags = InstallStrategy.COMPARE_VERSION | InstallStrategy.ART_FLY_MODE;
+        int flags = InstallStrategy.COMPARE_VERSION | InstallStrategy.SKIP_DEX_OPT;
+        info.fastOpen = false; // disable fast open for compile.
+        if (DeviceUtil.isMeizuBelowN()) {
+            info.fastOpen = true;
+        }
         if (info.fastOpen) {
             flags |= InstallStrategy.DEPEND_SYSTEM_IF_EXIST;
         }
+        if (info.disableMultiVersion) {
+            flags |= InstallStrategy.UPDATE_IF_EXIST;
+        }
         return VirtualCore.get().installPackage(info.path, flags);
     }
 
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/home/repo/PackageAppDataStorage.java b/VirtualApp/app/src/main/java/io/virtualapp/home/repo/PackageAppDataStorage.java
index ea2be27d0..b5f105044 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/home/repo/PackageAppDataStorage.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/home/repo/PackageAppDataStorage.java
@@ -1,12 +1,14 @@
 package io.virtualapp.home.repo;
 
+import android.content.pm.ApplicationInfo;
+
 import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.remote.InstalledAppInfo;
 
 import java.util.HashMap;
 import java.util.Map;
 
-import io.virtualapp.VApp;
+import io.virtualapp.XApp;
 import io.virtualapp.abs.Callback;
 import io.virtualapp.abs.ui.VUiKit;
 import io.virtualapp.home.models.PackageAppData;
@@ -45,7 +47,7 @@ public void acquire(String packageName, Callback<PackageAppData> callback) {
     private PackageAppData loadAppData(String packageName) {
         InstalledAppInfo setting = VirtualCore.get().getInstalledAppInfo(packageName, 0);
         if (setting != null) {
-            PackageAppData data = new PackageAppData(VApp.getApp(), setting);
+            PackageAppData data = new PackageAppData(XApp.getApp(), setting);
             synchronized (packageDataMap) {
                 packageDataMap.put(packageName, data);
             }
@@ -54,4 +56,29 @@ private PackageAppData loadAppData(String packageName) {
         return null;
     }
 
+    public PackageAppData acquire(ApplicationInfo appInfo) {
+        PackageAppData data;
+        synchronized (packageDataMap) {
+            data = packageDataMap.get(appInfo.packageName);
+            if (data == null) {
+                data = loadAppData(appInfo);
+            }
+        }
+        return data;
+    }
+
+    public void acquire(ApplicationInfo appInfo, Callback<PackageAppData> callback) {
+        VUiKit.defer()
+                .when(() -> acquire(appInfo))
+                .done(callback::callback);
+    }
+
+    private PackageAppData loadAppData(ApplicationInfo appInfo) {
+        PackageAppData data = new PackageAppData(XApp.getApp(), appInfo);
+        synchronized (packageDataMap) {
+            packageDataMap.put(appInfo.packageName, data);
+        }
+        return data;
+    }
+
 }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/settings/AboutActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/settings/AboutActivity.java
new file mode 100644
index 000000000..161c51350
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/settings/AboutActivity.java
@@ -0,0 +1,184 @@
+package io.virtualapp.settings;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AlertDialog;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Toast;
+
+import java.util.Calendar;
+
+import io.virtualapp.R;
+import io.virtualapp.abs.ui.VActivity;
+import io.virtualapp.update.VAVersionService;
+import mehdi.sakout.aboutpage.AboutPage;
+import mehdi.sakout.aboutpage.Element;
+
+/**
+ * author: weishu on 18/1/12.
+ */
+public class AboutActivity extends VActivity {
+
+    private AboutPage mPage;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mPage = new AboutPage(this)
+                .isRTL(false)
+                .setImage(R.mipmap.ic_launcher)
+                .addItem(getCopyRightsElement())
+                .addItem(getVersionElement())
+                .addItem(getCheckUpdateElement())
+                .addItem(getFeedbackEmailElement())
+                .addItem(getThanksElement())
+                .addItem(getFeedbacTelegramElement())
+                .addItem(getWebsiteElement())
+                .addGitHub("tiann");
+
+        View aboutPage = mPage.create();
+
+        setContentView(aboutPage);
+    }
+
+    Element getCopyRightsElement() {
+        Element copyRightsElement = new Element();
+        final String copyrights = String.format(getString(R.string.copy_right), Calendar.getInstance().get(Calendar.YEAR));
+        copyRightsElement.setTitle(copyrights);
+        copyRightsElement.setGravity(Gravity.START);
+        return copyRightsElement;
+    }
+
+    Element getVersionElement() {
+        Element version = new Element();
+        String versionName = "unknown";
+        try {
+            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
+            versionName = packageInfo.versionName;
+        } catch (PackageManager.NameNotFoundException ignored) {
+        }
+        version.setTitle(getResources().getString(R.string.about_version_title, versionName));
+
+        final int[] clickCount = {0};
+        version.setOnClickListener(v -> {
+            clickCount[0]++;
+            if (clickCount[0] == 3) {
+                mPage.addItem(getFeedbackQQElement());
+                mPage.addItem(getFeedbackWechatElement());
+            }
+        });
+        return version;
+    }
+
+    Element getFeedbackQQElement() {
+        Element feedback = new Element();
+        final String qqGroup = "597478474";
+        feedback.setTitle(getResources().getString(R.string.about_feedback_qq_title));
+
+        feedback.setOnClickListener(v -> {
+            ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+            if (clipboardManager != null) {
+                clipboardManager.setPrimaryClip(ClipData.newPlainText(null, qqGroup));
+            }
+            Toast.makeText(v.getContext(), getResources().getString(R.string.about_feedback_tips), Toast.LENGTH_SHORT).show();
+        });
+        return feedback;
+    }
+
+    Element getFeedbackEmailElement() {
+        Element emailElement = new Element();
+        final String email = "virtualxposed@gmail.com";
+        String title = getResources().getString(R.string.about_feedback_title);
+        emailElement.setTitle(title);
+
+        Uri uri = Uri.parse("mailto:" + email);
+        Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
+        intent.putExtra(Intent.EXTRA_SUBJECT, title); // 主题
+
+        String hint = getResources().getString(R.string.about_feedback_hint);
+        intent.putExtra(Intent.EXTRA_TEXT, hint);
+        emailElement.setIntent(intent);
+        return emailElement;
+    }
+
+    Element getFeedbackWechatElement() {
+        Element feedback = new Element();
+        // final String weChatGroup = "CSYJZF";
+        feedback.setTitle(getResources().getString(R.string.about_feedback_wechat_title));
+
+        feedback.setOnClickListener(v -> {
+            ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+            if (clipboardManager != null) {
+                clipboardManager.setPrimaryClip(ClipData.newPlainText(null, "VirtualXposed"));
+            }
+            Toast.makeText(v.getContext(), getResources().getString(R.string.about_feedback_tips), Toast.LENGTH_SHORT).show();
+        });
+        return feedback;
+    }
+
+    Element getFeedbacTelegramElement() {
+        Element feedback = new Element();
+        final String weChatGroup = "VirtualXposed";
+        feedback.setTitle(getResources().getString(R.string.about_feedback_tel_title, weChatGroup));
+
+        feedback.setOnClickListener(v -> {
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            intent.setData(Uri.parse("https://t.me/joinchat/Gtti8Usj1JD4TchHQmy-ew"));
+            try {
+                startActivity(intent);
+            } catch (Throwable ignored) {
+            }
+        });
+        return feedback;
+    }
+
+    Element getWebsiteElement() {
+        Element feedback = new Element();
+        feedback.setTitle(getResources().getString(R.string.about_website_title));
+
+        feedback.setOnClickListener(v -> {
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            intent.setData(Uri.parse("http://vxposed.com"));
+            try {
+                startActivity(intent);
+            } catch (Throwable ignored) {
+            }
+        });
+        return feedback;
+    }
+
+    Element getThanksElement() {
+        Element thanks = new Element();
+        thanks.setTitle(getResources().getString(R.string.about_thanks));
+        thanks.setOnClickListener(v -> {
+            AlertDialog alertDialog = new AlertDialog.Builder(this, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                    .setTitle(R.string.thanks_dialog_title)
+                    .setMessage(R.string.thanks_dialog_content)
+                    .setPositiveButton(R.string.about_icon_yes, null)
+                    .create();
+            try {
+                alertDialog.show();
+            } catch (Throwable ignored) {
+                // BadTokenException.
+            }
+        });
+        return thanks;
+    }
+
+    Element getCheckUpdateElement() {
+        Element checkUpdate = new Element();
+        checkUpdate.setTitle(getResources().getString(R.string.check_update));
+        checkUpdate.setOnClickListener(v -> {
+            VAVersionService.checkUpdateImmediately(getApplicationContext(), true);
+        });
+        return checkUpdate;
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/settings/AppManageActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/settings/AppManageActivity.java
new file mode 100644
index 000000000..5e7f9923a
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/settings/AppManageActivity.java
@@ -0,0 +1,322 @@
+package io.virtualapp.settings;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.ipc.VirtualStorageManager;
+import com.lody.virtual.helper.ArtDexOptimizer;
+import com.lody.virtual.os.VEnvironment;
+import com.lody.virtual.remote.InstalledAppInfo;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.virtualapp.R;
+import io.virtualapp.abs.ui.VActivity;
+import io.virtualapp.abs.ui.VUiKit;
+import io.virtualapp.glide.GlideUtils;
+
+/**
+ * @author weishu
+ * @date 18/2/15.
+ */
+
+public class AppManageActivity extends VActivity {
+
+    private ListView mListView;
+    private List<AppManageInfo> mInstalledApps = new ArrayList<>();
+    private AppManageAdapter mAdapter;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_list);
+        mListView = (ListView) findViewById(R.id.list);
+        mAdapter = new AppManageAdapter();
+        mListView.setAdapter(mAdapter);
+
+        mListView.setOnItemClickListener((parent, view, position, id) -> {
+            AppManageInfo appManageInfo = mInstalledApps.get(position);
+            showContextMenu(appManageInfo, view);
+        });
+        loadAsync();
+    }
+
+    private void loadAsync() {
+        VUiKit.defer().when(this::loadApp).done((v) -> mAdapter.notifyDataSetChanged());
+    }
+
+    private void loadApp() {
+
+        List<AppManageInfo> ret = new ArrayList<>();
+        List<InstalledAppInfo> installedApps = VirtualCore.get().getInstalledApps(0);
+        PackageManager packageManager = getPackageManager();
+        for (InstalledAppInfo installedApp : installedApps) {
+            int[] installedUsers = installedApp.getInstalledUsers();
+            for (int installedUser : installedUsers) {
+                AppManageInfo info = new AppManageInfo();
+                info.userId = installedUser;
+                ApplicationInfo applicationInfo = installedApp.getApplicationInfo(installedUser);
+                info.name = applicationInfo.loadLabel(packageManager);
+//                info.icon = applicationInfo.loadIcon(packageManager);  //Use Glide to load icon async
+                info.pkgName = installedApp.packageName;
+                info.path = applicationInfo.sourceDir;
+                ret.add(info);
+            }
+        }
+        mInstalledApps.clear();
+        mInstalledApps.addAll(ret);
+    }
+
+    class AppManageAdapter extends BaseAdapter {
+
+        @Override
+        public int getCount() {
+            return mInstalledApps.size();
+        }
+
+        @Override
+        public AppManageInfo getItem(int position) {
+            return mInstalledApps.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return 0;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            ViewHolder holder;
+            if (convertView == null) {
+                holder = new ViewHolder(AppManageActivity.this, parent);
+                convertView = holder.root;
+                convertView.setTag(holder);
+            } else {
+                holder = (ViewHolder) convertView.getTag();
+            }
+
+            AppManageInfo item = getItem(position);
+
+            holder.label.setText(item.getName());
+
+            if (VirtualCore.get().isOutsideInstalled(item.pkgName)) {
+                GlideUtils.loadInstalledPackageIcon(getContext(), item.pkgName, holder.icon, android.R.drawable.sym_def_app_icon);
+            } else {
+                GlideUtils.loadPackageIconFromApkFile(getContext(), item.path, holder.icon, android.R.drawable.sym_def_app_icon);
+            }
+
+            holder.button.setOnClickListener(v -> showContextMenu(item, v));
+
+            return convertView;
+        }
+    }
+
+    private void showContextMenu(AppManageInfo appManageInfo, View anchor) {
+        if (appManageInfo == null) {
+            return;
+        }
+        PopupMenu popupMenu = new PopupMenu(this, anchor);
+        popupMenu.inflate(R.menu.app_manage_menu);
+        MenuItem redirectMenu = popupMenu.getMenu().findItem(R.id.action_redirect);
+
+        try {
+            final String packageName = appManageInfo.pkgName;
+            final int userId = appManageInfo.userId;
+            boolean virtualStorageEnable = VirtualStorageManager.get().isVirtualStorageEnable(packageName, userId);
+            redirectMenu.setTitle(virtualStorageEnable ? R.string.app_manage_redirect_off : R.string.app_manage_redirect_on);
+        } catch (Throwable e) {
+            redirectMenu.setVisible(false);
+        }
+
+        popupMenu.setOnMenuItemClickListener(item -> {
+            switch (item.getItemId()) {
+                case R.id.action_uninstall:
+                    showUninstallDialog(appManageInfo, appManageInfo.getName());
+                    break;
+                case R.id.action_repair:
+                    showRepairDialog(appManageInfo);
+                    break;
+                case R.id.action_redirect:
+                    showStorageRedirectDialog(appManageInfo);
+                    break;
+            }
+            return false;
+        });
+        try {
+            popupMenu.show();
+        } catch (Throwable ignored) {
+        }
+    }
+
+    private void showRepairDialog(AppManageInfo item) {
+        ProgressDialog dialog = new ProgressDialog(this);
+        dialog.setTitle(getResources().getString(R.string.app_manage_repairing));
+        try {
+            dialog.setCancelable(false);
+            dialog.show();
+        } catch (Throwable e) {
+            return;
+        }
+
+        VUiKit.defer().when(() -> {
+            NougatPolicy.fullCompile(getApplicationContext());
+
+            String packageName = item.pkgName;
+            String apkPath = item.path;
+
+            if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(apkPath)) {
+                return;
+            }
+
+            // 1. kill package
+            VirtualCore.get().killApp(packageName, item.userId);
+
+            // 2. backup the odex file
+            File odexFile = VEnvironment.getOdexFile(packageName);
+            if (odexFile.delete()) {
+                try {
+                    ArtDexOptimizer.compileDex2Oat(apkPath, odexFile.getPath());
+                } catch (IOException e) {
+                    throw new RuntimeException("compile failed.");
+                }
+            }
+        }).done((v) -> {
+            dismiss(dialog);
+            showAppDetailDialog();
+        }).fail((v) -> {
+            dismiss(dialog);
+            Toast.makeText(this, R.string.app_manage_repair_failed_tips, Toast.LENGTH_SHORT).show();
+        });
+    }
+
+    private void showAppDetailDialog() {
+        AlertDialog alertDialog = new AlertDialog.Builder(AppManageActivity.this)
+                .setTitle(R.string.app_manage_repair_success_title)
+                .setMessage(getResources().getString(R.string.app_manage_repair_success_content))
+                .setPositiveButton(R.string.app_manage_repair_reboot_now, (dialog, which) -> {
+                    String packageName = getPackageName();
+                    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+                            Uri.fromParts("package", packageName, null));
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                    startActivity(intent);
+                })
+                .create();
+
+        alertDialog.setCancelable(false);
+
+        try {
+            alertDialog.show();
+        } catch (Throwable ignored) {
+        }
+    }
+
+    private void showUninstallDialog(AppManageInfo item, CharSequence name) {
+        AlertDialog alertDialog = new AlertDialog.Builder(AppManageActivity.this)
+                .setTitle(com.android.launcher3.R.string.home_menu_delete_title)
+                .setMessage(getResources().getString(com.android.launcher3.R.string.home_menu_delete_content, name))
+                .setPositiveButton(android.R.string.yes, (dialog, which) -> {
+                    VirtualCore.get().uninstallPackageAsUser(item.pkgName, item.userId);
+                    loadAsync();
+                })
+                .setNegativeButton(android.R.string.no, null)
+                .create();
+        try {
+            alertDialog.show();
+        } catch (Throwable ignored) {
+        }
+    }
+
+    private void showStorageRedirectDialog(AppManageInfo item) {
+        final String packageName = item.pkgName;
+        final int userId = item.userId;
+        boolean virtualStorageEnable;
+        try {
+            virtualStorageEnable = VirtualStorageManager.get().isVirtualStorageEnable(packageName, userId);
+        } catch (Throwable e) {
+            return;
+        }
+
+        AlertDialog alertDialog = new AlertDialog.Builder(AppManageActivity.this)
+                .setTitle(virtualStorageEnable ? R.string.app_manage_redirect_off : R.string.app_manage_redirect_on)
+                .setMessage(getResources().getString(R.string.app_manage_redirect_desc))
+                .setPositiveButton(virtualStorageEnable ? R.string.app_manage_redirect_off_confirm : R.string.app_manage_redirect_on_confirm,
+                        (dialog, which) -> {
+                            try {
+                                VirtualStorageManager.get().setVirtualStorageState(packageName, userId, !virtualStorageEnable);
+                            } catch (Throwable ignored) {
+                            }
+                        })
+                .setNegativeButton(android.R.string.no, null)
+                .create();
+        try {
+            alertDialog.show();
+        } catch (Throwable ignored) {
+        }
+    }
+
+    static class ViewHolder {
+        ImageView icon;
+        TextView label;
+        ImageView button;
+
+        View root;
+
+        ViewHolder(Context context, ViewGroup parent) {
+            root = LayoutInflater.from(context).inflate(R.layout.item_app_manage, parent, false);
+            icon = root.findViewById(R.id.item_app_icon);
+            label = root.findViewById(R.id.item_app_name);
+            button = root.findViewById(R.id.item_app_button);
+        }
+    }
+
+    static class AppManageInfo {
+        CharSequence name;
+        int userId;
+        Drawable icon;
+        String pkgName;
+        String path;
+
+        CharSequence getName() {
+            if (userId == 0) {
+                return name;
+            } else {
+                return name + "[" + (userId + 1) + "]";
+            }
+        }
+    }
+
+    private static void dismiss(Dialog dialog) {
+        if (dialog == null) {
+            return;
+        }
+        try {
+            dialog.dismiss();
+        } catch (Throwable ignored) {
+        }
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/settings/NougatPolicy.java b/VirtualApp/app/src/main/java/io/virtualapp/settings/NougatPolicy.java
new file mode 100644
index 000000000..92818e4bd
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/settings/NougatPolicy.java
@@ -0,0 +1,59 @@
+package io.virtualapp.settings;
+
+import android.content.Context;
+import android.os.Build;
+
+import java.lang.reflect.Method;
+
+/**
+ * Android 7.0 全量编译策略
+ * Created by weishu on 17/6/12.
+ */
+
+public class NougatPolicy {
+
+    static boolean fullCompile(Context context) {
+        if (Build.VERSION.SDK_INT < 24) {
+            return true;
+        }
+        try {
+            Object pm = getPackageManagerBinderProxy();
+            if (pm == null) {
+                return false;
+            }
+            /*
+            @Override
+            public boolean performDexOptMode(String packageName,
+            boolean checkProfiles, String targetCompilerFilter, boolean force) {
+                int dexOptStatus = performDexOptTraced(packageName, checkProfiles,
+                        targetCompilerFilter, force);
+                return dexOptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
+            */
+
+            final Method performDexOptMode = pm.getClass().getDeclaredMethod("performDexOptMode",
+                    String.class, boolean.class, String.class, boolean.class);
+            return (boolean) performDexOptMode.invoke(pm, context.getPackageName(), false, "speed", true);
+        } catch (Throwable e) {
+            return false;
+        }
+    }
+
+    public static boolean clearCompileData(Context context) {
+        boolean ret;
+        try {
+            Object pm = getPackageManagerBinderProxy();
+            final Method performDexOpt = pm.getClass().getDeclaredMethod("performDexOpt", String.class,
+                    boolean.class, int.class, boolean.class);
+            ret = (Boolean) performDexOpt.invoke(pm, context.getPackageName(), false, 2 /*install*/, true);
+        } catch (Throwable e) {
+            ret = false;
+        }
+        return ret;
+    }
+
+    private static Object getPackageManagerBinderProxy() throws Exception {
+        Class<?> activityThread = Class.forName("android.app.ActivityThread");
+        final Method getPackageManager = activityThread.getDeclaredMethod("getPackageManager");
+        return getPackageManager.invoke(null);
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/settings/OnlinePlugin.java b/VirtualApp/app/src/main/java/io/virtualapp/settings/OnlinePlugin.java
new file mode 100644
index 000000000..170c86565
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/settings/OnlinePlugin.java
@@ -0,0 +1,199 @@
+package io.virtualapp.settings;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.os.SystemClock;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.lody.virtual.client.core.InstallStrategy;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.os.VEnvironment;
+import com.lody.virtual.remote.InstallResult;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import io.virtualapp.R;
+import io.virtualapp.gms.FakeGms;
+import io.virtualapp.home.LoadingActivity;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+import static io.virtualapp.utils.DialogUtil.showDialog;
+
+/**
+ * @author weishu
+ * @date 2018/7/5.
+ */
+public class OnlinePlugin {
+
+    private static final String TAG = "OnlinePlugin";
+
+    public static final String FILE_MANAGE_PACKAGE = "com.amaze.filemanager";
+    public static final String FILE_MANAGE_URL = "http://vaexposed.weishu.me/amaze.json";
+
+    public static final String PERMISSION_MANAGE_PACKAGE = "eu.faircode.xlua";
+    public static final String PERMISSION_MANAGE_URL = "http://vaexposed.weishu.me/xlua.json";
+
+    public static void openOrDownload(Activity context, String packageName, String url, String tips) {
+        if (context == null || packageName == null) {
+            return;
+        }
+
+        if (VirtualCore.get().isAppInstalled(packageName)) {
+            LoadingActivity.launch(context, packageName, 0);
+            return;
+        }
+
+        AlertDialog failDialog = new AlertDialog.Builder(context, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                .setTitle(android.R.string.dialog_alert_title)
+                .setMessage(tips)
+                .setPositiveButton(android.R.string.ok, ((dialog1, which1) -> {
+                    ProgressDialog progressDialog = new ProgressDialog(context);
+                    progressDialog.setCancelable(false);
+                    progressDialog.show();
+
+                    Executors.newSingleThreadExecutor().submit(() -> {
+                        String error = downloadAndInstall(context, progressDialog, url, packageName);
+                        try {
+                            progressDialog.dismiss();
+                        } catch (Throwable e) {
+                            e.printStackTrace();
+                        }
+
+                        if (error == null) {
+                            context.runOnUiThread(() -> {
+                                LoadingActivity.launch(context, packageName, 0);
+                            });
+                        } else {
+                            context.runOnUiThread(() -> Toast.makeText(context, error, Toast.LENGTH_SHORT).show());
+                        }
+
+                    });
+
+                }))
+                .setNegativeButton(android.R.string.cancel, null)
+                .create();
+
+        showDialog(failDialog);
+    }
+
+    private static String downloadAndInstall(Activity activity, ProgressDialog dialog, String url, String packageName) {
+        OkHttpClient client = new OkHttpClient.Builder()
+                .connectTimeout(30, TimeUnit.SECONDS)
+                .readTimeout(30, TimeUnit.SECONDS)
+                .writeTimeout(30, TimeUnit.SECONDS)
+                .build();
+
+        Request request = new Request.Builder()
+                .url(url)
+                .build();
+
+        updateMessage(activity, dialog, "Prepare download...");
+        Response response;
+        try {
+            response = client.newCall(request).execute();
+        } catch (IOException e) {
+            return "Download failed, please check your network, error: 0";
+        }
+
+        if (!response.isSuccessful()) {
+            return "Download failed, please check your network, error: 1";
+        }
+
+        Log.i(TAG, "response success: " + response.code());
+        if (200 != response.code()) {
+            return "Download failed, please check your network, error: 2";
+        }
+
+        updateMessage(activity, dialog, "Parsing config...");
+        ResponseBody body = response.body();
+        if (body == null) {
+            return "Download failed, please check your network, error: 3";
+        }
+
+        String string;
+        try {
+            string = body.string();
+        } catch (IOException e) {
+            return "Download failed, please check your network, error: 4";
+        }
+
+        JSONObject jsonObject;
+        try {
+            jsonObject = new JSONObject(string);
+        } catch (JSONException e) {
+            return "Download failed, please check your network, error: 5";
+        }
+        String downloadLink;
+        boolean isXposed;
+        try {
+            downloadLink = jsonObject.getString("url");
+            isXposed = jsonObject.optBoolean("xposed", false);
+        } catch (JSONException e) {
+            return "Download failed, please check your network, error: 6";
+        }
+
+        File outFile = new File(activity.getCacheDir(), packageName + ".apk");
+        FakeGms.downloadFile(downloadLink, outFile, progress -> updateMessage(activity, dialog, "download " + packageName + "..." + progress + "%"));
+
+        updateMessage(activity, dialog, "installing " + packageName);
+
+        InstallResult installResult = VirtualCore.get().installPackage(outFile.getAbsolutePath(), InstallStrategy.UPDATE_IF_EXIST);
+        if (!installResult.isSuccess) {
+            return "install " + packageName + " failed: " + installResult.error;
+        }
+
+        if (isXposed) {
+            // Enable the Xposed module.
+            updateMessage(activity, dialog, "enable " + packageName + " in Xposed Installer");
+
+            File dataDir = VEnvironment.getDataUserPackageDirectory(0, "de.robv.android.xposed.installer");
+            File modulePath = VEnvironment.getPackageResourcePath(packageName);
+            File configDir = new File(dataDir, "exposed_conf" + File.separator + "modules.list");
+            FileWriter writer = null;
+            try {
+                writer = new FileWriter(configDir, true);
+                writer.append(modulePath.getAbsolutePath());
+                writer.flush();
+
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                if (writer != null) {
+                    try {
+                        writer.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+
+        updateMessage(activity, dialog, " install success!!");
+        SystemClock.sleep(300);
+
+        return null;
+    }
+
+    private static void updateMessage(Activity activity, ProgressDialog dialog, String msg) {
+        if (activity == null || dialog == null || TextUtils.isEmpty(msg)) {
+            return;
+        }
+        Log.i(TAG, "update dialog message: " + msg);
+        activity.runOnUiThread(() -> {
+            dialog.setMessage(msg);
+        });
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/settings/RecommendPluginActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/settings/RecommendPluginActivity.java
new file mode 100644
index 000000000..6f7316b2a
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/settings/RecommendPluginActivity.java
@@ -0,0 +1,177 @@
+package io.virtualapp.settings;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.virtualapp.R;
+import io.virtualapp.abs.ui.VActivity;
+
+/**
+ * author: weishu on 2018/5/9.
+ */
+
+public class RecommendPluginActivity extends VActivity {
+
+    private List<PluginInfo> mData = new ArrayList<>();
+    private PluginAdapter mAdapter;
+    private ProgressDialog mLoadingDialog;
+
+    private static final String ADDRESS = "http://vaexposed.weishu.me/plugin.json";
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_list);
+        mLoadingDialog = new ProgressDialog(this);
+        mLoadingDialog.setTitle("Loading");
+
+        ListView mListView = findViewById(R.id.list);
+        mAdapter = new PluginAdapter();
+        mListView.setAdapter(mAdapter);
+        mListView.setEmptyView(findViewById(R.id.empty_view));
+
+        mListView.setOnItemClickListener((parent, view, position, id) -> {
+            try {
+                PluginInfo item = mAdapter.getItem(position);
+                Intent intent = new Intent(Intent.ACTION_VIEW);
+                intent.setData(Uri.parse(item.link));
+                startActivity(intent);
+            } catch (Throwable ignored) {
+                ignored.printStackTrace();
+            }
+        });
+
+        loadRecommend();
+    }
+
+    private void loadRecommend() {
+        try {
+            mLoadingDialog.show();
+        } catch (Throwable ignored) {
+        }
+
+        defer().when(() -> {
+            JSONArray jsonArray = null;
+
+            URL url = new URL(ADDRESS);
+            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("GET");
+            connection.setConnectTimeout(30000);
+            connection.setReadTimeout(30000);
+            if(connection.getResponseCode() == 200){
+                BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
+                String line;
+                StringBuilder sb = new StringBuilder();
+                while ((line = br.readLine()) != null) {
+                    sb.append(line);
+                }
+                jsonArray = new JSONArray(sb.toString());
+                br.close();
+            }
+
+            connection.disconnect();
+            return jsonArray;
+        }).done(jsonArray -> {
+            mLoadingDialog.dismiss();
+
+            if (jsonArray == null) {
+                return;
+            }
+            mData.clear();
+
+            int length = jsonArray.length();
+            for (int i = 0; i < length; i++) {
+                PluginInfo info = new PluginInfo();
+                try {
+                    JSONObject jsonObject = jsonArray.getJSONObject(i);
+                    info.name = jsonObject.getString("name");
+                    info.desc = jsonObject.getString("desc");
+                    info.link = jsonObject.getString("link");
+                } catch (JSONException e) {
+                    continue;
+                }
+                mData.add(info);
+            }
+
+            mAdapter.notifyDataSetChanged();
+        }).fail((v) -> {
+            mLoadingDialog.dismiss();
+        });
+    }
+
+    static class PluginInfo {
+        String name;
+        String desc;
+        String link;
+    }
+
+    static class ViewHolder {
+        TextView title;
+        TextView summary;
+
+        View root;
+
+        public ViewHolder(Context context, ViewGroup parent) {
+            root = LayoutInflater.from(context).inflate(R.layout.item_plugin_recommend, parent, false);
+            title = root.findViewById(R.id.item_plugin_name);
+            summary = root.findViewById(R.id.item_plugin_summary);
+        }
+    }
+
+    class PluginAdapter extends BaseAdapter {
+
+        @Override
+        public int getCount() {
+            return mData.size();
+        }
+
+        @Override
+        public PluginInfo getItem(int position) {
+            return mData.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return 0;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            ViewHolder holder;
+            if (convertView == null) {
+                holder = new ViewHolder(RecommendPluginActivity.this, parent);
+                convertView = holder.root;
+                convertView.setTag(holder);
+            } else {
+                holder = (ViewHolder) convertView.getTag();
+            }
+
+            PluginInfo info = getItem(position);
+            holder.title.setText(info.name);
+            holder.summary.setText(info.desc);
+
+            return convertView;
+        }
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/settings/SettingsActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/settings/SettingsActivity.java
new file mode 100644
index 000000000..1f8bb443d
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/settings/SettingsActivity.java
@@ -0,0 +1,330 @@
+package io.virtualapp.settings;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.widget.Toast;
+
+import com.android.launcher3.LauncherFiles;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.env.Constants;
+import com.lody.virtual.client.ipc.VActivityManager;
+
+import java.io.File;
+import java.io.IOException;
+
+import io.virtualapp.R;
+import io.virtualapp.gms.FakeGms;
+import io.virtualapp.home.ListAppActivity;
+import io.virtualapp.utils.Misc;
+
+/**
+ * Settings activity for Launcher. Currently implements the following setting: Allow rotation
+ */
+public class SettingsActivity extends Activity {
+
+    private static final String ADVANCE_SETTINGS_KEY = "settings_advance";
+    private static final String ADD_APP_KEY = "settings_add_app";
+    private static final String MODULE_MANAGE_KEY = "settings_module_manage";
+    private static final String APP_MANAGE_KEY = "settings_app_manage";
+    private static final String TASK_MANAGE_KEY = "settings_task_manage";
+    private static final String DESKTOP_SETTINGS_KEY = "settings_desktop";
+    private static final String FAQ_SETTINGS_KEY = "settings_faq";
+    private static final String DONATE_KEY = "settings_donate";
+    private static final String ABOUT_KEY = "settings_about";
+    private static final String REBOOT_KEY = "settings_reboot";
+    private static final String HIDE_SETTINGS_KEY = "advance_settings_hide_settings";
+    private static final String DISABLE_INSTALLER_KEY = "advance_settings_disable_installer";
+    public static final String ENABLE_LAUNCHER = "advance_settings_enable_launcher";
+    private static final String INSTALL_GMS_KEY = "advance_settings_install_gms";
+    public static final String DIRECTLY_BACK_KEY = "advance_settings_directly_back";
+    private static final String RECOMMEND_PLUGIN = "settings_plugin_recommend";
+    private static final String DISABLE_RESIDENT_NOTIFICATION = "advance_settings_disable_resident_notification";
+    private static final String ALLOW_FAKE_SIGNATURE = "advance_settings_allow_fake_signature";
+    private static final String DISABLE_XPOSED = "advance_settings_disable_xposed";
+    private static final String FILE_MANAGE = "settings_file_manage";
+    private static final String PERMISSION_MANAGE = "settings_permission_manage";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState == null) {
+            // Display the fragment as the main content.
+            getFragmentManager().beginTransaction()
+                    .replace(android.R.id.content, new SettingsFragment())
+                    .commit();
+        }
+    }
+
+    /**
+     * This fragment shows the launcher preferences.
+     */
+    public static class SettingsFragment extends PreferenceFragment {
+
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
+            addPreferencesFromResource(R.xml.settings_preferences);
+
+            // Setup allow rotation preference
+
+            Preference addApp = findPreference(ADD_APP_KEY);
+            Preference moduleManage = findPreference(MODULE_MANAGE_KEY);
+            Preference recommend = findPreference(RECOMMEND_PLUGIN);
+            Preference appManage = findPreference(APP_MANAGE_KEY);
+            Preference taskManage = findPreference(TASK_MANAGE_KEY);
+            Preference desktop = findPreference(DESKTOP_SETTINGS_KEY);
+            Preference faq = findPreference(FAQ_SETTINGS_KEY);
+            Preference donate = findPreference(DONATE_KEY);
+            Preference about = findPreference(ABOUT_KEY);
+            Preference reboot = findPreference(REBOOT_KEY);
+            Preference fileMange = findPreference(FILE_MANAGE);
+            Preference permissionManage = findPreference(PERMISSION_MANAGE);
+
+
+            SwitchPreference disableInstaller = (SwitchPreference) findPreference(DISABLE_INSTALLER_KEY);
+            SwitchPreference enableLauncher = (SwitchPreference) findPreference(ENABLE_LAUNCHER);
+            SwitchPreference disableResidentNotification = (SwitchPreference) findPreference(DISABLE_RESIDENT_NOTIFICATION);
+            SwitchPreference allowFakeSignature = (SwitchPreference) findPreference(ALLOW_FAKE_SIGNATURE);
+            SwitchPreference disableXposed = (SwitchPreference) findPreference(DISABLE_XPOSED);
+
+            addApp.setOnPreferenceClickListener(preference -> {
+                ListAppActivity.gotoListApp(getActivity());
+                return false;
+            });
+
+            moduleManage.setOnPreferenceClickListener(preference -> {
+                try {
+                    Intent t = new Intent();
+                    t.setComponent(new ComponentName("de.robv.android.xposed.installer", "de.robv.android.xposed.installer.WelcomeActivity"));
+                    t.putExtra("fragment", 1);
+                    int ret = VActivityManager.get().startActivity(t, 0);
+                    if (ret < 0) {
+                        Toast.makeText(getActivity(), R.string.xposed_installer_not_found, Toast.LENGTH_SHORT).show();
+                    }
+                } catch (Throwable ignored) {
+                    ignored.printStackTrace();
+                }
+                return false;
+            });
+
+            recommend.setOnPreferenceClickListener(preference -> {
+                startActivity(new Intent(getActivity(), RecommendPluginActivity.class));
+                return false;
+            });
+
+            boolean xposedEnabled = VirtualCore.get().isXposedEnabled();
+            if (!xposedEnabled) {
+                getPreferenceScreen().removePreference(moduleManage);
+                getPreferenceScreen().removePreference(recommend);
+            }
+
+            appManage.setOnPreferenceClickListener(preference -> {
+                startActivity(new Intent(getActivity(), AppManageActivity.class));
+                return false;
+            });
+
+            taskManage.setOnPreferenceClickListener(preference -> {
+                startActivity(new Intent(getActivity(), TaskManageActivity.class));
+                return false;
+            });
+
+            faq.setOnPreferenceClickListener(preference -> {
+                Uri uri = Uri.parse("https://github.com/android-hacker/VAExposed/wiki/FAQ");
+                Intent t = new Intent(Intent.ACTION_VIEW, uri);
+                startActivity(t);
+                return false;
+            });
+
+            desktop.setOnPreferenceClickListener(preference -> {
+                startActivity(new Intent(getActivity(), com.google.android.apps.nexuslauncher.SettingsActivity.class));
+                return false;
+            });
+
+            donate.setOnPreferenceClickListener(preference -> {
+                Misc.showDonate(getActivity());
+                return false;
+            });
+            about.setOnPreferenceClickListener(preference -> {
+                startActivity(new Intent(getActivity(), AboutActivity.class));
+                return false;
+            });
+
+            reboot.setOnPreferenceClickListener(preference -> {
+                android.app.AlertDialog alertDialog = new android.app.AlertDialog.Builder(getActivity())
+                        .setTitle(R.string.settings_reboot_title)
+                        .setMessage(getResources().getString(R.string.settings_reboot_content))
+                        .setPositiveButton(android.R.string.yes, (dialog, which) -> {
+                            VirtualCore.get().killAllApps();
+                            Toast.makeText(getActivity(), R.string.reboot_tips_1, Toast.LENGTH_SHORT).show();
+                        })
+                        .setNegativeButton(android.R.string.no, null)
+                        .create();
+                try {
+                    alertDialog.show();
+                } catch (Throwable ignored) {
+                }
+                return false;
+            });
+
+            disableInstaller.setOnPreferenceChangeListener((preference, newValue) -> {
+                if (!(newValue instanceof Boolean)) {
+                    return false;
+                }
+                try {
+                    boolean disable = (boolean) newValue;
+                    PackageManager packageManager = getActivity().getPackageManager();
+                    packageManager.setComponentEnabledSetting(new ComponentName(getActivity().getPackageName(), "vxp.installer"),
+                            !disable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                            PackageManager.DONT_KILL_APP);
+                    return true;
+                } catch (Throwable ignored) {
+                    return false;
+                }
+            });
+
+            enableLauncher.setOnPreferenceChangeListener((preference, newValue) -> {
+                if (!(newValue instanceof Boolean)) {
+                    return false;
+                }
+                try {
+                    boolean enable = (boolean) newValue;
+                    PackageManager packageManager = getActivity().getPackageManager();
+                    packageManager.setComponentEnabledSetting(new ComponentName(getActivity().getPackageName(), "vxp.launcher"),
+                            enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                            PackageManager.DONT_KILL_APP);
+                    return true;
+                } catch (Throwable ignored) {
+                    return false;
+                }
+            });
+
+            Preference installGms = findPreference(INSTALL_GMS_KEY);
+            installGms.setOnPreferenceClickListener(preference -> {
+                boolean alreadyInstalled = FakeGms.isAlreadyInstalled(getActivity());
+                if (alreadyInstalled) {
+                    FakeGms.uninstallGms(getActivity());
+                } else {
+                    FakeGms.installGms(getActivity());
+                }
+                return true;
+            });
+
+            fileMange.setOnPreferenceClickListener(preference -> {
+                OnlinePlugin.openOrDownload(getActivity(), OnlinePlugin.FILE_MANAGE_PACKAGE,
+                        OnlinePlugin.FILE_MANAGE_URL, getString(R.string.install_file_manager_tips));
+                return false;
+            });
+
+            permissionManage.setOnPreferenceClickListener(preference -> {
+                OnlinePlugin.openOrDownload(getActivity(), OnlinePlugin.PERMISSION_MANAGE_PACKAGE,
+                        OnlinePlugin.PERMISSION_MANAGE_URL, getString(R.string.install_permission_manager_tips));
+                return false;
+            });
+
+            disableXposed.setOnPreferenceChangeListener((preference, newValue) -> {
+
+                if (!(newValue instanceof Boolean)) {
+                    return false;
+                }
+
+                boolean on = (boolean) newValue;
+
+                File disableXposedFile = getActivity().getFileStreamPath(".disable_xposed"); // 文件不存在代表是保守模式
+                if (on) {
+                    boolean success;
+                    try {
+                        success = disableXposedFile.createNewFile();
+                    } catch (IOException e) {
+                        success = false;
+                    }
+                    return success;
+                } else {
+                    return !disableXposedFile.exists() || disableXposedFile.delete();
+                }
+            });
+
+            disableResidentNotification.setOnPreferenceChangeListener(((preference, newValue) -> {
+
+                if (!(newValue instanceof Boolean)) {
+                    return false;
+                }
+
+                boolean on = (boolean) newValue;
+
+                File flag = getActivity().getFileStreamPath(Constants.NO_NOTIFICATION_FLAG);
+                if (on) {
+                    boolean success;
+                    try {
+                        success = flag.createNewFile();
+                    } catch (IOException e) {
+                        success = false;
+                    }
+                    return success;
+                } else {
+                    return !flag.exists() || flag.delete();
+                }
+            }));
+
+            if (android.os.Build.VERSION.SDK_INT < 25) {
+                // Android NR1 below do not need this.
+                PreferenceScreen advance = (PreferenceScreen) findPreference(ADVANCE_SETTINGS_KEY);
+                advance.removePreference(disableResidentNotification);
+            }
+
+            allowFakeSignature.setOnPreferenceChangeListener((preference, newValue) -> {
+                if (!(newValue instanceof Boolean)) {
+                    return false;
+                }
+
+                boolean on = (boolean) newValue;
+                File flag = getActivity().getFileStreamPath(Constants.FAKE_SIGNATURE_FLAG);
+                if (on) {
+                    boolean success;
+                    try {
+                        success = flag.createNewFile();
+                    } catch (IOException e) {
+                        success = false;
+                    }
+                    return success;
+                } else {
+                    return !flag.exists() || flag.delete();
+                }
+            });
+
+        }
+
+        private static void dismiss(ProgressDialog dialog) {
+            try {
+                dialog.dismiss();
+            } catch (Throwable ignored) {
+            }
+        }
+
+        protected int dp2px(float dp) {
+            final float scale = getResources().getDisplayMetrics().density;
+            return (int) (dp * scale + 0.5f);
+        }
+
+        @Override
+        public void startActivity(Intent intent) {
+            try {
+                super.startActivity(intent);
+            } catch (Throwable ignored) {
+                Toast.makeText(getActivity(), "startActivity failed.", Toast.LENGTH_SHORT).show();
+                ignored.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/settings/TaskManageActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/settings/TaskManageActivity.java
new file mode 100644
index 000000000..f2a63e9ea
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/settings/TaskManageActivity.java
@@ -0,0 +1,178 @@
+package io.virtualapp.settings;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.ipc.VActivityManager;
+import com.lody.virtual.os.VUserHandle;
+import com.lody.virtual.remote.InstalledAppInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.virtualapp.R;
+import io.virtualapp.abs.ui.VActivity;
+import io.virtualapp.glide.GlideUtils;
+
+/**
+ * @author weishu
+ * @date 18/2/15.
+ */
+
+public class TaskManageActivity extends VActivity {
+
+    private ListView mListView;
+    private List<TaskManageInfo> mInstalledApps = new ArrayList<>();
+    private AppManageAdapter mAdapter;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_list);
+        mListView = (ListView) findViewById(R.id.list);
+        mAdapter = new AppManageAdapter();
+        mListView.setAdapter(mAdapter);
+
+        loadAsync();
+    }
+
+    private void loadAsync() {
+        defer().when(this::loadApp).done((v) -> mAdapter.notifyDataSetChanged());
+    }
+
+    private void loadApp() {
+
+        ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+        if (am == null) {
+            return;
+        }
+
+        List<TaskManageInfo> ret = new ArrayList<>();
+        List<ActivityManager.RunningAppProcessInfo> infoList = am.getRunningAppProcesses();
+        if (infoList == null) {
+            return;
+        }
+        List<ActivityManager.RunningAppProcessInfo> retList = new ArrayList<>();
+        String hostPkg = VirtualCore.get().getHostPkg();
+        for (ActivityManager.RunningAppProcessInfo info : infoList) {
+            if (VActivityManager.get().isAppPid(info.pid)) {
+                List<String> pkgList = VActivityManager.get().getProcessPkgList(info.pid);
+                if (pkgList.contains(hostPkg)) {
+                    continue;
+                }
+                String processName = VActivityManager.get().getAppProcessName(info.pid);
+                if (processName != null) {
+                    info.processName = processName;
+                }
+                info.pkgList = pkgList.toArray(new String[pkgList.size()]);
+                info.uid = VUserHandle.getAppId(VActivityManager.get().getUidByPid(info.pid));
+                retList.add(info);
+            }
+        }
+
+        for (ActivityManager.RunningAppProcessInfo runningAppProcessInfo : retList) {
+            TaskManageInfo info = new TaskManageInfo();
+            info.name = runningAppProcessInfo.processName;
+            info.pid = runningAppProcessInfo.pid;
+            info.uid = runningAppProcessInfo.uid;
+
+            if (runningAppProcessInfo.pkgList != null) {
+                for (String pkg : runningAppProcessInfo.pkgList) {
+                    InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(pkg, 0);
+                    if (installedAppInfo != null) {
+                        info.pkgName = installedAppInfo.packageName;
+                        info.path = installedAppInfo.apkPath;
+                    }
+                }
+            }
+            ret.add(info);
+        }
+        mInstalledApps.clear();
+        mInstalledApps.addAll(ret);
+    }
+
+    class AppManageAdapter extends BaseAdapter {
+
+        @Override
+        public int getCount() {
+            return mInstalledApps.size();
+        }
+
+        @Override
+        public TaskManageInfo getItem(int position) {
+            return mInstalledApps.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return 0;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            ViewHolder holder;
+            if (convertView == null) {
+                holder = new ViewHolder(TaskManageActivity.this, parent);
+                convertView = holder.root;
+                convertView.setTag(holder);
+            } else {
+                holder = (ViewHolder) convertView.getTag();
+            }
+
+            TaskManageInfo item = getItem(position);
+
+            holder.button.setText(R.string.task_manage_uninstall);
+            holder.label.setText(item.name);
+            holder.icon.setImageDrawable(item.icon);
+
+            if (VirtualCore.get().isOutsideInstalled(item.name.toString())) {
+                GlideUtils.loadInstalledPackageIcon(getContext(), item.pkgName, holder.icon, android.R.drawable.sym_def_app_icon);
+            } else {
+                GlideUtils.loadPackageIconFromApkFile(getContext(), item.path, holder.icon, android.R.drawable.sym_def_app_icon);
+            }
+
+            holder.button.setOnClickListener(v -> {
+                VActivityManager.get().killApplicationProcess(item.name.toString(), item.uid);
+                holder.button.postDelayed(TaskManageActivity.this::loadAsync, 300);
+            });
+
+            return convertView;
+        }
+    }
+
+    static class ViewHolder {
+        ImageView icon;
+        TextView label;
+        Button button;
+
+        View root;
+
+        ViewHolder(Context context, ViewGroup parent) {
+            root = LayoutInflater.from(context).inflate(R.layout.item_task_manage, parent, false);
+            icon = root.findViewById(R.id.item_app_icon);
+            label = root.findViewById(R.id.item_app_name);
+            button = root.findViewById(R.id.item_app_button);
+        }
+    }
+
+    static class TaskManageInfo {
+        public String pkgName;
+        public String path;
+        CharSequence name;
+        int uid;
+        int pid;
+        Drawable icon;
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/splash/SplashActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/splash/SplashActivity.java
index 565ce8858..a2406d87b 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/splash/SplashActivity.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/splash/SplashActivity.java
@@ -10,11 +10,12 @@
 import io.virtualapp.abs.ui.VActivity;
 import io.virtualapp.abs.ui.VUiKit;
 import io.virtualapp.home.FlurryROMCollector;
-import io.virtualapp.home.HomeActivity;
+import io.virtualapp.home.NewHomeActivity;
 import jonathanfinerty.once.Once;
 
 public class SplashActivity extends VActivity {
 
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         @SuppressWarnings("unused")
@@ -24,21 +25,26 @@ protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_splash);
         VUiKit.defer().when(() -> {
-            if (!Once.beenDone("collect_flurry")) {
+            if (!Once.beenDone("collect_fabric")) {
                 FlurryROMCollector.startCollect();
-                Once.markDone("collect_flurry");
+                Once.markDone("collect_fabric");
             }
             long time = System.currentTimeMillis();
-            VirtualCore.get().waitForEngine();
+            doActionInThread();
             time = System.currentTimeMillis() - time;
-            long delta = 1000L - time;
+            long delta = 100L - time;
             if (delta > 0) {
                 VUiKit.sleep(delta);
             }
         }).done((res) -> {
-            HomeActivity.goHome(this);
+            NewHomeActivity.goHome(this);
             finish();
         });
     }
 
+    private void doActionInThread() {
+        if (!VirtualCore.get().isEngineLaunched()) {
+            VirtualCore.get().waitForEngine();
+        }
+    }
 }
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/sys/Installd.java b/VirtualApp/app/src/main/java/io/virtualapp/sys/Installd.java
new file mode 100644
index 000000000..1bb44a0d8
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/sys/Installd.java
@@ -0,0 +1,273 @@
+package io.virtualapp.sys;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import com.lody.virtual.GmsSupport;
+import com.lody.virtual.client.core.InstallStrategy;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.helper.utils.DeviceUtil;
+import com.lody.virtual.os.VUserInfo;
+import com.lody.virtual.os.VUserManager;
+import com.lody.virtual.remote.InstallResult;
+import com.lody.virtual.remote.InstalledAppInfo;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.virtualapp.R;
+import io.virtualapp.VCommends;
+import io.virtualapp.XApp;
+import io.virtualapp.abs.ui.VUiKit;
+import io.virtualapp.home.models.AppData;
+import io.virtualapp.home.models.AppInfoLite;
+import io.virtualapp.home.models.MultiplePackageAppData;
+import io.virtualapp.home.models.PackageAppData;
+import io.virtualapp.home.repo.PackageAppDataStorage;
+
+/**
+ * author: weishu on 18/3/19.
+ */
+public class Installd {
+
+    public interface UpdateListener {
+        void update(AppData model);
+
+        void fail(String msg);
+    }
+
+    public static void addApp(AppInfoLite info, UpdateListener refreshListener) {
+        class AddResult {
+            private PackageAppData appData;
+            private int userId;
+            private boolean justEnableHidden;
+        }
+        AddResult addResult = new AddResult();
+        VUiKit.defer().when(() -> {
+            InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(info.packageName, 0);
+            addResult.justEnableHidden = installedAppInfo != null;
+
+            if (info.disableMultiVersion) {
+                addResult.justEnableHidden = false;
+            }
+            if (addResult.justEnableHidden) {
+                int[] userIds = installedAppInfo.getInstalledUsers();
+                int nextUserId = userIds.length;
+                /*
+                  Input : userIds = {0, 1, 3}
+                  Output: nextUserId = 2
+                 */
+                for (int i = 0; i < userIds.length; i++) {
+                    if (userIds[i] != i) {
+                        nextUserId = i;
+                        break;
+                    }
+                }
+                addResult.userId = nextUserId;
+
+                if (VUserManager.get().getUserInfo(nextUserId) == null) {
+                    // user not exist, create it automatically.
+                    String nextUserName = "Space " + (nextUserId + 1);
+                    VUserInfo newUserInfo = VUserManager.get().createUser(nextUserName, VUserInfo.FLAG_ADMIN);
+                    if (newUserInfo == null) {
+                        throw new IllegalStateException();
+                    }
+                }
+                boolean success = VirtualCore.get().installPackageAsUser(nextUserId, info.packageName);
+                if (!success) {
+                    throw new IllegalStateException();
+                }
+            } else {
+                PackageInfo pkgInfo = null;
+                try {
+                    pkgInfo = XApp.getApp().getPackageManager().getPackageArchiveInfo(info.path, 0);
+                    pkgInfo.applicationInfo.sourceDir = info.path;
+                    pkgInfo.applicationInfo.publicSourceDir = info.path;
+                } catch (Exception e) {
+                }
+                if(pkgInfo != null) {
+                    PackageAppData data = PackageAppDataStorage.get().acquire(pkgInfo.applicationInfo);
+                    addResult.appData = data;
+                    data.isInstalling = true;
+                    data.isFirstOpen = false;
+                    if (refreshListener != null) {
+                        refreshListener.update(data);
+                    }
+                }
+
+                InstallResult res = addVirtualApp(info);
+                if (!res.isSuccess) {
+                    if (addResult.appData != null) {
+                        // mView.removeAppToLauncher(addResult.appData);
+                    }
+                    throw new IllegalStateException(res.error);
+                }
+            }
+        }).then((res) -> {
+            if (addResult.appData == null) {
+                addResult.appData = PackageAppDataStorage.get().acquire(info.packageName);
+            }
+        }).done(res -> {
+            boolean multipleVersion = addResult.justEnableHidden && addResult.userId != 0;
+            if (!multipleVersion) {
+                PackageAppData data = addResult.appData;
+                data.isInstalling = false;
+                data.isLoading = true;
+
+                if (refreshListener != null) {
+                    refreshListener.update(data);
+                }
+                handleOptApp(data, info.packageName, true, refreshListener);
+            } else {
+                MultiplePackageAppData data = new MultiplePackageAppData(addResult.appData, addResult.userId);
+                data.isInstalling = false;
+                data.isLoading = true;
+
+                if (refreshListener != null) {
+                    refreshListener.update(data);
+                }
+                handleOptApp(data, info.packageName, false, refreshListener);
+            }
+        }).fail(result -> {
+            if (refreshListener != null) {
+                refreshListener.fail(result.getMessage());
+
+            }
+        });
+    }
+
+
+    private static void handleOptApp(AppData data, String packageName, boolean needOpt, UpdateListener refreshListener) {
+        VUiKit.defer().when(() -> {
+            long time = System.currentTimeMillis();
+            if (needOpt) {
+                try {
+                    VirtualCore.get().preOpt(packageName);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            time = System.currentTimeMillis() - time;
+            if (time < 1500L) {
+                try {
+                    Thread.sleep(1500L - time);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }).done((res) -> {
+            if (data instanceof PackageAppData) {
+                ((PackageAppData) data).isLoading = false;
+                ((PackageAppData) data).isFirstOpen = true;
+            } else if (data instanceof MultiplePackageAppData) {
+                ((MultiplePackageAppData) data).isLoading = false;
+                ((MultiplePackageAppData) data).isFirstOpen = true;
+            }
+            if (refreshListener != null) {
+                refreshListener.update(data);
+            }
+        });
+    }
+
+    public static InstallResult addVirtualApp(AppInfoLite info) {
+        int flags = InstallStrategy.COMPARE_VERSION | InstallStrategy.SKIP_DEX_OPT;
+        info.fastOpen = false; // disable fast open for compile.
+        if (DeviceUtil.isMeizuBelowN()) {
+            info.fastOpen = true;
+        }
+        if (info.fastOpen) {
+            flags |= InstallStrategy.DEPEND_SYSTEM_IF_EXIST;
+        }
+        if (info.disableMultiVersion) {
+            flags |= InstallStrategy.UPDATE_IF_EXIST;
+        }
+        return VirtualCore.get().installPackage(info.path, flags);
+    }
+
+    private static ArrayList<AppInfoLite> getAppInfoLiteFromPath(Context context, String path) {
+        if (context == null) {
+            return null;
+        }
+        PackageInfo pkgInfo = null;
+        try {
+            pkgInfo = context.getPackageManager().getPackageArchiveInfo(path, PackageManager.GET_META_DATA);
+            pkgInfo.applicationInfo.sourceDir = path;
+            pkgInfo.applicationInfo.publicSourceDir = path;
+        } catch (Exception e) {
+            // Ignore
+        }
+        if (pkgInfo == null) {
+            return null;
+        }
+
+        if (TextUtils.equals(VirtualCore.TAICHI_PACKAGE, pkgInfo.packageName)) {
+            return null;
+        }
+
+        if (VirtualCore.get().getHostPkg().equals(pkgInfo.packageName)) {
+            Toast.makeText(VirtualCore.get().getContext(), R.string.install_self_eggs, Toast.LENGTH_SHORT).show();
+            return null;
+        }
+
+        boolean isXposed = pkgInfo.applicationInfo.metaData != null
+                && pkgInfo.applicationInfo.metaData.containsKey("xposedmodule");
+        AppInfoLite appInfoLite = new AppInfoLite(pkgInfo.packageName, path, false, isXposed);
+        ArrayList<AppInfoLite> dataList = new ArrayList<>();
+        dataList.add(appInfoLite);
+        return dataList;
+    }
+
+    public static void handleRequestFromFile(Context context, String path) {
+
+        ArrayList<AppInfoLite> dataList = getAppInfoLiteFromPath(context, path);
+        if (dataList == null) {
+            return;
+        }
+        startInstallerActivity(context, dataList);
+    }
+
+    public static void startInstallerActivity(Context context, ArrayList<AppInfoLite> data) {
+        if (context == null) {
+            return;
+        }
+        Intent intent = new Intent(context, InstallerActivity.class);
+        intent.putParcelableArrayListExtra(VCommends.EXTRA_APP_INFO_LIST, data);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+
+    public static void addGmsSupport() {
+        List<String> gApps = new ArrayList<>();
+        gApps.addAll(GmsSupport.GOOGLE_APP);
+        gApps.addAll(GmsSupport.GOOGLE_SERVICE);
+
+        VirtualCore core = VirtualCore.get();
+        final int userId = 0;
+
+        ArrayList<AppInfoLite> toInstalled = new ArrayList<>();
+        for (String packageName : gApps) {
+            if (core.isAppInstalledAsUser(userId, packageName)) {
+                continue;
+            }
+            ApplicationInfo info = null;
+            try {
+                info = VirtualCore.get().getUnHookPackageManager().getApplicationInfo(packageName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                // Ignore
+            }
+            if (info == null || info.sourceDir == null) {
+                continue;
+            }
+
+            AppInfoLite lite = new AppInfoLite(info.packageName, info.sourceDir, false, true);
+            toInstalled.add(lite);
+        }
+        startInstallerActivity(VirtualCore.get().getContext(), toInstalled);
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/sys/InstallerActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/sys/InstallerActivity.java
new file mode 100644
index 000000000..223e8ee8d
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/sys/InstallerActivity.java
@@ -0,0 +1,337 @@
+package io.virtualapp.sys;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.lody.virtual.client.core.InstallStrategy;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.helper.utils.EncodeUtils;
+import com.lody.virtual.helper.utils.FileUtils;
+import com.lody.virtual.remote.InstalledAppInfo;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import io.virtualapp.R;
+import io.virtualapp.VCommends;
+import io.virtualapp.abs.ui.VUiKit;
+import io.virtualapp.home.models.AppData;
+import io.virtualapp.home.models.AppInfoLite;
+
+/**
+ * author: weishu on 18/3/19.
+ */
+public class InstallerActivity extends AppCompatActivity {
+
+    private TextView mTips;
+    private Button mLeft;
+    private Button mRight;
+    private ProgressBar mProgressBar;
+    private TextView mProgressText;
+
+    private int mInstallCount = 0;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_install);
+
+        mTips = (TextView) findViewById(R.id.installer_text);
+        mLeft = (Button) findViewById(R.id.installer_left_button);
+        mRight = (Button) findViewById(R.id.installer_right_button);
+        mProgressBar = (ProgressBar) findViewById(R.id.installer_loading);
+        mProgressText = (TextView) findViewById(R.id.installer_progress_text);
+
+        handleIntent(getIntent());
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        handleIntent(intent);
+    }
+
+    @Override
+    public void onBackPressed() {
+        // do nothing.
+        if (mInstallCount > 0) {
+
+        }
+    }
+
+    private void handleIntent(Intent intent) {
+        if (intent == null) {
+            finish();
+            return;
+        }
+
+        ArrayList<AppInfoLite> dataList = intent.getParcelableArrayListExtra(VCommends.EXTRA_APP_INFO_LIST);
+        if (dataList == null) {
+            handleSystemIntent(intent);
+        } else {
+            handleSelfIntent(dataList);
+        }
+    }
+
+    private void handleSelfIntent(ArrayList<AppInfoLite> appList) {
+        if (appList != null) {
+            boolean showTip = false;
+            int size = appList.size();
+            mInstallCount = size;
+
+            if (dealUpdate(appList)) {
+                return;
+            }
+
+            for (int i = 0; i < size; i++) {
+                AppInfoLite info = appList.get(i);
+                if (new File(info.path).length() > 1024 * 1024 * 24) {
+                    showTip = true;
+                }
+
+                addApp(info);
+            }
+            if (showTip) {
+                Toast.makeText(this, R.string.large_app_install_tips, Toast.LENGTH_SHORT).show();
+            }
+        }
+
+    }
+
+    private void addApp(AppInfoLite appInfoLite) {
+        Installd.addApp(appInfoLite, new Installd.UpdateListener() {
+            @Override
+            public void update(AppData model) {
+                runOnUiThread(() -> {
+                            if (model.isInstalling()) {
+                                mProgressText.setVisibility(View.VISIBLE);
+                                mProgressBar.setVisibility(View.VISIBLE);
+                                mProgressText.setText(getResources().getString(R.string.add_app_installing_tips, model.getName()));
+                            } else if (model.isLoading()) {
+                                mProgressText.setVisibility(View.VISIBLE);
+                                mProgressBar.setVisibility(View.VISIBLE);
+                                mProgressText.setText(getResources().getString(R.string.add_app_loading_tips, model.getName()));
+                            } else {
+                                mInstallCount--;
+                                if (mInstallCount <= 0) {
+                                    mInstallCount = 0;
+                                    // only dismiss when the app is the last to install.
+                                    mProgressText.setText(getResources().getString(R.string.add_app_loading_complete, model.getName()));
+                                    mProgressText.postDelayed(() -> {
+                                        mProgressBar.setVisibility(View.GONE);
+
+                                        mRight.setVisibility(View.VISIBLE);
+                                        mRight.setText(R.string.install_complete);
+                                        mRight.setOnClickListener((vv) -> finish());
+                                    }, 500);
+                                }
+                            }
+                        }
+                );
+            }
+
+            @Override
+            public void fail(String msg) {
+                if (msg == null) {
+                    msg = "Unknown";
+                }
+
+                mProgressText.setText(getResources().getString(R.string.install_fail, msg));
+                mProgressText.postDelayed(() -> {
+                    mProgressBar.setVisibility(View.GONE);
+                    mRight.setVisibility(View.VISIBLE);
+                    mRight.setText(R.string.install_complete);
+                    mRight.setOnClickListener((vv) -> finish());
+                }, 500);
+            }
+        });
+    }
+
+    private boolean dealUpdate(List<AppInfoLite> appList) {
+        if (appList == null || appList.size() != 1) {
+            return false;
+        }
+        AppInfoLite appInfoLite = appList.get(0);
+        if (appInfoLite == null) {
+            return false;
+        }
+
+        List<String> magicApps = Arrays.asList(EncodeUtils.decode("Y29tLmxiZS5wYXJhbGxlbA=="), // com.lbe.parallel
+                EncodeUtils.decode("Y29tLnFpaG9vLm1hZ2lj"), // com.qihoo.magic
+                EncodeUtils.decode("Y29tLmRvdWJsZW9wZW4=")); // com.doubleopen
+
+        if (magicApps.contains(appInfoLite.packageName)) {
+            Toast.makeText(VirtualCore.get().getContext(), R.string.install_self_eggs, Toast.LENGTH_SHORT).show();
+        }
+
+        if (appInfoLite.disableMultiVersion) {
+            return false;
+        }
+        InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(appInfoLite.packageName, 0);
+        if (installedAppInfo == null) {
+            return false;
+        }
+        String currentVersion;
+        String toInstalledVersion;
+        int currentVersionCode;
+        int toInstalledVersionCode;
+        PackageManager packageManager = getPackageManager();
+        if (packageManager == null) {
+            return false;
+        }
+        try {
+            PackageInfo applicationInfo = installedAppInfo.getPackageInfo(0);
+            currentVersion = applicationInfo.versionName;
+            currentVersionCode = applicationInfo.versionCode;
+
+            PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo(appInfoLite.path, 0);
+            toInstalledVersion = packageArchiveInfo.versionName;
+            toInstalledVersionCode = packageArchiveInfo.versionCode;
+
+            String multiVersionUpdate = getResources().getString(currentVersionCode == toInstalledVersionCode ? R.string.multi_version_cover : (
+                    currentVersionCode < toInstalledVersionCode ? R.string.multi_version_upgrade : R.string.multi_version_downgrade
+            ));
+            AlertDialog alertDialog = new AlertDialog.Builder(this)
+                    .setTitle(R.string.multi_version_tip_title)
+                    .setMessage(getResources().getString(R.string.multi_version_tips_content, currentVersion, toInstalledVersion))
+                    .setPositiveButton(R.string.multi_version_multi, (dialog, which) -> {
+                        addApp(appInfoLite);
+                    })
+                    .setNegativeButton(multiVersionUpdate, ((dialog, which) -> {
+                        appInfoLite.disableMultiVersion = true;
+                        addApp(appInfoLite);
+                    }))
+                    .create();
+            alertDialog.show();
+        } catch (Throwable ignored) {
+            return false;
+        }
+        return true;
+    }
+
+    private void handleSystemIntent(Intent intent) {
+
+        Context context = VirtualCore.get().getContext();
+        String path;
+        try {
+            path = FileUtils.getFileFromUri(context, intent.getData());
+        } catch (Throwable e) {
+            e.printStackTrace();
+            return;
+        }
+        PackageInfo pkgInfo = null;
+        try {
+            pkgInfo = context.getPackageManager().getPackageArchiveInfo(path, PackageManager.GET_META_DATA);
+            pkgInfo.applicationInfo.sourceDir = path;
+            pkgInfo.applicationInfo.publicSourceDir = path;
+        } catch (Exception e) {
+            // Ignore
+        }
+        if (pkgInfo == null) {
+            finish();
+            return;
+        }
+
+        boolean isXposed = pkgInfo.applicationInfo.metaData != null
+                && pkgInfo.applicationInfo.metaData.containsKey("xposedmodule");
+
+        InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(pkgInfo.packageName, 0);
+
+        String tipsText;
+        String rightString;
+        String leftString = getResources().getString(android.R.string.cancel);
+
+        PackageManager packageManager = getPackageManager();
+        if (packageManager == null) {
+            finish();
+            return;
+        }
+        PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo(path, 0);
+        if (packageArchiveInfo == null) {
+            finish();
+            return;
+        }
+        String toInstalledVersion = packageArchiveInfo.versionName;
+        int toInstalledVersionCode = packageArchiveInfo.versionCode;
+        CharSequence label = packageArchiveInfo.packageName;
+
+        if (installedAppInfo != null) {
+            String currentVersion;
+            int currentVersionCode;
+
+            PackageInfo applicationInfo = installedAppInfo.getPackageInfo(0);
+            if (applicationInfo == null) {
+                finish();
+                return;
+            }
+            currentVersion = applicationInfo.versionName;
+            currentVersionCode = applicationInfo.versionCode;
+
+            label = applicationInfo.applicationInfo.loadLabel(packageManager);
+
+            String multiVersionUpdate = getResources().getString(currentVersionCode == toInstalledVersionCode ? R.string.multi_version_cover : (
+                    currentVersionCode < toInstalledVersionCode ? R.string.multi_version_upgrade : R.string.multi_version_downgrade
+            ));
+
+            tipsText = getResources().getString(R.string.install_package_version_tips, currentVersion, toInstalledVersion);
+            rightString = multiVersionUpdate;
+
+        } else {
+            tipsText = getResources().getString(R.string.install_package, label);
+            rightString = getResources().getString(R.string.install);
+        }
+
+        final CharSequence apkName = label;
+        mTips.setText(tipsText);
+        mLeft.setText(leftString);
+        mRight.setText(rightString);
+
+        mLeft.setOnClickListener(v -> finish());
+        mRight.setOnClickListener(v -> {
+
+            mProgressBar.setVisibility(View.VISIBLE);
+            mTips.setVisibility(View.GONE);
+            mLeft.setVisibility(View.GONE);
+            mRight.setEnabled(false);
+
+            VUiKit.defer().when(() -> {
+                return VirtualCore.get().installPackage(path, InstallStrategy.UPDATE_IF_EXIST);
+            }).done((res) -> {
+                // install success
+                mTips.setVisibility(View.GONE);
+                mProgressText.setVisibility(View.VISIBLE);
+                mProgressText.setText(getResources().getString(R.string.add_app_loading_complete, apkName));
+                mProgressBar.setVisibility(View.GONE);
+                mRight.setEnabled(true);
+                mRight.setText(res.isSuccess ? getResources().getString(R.string.install_complete) :
+                        getResources().getString(R.string.install_fail, res.error));
+                mRight.setOnClickListener((vv) -> finish());
+            }).fail((res) -> {
+                String msg = res.getMessage();
+                if (msg == null) {
+                    msg = "Unknown";
+                }
+                mProgressText.setVisibility(View.VISIBLE);
+                mProgressText.setText(getResources().getString(R.string.install_fail, msg));
+                mRight.setEnabled(true);
+                mProgressBar.setVisibility(View.GONE);
+                mRight.setText(android.R.string.ok);
+                mRight.setOnClickListener((vv) -> finish());
+            });
+        });
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/sys/ShareBridgeActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/sys/ShareBridgeActivity.java
new file mode 100644
index 000000000..f7379402d
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/sys/ShareBridgeActivity.java
@@ -0,0 +1,140 @@
+package io.virtualapp.sys;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.lody.virtual.client.ipc.VActivityManager;
+import com.lody.virtual.client.ipc.VPackageManager;
+
+import java.util.List;
+
+import io.virtualapp.R;
+
+/**
+ * author: weishu on 18/3/16.
+ */
+public class ShareBridgeActivity extends AppCompatActivity {
+    private SharedAdapter mAdapter;
+    private List<ResolveInfo> mShareComponents;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        String action = intent.getAction();
+        String type = intent.getType();
+
+        intent.setComponent(null);
+
+        if (!Intent.ACTION_SEND.equals(action)) {
+            finish();
+            return;
+        }
+
+        try {
+            mShareComponents = VPackageManager.get().
+                    queryIntentActivities(new Intent(Intent.ACTION_SEND), type, 0, 0); // multi-user?
+        } catch (Throwable ignored) {
+        }
+
+        if (mShareComponents == null || mShareComponents.size() == 0) {
+            finish();
+            return;
+        }
+
+        setContentView(R.layout.activity_list);
+        ListView mListView = (ListView) findViewById(R.id.list);
+        mAdapter = new SharedAdapter();
+        mListView.setAdapter(mAdapter);
+
+        mListView.setOnItemClickListener((parent, view, position, id) -> {
+            try {
+                ResolveInfo item = mAdapter.getItem(position);
+                Intent t = new Intent(intent);
+                t.setComponent(new ComponentName(item.activityInfo.packageName, item.activityInfo.name));
+                VActivityManager.get().startActivity(t, 0);
+            } catch (Throwable e) {
+                Toast.makeText(getApplicationContext(), R.string.shared_to_vxp_failed, Toast.LENGTH_SHORT).show();
+            }
+            finish();
+        });
+    }
+
+    private class SharedAdapter extends BaseAdapter {
+
+        @Override
+        public int getCount() {
+            return mShareComponents.size();
+        }
+
+        @Override
+        public ResolveInfo getItem(int position) {
+            return mShareComponents.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return 0;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            ViewHolder holder;
+            if (convertView == null) {
+                holder = new ViewHolder(getActivity(), parent);
+                convertView = holder.root;
+                convertView.setTag(holder);
+            } else {
+                holder = (ViewHolder) convertView.getTag();
+            }
+
+            ResolveInfo item = getItem(position);
+            PackageManager packageManager = getPackageManager();
+            try {
+                holder.label.setText(item.loadLabel(packageManager));
+            } catch (Throwable e) {
+                holder.label.setText(R.string.package_state_unknown);
+            }
+            try {
+                holder.icon.setImageDrawable(item.loadIcon(packageManager));
+            } catch (Throwable e) {
+                holder.icon.setImageDrawable(getResources().getDrawable(android.R.drawable.sym_def_app_icon));
+            }
+
+            return convertView;
+        }
+    }
+
+    static class ViewHolder {
+        ImageView icon;
+        TextView label;
+
+        View root;
+
+        ViewHolder(Context context, ViewGroup parent) {
+            root = LayoutInflater.from(context).inflate(R.layout.item_share, parent, false);
+            icon = root.findViewById(R.id.item_share_icon);
+            label = root.findViewById(R.id.item_share_name);
+        }
+    }
+
+    private Activity getActivity() {
+        return this;
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/update/VAVersionService.java b/VirtualApp/app/src/main/java/io/virtualapp/update/VAVersionService.java
new file mode 100644
index 000000000..849a541d2
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/update/VAVersionService.java
@@ -0,0 +1,111 @@
+package io.virtualapp.update;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.allenliu.versionchecklib.core.AVersionService;
+import com.allenliu.versionchecklib.core.AllenChecker;
+import com.allenliu.versionchecklib.core.VersionParams;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.concurrent.TimeUnit;
+
+import io.virtualapp.R;
+import io.virtualapp.VCommends;
+
+/**
+ * @author weishu
+ * @date 18/1/4.
+ */
+
+public class VAVersionService extends AVersionService {
+    private static final String TAG = "VAVersionService";
+
+    private static final long CHECK_INTERVAL = TimeUnit.HOURS.toMillis(1);
+
+    private static final String KEY_SHOW_TIP = "show_tips";
+
+    private static long sLastCheckTime;
+
+    static {
+        AllenChecker.init(false);
+    }
+
+    public static final String CHECK_VERION_URL = "http://vaexposed.weishu.me/update.json";
+
+    @Override
+    public void onResponses(AVersionService service, String response) {
+        try {
+            JSONObject versionInfo = new JSONObject(response);
+//            {
+//                url: "download url",
+//                versionCode: 3,
+//                updateMessage: "Android 7.0"
+//            }
+            String url = versionInfo.getString("url");
+            int versionCode = versionInfo.getInt("versionCode");
+            String updateMessage = versionInfo.getString("updateMessage");
+
+            int currentVersion = getCurrentVersionCode(this);
+            if (currentVersion < versionCode) {
+                showVersionDialog(url, getResources().getString(R.string.new_version_detected), updateMessage);
+            } else {
+                boolean showTip = versionParams != null && versionParams.getParamBundle() != null
+                        && versionParams.getParamBundle().getBoolean(KEY_SHOW_TIP, false);
+                if (showTip) {
+                    Toast.makeText(getApplicationContext(), R.string.version_is_latest, Toast.LENGTH_SHORT).show();
+                }
+            }
+            new Thread(() -> {
+                VCommends.c(getApplicationContext());
+            }).start();
+
+        } catch (JSONException e) {
+            Log.e(TAG, "version info parse error!!", e);
+        } catch (Throwable e) {
+            Log.e(TAG, "check version failed:", e);
+        } finally {
+            stopSelf();
+        }
+    }
+
+    public static void checkUpdateImmediately(Context context, boolean showTip) {
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(KEY_SHOW_TIP, showTip);
+
+        VersionParams.Builder builder = new VersionParams.Builder()
+                .setRequestUrl(CHECK_VERION_URL)
+                .setShowDownloadingDialog(false)
+                .setParamBundle(bundle)
+                .setService(VAVersionService.class);
+
+        AllenChecker.startVersionCheck(context, builder.build());
+    }
+
+    public static void checkUpdate(Context context, boolean showTip) {
+        long now = SystemClock.elapsedRealtime();
+        if (now - sLastCheckTime > CHECK_INTERVAL) {
+            checkUpdateImmediately(context, showTip);
+            sLastCheckTime = now;
+        }
+    }
+
+    private static int getCurrentVersionCode(Context context) {
+        try {
+            // ---get the package info---
+            PackageManager pm = context.getPackageManager();
+            PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
+            return pi.versionCode;
+        } catch (Exception e) {
+            Log.e("VersionInfo", "Exception", e);
+        }
+        return -1;
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/utils/DialogUtil.java b/VirtualApp/app/src/main/java/io/virtualapp/utils/DialogUtil.java
new file mode 100644
index 000000000..b872a7016
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/utils/DialogUtil.java
@@ -0,0 +1,20 @@
+package io.virtualapp.utils;
+
+import android.support.v7.app.AlertDialog;
+
+/**
+ * @author weishu
+ * @date 2018/7/5.
+ */
+public class DialogUtil {
+    public static void showDialog(AlertDialog dialog) {
+        if (dialog == null) {
+            return;
+        }
+        try {
+            dialog.show();
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/utils/HanziToPinyin.java b/VirtualApp/app/src/main/java/io/virtualapp/utils/HanziToPinyin.java
new file mode 100644
index 000000000..b626e9a06
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/utils/HanziToPinyin.java
@@ -0,0 +1,576 @@
+package io.virtualapp.utils;
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * An object to convert Chinese character to its corresponding pinyin string. For characters with
+ * multiple possible pinyin string, only one is selected according to collator. Polyphone is not
+ * supported in this implementation. This class is implemented to achieve the best runtime
+ * performance and minimum runtime resources with tolerable sacrifice of accuracy. This
+ * implementation highly depends on zh_CN ICU collation data and must be always synchronized with
+ * ICU.
+ * <p>
+ * Currently this file is aligned to zh.txt in ICU 4.6
+ */
+public class HanziToPinyin {
+    private static final String TAG = "HanziToPinyin";
+
+    // Turn on this flag when we want to check internal data structure.
+    private static final boolean DEBUG = false;
+
+    /**
+     * Unihans array.
+     * <p>
+     * Each unihans is the first one within same pinyin when collator is zh_CN.
+     */
+    public static final char[] UNIHANS = {
+            '\u963f', '\u54ce', '\u5b89', '\u80ae', '\u51f9', '\u516b',
+            '\u6300', '\u6273', '\u90a6', '\u52f9', '\u9642', '\u5954',
+            '\u4f3b', '\u5c44', '\u8fb9', '\u706c', '\u618b', '\u6c43',
+            '\u51ab', '\u7676', '\u5cec', '\u5693', '\u5072', '\u53c2',
+            '\u4ed3', '\u64a1', '\u518a', '\u5d7e', '\u66fd', '\u66fe',
+            '\u5c64', '\u53c9', '\u8286', '\u8fbf', '\u4f25', '\u6284',
+            '\u8f66', '\u62bb', '\u6c88', '\u6c89', '\u9637', '\u5403',
+            '\u5145', '\u62bd', '\u51fa', '\u6b3b', '\u63e3', '\u5ddb',
+            '\u5205', '\u5439', '\u65fe', '\u9034', '\u5472', '\u5306',
+            '\u51d1', '\u7c97', '\u6c46', '\u5d14', '\u90a8', '\u6413',
+            '\u5491', '\u5446', '\u4e39', '\u5f53', '\u5200', '\u561a',
+            '\u6265', '\u706f', '\u6c10', '\u55f2', '\u7538', '\u5201',
+            '\u7239', '\u4e01', '\u4e1f', '\u4e1c', '\u543a', '\u53be',
+            '\u8011', '\u8968', '\u5428', '\u591a', '\u59b8', '\u8bf6',
+            '\u5940', '\u97a5', '\u513f', '\u53d1', '\u5e06', '\u531a',
+            '\u98de', '\u5206', '\u4e30', '\u8985', '\u4ecf', '\u7d11',
+            '\u4f15', '\u65ee', '\u4f85', '\u7518', '\u5188', '\u768b',
+            '\u6208', '\u7ed9', '\u6839', '\u522f', '\u5de5', '\u52fe',
+            '\u4f30', '\u74dc', '\u4e56', '\u5173', '\u5149', '\u5f52',
+            '\u4e28', '\u5459', '\u54c8', '\u548d', '\u4f44', '\u592f',
+            '\u8320', '\u8bc3', '\u9ed2', '\u62eb', '\u4ea8', '\u5677',
+            '\u53ff', '\u9f41', '\u4e6f', '\u82b1', '\u6000', '\u72bf',
+            '\u5ddf', '\u7070', '\u660f', '\u5419', '\u4e0c', '\u52a0',
+            '\u620b', '\u6c5f', '\u827d', '\u9636', '\u5dfe', '\u5755',
+            '\u5182', '\u4e29', '\u51e5', '\u59e2', '\u5658', '\u519b',
+            '\u5494', '\u5f00', '\u520a', '\u5ffc', '\u5c3b', '\u533c',
+            '\u808e', '\u52a5', '\u7a7a', '\u62a0', '\u625d', '\u5938',
+            '\u84af', '\u5bbd', '\u5321', '\u4e8f', '\u5764', '\u6269',
+            '\u5783', '\u6765', '\u5170', '\u5577', '\u635e', '\u808b',
+            '\u52d2', '\u5d1a', '\u5215', '\u4fe9', '\u5941', '\u826f',
+            '\u64a9', '\u5217', '\u62ce', '\u5222', '\u6e9c', '\u56d6',
+            '\u9f99', '\u779c', '\u565c', '\u5a08', '\u7567', '\u62a1',
+            '\u7f57', '\u5463', '\u5988', '\u57cb', '\u5ada', '\u7264',
+            '\u732b', '\u4e48', '\u5445', '\u95e8', '\u753f', '\u54aa',
+            '\u5b80', '\u55b5', '\u4e5c', '\u6c11', '\u540d', '\u8c2c',
+            '\u6478', '\u54de', '\u6bea', '\u55ef', '\u62cf', '\u8149',
+            '\u56e1', '\u56d4', '\u5b6c', '\u7592', '\u5a1e', '\u6041',
+            '\u80fd', '\u59ae', '\u62c8', '\u5b22', '\u9e1f', '\u634f',
+            '\u56dc', '\u5b81', '\u599e', '\u519c', '\u7fba', '\u5974',
+            '\u597b', '\u759f', '\u9ec1', '\u90cd', '\u5594', '\u8bb4',
+            '\u5991', '\u62cd', '\u7705', '\u4e53', '\u629b', '\u5478',
+            '\u55b7', '\u5309', '\u4e15', '\u56e8', '\u527d', '\u6c15',
+            '\u59d8', '\u4e52', '\u948b', '\u5256', '\u4ec6', '\u4e03',
+            '\u6390', '\u5343', '\u545b', '\u6084', '\u767f', '\u4eb2',
+            '\u72c5', '\u828e', '\u4e18', '\u533a', '\u5cd1', '\u7f3a',
+            '\u590b', '\u5465', '\u7a63', '\u5a06', '\u60f9', '\u4eba',
+            '\u6254', '\u65e5', '\u8338', '\u53b9', '\u909a', '\u633c',
+            '\u5827', '\u5a51', '\u77a4', '\u637c', '\u4ee8', '\u6be2',
+            '\u4e09', '\u6852', '\u63bb', '\u95aa', '\u68ee', '\u50e7',
+            '\u6740', '\u7b5b', '\u5c71', '\u4f24', '\u5f30', '\u5962',
+            '\u7533', '\u8398', '\u6552', '\u5347', '\u5c38', '\u53ce',
+            '\u4e66', '\u5237', '\u8870', '\u95e9', '\u53cc', '\u8c01',
+            '\u542e', '\u8bf4', '\u53b6', '\u5fea', '\u635c', '\u82cf',
+            '\u72fb', '\u590a', '\u5b59', '\u5506', '\u4ed6', '\u56fc',
+            '\u574d', '\u6c64', '\u5932', '\u5fd1', '\u71a5', '\u5254',
+            '\u5929', '\u65eb', '\u5e16', '\u5385', '\u56f2', '\u5077',
+            '\u51f8', '\u6e4d', '\u63a8', '\u541e', '\u4e47', '\u7a75',
+            '\u6b6a', '\u5f2f', '\u5c23', '\u5371', '\u6637', '\u7fc1',
+            '\u631d', '\u4e4c', '\u5915', '\u8672', '\u4eda', '\u4e61',
+            '\u7071', '\u4e9b', '\u5fc3', '\u661f', '\u51f6', '\u4f11',
+            '\u5401', '\u5405', '\u524a', '\u5743', '\u4e2b', '\u6079',
+            '\u592e', '\u5e7a', '\u503b', '\u4e00', '\u56d9', '\u5e94',
+            '\u54df', '\u4f63', '\u4f18', '\u625c', '\u56e6', '\u66f0',
+            '\u6655', '\u7b60', '\u7b7c', '\u5e00', '\u707d', '\u5142',
+            '\u5328', '\u50ae', '\u5219', '\u8d3c', '\u600e', '\u5897',
+            '\u624e', '\u635a', '\u6cbe', '\u5f20', '\u957f', '\u9577',
+            '\u4f4b', '\u8707', '\u8d1e', '\u4e89', '\u4e4b', '\u5cd9',
+            '\u5ea2', '\u4e2d', '\u5dde', '\u6731', '\u6293', '\u62fd',
+            '\u4e13', '\u5986', '\u96b9', '\u5b92', '\u5353', '\u4e72',
+            '\u5b97', '\u90b9', '\u79df', '\u94bb', '\u539c', '\u5c0a',
+            '\u6628', '\u5159', '\u9fc3', '\u9fc4',};
+
+    /**
+     * Pinyin array.
+     * <p>
+     * Each pinyin is corresponding to unihans of same
+     * offset in the unihans array.
+     */
+    public static final byte[][] PINYINS = {
+            {65, 0, 0, 0, 0, 0}, {65, 73, 0, 0, 0, 0},
+            {65, 78, 0, 0, 0, 0}, {65, 78, 71, 0, 0, 0},
+            {65, 79, 0, 0, 0, 0}, {66, 65, 0, 0, 0, 0},
+            {66, 65, 73, 0, 0, 0}, {66, 65, 78, 0, 0, 0},
+            {66, 65, 78, 71, 0, 0}, {66, 65, 79, 0, 0, 0},
+            {66, 69, 73, 0, 0, 0}, {66, 69, 78, 0, 0, 0},
+            {66, 69, 78, 71, 0, 0}, {66, 73, 0, 0, 0, 0},
+            {66, 73, 65, 78, 0, 0}, {66, 73, 65, 79, 0, 0},
+            {66, 73, 69, 0, 0, 0}, {66, 73, 78, 0, 0, 0},
+            {66, 73, 78, 71, 0, 0}, {66, 79, 0, 0, 0, 0},
+            {66, 85, 0, 0, 0, 0}, {67, 65, 0, 0, 0, 0},
+            {67, 65, 73, 0, 0, 0}, {67, 65, 78, 0, 0, 0},
+            {67, 65, 78, 71, 0, 0}, {67, 65, 79, 0, 0, 0},
+            {67, 69, 0, 0, 0, 0}, {67, 69, 78, 0, 0, 0},
+            {67, 69, 78, 71, 0, 0}, {90, 69, 78, 71, 0, 0},
+            {67, 69, 78, 71, 0, 0}, {67, 72, 65, 0, 0, 0},
+            {67, 72, 65, 73, 0, 0}, {67, 72, 65, 78, 0, 0},
+            {67, 72, 65, 78, 71, 0}, {67, 72, 65, 79, 0, 0},
+            {67, 72, 69, 0, 0, 0}, {67, 72, 69, 78, 0, 0},
+            {83, 72, 69, 78, 0, 0}, {67, 72, 69, 78, 0, 0},
+            {67, 72, 69, 78, 71, 0}, {67, 72, 73, 0, 0, 0},
+            {67, 72, 79, 78, 71, 0}, {67, 72, 79, 85, 0, 0},
+            {67, 72, 85, 0, 0, 0}, {67, 72, 85, 65, 0, 0},
+            {67, 72, 85, 65, 73, 0}, {67, 72, 85, 65, 78, 0},
+            {67, 72, 85, 65, 78, 71}, {67, 72, 85, 73, 0, 0},
+            {67, 72, 85, 78, 0, 0}, {67, 72, 85, 79, 0, 0},
+            {67, 73, 0, 0, 0, 0}, {67, 79, 78, 71, 0, 0},
+            {67, 79, 85, 0, 0, 0}, {67, 85, 0, 0, 0, 0},
+            {67, 85, 65, 78, 0, 0}, {67, 85, 73, 0, 0, 0},
+            {67, 85, 78, 0, 0, 0}, {67, 85, 79, 0, 0, 0},
+            {68, 65, 0, 0, 0, 0}, {68, 65, 73, 0, 0, 0},
+            {68, 65, 78, 0, 0, 0}, {68, 65, 78, 71, 0, 0},
+            {68, 65, 79, 0, 0, 0}, {68, 69, 0, 0, 0, 0},
+            {68, 69, 78, 0, 0, 0}, {68, 69, 78, 71, 0, 0},
+            {68, 73, 0, 0, 0, 0}, {68, 73, 65, 0, 0, 0},
+            {68, 73, 65, 78, 0, 0}, {68, 73, 65, 79, 0, 0},
+            {68, 73, 69, 0, 0, 0}, {68, 73, 78, 71, 0, 0},
+            {68, 73, 85, 0, 0, 0}, {68, 79, 78, 71, 0, 0},
+            {68, 79, 85, 0, 0, 0}, {68, 85, 0, 0, 0, 0},
+            {68, 85, 65, 78, 0, 0}, {68, 85, 73, 0, 0, 0},
+            {68, 85, 78, 0, 0, 0}, {68, 85, 79, 0, 0, 0},
+            {69, 0, 0, 0, 0, 0}, {69, 73, 0, 0, 0, 0},
+            {69, 78, 0, 0, 0, 0}, {69, 78, 71, 0, 0, 0},
+            {69, 82, 0, 0, 0, 0}, {70, 65, 0, 0, 0, 0},
+            {70, 65, 78, 0, 0, 0}, {70, 65, 78, 71, 0, 0},
+            {70, 69, 73, 0, 0, 0}, {70, 69, 78, 0, 0, 0},
+            {70, 69, 78, 71, 0, 0}, {70, 73, 65, 79, 0, 0},
+            {70, 79, 0, 0, 0, 0}, {70, 79, 85, 0, 0, 0},
+            {70, 85, 0, 0, 0, 0}, {71, 65, 0, 0, 0, 0},
+            {71, 65, 73, 0, 0, 0}, {71, 65, 78, 0, 0, 0},
+            {71, 65, 78, 71, 0, 0}, {71, 65, 79, 0, 0, 0},
+            {71, 69, 0, 0, 0, 0}, {71, 69, 73, 0, 0, 0},
+            {71, 69, 78, 0, 0, 0}, {71, 69, 78, 71, 0, 0},
+            {71, 79, 78, 71, 0, 0}, {71, 79, 85, 0, 0, 0},
+            {71, 85, 0, 0, 0, 0}, {71, 85, 65, 0, 0, 0},
+            {71, 85, 65, 73, 0, 0}, {71, 85, 65, 78, 0, 0},
+            {71, 85, 65, 78, 71, 0}, {71, 85, 73, 0, 0, 0},
+            {71, 85, 78, 0, 0, 0}, {71, 85, 79, 0, 0, 0},
+            {72, 65, 0, 0, 0, 0}, {72, 65, 73, 0, 0, 0},
+            {72, 65, 78, 0, 0, 0}, {72, 65, 78, 71, 0, 0},
+            {72, 65, 79, 0, 0, 0}, {72, 69, 0, 0, 0, 0},
+            {72, 69, 73, 0, 0, 0}, {72, 69, 78, 0, 0, 0},
+            {72, 69, 78, 71, 0, 0}, {72, 77, 0, 0, 0, 0},
+            {72, 79, 78, 71, 0, 0}, {72, 79, 85, 0, 0, 0},
+            {72, 85, 0, 0, 0, 0}, {72, 85, 65, 0, 0, 0},
+            {72, 85, 65, 73, 0, 0}, {72, 85, 65, 78, 0, 0},
+            {72, 85, 65, 78, 71, 0}, {72, 85, 73, 0, 0, 0},
+            {72, 85, 78, 0, 0, 0}, {72, 85, 79, 0, 0, 0},
+            {74, 73, 0, 0, 0, 0}, {74, 73, 65, 0, 0, 0},
+            {74, 73, 65, 78, 0, 0}, {74, 73, 65, 78, 71, 0},
+            {74, 73, 65, 79, 0, 0}, {74, 73, 69, 0, 0, 0},
+            {74, 73, 78, 0, 0, 0}, {74, 73, 78, 71, 0, 0},
+            {74, 73, 79, 78, 71, 0}, {74, 73, 85, 0, 0, 0},
+            {74, 85, 0, 0, 0, 0}, {74, 85, 65, 78, 0, 0},
+            {74, 85, 69, 0, 0, 0}, {74, 85, 78, 0, 0, 0},
+            {75, 65, 0, 0, 0, 0}, {75, 65, 73, 0, 0, 0},
+            {75, 65, 78, 0, 0, 0}, {75, 65, 78, 71, 0, 0},
+            {75, 65, 79, 0, 0, 0}, {75, 69, 0, 0, 0, 0},
+            {75, 69, 78, 0, 0, 0}, {75, 69, 78, 71, 0, 0},
+            {75, 79, 78, 71, 0, 0}, {75, 79, 85, 0, 0, 0},
+            {75, 85, 0, 0, 0, 0}, {75, 85, 65, 0, 0, 0},
+            {75, 85, 65, 73, 0, 0}, {75, 85, 65, 78, 0, 0},
+            {75, 85, 65, 78, 71, 0}, {75, 85, 73, 0, 0, 0},
+            {75, 85, 78, 0, 0, 0}, {75, 85, 79, 0, 0, 0},
+            {76, 65, 0, 0, 0, 0}, {76, 65, 73, 0, 0, 0},
+            {76, 65, 78, 0, 0, 0}, {76, 65, 78, 71, 0, 0},
+            {76, 65, 79, 0, 0, 0}, {76, 69, 0, 0, 0, 0},
+            {76, 69, 73, 0, 0, 0}, {76, 69, 78, 71, 0, 0},
+            {76, 73, 0, 0, 0, 0}, {76, 73, 65, 0, 0, 0},
+            {76, 73, 65, 78, 0, 0}, {76, 73, 65, 78, 71, 0},
+            {76, 73, 65, 79, 0, 0}, {76, 73, 69, 0, 0, 0},
+            {76, 73, 78, 0, 0, 0}, {76, 73, 78, 71, 0, 0},
+            {76, 73, 85, 0, 0, 0}, {76, 79, 0, 0, 0, 0},
+            {76, 79, 78, 71, 0, 0}, {76, 79, 85, 0, 0, 0},
+            {76, 85, 0, 0, 0, 0}, {76, 85, 65, 78, 0, 0},
+            {76, 85, 69, 0, 0, 0}, {76, 85, 78, 0, 0, 0},
+            {76, 85, 79, 0, 0, 0}, {77, 0, 0, 0, 0, 0},
+            {77, 65, 0, 0, 0, 0}, {77, 65, 73, 0, 0, 0},
+            {77, 65, 78, 0, 0, 0}, {77, 65, 78, 71, 0, 0},
+            {77, 65, 79, 0, 0, 0}, {77, 69, 0, 0, 0, 0},
+            {77, 69, 73, 0, 0, 0}, {77, 69, 78, 0, 0, 0},
+            {77, 69, 78, 71, 0, 0}, {77, 73, 0, 0, 0, 0},
+            {77, 73, 65, 78, 0, 0}, {77, 73, 65, 79, 0, 0},
+            {77, 73, 69, 0, 0, 0}, {77, 73, 78, 0, 0, 0},
+            {77, 73, 78, 71, 0, 0}, {77, 73, 85, 0, 0, 0},
+            {77, 79, 0, 0, 0, 0}, {77, 79, 85, 0, 0, 0},
+            {77, 85, 0, 0, 0, 0}, {78, 0, 0, 0, 0, 0},
+            {78, 65, 0, 0, 0, 0}, {78, 65, 73, 0, 0, 0},
+            {78, 65, 78, 0, 0, 0}, {78, 65, 78, 71, 0, 0},
+            {78, 65, 79, 0, 0, 0}, {78, 69, 0, 0, 0, 0},
+            {78, 69, 73, 0, 0, 0}, {78, 69, 78, 0, 0, 0},
+            {78, 69, 78, 71, 0, 0}, {78, 73, 0, 0, 0, 0},
+            {78, 73, 65, 78, 0, 0}, {78, 73, 65, 78, 71, 0},
+            {78, 73, 65, 79, 0, 0}, {78, 73, 69, 0, 0, 0},
+            {78, 73, 78, 0, 0, 0}, {78, 73, 78, 71, 0, 0},
+            {78, 73, 85, 0, 0, 0}, {78, 79, 78, 71, 0, 0},
+            {78, 79, 85, 0, 0, 0}, {78, 85, 0, 0, 0, 0},
+            {78, 85, 65, 78, 0, 0}, {78, 85, 69, 0, 0, 0},
+            {78, 85, 78, 0, 0, 0}, {78, 85, 79, 0, 0, 0},
+            {79, 0, 0, 0, 0, 0}, {79, 85, 0, 0, 0, 0},
+            {80, 65, 0, 0, 0, 0}, {80, 65, 73, 0, 0, 0},
+            {80, 65, 78, 0, 0, 0}, {80, 65, 78, 71, 0, 0},
+            {80, 65, 79, 0, 0, 0}, {80, 69, 73, 0, 0, 0},
+            {80, 69, 78, 0, 0, 0}, {80, 69, 78, 71, 0, 0},
+            {80, 73, 0, 0, 0, 0}, {80, 73, 65, 78, 0, 0},
+            {80, 73, 65, 79, 0, 0}, {80, 73, 69, 0, 0, 0},
+            {80, 73, 78, 0, 0, 0}, {80, 73, 78, 71, 0, 0},
+            {80, 79, 0, 0, 0, 0}, {80, 79, 85, 0, 0, 0},
+            {80, 85, 0, 0, 0, 0}, {81, 73, 0, 0, 0, 0},
+            {81, 73, 65, 0, 0, 0}, {81, 73, 65, 78, 0, 0},
+            {81, 73, 65, 78, 71, 0}, {81, 73, 65, 79, 0, 0},
+            {81, 73, 69, 0, 0, 0}, {81, 73, 78, 0, 0, 0},
+            {81, 73, 78, 71, 0, 0}, {81, 73, 79, 78, 71, 0},
+            {81, 73, 85, 0, 0, 0}, {81, 85, 0, 0, 0, 0},
+            {81, 85, 65, 78, 0, 0}, {81, 85, 69, 0, 0, 0},
+            {81, 85, 78, 0, 0, 0}, {82, 65, 78, 0, 0, 0},
+            {82, 65, 78, 71, 0, 0}, {82, 65, 79, 0, 0, 0},
+            {82, 69, 0, 0, 0, 0}, {82, 69, 78, 0, 0, 0},
+            {82, 69, 78, 71, 0, 0}, {82, 73, 0, 0, 0, 0},
+            {82, 79, 78, 71, 0, 0}, {82, 79, 85, 0, 0, 0},
+            {82, 85, 0, 0, 0, 0}, {82, 85, 65, 0, 0, 0},
+            {82, 85, 65, 78, 0, 0}, {82, 85, 73, 0, 0, 0},
+            {82, 85, 78, 0, 0, 0}, {82, 85, 79, 0, 0, 0},
+            {83, 65, 0, 0, 0, 0}, {83, 65, 73, 0, 0, 0},
+            {83, 65, 78, 0, 0, 0}, {83, 65, 78, 71, 0, 0},
+            {83, 65, 79, 0, 0, 0}, {83, 69, 0, 0, 0, 0},
+            {83, 69, 78, 0, 0, 0}, {83, 69, 78, 71, 0, 0},
+            {83, 72, 65, 0, 0, 0}, {83, 72, 65, 73, 0, 0},
+            {83, 72, 65, 78, 0, 0}, {83, 72, 65, 78, 71, 0},
+            {83, 72, 65, 79, 0, 0}, {83, 72, 69, 0, 0, 0},
+            {83, 72, 69, 78, 0, 0}, {88, 73, 78, 0, 0, 0},
+            {83, 72, 69, 78, 0, 0}, {83, 72, 69, 78, 71, 0},
+            {83, 72, 73, 0, 0, 0}, {83, 72, 79, 85, 0, 0},
+            {83, 72, 85, 0, 0, 0}, {83, 72, 85, 65, 0, 0},
+            {83, 72, 85, 65, 73, 0}, {83, 72, 85, 65, 78, 0},
+            {83, 72, 85, 65, 78, 71}, {83, 72, 85, 73, 0, 0},
+            {83, 72, 85, 78, 0, 0}, {83, 72, 85, 79, 0, 0},
+            {83, 73, 0, 0, 0, 0}, {83, 79, 78, 71, 0, 0},
+            {83, 79, 85, 0, 0, 0}, {83, 85, 0, 0, 0, 0},
+            {83, 85, 65, 78, 0, 0}, {83, 85, 73, 0, 0, 0},
+            {83, 85, 78, 0, 0, 0}, {83, 85, 79, 0, 0, 0},
+            {84, 65, 0, 0, 0, 0}, {84, 65, 73, 0, 0, 0},
+            {84, 65, 78, 0, 0, 0}, {84, 65, 78, 71, 0, 0},
+            {84, 65, 79, 0, 0, 0}, {84, 69, 0, 0, 0, 0},
+            {84, 69, 78, 71, 0, 0}, {84, 73, 0, 0, 0, 0},
+            {84, 73, 65, 78, 0, 0}, {84, 73, 65, 79, 0, 0},
+            {84, 73, 69, 0, 0, 0}, {84, 73, 78, 71, 0, 0},
+            {84, 79, 78, 71, 0, 0}, {84, 79, 85, 0, 0, 0},
+            {84, 85, 0, 0, 0, 0}, {84, 85, 65, 78, 0, 0},
+            {84, 85, 73, 0, 0, 0}, {84, 85, 78, 0, 0, 0},
+            {84, 85, 79, 0, 0, 0}, {87, 65, 0, 0, 0, 0},
+            {87, 65, 73, 0, 0, 0}, {87, 65, 78, 0, 0, 0},
+            {87, 65, 78, 71, 0, 0}, {87, 69, 73, 0, 0, 0},
+            {87, 69, 78, 0, 0, 0}, {87, 69, 78, 71, 0, 0},
+            {87, 79, 0, 0, 0, 0}, {87, 85, 0, 0, 0, 0},
+            {88, 73, 0, 0, 0, 0}, {88, 73, 65, 0, 0, 0},
+            {88, 73, 65, 78, 0, 0}, {88, 73, 65, 78, 71, 0},
+            {88, 73, 65, 79, 0, 0}, {88, 73, 69, 0, 0, 0},
+            {88, 73, 78, 0, 0, 0}, {88, 73, 78, 71, 0, 0},
+            {88, 73, 79, 78, 71, 0}, {88, 73, 85, 0, 0, 0},
+            {88, 85, 0, 0, 0, 0}, {88, 85, 65, 78, 0, 0},
+            {88, 85, 69, 0, 0, 0}, {88, 85, 78, 0, 0, 0},
+            {89, 65, 0, 0, 0, 0}, {89, 65, 78, 0, 0, 0},
+            {89, 65, 78, 71, 0, 0}, {89, 65, 79, 0, 0, 0},
+            {89, 69, 0, 0, 0, 0}, {89, 73, 0, 0, 0, 0},
+            {89, 73, 78, 0, 0, 0}, {89, 73, 78, 71, 0, 0},
+            {89, 79, 0, 0, 0, 0}, {89, 79, 78, 71, 0, 0},
+            {89, 79, 85, 0, 0, 0}, {89, 85, 0, 0, 0, 0},
+            {89, 85, 65, 78, 0, 0}, {89, 85, 69, 0, 0, 0},
+            {89, 85, 78, 0, 0, 0}, {74, 85, 78, 0, 0, 0},
+            {89, 85, 78, 0, 0, 0}, {90, 65, 0, 0, 0, 0},
+            {90, 65, 73, 0, 0, 0}, {90, 65, 78, 0, 0, 0},
+            {90, 65, 78, 71, 0, 0}, {90, 65, 79, 0, 0, 0},
+            {90, 69, 0, 0, 0, 0}, {90, 69, 73, 0, 0, 0},
+            {90, 69, 78, 0, 0, 0}, {90, 69, 78, 71, 0, 0},
+            {90, 72, 65, 0, 0, 0}, {90, 72, 65, 73, 0, 0},
+            {90, 72, 65, 78, 0, 0}, {90, 72, 65, 78, 71, 0},
+            {67, 72, 65, 78, 71, 0}, {90, 72, 65, 78, 71, 0},
+            {90, 72, 65, 79, 0, 0}, {90, 72, 69, 0, 0, 0},
+            {90, 72, 69, 78, 0, 0}, {90, 72, 69, 78, 71, 0},
+            {90, 72, 73, 0, 0, 0}, {83, 72, 73, 0, 0, 0},
+            {90, 72, 73, 0, 0, 0}, {90, 72, 79, 78, 71, 0},
+            {90, 72, 79, 85, 0, 0}, {90, 72, 85, 0, 0, 0},
+            {90, 72, 85, 65, 0, 0}, {90, 72, 85, 65, 73, 0},
+            {90, 72, 85, 65, 78, 0}, {90, 72, 85, 65, 78, 71},
+            {90, 72, 85, 73, 0, 0}, {90, 72, 85, 78, 0, 0},
+            {90, 72, 85, 79, 0, 0}, {90, 73, 0, 0, 0, 0},
+            {90, 79, 78, 71, 0, 0}, {90, 79, 85, 0, 0, 0},
+            {90, 85, 0, 0, 0, 0}, {90, 85, 65, 78, 0, 0},
+            {90, 85, 73, 0, 0, 0}, {90, 85, 78, 0, 0, 0},
+            {90, 85, 79, 0, 0, 0}, {0, 0, 0, 0, 0, 0},
+            {83, 72, 65, 78, 0, 0}, {0, 0, 0, 0, 0, 0},};
+
+    /**
+     * First and last Chinese character with known Pinyin according to zh collation
+     */
+    private static final String FIRST_PINYIN_UNIHAN = "\u963F";
+    private static final String LAST_PINYIN_UNIHAN = "\u9FFF";
+
+    private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA);
+
+    private static HanziToPinyin sInstance;
+    private final boolean mHasChinaCollator;
+
+    public static class Token {
+        /**
+         * Separator between target string for each source char
+         */
+        public static final String SEPARATOR = " ";
+
+        public static final int LATIN = 1;
+        public static final int PINYIN = 2;
+        public static final int UNKNOWN = 3;
+
+        public Token() {
+        }
+
+        public Token(int type, String source, String target) {
+            this.type = type;
+            this.source = source;
+            this.target = target;
+        }
+
+        /**
+         * Type of this token, ASCII, PINYIN or UNKNOWN.
+         */
+        public int type;
+        /**
+         * Original string before translation.
+         */
+        public String source;
+        /**
+         * Translated string of source. For Han, target is corresponding Pinyin. Otherwise target is
+         * original string in source.
+         */
+        public String target;
+    }
+
+    protected HanziToPinyin(boolean hasChinaCollator) {
+        mHasChinaCollator = hasChinaCollator;
+    }
+
+    public static HanziToPinyin getInstance() {
+        synchronized (HanziToPinyin.class) {
+            if (sInstance != null) {
+                return sInstance;
+            }
+            // Check if zh_CN collation data is available
+            final Locale locale[] = Collator.getAvailableLocales();
+            for (int i = 0; i < locale.length; i++) {
+                if (locale[i].equals(Locale.CHINA) || locale[i].getLanguage().contains("zh")) {
+                    // Do self validation just once.
+                    if (DEBUG) {
+                        Log.d(TAG, "Self validation. Result: " + doSelfValidation());
+                    }
+                    sInstance = new HanziToPinyin(true);
+                    return sInstance;
+                }
+            }
+            if (sInstance == null){//这个判断是用于处理国产ROM的兼容性问题
+                if (Locale.CHINA.equals(Locale.getDefault())){
+                    sInstance = new HanziToPinyin(true);
+                    return sInstance;
+                }
+            }
+            Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled");
+            sInstance = new HanziToPinyin(false);
+            return sInstance;
+        }
+    }
+
+    /**
+     * Validate if our internal table has some wrong value.
+     *
+     * @return true when the table looks correct.
+     */
+    private static boolean doSelfValidation() {
+        char lastChar = UNIHANS[0];
+        String lastString = Character.toString(lastChar);
+        for (char c : UNIHANS) {
+            if (lastChar == c) {
+                continue;
+            }
+            final String curString = Character.toString(c);
+            int cmp = COLLATOR.compare(lastString, curString);
+            if (cmp >= 0) {
+                Log.e(TAG, "Internal error in Unihan table. " + "The last string \"" + lastString
+                        + "\" is greater than current string \"" + curString + "\".");
+                return false;
+            }
+            lastString = curString;
+        }
+        return true;
+    }
+
+    private Token getToken(char character) {
+        Token token = new Token();
+        final String letter = Character.toString(character);
+        token.source = letter;
+        int offset = -1;
+        int cmp;
+        if (character < 256) {
+            token.type = Token.LATIN;
+            token.target = letter;
+            return token;
+        } else {
+            cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN);
+            if (cmp < 0) {
+                token.type = Token.UNKNOWN;
+                token.target = letter;
+                return token;
+            } else if (cmp == 0) {
+                token.type = Token.PINYIN;
+                offset = 0;
+            } else {
+                cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN);
+                if (cmp > 0) {
+                    token.type = Token.UNKNOWN;
+                    token.target = letter;
+                    return token;
+                } else if (cmp == 0) {
+                    token.type = Token.PINYIN;
+                    offset = UNIHANS.length - 1;
+                }
+            }
+        }
+
+        token.type = Token.PINYIN;
+        if (offset < 0) {
+            int begin = 0;
+            int end = UNIHANS.length - 1;
+            while (begin <= end) {
+                offset = (begin + end) / 2;
+                final String unihan = Character.toString(UNIHANS[offset]);
+                cmp = COLLATOR.compare(letter, unihan);
+                if (cmp == 0) {
+                    break;
+                } else if (cmp > 0) {
+                    begin = offset + 1;
+                } else {
+                    end = offset - 1;
+                }
+            }
+        }
+        if (cmp < 0) {
+            offset--;
+        }
+        StringBuilder pinyin = new StringBuilder();
+        for (int j = 0; j < PINYINS[offset].length && PINYINS[offset][j] != 0; j++) {
+            pinyin.append((char) PINYINS[offset][j]);
+        }
+        token.target = pinyin.toString();
+        if (TextUtils.isEmpty(token.target)) {
+            token.type = Token.UNKNOWN;
+            token.target = token.source;
+        }
+        return token;
+    }
+
+    /**
+     * Convert the input to a array of tokens. The sequence of ASCII or Unknown characters without
+     * space will be put into a Token, One Hanzi character which has pinyin will be treated as a
+     * Token. If these is no China collator, the empty token array is returned.
+     */
+    public ArrayList<Token> get(final String input) {
+        ArrayList<Token> tokens = new ArrayList<Token>();
+        if (!mHasChinaCollator || TextUtils.isEmpty(input)) {
+            // return empty tokens.
+            return tokens;
+        }
+        final int inputLength = input.length();
+        final StringBuilder sb = new StringBuilder();
+        int tokenType = Token.LATIN;
+        // Go through the input, create a new token when
+        // a. Token type changed
+        // b. Get the Pinyin of current charater.
+        // c. current character is space.
+        for (int i = 0; i < inputLength; i++) {
+            final char character = input.charAt(i);
+            if (character == ' ') {
+                if (sb.length() > 0) {
+                    addToken(sb, tokens, tokenType);
+                }
+            } else if (character < 256) {
+                if (tokenType != Token.LATIN && sb.length() > 0) {
+                    addToken(sb, tokens, tokenType);
+                }
+                tokenType = Token.LATIN;
+                sb.append(character);
+            } else {
+                Token t = getToken(character);
+                if (t.type == Token.PINYIN) {
+                    if (sb.length() > 0) {
+                        addToken(sb, tokens, tokenType);
+                    }
+                    tokens.add(t);
+                    tokenType = Token.PINYIN;
+                } else {
+                    if (tokenType != t.type && sb.length() > 0) {
+                        addToken(sb, tokens, tokenType);
+                    }
+                    tokenType = t.type;
+                    sb.append(character);
+                }
+            }
+        }
+        if (sb.length() > 0) {
+            addToken(sb, tokens, tokenType);
+        }
+        return tokens;
+    }
+
+    private void addToken(
+            final StringBuilder sb, final ArrayList<Token> tokens, final int tokenType) {
+        String str = sb.toString();
+        tokens.add(new Token(tokenType, str, str));
+        sb.setLength(0);
+    }
+
+    public String toPinyinString(String string) {
+        if (string == null) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        ArrayList<Token> tokens = get(string);
+        for (Token token : tokens) {
+            sb.append(token.target);
+        }
+        return sb.toString().toLowerCase();
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/utils/Misc.java b/VirtualApp/app/src/main/java/io/virtualapp/utils/Misc.java
new file mode 100644
index 000000000..a9cd95810
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/utils/Misc.java
@@ -0,0 +1,59 @@
+package io.virtualapp.utils;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.v7.app.AlertDialog;
+import android.widget.Toast;
+
+import io.virtualapp.R;
+import moe.feng.alipay.zerosdk.AlipayZeroSdk;
+
+/**
+ * @author weishu
+ * @date 2018/10/29.
+ */
+public class Misc {
+    public static void showDonate(Activity context) {
+        final String alipay = context.getResources().getString(R.string.donate_alipay);
+        final String[] items = {alipay, "PayPal", "Bitcoin"};
+
+        AlertDialog chooseDialog = new AlertDialog.Builder(context, R.style.Theme_AppCompat_DayNight_Dialog_Alert)
+                .setTitle(R.string.donate_choose_title)
+                .setItems(items, (dialog1, which1) -> {
+                    dialog1.dismiss();
+                    if (which1 == 0) {
+                        if (!AlipayZeroSdk.hasInstalledAlipayClient(context)) {
+                            Toast.makeText(context, R.string.prompt_alipay_not_found, Toast.LENGTH_SHORT).show();
+                            return;
+                        }
+                        AlipayZeroSdk.startAlipayClient(context, "FKX016770URBZGZSR37U37");
+                    } else if (which1 == 1) {
+                        try {
+                            Intent t = new Intent(Intent.ACTION_VIEW);
+                            t.setData(Uri.parse("https://paypal.me/virtualxposed"));
+                            context.startActivity(t);
+                        } catch (Throwable ignored) {
+                            ignored.printStackTrace();
+                        }
+                    } else if (which1 == 2) {
+                        final String address = "39Wst8oL74pRP2vKPkPihH6RFQF4hWoBqU";
+
+                        try {
+                            ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+                            if (clipboardManager != null) {
+                                clipboardManager.setPrimaryClip(ClipData.newPlainText(null, address));
+                            }
+                            Toast.makeText(context, context.getResources().getString(R.string.donate_bitconins_tips), Toast.LENGTH_SHORT).show();
+                        } catch (Throwable ignored) {
+                            ignored.printStackTrace();
+                        }
+                    }
+                })
+                .create();
+        chooseDialog.show();
+    }
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/vs/VSManagerActivity.java b/VirtualApp/app/src/main/java/io/virtualapp/vs/VSManagerActivity.java
deleted file mode 100644
index 67114a80e..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/vs/VSManagerActivity.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package io.virtualapp.vs;
-
-import io.virtualapp.abs.ui.VActivity;
-
-/**
- * @author Lody
- *
- *
- *
- */
-public class VSManagerActivity extends VActivity {
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/BallGridBeatIndicator.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/BallGridBeatIndicator.java
deleted file mode 100644
index 46b820ac8..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/BallGridBeatIndicator.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.animation.ValueAnimator;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-
-import java.util.ArrayList;
-
-public class BallGridBeatIndicator extends Indicator {
-
-    private static final int ALPHA = 255;
-
-    private static final int[] ALPHAS = new int[]{ALPHA,
-            ALPHA,
-            ALPHA,
-            ALPHA,
-            ALPHA,
-            ALPHA,
-            ALPHA,
-            ALPHA,
-            ALPHA};
-
-    @Override
-    public void draw(Canvas canvas, Paint paint) {
-        float circleSpacing = 4;
-        float radius = (getWidth() - circleSpacing * 4) / 6;
-        float x = getWidth() / 2 - (radius * 2 + circleSpacing);
-        float y = getWidth() / 2 - (radius * 2 + circleSpacing);
-
-        for (int i = 0; i < 3; i++) {
-            for (int j = 0; j < 3; j++) {
-                canvas.save();
-                float translateX = x + (radius * 2) * j + circleSpacing * j;
-                float translateY = y + (radius * 2) * i + circleSpacing * i;
-                canvas.translate(translateX, translateY);
-                paint.setAlpha(ALPHAS[3 * i + j]);
-                canvas.drawCircle(0, 0, radius, paint);
-                canvas.restore();
-            }
-        }
-    }
-
-    @Override
-    public ArrayList<ValueAnimator> onCreateAnimators() {
-        ArrayList<ValueAnimator> animators = new ArrayList<>();
-
-        int[] durations = {960, 930, 1190, 1130, 1340, 940, 1200, 820, 1190};
-        int[] delays = {360, 400, 680, 410, 710, -150, -120, 10, 320};
-
-        for (int i = 0; i < 9; i++) {
-            final int index = i;
-            ValueAnimator alphaAnim = ValueAnimator.ofInt(255, 168, 255);
-            alphaAnim.setDuration(durations[i]);
-            alphaAnim.setRepeatCount(-1);
-            alphaAnim.setStartDelay(delays[i]);
-            addUpdateListener(alphaAnim, animation -> {
-                ALPHAS[index] = (int) animation.getAnimatedValue();
-                postInvalidate();
-            });
-            animators.add(alphaAnim);
-        }
-        return animators;
-    }
-
-
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/BallPulseIndicator.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/BallPulseIndicator.java
deleted file mode 100644
index 1d255a451..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/BallPulseIndicator.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.animation.ValueAnimator;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-
-import java.util.ArrayList;
-
-public class BallPulseIndicator extends Indicator {
-
-    public static final float SCALE = 1.0f;
-
-    //scale x ,y
-    private float[] scaleFloats = new float[]{SCALE,
-            SCALE,
-            SCALE};
-
-
-    @Override
-    public void draw(Canvas canvas, Paint paint) {
-        float circleSpacing = 4;
-        float radius = (Math.min(getWidth(), getHeight()) - circleSpacing * 2) / 6;
-        float x = getWidth() / 2 - (radius * 2 + circleSpacing);
-        float y = getHeight() / 2;
-        for (int i = 0; i < 3; i++) {
-            canvas.save();
-            float translateX = x + (radius * 2) * i + circleSpacing * i;
-            canvas.translate(translateX, y);
-            canvas.scale(scaleFloats[i], scaleFloats[i]);
-            canvas.drawCircle(0, 0, radius, paint);
-            canvas.restore();
-        }
-    }
-
-    @Override
-    public ArrayList<ValueAnimator> onCreateAnimators() {
-        ArrayList<ValueAnimator> animators = new ArrayList<>();
-        int[] delays = new int[]{120, 240, 360};
-        for (int i = 0; i < 3; i++) {
-            final int index = i;
-
-            ValueAnimator scaleAnim = ValueAnimator.ofFloat(1, 0.3f, 1);
-
-            scaleAnim.setDuration(750);
-            scaleAnim.setRepeatCount(-1);
-            scaleAnim.setStartDelay(delays[i]);
-
-            addUpdateListener(scaleAnim, animation -> {
-                scaleFloats[index] = (float) animation.getAnimatedValue();
-                postInvalidate();
-            });
-            animators.add(scaleAnim);
-        }
-        return animators;
-    }
-
-
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/BaseView.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/BaseView.java
index 3163ef87a..995154900 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/BaseView.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/widgets/BaseView.java
@@ -30,7 +30,7 @@ public BaseView(Context context, AttributeSet attrs, int defStyleAttr) {
 
     public void startAnim() {
         stopAnim();
-        startViewAnim(0f, 1f, 500);
+        startViewAnim(0f, 1f, 1250);
     }
 
     public void startAnim(int time) {
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/CircularAnim.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/CircularAnim.java
deleted file mode 100644
index 8b84b49f1..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/CircularAnim.java
+++ /dev/null
@@ -1,333 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-
-public class CircularAnim {
-
-    public static final long PERFECT_MILLS = 618;
-    public static final int MINI_RADIUS = 0;
-
-    private static Long sPerfectMills;
-    private static Long sFullActivityPerfectMills;
-    private static Integer sColorOrImageRes;
-
-    private static long getPerfectMills() {
-        if (sPerfectMills != null)
-            return sPerfectMills;
-        else
-            return PERFECT_MILLS;
-    }
-
-    private static long getFullActivityMills() {
-        if (sFullActivityPerfectMills != null)
-            return sFullActivityPerfectMills;
-        else
-            return PERFECT_MILLS;
-    }
-
-    private static int getColorOrImageRes() {
-        if (sColorOrImageRes != null)
-            return sColorOrImageRes;
-        else
-            return android.R.color.white;
-    }
-
-    public static VisibleBuilder show(View animView) {
-        return new VisibleBuilder(animView, true);
-    }
-
-    public static VisibleBuilder hide(View animView) {
-        return new VisibleBuilder(animView, false);
-    }
-
-    public static FullActivityBuilder fullActivity(Activity activity, View triggerView) {
-        return new FullActivityBuilder(activity, triggerView);
-    }
-
-    public static void init(long perfectMills, long fullActivityPerfectMills, int colorOrImageRes) {
-        sPerfectMills = perfectMills;
-        sFullActivityPerfectMills = fullActivityPerfectMills;
-        sColorOrImageRes = colorOrImageRes;
-    }
-
-    public interface OnAnimationEndListener {
-        void onAnimationEnd();
-    }
-
-    @SuppressLint("NewApi")
-    public static class VisibleBuilder {
-
-        private View mAnimView, mTriggerView;
-
-        private Float mStartRadius, mEndRadius;
-
-        private long mDurationMills = getPerfectMills();
-
-        private boolean isShow;
-
-        private OnAnimationEndListener mOnAnimationEndListener;
-
-        public VisibleBuilder(View animView, boolean isShow) {
-            mAnimView = animView;
-            this.isShow = isShow;
-
-            if (isShow) {
-                mStartRadius = MINI_RADIUS + 0F;
-            } else {
-                mEndRadius = MINI_RADIUS + 0F;
-            }
-        }
-
-        public VisibleBuilder triggerView(View triggerView) {
-            mTriggerView = triggerView;
-            return this;
-        }
-
-        public VisibleBuilder startRadius(float startRadius) {
-            mStartRadius = startRadius;
-            return this;
-        }
-
-        public VisibleBuilder endRadius(float endRadius) {
-            mEndRadius = endRadius;
-            return this;
-        }
-
-        public VisibleBuilder duration(long durationMills) {
-            mDurationMills = durationMills;
-            return this;
-        }
-
-        @Deprecated //You can use method - go(OnAnimationEndListener onAnimationEndListener).
-        public VisibleBuilder onAnimationEndListener(OnAnimationEndListener onAnimationEndListener) {
-            mOnAnimationEndListener = onAnimationEndListener;
-            return this;
-        }
-
-        public void go() {
-            go(null);
-        }
-
-        public void go(OnAnimationEndListener onAnimationEndListener) {
-            mOnAnimationEndListener = onAnimationEndListener;
-
-            // 版本判断
-            if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
-                doOnEnd();
-                return;
-            }
-
-            int rippleCX, rippleCY, maxRadius;
-            if (mTriggerView != null) {
-                int[] tvLocation = new int[2];
-                mTriggerView.getLocationInWindow(tvLocation);
-                final int tvCX = tvLocation[0] + mTriggerView.getWidth() / 2;
-                final int tvCY = tvLocation[1] + mTriggerView.getHeight() / 2;
-
-                int[] avLocation = new int[2];
-                mAnimView.getLocationInWindow(avLocation);
-                final int avLX = avLocation[0];
-                final int avTY = avLocation[1];
-
-                int triggerX = Math.max(avLX, tvCX);
-                triggerX = Math.min(triggerX, avLX + mAnimView.getWidth());
-
-                int triggerY = Math.max(avTY, tvCY);
-                triggerY = Math.min(triggerY, avTY + mAnimView.getHeight());
-
-                // 以上全为绝对坐标
-
-                int avW = mAnimView.getWidth();
-                int avH = mAnimView.getHeight();
-
-                rippleCX = triggerX - avLX;
-                rippleCY = triggerY - avTY;
-
-                // 计算水波中心点至 @mAnimView 边界的最大距离
-                int maxW = Math.max(rippleCX, avW - rippleCX);
-                int maxH = Math.max(rippleCY, avH - rippleCY);
-                maxRadius = (int) Math.sqrt(maxW * maxW + maxH * maxH) + 1;
-            } else {
-                rippleCX = (mAnimView.getLeft() + mAnimView.getRight()) / 2;
-                rippleCY = (mAnimView.getTop() + mAnimView.getBottom()) / 2;
-
-                int w = mAnimView.getWidth();
-                int h = mAnimView.getHeight();
-
-                // 勾股定理 & 进一法
-                maxRadius = (int) Math.sqrt(w * w + h * h) + 1;
-            }
-
-            if (isShow && mEndRadius == null)
-                mEndRadius = maxRadius + 0F;
-            else if (!isShow && mStartRadius == null)
-                mStartRadius = maxRadius + 0F;
-
-            try {
-                Animator anim = ViewAnimationUtils.createCircularReveal(
-                        mAnimView, rippleCX, rippleCY, mStartRadius, mEndRadius);
-
-
-                mAnimView.setVisibility(View.VISIBLE);
-                anim.setDuration(mDurationMills);
-
-                anim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        super.onAnimationEnd(animation);
-                        doOnEnd();
-                    }
-                });
-
-                anim.start();
-            } catch (Exception e) {
-                e.printStackTrace();
-                doOnEnd();
-            }
-        }
-
-        private void doOnEnd() {
-            if (isShow)
-                mAnimView.setVisibility(View.VISIBLE);
-            else
-                mAnimView.setVisibility(View.INVISIBLE);
-
-            if (mOnAnimationEndListener != null)
-                mOnAnimationEndListener.onAnimationEnd();
-        }
-
-    }
-
-    @SuppressLint("NewApi")
-    public static class FullActivityBuilder {
-        private Activity mActivity;
-        private View mTriggerView;
-        private float mStartRadius = MINI_RADIUS;
-        private int mColorOrImageRes = getColorOrImageRes();
-        private Long mDurationMills;
-        private OnAnimationEndListener mOnAnimationEndListener;
-        private int mEnterAnim = android.R.anim.fade_in, mExitAnim = android.R.anim.fade_out;
-
-        public FullActivityBuilder(Activity activity, View triggerView) {
-            mActivity = activity;
-            mTriggerView = triggerView;
-        }
-
-        public FullActivityBuilder startRadius(float startRadius) {
-            mStartRadius = startRadius;
-            return this;
-        }
-
-        public FullActivityBuilder colorOrImageRes(int colorOrImageRes) {
-            mColorOrImageRes = colorOrImageRes;
-            return this;
-        }
-
-        public FullActivityBuilder duration(long durationMills) {
-            mDurationMills = durationMills;
-            return this;
-        }
-
-        public FullActivityBuilder overridePendingTransition(int enterAnim, int exitAnim) {
-            mEnterAnim = enterAnim;
-            mExitAnim = exitAnim;
-            return this;
-        }
-
-        public void go(OnAnimationEndListener onAnimationEndListener) {
-            mOnAnimationEndListener = onAnimationEndListener;
-
-            // 版本判断,小于5.0则无动画.
-            if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
-                doOnEnd();
-                return;
-            }
-
-            int[] location = new int[2];
-            mTriggerView.getLocationInWindow(location);
-            final int cx = location[0] + mTriggerView.getWidth() / 2;
-            final int cy = location[1] + mTriggerView.getHeight() / 2;
-            final ImageView view = new ImageView(mActivity);
-            view.setScaleType(ImageView.ScaleType.CENTER_CROP);
-            view.setImageResource(mColorOrImageRes);
-            final ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView();
-            int w = decorView.getWidth();
-            int h = decorView.getHeight();
-            decorView.addView(view, w, h);
-
-            int maxW = Math.max(cx, w - cx);
-            int maxH = Math.max(cy, h - cy);
-            final int finalRadius = (int) Math.sqrt(maxW * maxW + maxH * maxH) + 1;
-
-            try {
-                Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, mStartRadius, finalRadius);
-
-                int maxRadius = (int) Math.sqrt(w * w + h * h) + 1;
-                if (mDurationMills == null) {
-                    double rate = 1d * finalRadius / maxRadius;
-                    mDurationMills = (long) (getFullActivityMills() * Math.sqrt(rate));
-                }
-                final long finalDuration = mDurationMills;
-                anim.setDuration((long) (finalDuration * 0.9));
-                anim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        super.onAnimationEnd(animation);
-
-                        doOnEnd();
-
-                        mActivity.overridePendingTransition(mEnterAnim, mExitAnim);
-
-                        mTriggerView.postDelayed(new Runnable() {
-                            @Override
-                            public void run() {
-                                if (mActivity.isFinishing()) return;
-                                try {
-                                    Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy,
-                                            finalRadius, mStartRadius);
-                                    anim.setDuration(finalDuration);
-                                    anim.addListener(new AnimatorListenerAdapter() {
-                                        @Override
-                                        public void onAnimationEnd(Animator animation) {
-                                            super.onAnimationEnd(animation);
-                                            try {
-                                                decorView.removeView(view);
-                                            } catch (Exception e) {
-                                                e.printStackTrace();
-                                            }
-                                        }
-                                    });
-                                    anim.start();
-                                } catch (Exception e) {
-                                    e.printStackTrace();
-                                    try {
-                                        decorView.removeView(view);
-                                    } catch (Exception e1) {
-                                        e1.printStackTrace();
-                                    }
-                                }
-                            }
-                        }, 1000);
-
-                    }
-                });
-                anim.start();
-            } catch (Exception e) {
-                e.printStackTrace();
-                doOnEnd();
-            }
-        }
-
-        private void doOnEnd() {
-            mOnAnimationEndListener.onAnimationEnd();
-        }
-    }
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/EatBeansView.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/EatBeansView.java
index 7cf3a096a..5f7ad8490 100644
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/EatBeansView.java
+++ b/VirtualApp/app/src/main/java/io/virtualapp/widgets/EatBeansView.java
@@ -4,7 +4,6 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.RectF;
 import android.util.AttributeSet;
@@ -12,12 +11,12 @@
 
 public class EatBeansView extends BaseView {
 
-    int eatSpeed = 5;
-    private Paint mPaint, mPaintEye;
+    int eatSpeed = 8;
+    private Paint mPaint, mPaintEye, mPaintBeans;
     private float mWidth = 0f;
     private float mHigh = 0f;
     private float mPadding = 5f;
-    private float eatErWidth = 60f;
+    private float eatErWidth = 50f;
     private float eatErPositionX = 0f;
     private float beansWidth = 10f;
 
@@ -25,6 +24,7 @@ public class EatBeansView extends BaseView {
     private float mAngle = 34;
     private float eatErStartAngle = mAngle;
     private float eatErEndAngle = 360 - 2 * eatErStartAngle;
+    private RectF mRect = new RectF();
 
     public EatBeansView(Context context) {
         super(context);
@@ -52,8 +52,8 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
         float eatRightX = mPadding + eatErWidth + eatErPositionX;
-        RectF rectF = new RectF(mPadding + eatErPositionX, mHigh / 2 - eatErWidth / 2, eatRightX, mHigh / 2 + eatErWidth / 2);
-        canvas.drawArc(rectF, eatErStartAngle, eatErEndAngle
+        mRect.set(mPadding + eatErPositionX, mHigh / 2 - eatErWidth / 2, eatRightX, mHigh / 2 + eatErWidth / 2);
+        canvas.drawArc(mRect, eatErStartAngle, eatErEndAngle
                 , true, mPaint);
         canvas.drawCircle(mPadding + eatErPositionX + eatErWidth / 2,
                 mHigh / 2 - eatErWidth / 4,
@@ -65,7 +65,7 @@ protected void onDraw(Canvas canvas) {
             float x = beansCount * i + beansWidth / 2 + mPadding + eatErWidth;
             if (x > eatRightX) {
                 canvas.drawCircle(x,
-                        mHigh / 2, beansWidth / 2, mPaint);
+                        mHigh / 2, beansWidth / 2, mPaintBeans);
             }
         }
 
@@ -76,13 +76,17 @@ private void initPaint() {
         mPaint = new Paint();
         mPaint.setAntiAlias(true);
         mPaint.setStyle(Paint.Style.FILL);
-        mPaint.setColor(Color.WHITE);
+        mPaint.setColor(0xDDDDDDDD);
+
+        mPaintBeans = new Paint();
+        mPaintBeans.setAntiAlias(true);
+        mPaintBeans.setStyle(Paint.Style.FILL);
+        mPaintBeans.setColor(0xFFBBBBBB);
 
         mPaintEye = new Paint();
         mPaintEye.setAntiAlias(true);
         mPaintEye.setStyle(Paint.Style.FILL);
-        mPaintEye.setColor(Color.BLACK);
-
+        mPaintEye.setColor(0xFF888888);
     }
 
 
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/LauncherIconView.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/LauncherIconView.java
deleted file mode 100644
index 646f9d6c0..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/LauncherIconView.java
+++ /dev/null
@@ -1,458 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.RectF;
-import android.support.v7.widget.AppCompatImageView;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.animation.DecelerateInterpolator;
-
-import io.virtualapp.R;
-
-import static android.graphics.Canvas.ALL_SAVE_FLAG;
-
-public class LauncherIconView extends AppCompatImageView implements ShimmerViewBase {
-    private static final int SMOOTH_ANIM_THRESHOLD = 5;
-
-    private static final String TAG = "LauncherIconView";
-
-    private ShimmerViewHelper mShimmerViewHelper;
-    private Shimmer mShimmer;
-
-    private float mProgress;
-    private int mHeight;
-    private int mWidth;
-    private int mStrokeWidth;
-    private float mRadius;
-    private float mInterDelta;
-    private int mMaskColor;
-
-    private float mMaxMaskRadius;
-    private float mMaskAnimDelta;
-    private boolean mIsSquare;
-    private boolean mMaskAnimRunning;
-
-    private long mMediumAnimTime;
-
-    private Paint mShimmerPaint;
-    private Paint mPaint;
-    private RectF mProgressOval;
-    private ValueAnimator mInterAnim;
-    private ValueAnimator mProgressAnimator;
-
-    public LauncherIconView(Context context) {
-        super(context);
-        init(context, null);
-    }
-
-    public LauncherIconView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init(context, attrs);
-    }
-
-    public LauncherIconView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        init(context, attrs);
-    }
-
-    private void init(Context context, AttributeSet attrs) {
-        mMediumAnimTime = getContext().getResources().getInteger(android.R.integer.config_mediumAnimTime);
-
-        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProgressImageView);
-        try {
-            this.mProgress = a.getInteger(R.styleable.ProgressImageView_pi_progress, 0);
-            this.mStrokeWidth = a.getDimensionPixelOffset(R.styleable.ProgressImageView_pi_stroke, 8);
-            this.mRadius = a.getDimensionPixelOffset(R.styleable.ProgressImageView_pi_radius, 0);
-            this.mIsSquare = a.getBoolean(R.styleable.ProgressImageView_pi_force_square, false);
-            this.mMaskColor = a.getColor(R.styleable.ProgressImageView_pi_mask_color, Color.argb(180, 0, 0, 0));
-
-            this.mPaint = new Paint();
-            mPaint.setColor(mMaskColor);
-            mPaint.setAntiAlias(true);
-
-            this.mShimmerPaint = new Paint();
-            mShimmerPaint.setColor(Color.WHITE);
-        } finally {
-            a.recycle();
-        }
-        mShimmerViewHelper = new ShimmerViewHelper(this, mShimmerPaint, attrs);
-    }
-
-    private void initParams() {
-        if (mWidth == 0)
-            mWidth = getWidth();
-
-        if (mHeight == 0)
-            mHeight = getHeight();
-
-        if (mWidth != 0 && mHeight != 0) {
-            if (mRadius == 0)
-                mRadius = Math.min(mWidth, mHeight) / 4f;
-
-            if (mMaxMaskRadius == 0)
-                mMaxMaskRadius = (float) (0.5f * Math.sqrt(mWidth * mWidth + mHeight * mHeight));
-
-            if (mProgressOval == null)
-                mProgressOval = new RectF(
-                        mWidth / 2f - mRadius + mStrokeWidth,
-                        mHeight / 2f - mRadius + mStrokeWidth,
-                        mWidth / 2f + mRadius - mStrokeWidth,
-                        mHeight / 2f + mRadius - mStrokeWidth);
-        }
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mShimmerViewHelper != null) {
-            mShimmerViewHelper.onDraw();
-        }
-        super.onDraw(canvas);
-        int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, ALL_SAVE_FLAG);
-
-        initParams();
-
-        if (mProgress < 100) {
-            drawMask(canvas);
-
-            if (mProgress == 0)
-                updateInterAnim(canvas);
-            else
-                drawProgress(canvas);
-        }
-
-        if (mMaskAnimRunning)
-            updateMaskAnim(canvas);
-
-        canvas.restoreToCount(sc);
-    }
-
-    private void drawMask(Canvas canvas) {
-        canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
-    }
-
-    private void drawProgress(Canvas canvas) {
-        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
-        canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius, mPaint);
-        mPaint.setXfermode(null);
-
-        //start angle : -90 ~ 270;sweep Angle : 360 ~ 0;
-        canvas.drawArc(mProgressOval, -90 + mProgress * 3.6f, 360 - mProgress * 3.6f, true, mPaint);
-    }
-
-    private void updateInterAnim(Canvas canvas) {
-//        if (!mInterAnimRunning) mInterDelta = 0.f;
-
-        //outer circle
-        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
-        canvas.drawCircle(mWidth / 2.f, mHeight / 2.f, mRadius, mPaint);
-        mPaint.setXfermode(null);
-
-        //inner circle
-        canvas.drawCircle(mWidth / 2.f, mHeight / 2.f, mRadius - mInterDelta, mPaint);
-    }
-
-    private void updateMaskAnim(Canvas canvas) {
-        canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
-
-        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
-        canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius + mMaskAnimDelta, mPaint);//mRatio : 0 ~ mRatio * 1.5
-        mPaint.setXfermode(null);
-    }
-
-    private void startInterAnim(final int progress) {
-        if (mInterAnim != null)
-            mInterAnim.cancel();
-
-        mInterAnim = ValueAnimator.ofFloat(0.f, mStrokeWidth);
-        mInterAnim.setInterpolator(new DecelerateInterpolator());
-        mInterAnim.setDuration(getContext().getResources().getInteger(android.R.integer.config_shortAnimTime));
-        mInterAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                mInterDelta = (float) animation.getAnimatedValue();
-                invalidate();
-            }
-        });
-        mInterAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                super.onAnimationStart(animation);
-//                mInterAnimRunning = true;
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                super.onAnimationCancel(animation);
-//                mInterAnimRunning = false;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-//                mInterAnimRunning = false;
-
-                if (progress > 0)
-                    startProgressAnim(0, progress);
-            }
-        });
-        mInterAnim.start();
-    }
-
-    private void startProgressAnim(float from, float to) {
-        if (mProgressAnimator != null)
-            mProgressAnimator.cancel();
-
-        final boolean isReverse = from > to;
-
-        mProgressAnimator = ValueAnimator.ofFloat(from, to);
-        mProgressAnimator.setInterpolator(new DecelerateInterpolator());
-        mProgressAnimator.setDuration(mMediumAnimTime);
-        mProgressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                mProgress = (float) animation.getAnimatedValue();
-
-                if (0 < mProgress && mProgress < 100)
-                    invalidate();
-                else if (mProgress == 100 && !isReverse)
-                    startMaskAnim();
-            }
-        });
-        mProgressAnimator.start();
-    }
-
-    private void startMaskAnim() {
-        if (mProgressAnimator != null)
-            mProgressAnimator.cancel();
-
-        ValueAnimator animator = ValueAnimator.ofFloat(0.f, mMaxMaskRadius);
-        animator.setInterpolator(new DecelerateInterpolator());
-        animator.setDuration(mMediumAnimTime);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                mMaskAnimRunning = true;
-                mMaskAnimDelta = (float) animation.getAnimatedValue();
-                invalidate();
-            }
-        });
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                super.onAnimationStart(animation);
-                mMaskAnimRunning = true;
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                super.onAnimationCancel(animation);
-                mMaskAnimRunning = false;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                mMaskAnimRunning = false;
-            }
-        });
-        animator.start();
-    }
-
-    /**
-     * get the stroke width.
-     *
-     * @return the stroke width in pixel.
-     */
-    public int getStrokeWidth() {
-        return mStrokeWidth;
-    }
-
-    /**
-     * set the stroke width.default is 8dp.
-     *
-     * @param strokeWidth stroke width in pixel
-     */
-    public void setStrokeWidth(int strokeWidth) {
-        this.mStrokeWidth = strokeWidth;
-        this.mProgressOval = null;
-        invalidate();
-    }
-
-    /**
-     * get the radius of inner progress circle.
-     *
-     * @return the inner circle radius in pixel.
-     */
-    public float getRadius() {
-        return mRadius;
-    }
-
-    /**
-     * set the radius of the inner progress circle.
-     *
-     * @param radius radius in pixel
-     */
-    public void setRadius(float radius) {
-        this.mRadius = radius;
-        this.mProgressOval = null;
-        invalidate();
-    }
-
-    /**
-     * get the color for mask .
-     *
-     * @return the mask color
-     */
-    public int getMaskColor() {
-        return mMaskColor;
-    }
-
-    /**
-     * set the color for mask. Argb will looks better. Default is Color.argb(180,0,0,0)
-     *
-     * @param maskColor the color value.
-     */
-    public void setMaskColor(int maskColor) {
-        mMaskColor = maskColor;
-        mPaint.setColor(mMaskColor);
-        invalidate();
-    }
-
-    /**
-     * get current progress.
-     *
-     * @return current progress value.
-     */
-    public int getProgress() {
-        return (int) mProgress;
-    }
-
-    /**
-     * @param progress the progress ,range [0,100]
-     */
-    public void setProgress(int progress) {
-        setProgress(progress, true);
-    }
-
-    /**
-     * @param progress the progress in [0,100]
-     * @param animate  true to enable smooth animation when progress changed more than 5.
-     */
-    public void setProgress(int progress, boolean animate) {
-        progress = Math.min(Math.max(progress, 0), 100);
-
-        Log.d(TAG, "setProgress: p:" + progress + ",mp:" + mProgress);
-
-        if (Math.abs(progress - mProgress) > SMOOTH_ANIM_THRESHOLD && animate) {
-            if (mProgress == 0) {
-                startInterAnim(progress);
-            } else {
-                startProgressAnim(mProgress, progress);
-            }
-        } else if (progress == 100 && animate) {
-            mProgress = 100;
-            startMaskAnim();
-        } else {
-            mProgress = progress;
-
-            if (mProgress == 0.f)
-                mInterDelta = 0.f;
-
-            invalidate();
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        if (mIsSquare) {
-            int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
-            int size = measuredWidth == 0 ? MeasureSpec.getSize(heightMeasureSpec) : measuredWidth;
-            setMeasuredDimension(size, size);
-        }
-    }
-
-
-    @Override
-    public float getGradientX() {
-        return mShimmerViewHelper.getGradientX();
-    }
-
-    @Override
-    public void setGradientX(float gradientX) {
-        mShimmerViewHelper.setGradientX(gradientX);
-    }
-
-    @Override
-    public boolean isShimmering() {
-        return mShimmerViewHelper.isShimmering();
-    }
-
-    @Override
-    public void setShimmering(boolean isShimmering) {
-        mShimmerViewHelper.setShimmering(isShimmering);
-    }
-
-    @Override
-    public boolean isSetUp() {
-        return mShimmerViewHelper.isSetUp();
-    }
-
-    @Override
-    public void setAnimationSetupCallback(ShimmerViewHelper.AnimationSetupCallback callback) {
-        mShimmerViewHelper.setAnimationSetupCallback(callback);
-    }
-
-    @Override
-    public int getPrimaryColor() {
-        return mShimmerViewHelper.getPrimaryColor();
-    }
-
-    @Override
-    public void setPrimaryColor(int primaryColor) {
-        mShimmerViewHelper.setPrimaryColor(primaryColor);
-    }
-
-    @Override
-    public int getReflectionColor() {
-        return mShimmerViewHelper.getReflectionColor();
-    }
-
-    @Override
-    public void setReflectionColor(int reflectionColor) {
-        mShimmerViewHelper.setReflectionColor(reflectionColor);
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-        if (mShimmerViewHelper != null) {
-            mShimmerViewHelper.onSizeChanged();
-        }
-    }
-
-    public void stopShimmer() {
-        if (mShimmer != null && mShimmer.isAnimating()) {
-            mShimmer.cancel();
-            mShimmer = null;
-        }
-    }
-
-    public void startShimmer() {
-        stopShimmer();
-        mShimmer = new Shimmer();
-        mShimmer.setRepeatCount(1)
-                .setStartDelay(800L)
-                .setDirection(Shimmer.ANIMATION_DIRECTION_LTR)
-                .start(this);
-    }
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/LoadingIndicatorView.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/LoadingIndicatorView.java
deleted file mode 100644
index a569d0630..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/LoadingIndicatorView.java
+++ /dev/null
@@ -1,408 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.animation.AnimationUtils;
-
-import io.virtualapp.R;
-
-public class LoadingIndicatorView extends View {
-
-    private static final String TAG = "LoadingIndicatorView";
-
-    private static final Indicator DEFAULT_INDICATOR = new BallGridBeatIndicator();
-
-    private static final int MIN_SHOW_TIME = 500; // ms
-    private static final int MIN_DELAY = 500; // ms
-    int mMinWidth;
-    int mMaxWidth;
-    int mMinHeight;
-    int mMaxHeight;
-    private long mStartTime = -1;
-    private boolean mPostedHide = false;
-    private boolean mPostedShow = false;
-    private boolean mDismissed = false;
-    private Indicator mIndicator;
-    private int mIndicatorColor;
-    private boolean mShouldStartAnimationDrawable;
-    private final Runnable mDelayedHide = new Runnable() {
-
-        @Override
-        public void run() {
-            mPostedHide = false;
-            mStartTime = -1;
-            setVisibility(View.GONE);
-        }
-    };
-    private final Runnable mDelayedShow = new Runnable() {
-
-        @Override
-        public void run() {
-            mPostedShow = false;
-            if (!mDismissed) {
-                mStartTime = System.currentTimeMillis();
-                setVisibility(View.VISIBLE);
-            }
-        }
-    };
-
-    public LoadingIndicatorView(Context context) {
-        super(context);
-        init(context, null, 0, 0);
-    }
-
-    public LoadingIndicatorView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init(context, attrs, 0, R.style.AVLoadingIndicatorView);
-    }
-
-    public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        init(context, attrs, defStyleAttr, R.style.AVLoadingIndicatorView);
-    }
-
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        init(context, attrs, defStyleAttr, R.style.AVLoadingIndicatorView);
-    }
-
-    private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        mMinWidth = 24;
-        mMaxWidth = 48;
-        mMinHeight = 24;
-        mMaxHeight = 48;
-
-        final TypedArray a = context.obtainStyledAttributes(
-                attrs, R.styleable.LoadingIndicatorView, defStyleAttr, defStyleRes);
-
-        mMinWidth = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_minWidth, mMinWidth);
-        mMaxWidth = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_maxWidth, mMaxWidth);
-        mMinHeight = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_minHeight, mMinHeight);
-        mMaxHeight = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_maxHeight, mMaxHeight);
-        String indicatorName = a.getString(R.styleable.LoadingIndicatorView_indicatorName);
-        mIndicatorColor = a.getColor(R.styleable.LoadingIndicatorView_indicatorColor, Color.WHITE);
-        setIndicator(indicatorName);
-        if (mIndicator == null) {
-            setIndicator(DEFAULT_INDICATOR);
-        }
-        a.recycle();
-    }
-
-    public Indicator getIndicator() {
-        return mIndicator;
-    }
-
-    /**
-     * You should pay attention to pass this parameter with two way:
-     * for example:
-     * 1. Only class Name,like "SimpleIndicator".(This way would use default package name with
-     * "com.wang.avi.indicators")
-     * 2. Class name with full package,like "com.my.android.indicators.SimpleIndicator".
-     *
-     * @param indicatorName the class must be extend Indicator .
-     */
-    public void setIndicator(String indicatorName) {
-        if (TextUtils.isEmpty(indicatorName)) {
-            return;
-        }
-        StringBuilder drawableClassName = new StringBuilder();
-        if (!indicatorName.contains(".")) {
-            String defaultPackageName = getClass().getPackage().getName();
-            drawableClassName.append(defaultPackageName)
-                    .append(".");
-        }
-        drawableClassName.append(indicatorName);
-        try {
-            Class<?> drawableClass = Class.forName(drawableClassName.toString());
-            Indicator indicator = (Indicator) drawableClass.newInstance();
-            setIndicator(indicator);
-        } catch (ClassNotFoundException e) {
-            Log.e(TAG, "Didn't find your class , check the name again !");
-        } catch (InstantiationException e) {
-            e.printStackTrace();
-        } catch (IllegalAccessException e) {
-            e.printStackTrace();
-        }
-    }
-
-    public void setIndicator(Indicator d) {
-        if (mIndicator != d) {
-            if (mIndicator != null) {
-                mIndicator.setCallback(null);
-                unscheduleDrawable(mIndicator);
-            }
-
-            mIndicator = d;
-            //need to set indicator color again if you didn't specified when you update the indicator .
-            setIndicatorColor(mIndicatorColor);
-            if (d != null) {
-                d.setCallback(this);
-            }
-            postInvalidate();
-        }
-    }
-
-    /**
-     * setIndicatorColor(0xFF00FF00)
-     * or
-     * setIndicatorColor(Color.BLUE)
-     * or
-     * setIndicatorColor(Color.parseColor("#FF4081"))
-     * or
-     * setIndicatorColor(0xFF00FF00)
-     * or
-     * setIndicatorColor(getResources().getColor(android.R.color.black))
-     *
-     * @param color
-     */
-    public void setIndicatorColor(int color) {
-        this.mIndicatorColor = color;
-        mIndicator.setColor(color);
-    }
-
-    public void smoothToShow() {
-        startAnimation(AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_in));
-        setVisibility(VISIBLE);
-    }
-
-    public void smoothToHide() {
-        startAnimation(AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_out));
-        setVisibility(GONE);
-    }
-
-    public void hide() {
-        mDismissed = true;
-        removeCallbacks(mDelayedShow);
-        long diff = System.currentTimeMillis() - mStartTime;
-        if (diff >= MIN_SHOW_TIME || mStartTime == -1) {
-            // The progress spinner has been shown long enough
-            // OR was not shown yet. If it wasn't shown yet,
-            // it will just never be shown.
-            setVisibility(View.GONE);
-        } else {
-            // The progress spinner is shown, but not long enough,
-            // so put a delayed message in to hide it when its been
-            // shown long enough.
-            if (!mPostedHide) {
-                postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
-                mPostedHide = true;
-            }
-        }
-    }
-
-    public void show() {
-        // Reset the start time.
-        mStartTime = -1;
-        mDismissed = false;
-        removeCallbacks(mDelayedHide);
-        if (!mPostedShow) {
-            postDelayed(mDelayedShow, MIN_DELAY);
-            mPostedShow = true;
-        }
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return who == mIndicator
-                || super.verifyDrawable(who);
-    }
-
-    void startAnimation() {
-        if (getVisibility() != VISIBLE) {
-            return;
-        }
-
-        if (mIndicator instanceof Animatable) {
-            mShouldStartAnimationDrawable = true;
-        }
-        postInvalidate();
-    }
-
-    void stopAnimation() {
-        if (mIndicator instanceof Animatable) {
-            mIndicator.stop();
-            mShouldStartAnimationDrawable = false;
-        }
-        postInvalidate();
-    }
-
-    @Override
-    public void setVisibility(int v) {
-        if (getVisibility() != v) {
-            super.setVisibility(v);
-            if (v == GONE || v == INVISIBLE) {
-                stopAnimation();
-            } else {
-                startAnimation();
-            }
-        }
-    }
-
-    @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
-        super.onVisibilityChanged(changedView, visibility);
-        if (visibility == GONE || visibility == INVISIBLE) {
-            stopAnimation();
-        } else {
-            startAnimation();
-        }
-    }
-
-    @Override
-    public void invalidateDrawable(Drawable dr) {
-        if (verifyDrawable(dr)) {
-            final Rect dirty = dr.getBounds();
-            final int scrollX = getScrollX() + getPaddingLeft();
-            final int scrollY = getScrollY() + getPaddingTop();
-
-            invalidate(dirty.left + scrollX, dirty.top + scrollY,
-                    dirty.right + scrollX, dirty.bottom + scrollY);
-        } else {
-            super.invalidateDrawable(dr);
-        }
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        updateDrawableBounds(w, h);
-    }
-
-    private void updateDrawableBounds(int w, int h) {
-        // onDraw will translate the canvas so we draw starting at 0,0.
-        // Subtract out padding for the purposes of the calculations below.
-        w -= getPaddingRight() + getPaddingLeft();
-        h -= getPaddingTop() + getPaddingBottom();
-
-        int right = w;
-        int bottom = h;
-        int top = 0;
-        int left = 0;
-
-        if (mIndicator != null) {
-            // Maintain aspect ratio. Certain kinds of animated drawables
-            // get very confused otherwise.
-            final int intrinsicWidth = mIndicator.getIntrinsicWidth();
-            final int intrinsicHeight = mIndicator.getIntrinsicHeight();
-            final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
-            final float boundAspect = (float) w / h;
-            if (intrinsicAspect != boundAspect) {
-                if (boundAspect > intrinsicAspect) {
-                    // New width is larger. Make it smaller to match height.
-                    final int width = (int) (h * intrinsicAspect);
-                    left = (w - width) / 2;
-                    right = left + width;
-                } else {
-                    // New height is larger. Make it smaller to match width.
-                    final int height = (int) (w * (1 / intrinsicAspect));
-                    top = (h - height) / 2;
-                    bottom = top + height;
-                }
-            }
-            mIndicator.setBounds(left, top, right, bottom);
-        }
-    }
-
-    @Override
-    protected synchronized void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        drawTrack(canvas);
-    }
-
-    void drawTrack(Canvas canvas) {
-        final Drawable d = mIndicator;
-        if (d != null) {
-            // Translate canvas so a indeterminate circular progress bar with padding
-            // rotates properly in its animation
-            final int saveCount = canvas.save();
-
-            canvas.translate(getPaddingLeft(), getPaddingTop());
-
-            d.draw(canvas);
-            canvas.restoreToCount(saveCount);
-
-            if (mShouldStartAnimationDrawable && d instanceof Animatable) {
-                ((Animatable) d).start();
-                mShouldStartAnimationDrawable = false;
-            }
-        }
-    }
-
-    @Override
-    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int dw = 0;
-        int dh = 0;
-
-        final Drawable d = mIndicator;
-        if (d != null) {
-            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
-            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
-        }
-
-        updateDrawableState();
-
-        dw += getPaddingLeft() + getPaddingRight();
-        dh += getPaddingTop() + getPaddingBottom();
-
-        final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
-        final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
-        setMeasuredDimension(measuredWidth, measuredHeight);
-    }
-
-    @Override
-    protected void drawableStateChanged() {
-        super.drawableStateChanged();
-        updateDrawableState();
-    }
-
-    private void updateDrawableState() {
-        final int[] state = getDrawableState();
-        if (mIndicator != null && mIndicator.isStateful()) {
-            mIndicator.setState(state);
-        }
-    }
-
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    @Override
-    public void drawableHotspotChanged(float x, float y) {
-        super.drawableHotspotChanged(x, y);
-
-        if (mIndicator != null) {
-            mIndicator.setHotspot(x, y);
-        }
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        startAnimation();
-        removeCallbacks();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        stopAnimation();
-        // This should come after stopAnimation(), otherwise an invalidate message remains in the
-        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
-        super.onDetachedFromWindow();
-        removeCallbacks();
-    }
-
-    private void removeCallbacks() {
-        removeCallbacks(mDelayedHide);
-        removeCallbacks(mDelayedShow);
-    }
-
-
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/MaterialRippleLayout.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/MaterialRippleLayout.java
deleted file mode 100644
index 4d9d3b25a..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/MaterialRippleLayout.java
+++ /dev/null
@@ -1,796 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.AttributeSet;
-import android.util.Property;
-import android.util.TypedValue;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.LinearInterpolator;
-import android.widget.AdapterView;
-import android.widget.FrameLayout;
-
-import io.virtualapp.R;
-
-import static android.view.GestureDetector.SimpleOnGestureListener;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
-public class MaterialRippleLayout extends FrameLayout {
-
-    private static final int     DEFAULT_DURATION        = 350;
-    private static final int     DEFAULT_FADE_DURATION   = 75;
-    private static final float   DEFAULT_DIAMETER_DP     = 35;
-    private static final float   DEFAULT_ALPHA           = 0.2f;
-    private static final int     DEFAULT_COLOR           = Color.BLACK;
-    private static final int     DEFAULT_BACKGROUND      = Color.TRANSPARENT;
-    private static final boolean DEFAULT_HOVER           = true;
-    private static final boolean DEFAULT_DELAY_CLICK     = true;
-    private static final boolean DEFAULT_PERSISTENT      = false;
-    private static final boolean DEFAULT_SEARCH_ADAPTER  = false;
-    private static final boolean DEFAULT_RIPPLE_OVERLAY  = false;
-    private static final int     DEFAULT_ROUNDED_CORNERS = 0;
-
-    private static final int  FADE_EXTRA_DELAY = 50;
-    private static final long HOVER_DURATION   = 2500;
-
-    private final Paint paint  = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private final Rect  bounds = new Rect();
-
-    private int      rippleColor;
-    private boolean  rippleOverlay;
-    private boolean  rippleHover;
-    private int      rippleDiameter;
-    private int      rippleDuration;
-    private int      rippleAlpha;
-    private boolean  rippleDelayClick;
-    private int      rippleFadeDuration;
-    private boolean  ripplePersistent;
-    private Drawable rippleBackground;
-    private boolean  rippleInAdapter;
-    private float    rippleRoundedCorners;
-
-    private float radius;
-
-    private AdapterView parentAdapter;
-    private View        childView;
-
-    private AnimatorSet    rippleAnimator;
-    private ObjectAnimator hoverAnimator;
-
-    private Point currentCoords  = new Point();
-    private Point previousCoords = new Point();
-
-    private int layerType;
-
-    private boolean eventCancelled;
-    private boolean prepressed;
-    private int     positionInAdapter;
-
-    private GestureDetector   gestureDetector;
-    private PerformClickEvent pendingClickEvent;
-    private PressedEvent      pendingPressEvent;
-    private boolean hasPerformedLongPress;
-    /*
-     * Animations
-     */
-    private Property<MaterialRippleLayout, Float> radiusProperty
-        = new Property<MaterialRippleLayout, Float>(Float.class, "radius") {
-        @Override
-        public Float get(MaterialRippleLayout object) {
-            return object.getRadius();
-        }
-
-        @Override
-        public void set(MaterialRippleLayout object, Float value) {
-            object.setRadius(value);
-        }
-    };
-    private Property<MaterialRippleLayout, Integer> circleAlphaProperty
-        = new Property<MaterialRippleLayout, Integer>(Integer.class, "rippleAlpha") {
-        @Override
-        public Integer get(MaterialRippleLayout object) {
-            return object.getRippleAlpha();
-        }
-
-        @Override
-        public void set(MaterialRippleLayout object, Integer value) {
-            object.setRippleAlpha(value);
-        }
-    };
-    private SimpleOnGestureListener longClickListener = new GestureDetector.SimpleOnGestureListener() {
-        public void onLongPress(MotionEvent e) {
-            hasPerformedLongPress = childView.performLongClick();
-            if (hasPerformedLongPress) {
-                if (rippleHover) {
-                    startRipple(null);
-                }
-                cancelPressedEvent();
-            }
-        }
-
-        @Override
-        public boolean onDown(MotionEvent e) {
-            hasPerformedLongPress = false;
-            return super.onDown(e);
-        }
-    };
-
-
-    public MaterialRippleLayout(Context context) {
-        this(context, null, 0);
-    }
-
-    public MaterialRippleLayout(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public MaterialRippleLayout(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        setWillNotDraw(false);
-        gestureDetector = new GestureDetector(context, longClickListener);
-
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MaterialRippleLayout);
-        rippleColor = a.getColor(R.styleable.MaterialRippleLayout_mrl_rippleColor, DEFAULT_COLOR);
-        rippleDiameter = a.getDimensionPixelSize(
-            R.styleable.MaterialRippleLayout_mrl_rippleDimension,
-            (int) dpToPx(getResources(), DEFAULT_DIAMETER_DP)
-        );
-        rippleOverlay = a.getBoolean(R.styleable.MaterialRippleLayout_mrl_rippleOverlay, DEFAULT_RIPPLE_OVERLAY);
-        rippleHover = a.getBoolean(R.styleable.MaterialRippleLayout_mrl_rippleHover, DEFAULT_HOVER);
-        rippleDuration = a.getInt(R.styleable.MaterialRippleLayout_mrl_rippleDuration, DEFAULT_DURATION);
-        rippleAlpha = (int) (255 * a.getFloat(R.styleable.MaterialRippleLayout_mrl_rippleAlpha, DEFAULT_ALPHA));
-        rippleDelayClick = a.getBoolean(R.styleable.MaterialRippleLayout_mrl_rippleDelayClick, DEFAULT_DELAY_CLICK);
-        rippleFadeDuration = a.getInteger(R.styleable.MaterialRippleLayout_mrl_rippleFadeDuration, DEFAULT_FADE_DURATION);
-        rippleBackground = new ColorDrawable(a.getColor(R.styleable.MaterialRippleLayout_mrl_rippleBackground, DEFAULT_BACKGROUND));
-        ripplePersistent = a.getBoolean(R.styleable.MaterialRippleLayout_mrl_ripplePersistent, DEFAULT_PERSISTENT);
-        rippleInAdapter = a.getBoolean(R.styleable.MaterialRippleLayout_mrl_rippleInAdapter, DEFAULT_SEARCH_ADAPTER);
-        rippleRoundedCorners = a.getDimensionPixelSize(R.styleable.MaterialRippleLayout_mrl_rippleRoundedCorners, DEFAULT_ROUNDED_CORNERS);
-
-        a.recycle();
-
-        paint.setColor(rippleColor);
-        paint.setAlpha(rippleAlpha);
-
-        enableClipPathSupportIfNecessary();
-    }
-
-    public static RippleBuilder on(View view) {
-        return new RippleBuilder(view);
-    }
-
-    static float dpToPx(Resources resources, float dp) {
-        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());
-    }
-
-    @SuppressWarnings("unchecked")
-    public <T extends View> T getChildView() {
-        return (T) childView;
-    }
-
-    @Override
-    public final void addView(View child, int index, ViewGroup.LayoutParams params) {
-        if (getChildCount() > 0) {
-            throw new IllegalStateException("MaterialRippleLayout can host only one child");
-        }
-        //noinspection unchecked
-        childView = child;
-        super.addView(child, index, params);
-    }
-
-    @Override
-    public void setOnClickListener(OnClickListener onClickListener) {
-        if (childView == null) {
-            throw new IllegalStateException("MaterialRippleLayout must have a child view to handle clicks");
-        }
-        childView.setOnClickListener(onClickListener);
-    }
-
-    @Override
-    public void setOnLongClickListener(OnLongClickListener onClickListener) {
-        if (childView == null) {
-            throw new IllegalStateException("MaterialRippleLayout must have a child view to handle clicks");
-        }
-        childView.setOnLongClickListener(onClickListener);
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        return !findClickableViewInChild(childView, (int) event.getX(), (int) event.getY());
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        boolean superOnTouchEvent = super.onTouchEvent(event);
-
-        if (!isEnabled() || !childView.isEnabled()) return superOnTouchEvent;
-
-        boolean isEventInBounds = bounds.contains((int) event.getX(), (int) event.getY());
-
-        if (isEventInBounds) {
-            previousCoords.set(currentCoords.x, currentCoords.y);
-            currentCoords.set((int) event.getX(), (int) event.getY());
-        }
-
-        boolean gestureResult = gestureDetector.onTouchEvent(event);
-        if (gestureResult || hasPerformedLongPress) {
-            return true;
-        } else {
-            int action = event.getActionMasked();
-            switch (action) {
-                case MotionEvent.ACTION_UP:
-                    pendingClickEvent = new PerformClickEvent();
-
-                    if (prepressed) {
-                        childView.setPressed(true);
-                        postDelayed(
-                            new Runnable() {
-                                @Override public void run() {
-                                    childView.setPressed(false);
-                                }
-                            }, ViewConfiguration.getPressedStateDuration());
-                    }
-
-                    if (isEventInBounds) {
-                        startRipple(pendingClickEvent);
-                    } else if (!rippleHover) {
-                        setRadius(0);
-                    }
-                    if (!rippleDelayClick && isEventInBounds) {
-                        pendingClickEvent.run();
-                    }
-                    cancelPressedEvent();
-                    break;
-                case MotionEvent.ACTION_DOWN:
-                    setPositionInAdapter();
-                    eventCancelled = false;
-                    pendingPressEvent = new PressedEvent(event);
-                    if (isInScrollingContainer()) {
-                        cancelPressedEvent();
-                        prepressed = true;
-                        postDelayed(pendingPressEvent, ViewConfiguration.getTapTimeout());
-                    } else {
-                        pendingPressEvent.run();
-                    }
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                    if (rippleInAdapter) {
-                        // dont use current coords in adapter since they tend to jump drastically on scroll
-                        currentCoords.set(previousCoords.x, previousCoords.y);
-                        previousCoords = new Point();
-                    }
-                    childView.onTouchEvent(event);
-                    if (rippleHover) {
-                        if (!prepressed) {
-                            startRipple(null);
-                        }
-                    } else {
-                        childView.setPressed(false);
-                    }
-                    cancelPressedEvent();
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    if (rippleHover) {
-                        if (isEventInBounds && !eventCancelled) {
-                            invalidate();
-                        } else if (!isEventInBounds) {
-                            startRipple(null);
-                        }
-                    }
-
-                    if (!isEventInBounds) {
-                        cancelPressedEvent();
-                        if (hoverAnimator != null) {
-                            hoverAnimator.cancel();
-                        }
-                        childView.onTouchEvent(event);
-                        eventCancelled = true;
-                    }
-                    break;
-            }
-            return true;
-        }
-    }
-
-    private void cancelPressedEvent() {
-        if (pendingPressEvent != null) {
-            removeCallbacks(pendingPressEvent);
-            prepressed = false;
-        }
-    }
-
-    private void startHover() {
-        if (eventCancelled) return;
-
-        if (hoverAnimator != null) {
-            hoverAnimator.cancel();
-        }
-        final float radius = (float) (Math.sqrt(Math.pow(getWidth(), 2) + Math.pow(getHeight(), 2)) * 1.2f);
-        hoverAnimator = ObjectAnimator.ofFloat(this, radiusProperty, rippleDiameter, radius)
-            .setDuration(HOVER_DURATION);
-        hoverAnimator.setInterpolator(new LinearInterpolator());
-        hoverAnimator.start();
-    }
-
-    private void startRipple(final Runnable animationEndRunnable) {
-        if (eventCancelled) return;
-
-        float endRadius = getEndRadius();
-
-        cancelAnimations();
-
-        rippleAnimator = new AnimatorSet();
-        rippleAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override public void onAnimationEnd(Animator animation) {
-                if (!ripplePersistent) {
-                    setRadius(0);
-                    setRippleAlpha(rippleAlpha);
-                }
-                if (animationEndRunnable != null && rippleDelayClick) {
-                    animationEndRunnable.run();
-                }
-                childView.setPressed(false);
-            }
-        });
-
-        ObjectAnimator ripple = ObjectAnimator.ofFloat(this, radiusProperty, radius, endRadius);
-        ripple.setDuration(rippleDuration);
-        ripple.setInterpolator(new DecelerateInterpolator());
-        ObjectAnimator fade = ObjectAnimator.ofInt(this, circleAlphaProperty, rippleAlpha, 0);
-        fade.setDuration(rippleFadeDuration);
-        fade.setInterpolator(new AccelerateInterpolator());
-        fade.setStartDelay(rippleDuration - rippleFadeDuration - FADE_EXTRA_DELAY);
-
-        if (ripplePersistent) {
-            rippleAnimator.play(ripple);
-        } else if (getRadius() > endRadius) {
-            fade.setStartDelay(0);
-            rippleAnimator.play(fade);
-        } else {
-            rippleAnimator.playTogether(ripple, fade);
-        }
-        rippleAnimator.start();
-    }
-
-    private void cancelAnimations() {
-        if (rippleAnimator != null) {
-            rippleAnimator.cancel();
-            rippleAnimator.removeAllListeners();
-        }
-
-        if (hoverAnimator != null) {
-            hoverAnimator.cancel();
-        }
-    }
-
-    private float getEndRadius() {
-        final int width = getWidth();
-        final int height = getHeight();
-
-        final int halfWidth = width / 2;
-        final int halfHeight = height / 2;
-
-        final float radiusX = halfWidth > currentCoords.x ? width - currentCoords.x : currentCoords.x;
-        final float radiusY = halfHeight > currentCoords.y ? height - currentCoords.y : currentCoords.y;
-
-        return (float) Math.sqrt(Math.pow(radiusX, 2) + Math.pow(radiusY, 2)) * 1.2f;
-    }
-
-    private boolean isInScrollingContainer() {
-        ViewParent p = getParent();
-        while (p != null && p instanceof ViewGroup) {
-            if (((ViewGroup) p).shouldDelayChildPressedState()) {
-                return true;
-            }
-            p = p.getParent();
-        }
-        return false;
-    }
-
-    private AdapterView findParentAdapterView() {
-        if (parentAdapter != null) {
-            return parentAdapter;
-        }
-        ViewParent current = getParent();
-        while (true) {
-            if (current instanceof AdapterView) {
-                parentAdapter = (AdapterView) current;
-                return parentAdapter;
-            } else {
-                try {
-                    current = current.getParent();
-                } catch (NullPointerException npe) {
-                    throw new RuntimeException("Could not find a parent AdapterView");
-                }
-            }
-        }
-    }
-
-    private void setPositionInAdapter() {
-        if (rippleInAdapter) {
-            positionInAdapter = findParentAdapterView().getPositionForView(MaterialRippleLayout.this);
-        }
-    }
-
-    private boolean adapterPositionChanged() {
-        if (rippleInAdapter) {
-            int newPosition = findParentAdapterView().getPositionForView(MaterialRippleLayout.this);
-            final boolean changed = newPosition != positionInAdapter;
-            positionInAdapter = newPosition;
-            if (changed) {
-                cancelPressedEvent();
-                cancelAnimations();
-                childView.setPressed(false);
-                setRadius(0);
-            }
-            return changed;
-        }
-        return false;
-    }
-
-    private boolean findClickableViewInChild(View view, int x, int y) {
-        if (view instanceof ViewGroup) {
-            ViewGroup viewGroup = (ViewGroup) view;
-            for (int i = 0; i < viewGroup.getChildCount(); i++) {
-                View child = viewGroup.getChildAt(i);
-                final Rect rect = new Rect();
-                child.getHitRect(rect);
-
-                final boolean contains = rect.contains(x, y);
-                if (contains) {
-                    return findClickableViewInChild(child, x - rect.left, y - rect.top);
-                }
-            }
-        } else if (view != childView) {
-            return (view.isEnabled() && (view.isClickable() || view.isLongClickable() || view.isFocusableInTouchMode()));
-        }
-
-        return view.isFocusableInTouchMode();
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-        bounds.set(0, 0, w, h);
-        rippleBackground.setBounds(bounds);
-    }
-
-    @Override
-    public boolean isInEditMode() {
-        return true;
-    }
-
-    /*
-     * Drawing
-     */
-    @Override
-    public void draw(Canvas canvas) {
-        final boolean positionChanged = adapterPositionChanged();
-        if (rippleOverlay) {
-            if (!positionChanged) {
-                rippleBackground.draw(canvas);
-            }
-            super.draw(canvas);
-            if (!positionChanged) {
-                if (rippleRoundedCorners != 0) {
-                    Path clipPath = new Path();
-                    RectF rect = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
-                    clipPath.addRoundRect(rect, rippleRoundedCorners, rippleRoundedCorners, Path.Direction.CW);
-                    canvas.clipPath(clipPath);
-                }
-                canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint);
-            }
-        } else {
-            if (!positionChanged) {
-                rippleBackground.draw(canvas);
-                canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint);
-            }
-            super.draw(canvas);
-        }
-    }
-
-    private float getRadius() {
-        return radius;
-    }
-
-    public void setRadius(float radius) {
-        this.radius = radius;
-        invalidate();
-    }
-
-    public int getRippleAlpha() {
-        return paint.getAlpha();
-    }
-
-    public void setRippleAlpha(Integer rippleAlpha) {
-        paint.setAlpha(rippleAlpha);
-        invalidate();
-    }
-
-    /*
-    * Accessor
-     */
-    public void setRippleColor(int rippleColor) {
-        this.rippleColor = rippleColor;
-        paint.setColor(rippleColor);
-        paint.setAlpha(rippleAlpha);
-        invalidate();
-    }
-
-    public void setRippleOverlay(boolean rippleOverlay) {
-        this.rippleOverlay = rippleOverlay;
-    }
-
-    public void setRippleDiameter(int rippleDiameter) {
-        this.rippleDiameter = rippleDiameter;
-    }
-
-    public void setRippleDuration(int rippleDuration) {
-        this.rippleDuration = rippleDuration;
-    }
-
-    public void setRippleBackground(int color) {
-        rippleBackground = new ColorDrawable(color);
-        rippleBackground.setBounds(bounds);
-        invalidate();
-    }
-
-    public void setRippleHover(boolean rippleHover) {
-        this.rippleHover = rippleHover;
-    }
-
-    public void setRippleDelayClick(boolean rippleDelayClick) {
-        this.rippleDelayClick = rippleDelayClick;
-    }
-
-    public void setRippleFadeDuration(int rippleFadeDuration) {
-        this.rippleFadeDuration = rippleFadeDuration;
-    }
-
-    public void setRipplePersistent(boolean ripplePersistent) {
-        this.ripplePersistent = ripplePersistent;
-    }
-
-    public void setRippleInAdapter(boolean rippleInAdapter) {
-        this.rippleInAdapter = rippleInAdapter;
-    }
-
-    public void setRippleRoundedCorners(int rippleRoundedCorner) {
-        this.rippleRoundedCorners = rippleRoundedCorner;
-        enableClipPathSupportIfNecessary();
-    }
-
-    public void setDefaultRippleAlpha(float alpha) {
-        this.rippleAlpha = (int) (255 * alpha);
-        paint.setAlpha(rippleAlpha);
-        invalidate();
-    }
-
-    public void performRipple() {
-        currentCoords = new Point(getWidth() / 2, getHeight() / 2);
-        startRipple(null);
-    }
-
-    public void performRipple(Point anchor) {
-        currentCoords = new Point(anchor.x, anchor.y);
-        startRipple(null);
-    }
-
-    /**
-     * {@link Canvas#clipPath(Path)} is not supported in hardware accelerated layers
-     * before API 18. Use software layer instead
-     * <p/>
-     * https://developer.android.com/guide/topics/graphics/hardware-accel.html#unsupported
-     */
-    private void enableClipPathSupportIfNecessary() {
-        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            if (rippleRoundedCorners != 0) {
-                layerType = getLayerType();
-                setLayerType(LAYER_TYPE_SOFTWARE, null);
-            } else {
-                setLayerType(layerType, null);
-            }
-        }
-    }
-
-    public static class RippleBuilder {
-
-        private final Context context;
-        private final View    child;
-
-        private int     rippleColor         = DEFAULT_COLOR;
-        private boolean rippleOverlay       = DEFAULT_RIPPLE_OVERLAY;
-        private boolean rippleHover         = DEFAULT_HOVER;
-        private float   rippleDiameter      = DEFAULT_DIAMETER_DP;
-        private int     rippleDuration      = DEFAULT_DURATION;
-        private float   rippleAlpha         = DEFAULT_ALPHA;
-        private boolean rippleDelayClick    = DEFAULT_DELAY_CLICK;
-        private int     rippleFadeDuration  = DEFAULT_FADE_DURATION;
-        private boolean ripplePersistent    = DEFAULT_PERSISTENT;
-        private int     rippleBackground    = DEFAULT_BACKGROUND;
-        private boolean rippleSearchAdapter = DEFAULT_SEARCH_ADAPTER;
-        private float   rippleRoundedCorner = DEFAULT_ROUNDED_CORNERS;
-
-        public RippleBuilder(View child) {
-            this.child = child;
-            this.context = child.getContext();
-        }
-
-        public RippleBuilder rippleColor(int color) {
-            this.rippleColor = color;
-            return this;
-        }
-
-        public RippleBuilder rippleOverlay(boolean overlay) {
-            this.rippleOverlay = overlay;
-            return this;
-        }
-
-        public RippleBuilder rippleHover(boolean hover) {
-            this.rippleHover = hover;
-            return this;
-        }
-
-        public RippleBuilder rippleDiameterDp(int diameterDp) {
-            this.rippleDiameter = diameterDp;
-            return this;
-        }
-
-        public RippleBuilder rippleDuration(int duration) {
-            this.rippleDuration = duration;
-            return this;
-        }
-
-        public RippleBuilder rippleAlpha(float alpha) {
-            this.rippleAlpha = alpha;
-            return this;
-        }
-
-        public RippleBuilder rippleDelayClick(boolean delayClick) {
-            this.rippleDelayClick = delayClick;
-            return this;
-        }
-
-        public RippleBuilder rippleFadeDuration(int fadeDuration) {
-            this.rippleFadeDuration = fadeDuration;
-            return this;
-        }
-
-        public RippleBuilder ripplePersistent(boolean persistent) {
-            this.ripplePersistent = persistent;
-            return this;
-        }
-
-        public RippleBuilder rippleBackground(int color) {
-            this.rippleBackground = color;
-            return this;
-        }
-
-        public RippleBuilder rippleInAdapter(boolean inAdapter) {
-            this.rippleSearchAdapter = inAdapter;
-            return this;
-        }
-
-        public RippleBuilder rippleRoundedCorners(int radiusDp) {
-            this.rippleRoundedCorner = radiusDp;
-            return this;
-        }
-
-        public MaterialRippleLayout create() {
-            MaterialRippleLayout layout = new MaterialRippleLayout(context);
-            layout.setRippleColor(rippleColor);
-            layout.setDefaultRippleAlpha(rippleAlpha);
-            layout.setRippleDelayClick(rippleDelayClick);
-            layout.setRippleDiameter((int) dpToPx(context.getResources(), rippleDiameter));
-            layout.setRippleDuration(rippleDuration);
-            layout.setRippleFadeDuration(rippleFadeDuration);
-            layout.setRippleHover(rippleHover);
-            layout.setRipplePersistent(ripplePersistent);
-            layout.setRippleOverlay(rippleOverlay);
-            layout.setRippleBackground(rippleBackground);
-            layout.setRippleInAdapter(rippleSearchAdapter);
-            layout.setRippleRoundedCorners((int) dpToPx(context.getResources(), rippleRoundedCorner));
-
-            ViewGroup.LayoutParams params = child.getLayoutParams();
-            ViewGroup parent = (ViewGroup) child.getParent();
-            int index = 0;
-
-            if (parent != null && parent instanceof MaterialRippleLayout) {
-                throw new IllegalStateException("MaterialRippleLayout could not be created: parent of the view already is a MaterialRippleLayout");
-            }
-
-            if (parent != null) {
-                index = parent.indexOfChild(child);
-                parent.removeView(child);
-            }
-
-            layout.addView(child, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
-            if (parent != null) {
-                parent.addView(layout, index, params);
-            }
-
-            return layout;
-        }
-    }
-
-    /*
-     * Helper
-     */
-    private class PerformClickEvent implements Runnable {
-
-        @Override public void run() {
-            if (hasPerformedLongPress) return;
-
-            // if parent is an AdapterView, try to call its ItemClickListener
-            if (getParent() instanceof AdapterView) {
-                // try clicking direct child first
-                if (!childView.performClick())
-                    // if it did not handle it dispatch to adapterView
-                    clickAdapterView((AdapterView) getParent());
-            } else if (rippleInAdapter) {
-                // find adapter view
-                clickAdapterView(findParentAdapterView());
-            } else {
-                // otherwise, just perform click on child
-                childView.performClick();
-            }
-        }
-
-        private void clickAdapterView(AdapterView parent) {
-            final int position = parent.getPositionForView(MaterialRippleLayout.this);
-            final long itemId = parent.getAdapter() != null
-                ? parent.getAdapter().getItemId(position)
-                : 0;
-            if (position != AdapterView.INVALID_POSITION) {
-                parent.performItemClick(MaterialRippleLayout.this, position, itemId);
-            }
-        }
-    }
-
-    /*
-     * Builder
-     */
-
-    private final class PressedEvent implements Runnable {
-
-        private final MotionEvent event;
-
-        public PressedEvent(MotionEvent event) {
-            this.event = event;
-        }
-
-        @Override
-        public void run() {
-            prepressed = false;
-            childView.setLongClickable(false);//prevent the child's long click,let's the ripple layout call it's performLongClick
-            childView.onTouchEvent(event);
-            childView.setPressed(true);
-            if (rippleHover) {
-                startHover();
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/RippleButton.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/RippleButton.java
deleted file mode 100644
index 36a8e356e..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/RippleButton.java
+++ /dev/null
@@ -1,244 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.RadialGradient;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.os.Build;
-import android.support.v7.widget.AppCompatButton;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.animation.AccelerateDecelerateInterpolator;
-
-import com.nineoldandroids.animation.Animator;
-import com.nineoldandroids.animation.ObjectAnimator;
-import com.nineoldandroids.view.ViewHelper;
-
-import io.virtualapp.R;
-
-@SuppressLint("ClickableViewAccessibility")
-public class RippleButton extends AppCompatButton {
-
-    private float mDownX;
-    private float mDownY;
-    private float mAlphaFactor;
-    private float mDensity;
-    private float mRadius;
-    private float mMaxRadius;
-
-    private int mRippleColor;
-    private boolean mIsAnimating = false;
-    private boolean mHover = true;
-
-    private RadialGradient mRadialGradient;
-    private Paint mPaint;
-    private ObjectAnimator mRadiusAnimator;
-    private boolean mAnimationIsCancel;
-    private Rect mRect;
-    private Path mPath = new Path();
-
-    public RippleButton(Context context) {
-        this(context, null);
-    }
-
-    public RippleButton(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public RippleButton(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        init();
-        TypedArray a = context.obtainStyledAttributes(attrs,
-                R.styleable.RippleButton);
-        mRippleColor = a.getColor(R.styleable.RippleButton_rippleColor,
-                mRippleColor);
-        mAlphaFactor = a.getFloat(R.styleable.RippleButton_alphaFactor,
-                mAlphaFactor);
-        mHover = a.getBoolean(R.styleable.RippleButton_hover, mHover);
-        a.recycle();
-    }
-
-    private int dp(int dp) {
-        return (int) (dp * mDensity + 0.5f);
-    }
-
-    public void init() {
-        mDensity = getContext().getResources().getDisplayMetrics().density;
-
-        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mPaint.setAlpha(100);
-        setRippleColor(Color.BLACK, 0.2f);
-
-    }
-
-    public void setRippleColor(int rippleColor, float alphaFactor) {
-        mRippleColor = rippleColor;
-        mAlphaFactor = alphaFactor;
-    }
-
-    public void setHover(boolean enabled) {
-        mHover = enabled;
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-        mMaxRadius = (float) Math.sqrt(w * w + h * h);
-    }
-
-    @Override
-    public boolean onTouchEvent(final MotionEvent event) {
-        Log.d("TouchEvent", String.valueOf(event.getActionMasked()));
-        Log.d("mIsAnimating", String.valueOf(mIsAnimating));
-        Log.d("mAnimationIsCancel", String.valueOf(mAnimationIsCancel));
-        boolean superResult = super.onTouchEvent(event);
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN
-                && this.isEnabled() && mHover) {
-            mRect = new Rect(getLeft(), getTop(), getRight(), getBottom());
-            mAnimationIsCancel = false;
-            mDownX = event.getX();
-            mDownY = event.getY();
-
-            mRadiusAnimator = ObjectAnimator.ofFloat(this, "radius", 0, dp(50))
-                    .setDuration(400);
-            mRadiusAnimator
-                    .setInterpolator(new AccelerateDecelerateInterpolator());
-            mRadiusAnimator.addListener(new Animator.AnimatorListener() {
-                @Override
-                public void onAnimationStart(Animator animator) {
-                    mIsAnimating = true;
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animator) {
-                    setRadius(0);
-                    ViewHelper.setAlpha(RippleButton.this, 1);
-                    mIsAnimating = false;
-                }
-
-                @Override
-                public void onAnimationCancel(Animator animator) {
-
-                }
-
-                @Override
-                public void onAnimationRepeat(Animator animator) {
-
-                }
-            });
-            mRadiusAnimator.start();
-            if (!superResult) {
-                return true;
-            }
-        } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE
-                && this.isEnabled() && mHover) {
-            mDownX = event.getX();
-            mDownY = event.getY();
-
-            // Cancel the ripple animation when moved outside
-            if (mAnimationIsCancel = !mRect.contains(
-                    getLeft() + (int) event.getX(),
-                    getTop() + (int) event.getY())) {
-                setRadius(0);
-            } else {
-                setRadius(dp(50));
-            }
-            if (!superResult) {
-                return true;
-            }
-        } else if (event.getActionMasked() == MotionEvent.ACTION_UP
-                && !mAnimationIsCancel && this.isEnabled()) {
-            mDownX = event.getX();
-            mDownY = event.getY();
-
-            final float tempRadius = (float) Math.sqrt(mDownX * mDownX + mDownY
-                    * mDownY);
-            float targetRadius = Math.max(tempRadius, mMaxRadius);
-
-            if (mIsAnimating) {
-                mRadiusAnimator.cancel();
-            }
-            mRadiusAnimator = ObjectAnimator.ofFloat(this, "radius", dp(50),
-                    targetRadius);
-            mRadiusAnimator.setDuration(500);
-            mRadiusAnimator
-                    .setInterpolator(new AccelerateDecelerateInterpolator());
-            mRadiusAnimator.addListener(new Animator.AnimatorListener() {
-                @Override
-                public void onAnimationStart(Animator animator) {
-                    mIsAnimating = true;
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animator) {
-                    setRadius(0);
-                    ViewHelper.setAlpha(RippleButton.this, 1);
-                    mIsAnimating = false;
-                }
-
-                @Override
-                public void onAnimationCancel(Animator animator) {
-
-                }
-
-                @Override
-                public void onAnimationRepeat(Animator animator) {
-
-                }
-            });
-            mRadiusAnimator.start();
-            if (!superResult) {
-                return true;
-            }
-        }
-        return superResult;
-    }
-
-    public int adjustAlpha(int color, float factor) {
-        int alpha = Math.round(Color.alpha(color) * factor);
-        int red = Color.red(color);
-        int green = Color.green(color);
-        int blue = Color.blue(color);
-        return Color.argb(alpha, red, green, blue);
-    }
-
-    public void setRadius(final float radius) {
-        mRadius = radius;
-        if (mRadius > 0) {
-            mRadialGradient = new RadialGradient(mDownX, mDownY, mRadius,
-                    adjustAlpha(mRippleColor, mAlphaFactor), mRippleColor,
-                    Shader.TileMode.MIRROR);
-            mPaint.setShader(mRadialGradient);
-        }
-        invalidate();
-    }
-
-    @Override
-    protected void onDraw(final Canvas canvas) {
-        super.onDraw(canvas);
-
-        if (isInEditMode()) {
-            return;
-        }
-
-        canvas.save(Canvas.CLIP_SAVE_FLAG);
-
-        mPath.reset();
-        mPath.addCircle(mDownX, mDownY, mRadius, Path.Direction.CW);
-
-        canvas.clipPath(mPath);
-
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
-            canvas.restore();
-
-        canvas.drawCircle(mDownX, mDownY, mRadius, mPaint);
-    }
-
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShadowProperty.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShadowProperty.java
deleted file mode 100644
index 0fb8dba4b..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShadowProperty.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package io.virtualapp.widgets;
-
-public class ShadowProperty {
-    public static final int ALL = 0x1111;
-    public static final int LEFT = 0x0001;
-    public static final int TOP = 0x0010;
-    public static final int RIGHT = 0x0100;
-    public static final int BOTTOM = 0x1000;
-
-    /**
-     * 阴影颜色
-     */
-    private int shadowColor;
-    /**
-     * 阴影半径
-     */
-    private int shadowRadius;
-    /**
-     * 阴影x偏移
-     */
-    private int shadowDx;
-    /**
-     * 阴影y偏移
-     */
-    private int shadowDy;
-
-    /**
-     * 阴影边
-     */
-    private int shadowSide = ALL;
-
-    public int getShadowSide() {
-        return shadowSide;
-    }
-
-    public ShadowProperty setShadowSide(int shadowSide) {
-        this.shadowSide = shadowSide;
-        return this;
-    }
-
-    public int getShadowOffset() {
-        return getShadowOffsetHalf() * 2;
-    }
-
-    public int getShadowOffsetHalf() {
-        return 0 >= shadowRadius ? 0 : Math.max(shadowDx, shadowDy) + shadowRadius;
-    }
-
-    public int getShadowColor() {
-        return shadowColor;
-    }
-
-    public ShadowProperty setShadowColor(int shadowColor) {
-        this.shadowColor = shadowColor;
-        return this;
-    }
-
-    public int getShadowRadius() {
-        return shadowRadius;
-    }
-
-    public ShadowProperty setShadowRadius(int shadowRadius) {
-        this.shadowRadius = shadowRadius;
-        return this;
-    }
-
-    public int getShadowDx() {
-        return shadowDx;
-    }
-
-    public ShadowProperty setShadowDx(int shadowDx) {
-        this.shadowDx = shadowDx;
-        return this;
-    }
-
-    public int getShadowDy() {
-        return shadowDy;
-    }
-
-    public ShadowProperty setShadowDy(int shadowDy) {
-        this.shadowDy = shadowDy;
-        return this;
-    }
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShadowViewDrawable.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShadowViewDrawable.java
deleted file mode 100644
index 75a3e30fb..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShadowViewDrawable.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.NonNull;
-
-public class ShadowViewDrawable extends Drawable {
-    private Paint paint;
-
-    private RectF bounds = new RectF();
-
-    private int width;
-    private int height;
-
-    private ShadowProperty shadowProperty;
-    private int shadowOffset;
-
-    private RectF drawRect;
-
-    private float rx;
-    private float ry;
-    private PorterDuffXfermode srcOut = new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT);
-
-    public ShadowViewDrawable(ShadowProperty shadowProperty, int color, float rx, float ry) {
-        this.shadowProperty = shadowProperty;
-        shadowOffset = this.shadowProperty.getShadowOffset();
-
-        this.rx = rx;
-        this.ry = ry;
-
-        paint = new Paint();
-        paint.setAntiAlias(true);
-        /**
-         * 解决旋转时的锯齿问题
-         */
-        paint.setFilterBitmap(true);
-        paint.setDither(true);
-        paint.setStyle(Paint.Style.FILL);
-        paint.setColor(color);
-        /**
-         * 设置阴影
-         */
-        paint.setShadowLayer(shadowProperty.getShadowRadius(), shadowProperty.getShadowDx(), shadowProperty.getShadowDy(), shadowProperty.getShadowColor());
-
-        drawRect = new RectF();
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        super.onBoundsChange(bounds);
-        if (bounds.right - bounds.left > 0 && bounds.bottom - bounds.top > 0) {
-            this.bounds.left = bounds.left;
-            this.bounds.right = bounds.right;
-            this.bounds.top = bounds.top;
-            this.bounds.bottom = bounds.bottom;
-            width = (int) (this.bounds.right - this.bounds.left);
-            height = (int) (this.bounds.bottom - this.bounds.top);
-
-
-            int shadowSide = shadowProperty.getShadowSide();
-            int left = (shadowSide & ShadowProperty.LEFT) == ShadowProperty.LEFT ? shadowOffset : 0;
-            int top = (shadowSide & ShadowProperty.TOP) == ShadowProperty.TOP ? shadowOffset : 0;
-            int right = width - ((shadowSide & ShadowProperty.RIGHT) == ShadowProperty.RIGHT ? shadowOffset : 0);
-            int bottom = height - ((shadowSide & ShadowProperty.BOTTOM) == ShadowProperty.BOTTOM ? shadowOffset : 0);
-
-            drawRect = new RectF(left, top, right, bottom);
-
-
-            invalidateSelf();
-
-        }
-    }
-
-    @Override
-    public void draw(@NonNull Canvas canvas) {
-        paint.setXfermode(null);
-        canvas.drawRoundRect(
-                drawRect,
-                rx, ry,
-                paint
-        );
-        paint.setXfermode(srcOut);
-        canvas.drawRoundRect(drawRect, rx, ry, paint);
-    }
-
-    public ShadowViewDrawable setColor(int color) {
-        paint.setColor(color);
-        return this;
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter cf) {
-
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.UNKNOWN;
-    }
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/Shimmer.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/Shimmer.java
deleted file mode 100644
index 6b931c1a3..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/Shimmer.java
+++ /dev/null
@@ -1,165 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.os.Build;
-import android.view.View;
-
-public class Shimmer {
-
-    public static final int ANIMATION_DIRECTION_LTR = 0;
-    public static final int ANIMATION_DIRECTION_RTL = 1;
-
-    private static final int DEFAULT_REPEAT_COUNT = ValueAnimator.INFINITE;
-    private static final long DEFAULT_DURATION = 1000;
-    private static final long DEFAULT_START_DELAY = 0;
-    private static final int DEFAULT_DIRECTION = ANIMATION_DIRECTION_LTR;
-
-    private int repeatCount;
-    private long duration;
-    private long startDelay;
-    private int direction;
-    private Animator.AnimatorListener animatorListener;
-
-    private ObjectAnimator animator;
-
-    public Shimmer() {
-        repeatCount = DEFAULT_REPEAT_COUNT;
-        duration = DEFAULT_DURATION;
-        startDelay = DEFAULT_START_DELAY;
-        direction = DEFAULT_DIRECTION;
-    }
-
-    public int getRepeatCount() {
-        return repeatCount;
-    }
-
-    public Shimmer setRepeatCount(int repeatCount) {
-        this.repeatCount = repeatCount;
-        return this;
-    }
-
-    public long getDuration() {
-        return duration;
-    }
-
-    public Shimmer setDuration(long duration) {
-        this.duration = duration;
-        return this;
-    }
-
-    public long getStartDelay() {
-        return startDelay;
-    }
-
-    public Shimmer setStartDelay(long startDelay) {
-        this.startDelay = startDelay;
-        return this;
-    }
-
-    public int getDirection() {
-        return direction;
-    }
-
-    public Shimmer setDirection(int direction) {
-
-        if (direction != ANIMATION_DIRECTION_LTR && direction != ANIMATION_DIRECTION_RTL) {
-            throw new IllegalArgumentException("The animation direction must be either ANIMATION_DIRECTION_LTR or ANIMATION_DIRECTION_RTL");
-        }
-
-        this.direction = direction;
-        return this;
-    }
-
-    public Animator.AnimatorListener getAnimatorListener() {
-        return animatorListener;
-    }
-
-    public Shimmer setAnimatorListener(Animator.AnimatorListener animatorListener) {
-        this.animatorListener = animatorListener;
-        return this;
-    }
-
-    public <V extends View & ShimmerViewBase> void start(final V shimmerView) {
-
-        if (isAnimating()) {
-            return;
-        }
-
-        final Runnable animate = new Runnable() {
-            @Override
-            public void run() {
-
-                shimmerView.setShimmering(true);
-
-                float fromX = 0;
-                float toX = shimmerView.getWidth();
-                if (direction == ANIMATION_DIRECTION_RTL) {
-                    fromX = shimmerView.getWidth();
-                    toX = 0;
-                }
-
-                animator = ObjectAnimator.ofFloat(shimmerView, "gradientX", fromX, toX);
-                animator.setRepeatCount(repeatCount);
-                animator.setDuration(duration);
-                animator.setStartDelay(startDelay);
-                animator.addListener(new Animator.AnimatorListener() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                    }
-
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        shimmerView.setShimmering(false);
-
-                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
-                            shimmerView.postInvalidate();
-                        } else {
-                            shimmerView.postInvalidateOnAnimation();
-                        }
-
-                        animator = null;
-                    }
-
-                    @Override
-                    public void onAnimationCancel(Animator animation) {
-
-                    }
-
-                    @Override
-                    public void onAnimationRepeat(Animator animation) {
-
-                    }
-                });
-
-                if (animatorListener != null) {
-                    animator.addListener(animatorListener);
-                }
-
-                animator.start();
-            }
-        };
-
-        if (!shimmerView.isSetUp()) {
-            shimmerView.setAnimationSetupCallback(new ShimmerViewHelper.AnimationSetupCallback() {
-                @Override
-                public void onSetupAnimation(final View target) {
-                    animate.run();
-                }
-            });
-        } else {
-            animate.run();
-        }
-    }
-
-    public void cancel() {
-        if (animator != null) {
-            animator.cancel();
-        }
-    }
-
-    public boolean isAnimating() {
-        return animator != null && animator.isRunning();
-    }
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShimmerViewBase.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShimmerViewBase.java
deleted file mode 100644
index 10f3f2c42..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShimmerViewBase.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package io.virtualapp.widgets;
-
-public interface ShimmerViewBase {
-
-    float getGradientX();
-
-    void setGradientX(float gradientX);
-
-    boolean isShimmering();
-
-    void setShimmering(boolean isShimmering);
-
-    boolean isSetUp();
-
-    void setAnimationSetupCallback(ShimmerViewHelper.AnimationSetupCallback callback);
-
-    int getPrimaryColor();
-
-    void setPrimaryColor(int primaryColor);
-
-    int getReflectionColor();
-
-    void setReflectionColor(int reflectionColor);
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShimmerViewHelper.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShimmerViewHelper.java
deleted file mode 100644
index df30ed3fc..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/ShimmerViewHelper.java
+++ /dev/null
@@ -1,174 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.content.res.TypedArray;
-import android.graphics.LinearGradient;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-import android.view.View;
-
-import io.virtualapp.R;
-
-public class ShimmerViewHelper {
-
-    private static final int DEFAULT_REFLECTION_COLOR = 0xFFFFFFFF;
-    private View view;
-    private Paint paint;
-    // center position of the gradient
-    private float gradientX;
-    // shader applied on the text view
-    // only null until the first global layout
-    private LinearGradient linearGradient;
-    // shader's local matrix
-    // never null
-    private Matrix linearGradientMatrix;
-    private int primaryColor;
-    // shimmer reflection color
-    private int reflectionColor;
-    // true when animating
-    private boolean isShimmering;
-    // true after first global layout
-    private boolean isSetUp;
-    // callback called after first global layout
-    private AnimationSetupCallback callback;
-
-    public ShimmerViewHelper(View view, Paint paint, AttributeSet attributeSet) {
-        this.view = view;
-        this.paint = paint;
-        init(attributeSet);
-    }
-
-    public float getGradientX() {
-        return gradientX;
-    }
-
-    public void setGradientX(float gradientX) {
-        this.gradientX = gradientX;
-        view.invalidate();
-    }
-
-    public boolean isShimmering() {
-        return isShimmering;
-    }
-
-    public void setShimmering(boolean isShimmering) {
-        this.isShimmering = isShimmering;
-    }
-
-    public boolean isSetUp() {
-        return isSetUp;
-    }
-
-    public void setAnimationSetupCallback(AnimationSetupCallback callback) {
-        this.callback = callback;
-    }
-
-    public int getPrimaryColor() {
-        return primaryColor;
-    }
-
-    public void setPrimaryColor(int primaryColor) {
-        this.primaryColor = primaryColor;
-        if (isSetUp) {
-            resetLinearGradient();
-        }
-    }
-
-    public int getReflectionColor() {
-        return reflectionColor;
-    }
-
-    public void setReflectionColor(int reflectionColor) {
-        this.reflectionColor = reflectionColor;
-        if (isSetUp) {
-            resetLinearGradient();
-        }
-    }
-
-    private void init(AttributeSet attributeSet) {
-
-        reflectionColor = DEFAULT_REFLECTION_COLOR;
-
-        if (attributeSet != null) {
-            TypedArray a = view.getContext().obtainStyledAttributes(attributeSet, R.styleable.ShimmerView, 0, 0);
-            if (a != null) {
-                try {
-                    reflectionColor = a.getColor(R.styleable.ShimmerView_reflectionColor, DEFAULT_REFLECTION_COLOR);
-                } catch (Exception e) {
-                    android.util.Log.e("ShimmerTextView", "Error while creating the view:", e);
-                } finally {
-                    a.recycle();
-                }
-            }
-        }
-
-        linearGradientMatrix = new Matrix();
-    }
-
-    private void resetLinearGradient() {
-
-        // our gradient is a simple linear gradient from textColor to reflectionColor. its axis is at the center
-        // when it's outside of the view, the outer color (textColor) will be repeated (Shader.TileMode.CLAMP)
-        // initially, the linear gradient is positioned on the left side of the view
-        linearGradient = new LinearGradient(-view.getWidth(), 0, 0, 0,
-                new int[]{
-                        primaryColor,
-                        reflectionColor,
-                        primaryColor,
-                },
-                new float[]{
-                        0,
-                        0.5f,
-                        1
-                },
-                Shader.TileMode.CLAMP
-        );
-
-        paint.setShader(linearGradient);
-    }
-
-    protected void onSizeChanged() {
-
-        resetLinearGradient();
-
-        if (!isSetUp) {
-            isSetUp = true;
-
-            if (callback != null) {
-                callback.onSetupAnimation(view);
-            }
-        }
-    }
-
-    /**
-     * content of the wrapping view's onDraw(Canvas)
-     * MUST BE CALLED BEFORE SUPER STATEMENT
-     */
-    public void onDraw() {
-
-        // only draw the shader gradient over the text while animating
-        if (isShimmering) {
-
-            // first onDraw() when shimmering
-            if (paint.getShader() == null) {
-                paint.setShader(linearGradient);
-            }
-
-            // translate the shader local matrix
-            linearGradientMatrix.setTranslate(2 * gradientX, 0);
-
-            // this is required in order to invalidate the shader's position
-            linearGradient.setLocalMatrix(linearGradientMatrix);
-
-        } else {
-            // we're not animating, remove the shader from the paint
-            paint.setShader(null);
-        }
-
-    }
-
-    public interface AnimationSetupCallback {
-        void onSetupAnimation(View target);
-    }
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/TwoGearsView.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/TwoGearsView.java
deleted file mode 100644
index 7be4c209d..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/TwoGearsView.java
+++ /dev/null
@@ -1,237 +0,0 @@
-package io.virtualapp.widgets;
-
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.util.AttributeSet;
-
-
-public class TwoGearsView extends BaseView {
-    ValueAnimator valueAnimator = null;
-    float mAnimatedValue = 0f;
-    float hypotenuse = 0f;
-    float smallRingCenterX = 0f;
-    float smallRingCenterY = 0f;
-    float bigRingCenterX = 0f;
-    float bigRingCenterY = 0f;
-    private float mWidth = 0f;
-    private Paint mPaint, mPaintAxle;
-    private Paint mPaintRing;
-    private float mPadding = 0f;
-    private float mWheelLength;
-    private int mWheelSmallSpace = 10;
-    private int mWheelBigSpace = 8;
-
-    public TwoGearsView(Context context) {
-        super(context);
-    }
-
-    public TwoGearsView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public TwoGearsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        if (getMeasuredWidth() > getHeight())
-            mWidth = getMeasuredHeight();
-        else
-            mWidth = getMeasuredWidth();
-    }
-
-
-    private void drawSmallRing(Canvas canvas) {
-
-        hypotenuse = (float) (mWidth * Math.sqrt(2));
-        smallRingCenterX = (float) ((hypotenuse / 6.f) * Math.cos(45 * Math.PI / 180f));
-        smallRingCenterY = (float) ((hypotenuse / 6.f) * Math.sin(45 * Math.PI / 180f));
-        mPaintRing.setStrokeWidth(dip2px(1.0f));
-        canvas.drawCircle(mPadding + smallRingCenterX, smallRingCenterY + mPadding, smallRingCenterX, mPaintRing);
-        mPaintRing.setStrokeWidth(dip2px(1.5f));
-        canvas.drawCircle(mPadding + smallRingCenterX, smallRingCenterY + mPadding, smallRingCenterX / 2, mPaintRing);
-    }
-
-
-    private void drawSmallGear(Canvas canvas) {
-
-        mPaint.setStrokeWidth(dip2px(1));
-
-        for (int i = 0; i < 360; i = i + mWheelSmallSpace) {
-            int angle = (int) (mAnimatedValue * mWheelSmallSpace + i);
-            float x3 = (float) ((smallRingCenterX) * Math.cos(angle * Math.PI / 180f));
-            float y3 = (float) ((smallRingCenterY) * Math.sin(angle * Math.PI / 180f));
-            float x4 = (float) ((smallRingCenterX + mWheelLength) * Math.cos(angle * Math.PI / 180f));
-            float y4 = (float) ((smallRingCenterY + mWheelLength) * Math.sin(angle * Math.PI / 180f));
-
-            canvas.drawLine(mPadding + smallRingCenterX - x4,
-                    smallRingCenterY + mPadding - y4,
-                    smallRingCenterX + mPadding - x3,
-                    smallRingCenterY + mPadding - y3,
-                    mPaint);
-
-        }
-
-    }
-
-    private void drawBigGear(Canvas canvas) {
-        bigRingCenterX = (float) ((hypotenuse / 2.f) * Math.cos(45 * Math.PI / 180f));
-        bigRingCenterY = (float) ((hypotenuse / 2.f) * Math.sin(45 * Math.PI / 180f));
-        float strokeWidth = dip2px(1.5f) / 4;
-        mPaint.setStrokeWidth(dip2px(1.5f));
-        for (int i = 0; i < 360; i = i + mWheelBigSpace) {
-            int angle = (int) (360 - (mAnimatedValue * mWheelBigSpace + i));
-            float x3 = (float) ((bigRingCenterX - smallRingCenterX) * Math.cos(angle * Math.PI / 180f));
-            float y3 = (float) ((bigRingCenterY - smallRingCenterY) * Math.sin(angle * Math.PI / 180f));
-            float x4 = (float) ((bigRingCenterX - smallRingCenterX + mWheelLength) * Math.cos(angle * Math.PI / 180f));
-            float y4 = (float) ((bigRingCenterY - smallRingCenterY + mWheelLength) * Math.sin(angle * Math.PI / 180f));
-            canvas.drawLine(bigRingCenterX + mPadding - x4 + mWheelLength * 2 + strokeWidth,
-                    bigRingCenterY + mPadding - y4 + mWheelLength * 2 + strokeWidth,
-                    bigRingCenterX + mPadding - x3 + mWheelLength * 2 + strokeWidth,
-                    bigRingCenterY + mPadding - y3 + mWheelLength * 2 + strokeWidth,
-                    mPaint);
-
-        }
-
-    }
-
-    private void drawBigRing(Canvas canvas) {
-        float strokeWidth = dip2px(1.5f) / 4;
-        mPaintRing.setStrokeWidth(dip2px(1.5f));
-        canvas.drawCircle(bigRingCenterX + mPadding + mWheelLength * 2 + strokeWidth,
-                bigRingCenterY + mPadding + mWheelLength * 2 + strokeWidth,
-                bigRingCenterX - smallRingCenterX - strokeWidth, mPaintRing);
-        mPaintRing.setStrokeWidth(dip2px(1.5f));
-        canvas.drawCircle(bigRingCenterX + mPadding + mWheelLength * 2 + strokeWidth,
-                bigRingCenterY + mPadding + mWheelLength * 2 + strokeWidth,
-                (bigRingCenterX - smallRingCenterX) / 2 - strokeWidth, mPaintRing);
-
-    }
-
-
-    private void drawAxle(Canvas canvas) {
-
-
-        for (int i = 0; i < 3; i++) {
-            float x3 = (float) ((smallRingCenterX) * Math.cos(i * (360 / 3) * Math.PI / 180f));
-            float y3 = (float) ((smallRingCenterY) * Math.sin(i * (360 / 3) * Math.PI / 180f));
-            canvas.drawLine(mPadding + smallRingCenterX,
-                    mPadding + smallRingCenterY,
-                    mPadding + smallRingCenterX - x3,
-                    mPadding + smallRingCenterY - y3, mPaintAxle);
-
-        }
-
-        for (int i = 0; i < 3; i++) {
-            float x3 = (float) ((bigRingCenterX - smallRingCenterX) * Math.cos(i * (360 / 3) * Math.PI / 180f));
-            float y3 = (float) ((bigRingCenterY - smallRingCenterY) * Math.sin(i * (360 / 3) * Math.PI / 180f));
-            canvas.drawLine(bigRingCenterX + mPadding + mWheelLength * 2,
-                    bigRingCenterY + mPadding + mWheelLength * 2,
-                    bigRingCenterX + mPadding + mWheelLength * 2 - x3,
-                    bigRingCenterY + mPadding + mWheelLength * 2 - y3,
-                    mPaintAxle);
-
-        }
-
-
-    }
-
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        mPadding = dip2px(5);
-        canvas.save();
-        canvas.rotate(180, mWidth / 2, mWidth / 2);
-        drawSmallRing(canvas);
-        drawSmallGear(canvas);
-        drawBigGear(canvas);
-        drawBigRing(canvas);
-        drawAxle(canvas);
-        canvas.restore();
-    }
-
-    private void initPaint() {
-        mPaintRing = new Paint();
-        mPaintRing.setAntiAlias(true);
-        mPaintRing.setStyle(Paint.Style.STROKE);
-        mPaintRing.setColor(Color.WHITE);
-        mPaintRing.setStrokeWidth(dip2px(1.5f));
-
-
-        mPaint = new Paint();
-        mPaint.setAntiAlias(true);
-        mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setColor(Color.WHITE);
-        mPaint.setStrokeWidth(dip2px(1));
-
-
-        mPaintAxle = new Paint();
-        mPaintAxle.setAntiAlias(true);
-        mPaintAxle.setStyle(Paint.Style.FILL);
-        mPaintAxle.setColor(Color.WHITE);
-        mPaintAxle.setStrokeWidth(dip2px(1.5f));
-        mWheelLength = dip2px(2f);
-
-
-    }
-
-    public void setViewColor(int color) {
-        mPaint.setColor(color);
-        mPaintAxle.setColor(color);
-        mPaintRing.setColor(color);
-        postInvalidate();
-    }
-
-
-    @Override
-    protected void InitPaint() {
-        initPaint();
-    }
-
-    @Override
-    protected void OnAnimationUpdate(ValueAnimator valueAnimator) {
-        mAnimatedValue = (float) valueAnimator.getAnimatedValue();
-        postInvalidate();
-    }
-
-    @Override
-    protected void OnAnimationRepeat(Animator animation) {
-
-    }
-
-    @Override
-    protected int OnStopAnim() {
-        postInvalidate();
-        return 1;
-    }
-
-
-    @Override
-    protected int SetAnimRepeatMode() {
-        return ValueAnimator.RESTART;
-    }
-
-    @Override
-    protected void AnimIsRunning() {
-
-    }
-
-    @Override
-    protected int SetAnimRepeatCount() {
-        return ValueAnimator.INFINITE;
-    }
-
-    private int dip2px(float dpValue) {
-        final float scale = getContext().getResources().getDisplayMetrics().density;
-        return (int) (dpValue * scale + 0.5f);
-    }
-
-}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/ViewHelper.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/ViewHelper.java
deleted file mode 100644
index d9db88ca9..000000000
--- a/VirtualApp/app/src/main/java/io/virtualapp/widgets/ViewHelper.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package io.virtualapp.widgets;
-
-import io.virtualapp.VApp;
-
-/**
- * @author Lody
- */
-public class ViewHelper {
-
-    public static int dip2px(float dpValue) {
-        final float scale = VApp.getApp().getResources().getDisplayMetrics().density;
-        return (int) (dpValue * scale + 0.5f);
-    }
-
-}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/BaseTextView.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/BaseTextView.java
new file mode 100644
index 000000000..464f36192
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/BaseTextView.java
@@ -0,0 +1,325 @@
+package io.virtualapp.widgets.fittext;
+
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.text.Layout;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+
+class BaseTextView extends TextView {
+    protected boolean mSingleLine = false;
+    protected boolean mIncludeFontPadding = true;
+    protected float mLineSpacingMult = 1;
+    protected float mLineSpacingAdd = 0;
+    protected int mMaxLines = Integer.MAX_VALUE;
+    protected boolean mLineEndNoSpace = true;
+    protected boolean mJustify = false;
+
+    /***
+     * 不拆分单词
+     */
+    protected boolean mKeepWord = true;
+    @SuppressWarnings("deprecation")
+    private static final int[] ANDROID_ATTRS = new int[]{
+            android.R.attr.includeFontPadding,
+            android.R.attr.lineSpacingMultiplier,
+            android.R.attr.lineSpacingExtra,
+            android.R.attr.maxLines,
+            android.R.attr.singleLine,
+            };
+
+    public BaseTextView(Context context) {
+        this(context, null);
+    }
+
+    public BaseTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        if (attrs != null) {
+            TypedArray a = context.obtainStyledAttributes(attrs, ANDROID_ATTRS);
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+                mIncludeFontPadding = a.getBoolean(a.getIndex(0),  mIncludeFontPadding);
+                mLineSpacingMult = a.getFloat(a.getIndex(1),  mLineSpacingMult);
+                mLineSpacingAdd = a.getDimensionPixelSize(a.getIndex(2),  (int) mLineSpacingAdd);
+                mMaxLines = a.getInteger(a.getIndex(3), mMaxLines);
+            }
+            mSingleLine = a.getBoolean(android.R.attr.singleLine, mSingleLine);
+            a.recycle();
+        }
+    }
+
+    public BaseTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs);
+    }
+
+    public boolean isKeepWord() {
+        return mKeepWord;
+    }
+
+    public void setKeepWord(boolean keepWord) {
+        mKeepWord = keepWord;
+    }
+
+    public boolean isJustify() {
+        return mJustify;
+    }
+
+    public void setJustify(boolean justify) {
+        mJustify = justify;
+    }
+
+    public boolean isLineEndNoSpace() {
+        return mLineEndNoSpace;
+    }
+
+    public void setLineEndNoSpace(boolean lineEndNoSpace) {
+        mLineEndNoSpace = lineEndNoSpace;
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    public boolean getIncludeFontPaddingCompat() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            return getIncludeFontPadding();
+        } else {
+            return mIncludeFontPadding;
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    public float getLineSpacingMultiplierCompat() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            return getLineSpacingMultiplier();
+        } else {
+            return mLineSpacingMult;
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    public float getLineSpacingExtraCompat() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            return getLineSpacingExtra();
+        } else {
+            return mLineSpacingAdd;
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    public int getMaxLinesCompat() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            return getMaxLines();
+        } else {
+            return mMaxLines;
+        }
+    }
+
+    @Override
+    public void setLineSpacing(float add, float mult) {
+        super.setLineSpacing(add, mult);
+        mLineSpacingAdd = add;
+        mLineSpacingMult = mult;
+    }
+
+    @Override
+    public void setIncludeFontPadding(boolean includepad) {
+        super.setIncludeFontPadding(includepad);
+        mIncludeFontPadding = includepad;
+    }
+
+    @Override
+    public void setMaxLines(int maxlines) {
+        super.setMaxLines(maxlines);
+        mMaxLines = maxlines;
+    }
+
+    @Override
+    public void setSingleLine(boolean singleLine) {
+        super.setSingleLine(singleLine);
+        mSingleLine = singleLine;
+    }
+
+
+    public int getTextWidth() {
+        return FitTextHelper.getTextWidth(this);
+    }
+
+    public int getTextHeight() {
+        return getMeasuredHeight() - getCompoundPaddingTop()
+                - getCompoundPaddingBottom();
+    }
+
+    /**
+     * 设置粗体
+     *
+     * @param bold 粗体
+     */
+    public void setBoldText(boolean bold) {
+        getPaint().setFakeBoldText(bold);
+    }
+
+    /**
+     * 设置斜体
+     *
+     * @param italic 斜体
+     */
+    public void setItalicText(boolean italic) {
+        getPaint().setTextSkewX(italic ? -0.25f : 0f);
+    }
+
+    public boolean isItalicText() {
+        return getPaint().getTextSkewX() != 0f;
+    }
+
+    public boolean isSingleLine() {
+        return mSingleLine;
+    }
+
+    public float getTextLineHeight() {
+        return getLineHeight();
+    }
+
+    public TextView getTextView() {
+        return this;
+    }
+
+    protected void onDraw(Canvas canvas) {
+        if (!mJustify || mSingleLine) {
+            super.onDraw(canvas);
+            return;
+        }
+        TextPaint paint = getPaint();
+//        paint.drawableState = getDrawableState();
+        float mViewWidth = getTextWidth();
+        if (isItalicText()) {
+            float letterW = getPaint().measureText("a");
+            mViewWidth -= letterW;
+        }
+        CharSequence text = getText();
+        Layout layout = getLayout();
+        if (layout == null) {
+            layout = FitTextHelper.getStaticLayout(this, getText(), getPaint());
+        }
+        int count = layout.getLineCount();
+        for (int i = 0; i < count; i++) {
+            int lineStart = layout.getLineStart(i);
+            int lineEnd = layout.getLineEnd(i);
+//            int top = layout.getLineTop(i);
+            float x = layout.getLineLeft(i);
+            int mLineY = layout.getTopPadding() + (i + 1) * getLineHeight();
+            CharSequence line = text.subSequence(lineStart, lineEnd);
+            if (line.length() == 0) {
+                continue;
+            }
+            if (mLineEndNoSpace) {
+                if (TextUtils.equals(line.subSequence(line.length() - 1, line.length()), " ")) {
+                    line = line.subSequence(0, line.length() - 1);
+                }
+                if (TextUtils.equals(line.subSequence(0, 1), " ")) {
+                    line = line.subSequence(1, line.length() - 1);
+                }
+            }
+            float lineWidth = getPaint().measureText(text, lineStart, lineEnd);
+            boolean needScale = i < (count - 1) && (needScale(text.subSequence(lineEnd - 1, lineEnd)));
+//            if (i < (count - 1) && needScale(line)) {
+
+            //float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, getPaint());
+//                drawScaledText(canvas, mViewWidth, mLineY, lineStart, line, width - getCompoundPaddingLeft() - getCompoundPaddingRight());
+//            } else {
+//                canvas.drawText(line, 0, line.length(), 0, mLineY, paint);
+//            }
+//            float x = getCompoundPaddingLeft();
+            if (needScale && mViewWidth > lineWidth) {
+//                float sc = mViewWidth / lineWidth;
+                //标点数
+                int clen = countEmpty(line);
+                float d = (mViewWidth - lineWidth) / clen;
+                for (int j = 0; j < line.length(); j++) {
+                    float cw = getPaint().measureText(line, j, j + 1);
+                    canvas.drawText(line, j, j + 1, x, mLineY, getPaint());
+                    x += cw;
+                    // 后面是标点
+                    if (isEmpty(line, j + 1, j + 2)) {
+                        x += d / 2;
+                    }
+                    //当前是标点
+                    if (isEmpty(line, j, j + 1)) {
+                        x += d / 2;
+                    }
+                }
+            } else {
+                canvas.drawText(line, 0, line.length(), x, mLineY, paint);
+            }
+        }
+    }
+
+    /**
+     * 共有多少个标点/空白字符
+     *
+     * @param text 内容
+     * @return 数量
+     */
+    protected int countEmpty(CharSequence text) {
+        int len = text.length();
+        int count = 0;
+        for (int i = 0; i < len; i++) {
+            if (isEmpty(text, i, i + 1)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * 是否是标点/空白字符
+     *
+     * @param c     内容
+     * @param start 开始
+     * @param end   结束
+     */
+    protected boolean isEmpty(CharSequence c, int start, int end) {
+        if (end >= c.length()) {
+            return false;
+        }
+        CharSequence ch = c.subSequence(start, end);
+        return TextUtils.equals(ch, "\t") || TextUtils.equals(ch, " ") || FitTextHelper.sSpcaeList.contains(ch);
+    }
+
+//    private void drawScaledText(Canvas canvas, int mViewWidth, int mLineY, int lineStart, CharSequence line, float lineWidth) {
+//        float x = 0;
+//        if (isFirstLineOfParagraph(lineStart, line)) {
+//            String blanks = "  ";
+//            canvas.drawText(blanks, x, mLineY, getPaint());
+//            float bw = StaticLayout.getDesiredWidth(blanks, getPaint());
+//            x += bw;
+//
+//            line = line.subSequence(3, line.length() - 3);
+//        }
+//
+//        float d = (mViewWidth - lineWidth) / line.length() - 1;
+//        for (int i = 0; i < line.length(); i++) {
+//            String c = String.valueOf(line.charAt(i));
+//            float cw = StaticLayout.getDesiredWidth(c, getPaint());
+//            canvas.drawText(c, x, mLineY, getPaint());
+//            x += cw + d;
+//        }
+//    }
+//
+//    private boolean isFirstLineOfParagraph(int lineStart, CharSequence line) {
+//        return line.length() > 3 && line.charAt(0) == ' ' && line.charAt(1) == ' ';
+//    }
+
+    /**
+     * 是否需要两端对齐
+     *
+     * @param end 结束字符
+     */
+    protected boolean needScale(CharSequence end) {
+        return TextUtils.equals(end, " ");// || !TextUtils.equals(end, "\n");
+    }
+
+}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/FitTextHelper.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/FitTextHelper.java
new file mode 100644
index 000000000..d7c785197
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/FitTextHelper.java
@@ -0,0 +1,364 @@
+package io.virtualapp.widgets.fittext;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.text.Layout;
+import android.text.SpannableStringBuilder;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.inputmethod.EditorInfo;
+import android.widget.TextView;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+/***
+ * 两端对齐
+ * 标点句尾
+ */
+class FitTextHelper {
+    protected static final float LIMIT = 0.001f;// 误差
+    private static final boolean LastNoSpace = false;
+    protected BaseTextView textView;
+
+    //region space list
+    public final static List<CharSequence> sSpcaeList = new ArrayList<>();
+
+    static {
+        sSpcaeList.add(",");
+        sSpcaeList.add(".");
+        sSpcaeList.add(";");
+        sSpcaeList.add("'");
+        sSpcaeList.add("\"");
+        sSpcaeList.add(":");
+        sSpcaeList.add("?");
+        sSpcaeList.add("~");
+        sSpcaeList.add("!");
+        sSpcaeList.add("‘");
+        sSpcaeList.add("’");
+        sSpcaeList.add("”");
+        sSpcaeList.add("“");
+        sSpcaeList.add(";");
+        sSpcaeList.add(":");
+        sSpcaeList.add(",");
+        sSpcaeList.add("。");
+        sSpcaeList.add("?");
+        sSpcaeList.add("!");
+        sSpcaeList.add("(");
+        sSpcaeList.add(")");
+        sSpcaeList.add("[");
+        sSpcaeList.add("]");
+        sSpcaeList.add("@");
+        sSpcaeList.add("/");
+        sSpcaeList.add("#");
+        sSpcaeList.add("$");
+        sSpcaeList.add("%");
+        sSpcaeList.add("^");
+        sSpcaeList.add("&");
+        sSpcaeList.add("*");
+//        sSpcaeList.add("{");
+//        sSpcaeList.add("}");
+        sSpcaeList.add("<");
+        sSpcaeList.add(">");
+//        sSpcaeList.add("/");
+//        sSpcaeList.add("\\");
+        sSpcaeList.add("+");
+        sSpcaeList.add("-");
+        sSpcaeList.add("·");
+//        sSpcaeList.add("●");
+//        sSpcaeList.add("【");
+//        sSpcaeList.add("】");
+//        sSpcaeList.add("《");
+//        sSpcaeList.add("》");
+//        sSpcaeList.add("『");
+//        sSpcaeList.add("』");
+//        sSpcaeList.add("/");
+    }
+    //endregion
+
+    protected volatile boolean mFittingText = false;
+
+    public FitTextHelper(BaseTextView textView) {
+        this.textView = textView;
+    }
+
+    /***
+     * @param textView textview
+     * @return 是否是单行
+     */
+    public static boolean isSingleLine(TextView textView) {
+        if (textView == null) return false;
+        if (textView instanceof BaseTextView) {
+            return ((BaseTextView) textView).isSingleLine();
+        }
+        if (textView == null) {
+            return false;
+        }
+        int type = textView.getInputType();
+        return (type & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+    }
+
+//    public float getLineHieght() {
+//        Paint.FontMetrics fm = textView.getPaint().getFontMetrics();
+//        float baseline = fm.descent - fm.ascent;
+//        float multi = textView.getLineSpacingMultiplierCompat();
+//        float space = textView.getLineSpacingExtraCompat();
+//        //字距
+//        return (baseline + fm.leading)
+//                * multi + space;
+//    }
+
+    /**
+     * @return 文本框的当前最大行数
+     */
+    protected int getMaxLineCount() {
+        float vspace = textView.getTextLineHeight();
+        float height = textView.getTextHeight();
+        return (int) (height / vspace);
+    }
+
+    //
+//    protected boolean isSingle(TextView textView) {
+//        int inputType = textView.getInputType();
+//        return (inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+//    }
+
+    /**
+     * 文本框的宽度
+     *
+     * @param textView 文本框
+     * @return 宽度
+     */
+    public static int getTextWidth(TextView textView) {
+        return textView.getMeasuredWidth() - textView.getCompoundPaddingLeft()
+                - textView.getCompoundPaddingRight();
+    }
+
+    /***
+     * @param text  文本
+     * @param paint 画笔
+     * @return 文本布局
+     */
+    public StaticLayout getStaticLayout(CharSequence text, TextPaint paint) {
+        return getStaticLayout(textView.getTextView(), text, paint);
+    }
+
+    /**
+     * @param textView 文本框
+     * @param text     文本
+     * @param paint    画笔
+     * @return 文本布局
+     */
+    public static StaticLayout getStaticLayout(TextView textView, CharSequence text, TextPaint paint) {
+        StaticLayout layout;
+        if (textView instanceof FitTextView) {
+            FitTextView fitTextView = (FitTextView) textView;
+            layout = new StaticLayout(text, paint, getTextWidth(textView),
+                    getLayoutAlignment(fitTextView), fitTextView.getLineSpacingMultiplierCompat(),
+                    fitTextView.getLineSpacingExtraCompat(), fitTextView.getIncludeFontPaddingCompat());
+        } else {
+            if (Build.VERSION.SDK_INT <= 16) {
+                layout = new StaticLayout(text, paint, getTextWidth(textView),
+                        getLayoutAlignment(textView), 0, 0, false);
+            } else {
+                layout = new StaticLayout(text, paint, getTextWidth(textView),
+                        getLayoutAlignment(textView), textView.getLineSpacingMultiplier(),
+                        textView.getLineSpacingExtra(), textView.getIncludeFontPadding());
+            }
+        }
+        if(isSingleLine(textView)) {
+            try {
+                Field field = StaticLayout.class.getDeclaredField("mMaximumVisibleLineCount");
+                if (field != null) {
+                    field.setAccessible(true);
+                    field.set(layout, 1);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return layout;
+    }
+
+    /**
+     * 判断内容是否在框内
+     *
+     * @param text  文本
+     * @param paint 画笔
+     * @return 没有超过框
+     */
+    protected boolean isFit(CharSequence text, TextPaint paint) {
+        // 自动换行
+        boolean mSingleLine = textView.isSingleLine();
+        int maxLines = textView.getMaxLinesCompat();
+        float multi = textView.getLineSpacingMultiplierCompat();
+        float space = textView.getLineSpacingExtraCompat();
+        space = space * multi;
+        int height = textView.getTextHeight();
+        if (!mSingleLine) {
+            if (!LastNoSpace) {
+                height += Math.round(space);
+            }
+        }
+
+        int lines = mSingleLine ? 1 : Math.max(1, maxLines);
+
+        StaticLayout layout = getStaticLayout(text, paint);
+
+        return layout.getLineCount() <= lines && layout.getHeight() <= height;
+    }
+
+    /**
+     * 调整字体大小
+     *
+     * @param oldPaint 旧画笔
+     * @param text     内容
+     * @param max      最大字体
+     * @param min      最小字体
+     * @return 适合字体大小
+     */
+    public float fitTextSize(TextPaint oldPaint, CharSequence text, float max, float min) {
+        if (TextUtils.isEmpty(text)) {
+            if (oldPaint != null) {
+                return oldPaint.getTextSize();
+            }
+            if (textView != null) {
+                return textView.getTextSize();
+            }
+        }
+        float low = min;
+        float high = max;
+        TextPaint paint = new TextPaint(oldPaint);
+        while (Math.abs(high - low) > LIMIT) {
+            paint.setTextSize((low + high) / 2.0f);
+            if (isFit(getLineBreaks(text, paint), paint)) {
+                low = paint.getTextSize();
+            } else {
+                high = paint.getTextSize();
+            }
+        }
+        return low;
+    }
+
+    /**
+     * 拆入换行符,解决中英文的换行问题
+     *
+     * @param text  内容
+     * @param paint 画笔
+     * @return 调整后的内容
+     */
+    public CharSequence getLineBreaks(
+            CharSequence text, TextPaint paint) {
+        int width = textView.getTextWidth();
+        boolean keepWord = textView.isKeepWord();
+        if (width <= 0 || keepWord)
+            return text;
+        int length = text.length();
+        int start = 0, end = 1;
+
+        SpannableStringBuilder ssb = new SpannableStringBuilder();
+        while (end <= length) {
+            CharSequence c = text.subSequence(end - 1, end);
+//            char c = text.charAt(end - 1);// cs最后一个字符
+//            boolean needCheck = false;
+            if (TextUtils.equals(c, "\n")) {// 已经换行
+                ssb.append(text, start, end);
+                start = end;
+//                needCheck = true;
+            } else {
+                float lw = paint.measureText(text, start, end);
+                if (lw > width) {// 超出宽度,退回一个位置
+                    ssb.append(text, start, end - 1);
+                    start = end - 1;
+                    if (end < length) {
+                        CharSequence c2 = text.subSequence(end - 1, end);
+                        if (!TextUtils.equals(c2, "\n"))
+                            ssb.append('\n');
+                    }
+//                    needCheck = true;
+                } else if (lw == width) {
+                    ssb.append(text, start, end);
+                    start = end;
+                    if (end < length) {
+                        CharSequence c2 = text.subSequence(end, end + 1);
+                        if (!TextUtils.equals(c2, "\n"))
+                            ssb.append('\n');
+                    }
+//                    needCheck = true;
+                } else if (end == length) {
+                    // 已经是最后一个字符
+                    ssb.append(text, start, end);
+                    start = end;
+                }
+            }
+            end++;
+        }
+        return ssb;
+    }
+
+    /***
+     * 获取文本框的布局
+     *
+     * @param textView
+     * @return
+     */
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public static Layout.Alignment getLayoutAlignment(TextView textView) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return Layout.Alignment.ALIGN_NORMAL;
+        }
+
+        Layout.Alignment alignment;
+        switch (textView.getTextAlignment()) {
+            case TextView.TEXT_ALIGNMENT_GRAVITY:
+                switch (textView.getGravity() & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.START:
+                        alignment = Layout.Alignment.ALIGN_NORMAL;
+                        break;
+                    case Gravity.END:
+                        alignment = Layout.Alignment.ALIGN_OPPOSITE;
+                        break;
+                    case Gravity.LEFT:
+                        alignment = (textView.getLayoutDirection() == TextView.LAYOUT_DIRECTION_RTL) ? Layout.Alignment.ALIGN_OPPOSITE
+                                : Layout.Alignment.ALIGN_NORMAL;
+                        break;
+                    case Gravity.RIGHT:
+                        alignment = (textView.getLayoutDirection() == TextView.LAYOUT_DIRECTION_RTL) ? Layout.Alignment.ALIGN_NORMAL
+                                : Layout.Alignment.ALIGN_OPPOSITE;
+                        break;
+                    case Gravity.CENTER_HORIZONTAL:
+                        alignment = Layout.Alignment.ALIGN_CENTER;
+                        break;
+                    default:
+                        alignment = Layout.Alignment.ALIGN_NORMAL;
+                        break;
+                }
+                break;
+            case TextView.TEXT_ALIGNMENT_TEXT_START:
+                alignment = Layout.Alignment.ALIGN_NORMAL;
+                break;
+            case TextView.TEXT_ALIGNMENT_TEXT_END:
+                alignment = Layout.Alignment.ALIGN_OPPOSITE;
+                break;
+            case TextView.TEXT_ALIGNMENT_CENTER:
+                alignment = Layout.Alignment.ALIGN_CENTER;
+                break;
+            case TextView.TEXT_ALIGNMENT_VIEW_START:
+                alignment = Layout.Alignment.ALIGN_NORMAL;
+                break;
+            case TextView.TEXT_ALIGNMENT_VIEW_END:
+                alignment = Layout.Alignment.ALIGN_OPPOSITE;
+                break;
+            case TextView.TEXT_ALIGNMENT_INHERIT:
+                //
+            default:
+                alignment = Layout.Alignment.ALIGN_NORMAL;
+                break;
+        }
+        return alignment;
+    }
+
+}
diff --git a/VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/FitTextView.java b/VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/FitTextView.java
new file mode 100644
index 000000000..82ab348ef
--- /dev/null
+++ b/VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/FitTextView.java
@@ -0,0 +1,171 @@
+package io.virtualapp.widgets.fittext;
+
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.TextView;
+
+import io.virtualapp.R;
+
+public class FitTextView extends BaseTextView {
+
+    private boolean mMeasured = false;
+    /**
+     * 不需要调整大小
+     */
+    private boolean mNeedFit = true;
+    protected float mOriginalTextSize = 0;
+    private float mMinTextSize, mMaxTextSize;
+    protected CharSequence mOriginalText;
+    /**
+     * 正在调整字体大小
+     */
+    protected volatile boolean mFittingText = false;
+    protected FitTextHelper mFitTextHelper;
+
+    public FitTextView(Context context) {
+        this(context, null);
+    }
+
+    public FitTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FitTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mOriginalTextSize = getTextSize();
+        if (attrs != null) {
+            TypedArray a = context.obtainStyledAttributes(attrs, new int[]{
+                    R.attr.ftMaxTextSize,
+                    R.attr.ftMinTextSize,
+                    });
+            mMaxTextSize = a.getDimension(0, mOriginalTextSize * 2.0f);
+            mMinTextSize = a.getDimension(1, mOriginalTextSize / 2.0f);
+            a.recycle();
+        } else {
+            mMinTextSize = mOriginalTextSize;
+            mMaxTextSize = mOriginalTextSize;
+        }
+    }
+
+    protected FitTextHelper getFitTextHelper() {
+        if (mFitTextHelper == null) {
+            mFitTextHelper = new FitTextHelper(this);
+        }
+        return mFitTextHelper;
+    }
+
+    /**
+     * @return 最小字体大小
+     */
+    public float getMinTextSize() {
+        return mMinTextSize;
+    }
+
+    /**
+     * @param minTextSize 最小字体大小
+     */
+    public void setMinTextSize(float minTextSize) {
+        mMinTextSize = minTextSize;
+    }
+
+    /**
+     * @return 最大字体大小
+     */
+    public float getMaxTextSize() {
+        return mMaxTextSize;
+    }
+
+    /**
+     * @param maxTextSize 最大字体大小
+     */
+    public void setMaxTextSize(float maxTextSize) {
+        mMaxTextSize = maxTextSize;
+    }
+
+    /**
+     * 是否需要调整字体
+     *
+     * @return
+     */
+    public boolean isNeedFit() {
+        return mNeedFit;
+    }
+
+    /**
+     * @param needFit 是否需要调整字体大小
+     */
+    public void setNeedFit(boolean needFit) {
+        mNeedFit = needFit;
+    }
+
+    @Override
+    public void setTextSize(int unit, float size) {
+        super.setTextSize(unit, size);
+        mOriginalTextSize = getTextSize();
+    }
+
+    public float getOriginalTextSize() {
+        return mOriginalTextSize;
+    }
+
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
+
+        if (widthMode == View.MeasureSpec.UNSPECIFIED
+                && heightMode == View.MeasureSpec.UNSPECIFIED) {
+            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mOriginalTextSize);
+            mMeasured = false;
+        } else {
+            mMeasured = true;
+            fitText(getOriginalText());
+        }
+    }
+
+    @Override
+    public void setText(CharSequence text, TextView.BufferType type) {
+        mOriginalText = text;
+        super.setText(text, type);
+        fitText(text);
+    }
+
+    public CharSequence getOriginalText() {
+        return mOriginalText;
+    }
+
+    /**
+     * 调整字体大小
+     *
+     * @param text 内容
+     */
+    protected void fitText(CharSequence text) {
+        if (!mNeedFit) {
+            return;
+        }
+        if (!mMeasured || mFittingText || mSingleLine || TextUtils.isEmpty(text))
+            return;
+        mFittingText = true;
+        TextPaint oldPaint = getPaint();
+        float size = getFitTextHelper().fitTextSize(oldPaint, text, mMaxTextSize, mMinTextSize);
+        super.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
+        super.setText(getFitTextHelper().getLineBreaks(text, getPaint()));
+        mFittingText = false;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+    }
+
+}
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/drawable-hdpi/ic_add_circle.png b/VirtualApp/app/src/main/res/drawable-hdpi/ic_add_circle.png
new file mode 100644
index 000000000..49b392bb9
Binary files /dev/null and b/VirtualApp/app/src/main/res/drawable-hdpi/ic_add_circle.png differ
diff --git a/VirtualApp/app/src/main/res/drawable-nodpi/about_icon_copy_right.xml b/VirtualApp/app/src/main/res/drawable-nodpi/about_icon_copy_right.xml
new file mode 100644
index 000000000..dee278839
--- /dev/null
+++ b/VirtualApp/app/src/main/res/drawable-nodpi/about_icon_copy_right.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M10.08,10.86c0.05,-0.33 0.16,-0.62 0.3,-0.87s0.34,-0.46 0.59,-0.62c0.24,-0.15 0.54,-0.22 0.91,-0.23 0.23,0.01 0.44,0.05 0.63,0.13 0.2,0.09 0.38,0.21 0.52,0.36s0.25,0.33 0.34,0.53 0.13,0.42 0.14,0.64h1.79c-0.02,-0.47 -0.11,-0.9 -0.28,-1.29s-0.4,-0.73 -0.7,-1.01 -0.66,-0.5 -1.08,-0.66 -0.88,-0.23 -1.39,-0.23c-0.65,0 -1.22,0.11 -1.7,0.34s-0.88,0.53 -1.2,0.92 -0.56,0.84 -0.71,1.36S8,11.29 8,11.87v0.27c0,0.58 0.08,1.12 0.23,1.64s0.39,0.97 0.71,1.35 0.72,0.69 1.2,0.91 1.05,0.34 1.7,0.34c0.47,0 0.91,-0.08 1.32,-0.23s0.77,-0.36 1.08,-0.63 0.56,-0.58 0.74,-0.94 0.29,-0.74 0.3,-1.15h-1.79c-0.01,0.21 -0.06,0.4 -0.15,0.58s-0.21,0.33 -0.36,0.46 -0.32,0.23 -0.52,0.3c-0.19,0.07 -0.39,0.09 -0.6,0.1 -0.36,-0.01 -0.66,-0.08 -0.89,-0.23 -0.25,-0.16 -0.45,-0.37 -0.59,-0.62s-0.25,-0.55 -0.3,-0.88 -0.08,-0.67 -0.08,-1v-0.27c0,-0.35 0.03,-0.68 0.08,-1.01zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z" />
+</vector>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/drawable-nodpi/ic_more.xml b/VirtualApp/app/src/main/res/drawable-nodpi/ic_more.xml
new file mode 100644
index 000000000..e1a17b913
--- /dev/null
+++ b/VirtualApp/app/src/main/res/drawable-nodpi/ic_more.xml
@@ -0,0 +1,7 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="32dp"
+    android:width="32dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path android:fillColor="#7f000000" android:pathData="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z" />
+</vector>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_about.png b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_about.png
new file mode 100644
index 000000000..764c260e4
Binary files /dev/null and b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_about.png differ
diff --git a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_account.png b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_account.png
new file mode 100644
index 000000000..07ca9e768
Binary files /dev/null and b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_account.png differ
diff --git a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_device.png b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_device.png
new file mode 100644
index 000000000..fb4c4e37b
Binary files /dev/null and b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_device.png differ
diff --git a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_menu_donate.png b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_menu_donate.png
new file mode 100644
index 000000000..83d414c44
Binary files /dev/null and b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_menu_donate.png differ
diff --git a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_notification.png b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_notification.png
new file mode 100644
index 000000000..eb84b5d21
Binary files /dev/null and b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_notification.png differ
diff --git a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_reboot.png b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_reboot.png
new file mode 100755
index 000000000..62598492d
Binary files /dev/null and b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_reboot.png differ
diff --git a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_settings.png b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_settings.png
new file mode 100644
index 000000000..7b18a957e
Binary files /dev/null and b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_settings.png differ
diff --git a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_user.png b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_user.png
deleted file mode 100644
index 0dc39fb0d..000000000
Binary files a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_user.png and /dev/null differ
diff --git a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_vs.png b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_vs.png
new file mode 100644
index 000000000..96d6301f0
Binary files /dev/null and b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_vs.png differ
diff --git a/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_wifi.png b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_wifi.png
new file mode 100644
index 000000000..ab483d37f
Binary files /dev/null and b/VirtualApp/app/src/main/res/drawable-xxhdpi/ic_wifi.png differ
diff --git a/VirtualApp/app/src/main/res/layout/activity_home.xml b/VirtualApp/app/src/main/res/layout/activity_home.xml
deleted file mode 100644
index da66d04eb..000000000
--- a/VirtualApp/app/src/main/res/layout/activity_home.xml
+++ /dev/null
@@ -1,152 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:fab="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@color/colorPrimaryDark">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="60dp"
-            android:layout_marginLeft="20dp"
-            android:layout_marginStart="20dp"
-            android:gravity="start"
-            android:orientation="horizontal"
-            fab:layout_heightPercent="12%">
-
-            <TextView
-                android:id="@+id/textView"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:gravity="center|start"
-                android:text="@string/app_name"
-                android:textColor="@android:color/white"
-                android:textSize="22sp" />
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:gravity="center|end"
-                android:orientation="horizontal"
-                android:visibility="invisible">
-
-                <io.virtualapp.widgets.MaterialRippleLayout
-                    android:layout_width="60dp"
-                    android:layout_height="60dp">
-
-                    <ImageView
-                        android:layout_width="match_parent"
-                        android:layout_height="match_parent"
-                        android:layout_margin="20dp"
-                        android:src="@drawable/ic_menu" />
-                </io.virtualapp.widgets.MaterialRippleLayout>
-
-            </LinearLayout>
-
-        </LinearLayout>
-
-
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
-
-            <android.support.v7.widget.RecyclerView
-                android:id="@+id/home_launcher"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:clipToPadding="false"
-                android:scrollbars="vertical"
-                tools:listitem="@layout/item_launcher_app" />
-
-            <io.virtualapp.widgets.TwoGearsView
-                android:id="@+id/pb_loading_app"
-                android:layout_width="100dp"
-                android:layout_height="100dp"
-                android:layout_gravity="center"
-                android:layout_marginBottom="30dp" />
-
-            <com.melnykov.fab.FloatingActionButton
-                android:id="@+id/home_fab"
-                android:layout_width="50dp"
-                android:layout_height="50dp"
-                android:layout_gravity="bottom|end"
-                android:layout_margin="30dp"
-                android:src="@drawable/ic_add"
-                fab:fab_colorNormal="@color/colorPrimary"
-                fab:fab_colorPressed="@color/colorPrimaryDark" />
-
-        </FrameLayout>
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/bottom_area"
-        android:layout_width="match_parent"
-        android:layout_height="60dp"
-        android:layout_gravity="bottom|center"
-        android:baselineAligned="false"
-        android:orientation="horizontal"
-        android:visibility="gone"
-        android:weightSum="1">
-
-        <LinearLayout
-            android:id="@+id/create_shortcut_area"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="0.5"
-            android:gravity="center"
-            android:orientation="horizontal">
-
-            <ImageView
-                android:layout_width="35dp"
-                android:layout_height="35dp"
-                android:src="@drawable/ic_shortcut" />
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="10dp"
-                android:layout_marginStart="10dp"
-                android:background="@color/colorPrimary"
-                android:gravity="center|start"
-                android:padding="5dp"
-                android:text="@string/create_shortcut"
-                android:textColor="@android:color/white" />
-
-        </LinearLayout>
-
-        <LinearLayout
-            android:id="@+id/delete_app_area"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="0.5"
-            android:gravity="center"
-            android:orientation="horizontal">
-
-            <ImageView
-                android:layout_width="35dp"
-                android:layout_height="35dp"
-                android:src="@drawable/ic_crash" />
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="10dp"
-                android:layout_marginStart="10dp"
-                android:background="@color/colorPrimary"
-                android:gravity="center|start"
-                android:padding="5dp"
-                android:text="@string/delete"
-                android:textColor="@android:color/white" />
-        </LinearLayout>
-
-
-    </LinearLayout>
-
-</FrameLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/activity_install.xml b/VirtualApp/app/src/main/res/layout/activity_install.xml
index 35ec53883..e705115a2 100644
--- a/VirtualApp/app/src/main/res/layout/activity_install.xml
+++ b/VirtualApp/app/src/main/res/layout/activity_install.xml
@@ -1,22 +1,51 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:orientation="vertical"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent">
+<RelativeLayout 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="10dp">
 
-    <FrameLayout
-        android:layout_weight="0.9"
-        android:layout_width="match_parent"
-        android:layout_height="0dp">
+    <TextView
+        android:id="@+id/installer_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:textColor="#000000"
+        android:textSize="18sp"
+        tools:text="install new version?" />
 
-    </FrameLayout>
+    <TextView
+        android:id="@+id/installer_progress_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:textColor="#000000"
+        android:visibility="invisible" />
 
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_weight="0.1"
-        android:layout_width="match_parent"
-        android:layout_height="0dp">
+    <ProgressBar
+        android:id="@+id/installer_loading"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_above="@id/installer_progress_text"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="10dp"
+        android:visibility="gone" />
 
-    </LinearLayout>
+    <Button
+        android:id="@+id/installer_right_button"
+        style="@style/Widget.AppCompat.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true" />
 
-</LinearLayout>
\ No newline at end of file
+    <Button
+        android:id="@+id/installer_left_button"
+        style="@style/Widget.AppCompat.Button.Borderless.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_toLeftOf="@id/installer_right_button" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/activity_list.xml b/VirtualApp/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 000000000..e40bcb385
--- /dev/null
+++ b/VirtualApp/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:fab="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <ListView
+        android:id="@+id/list"
+        style="@style/Widget.AppCompat.ListView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:id="@+id/empty_view"
+        android:gravity="center"
+        android:textSize="18sp"
+        android:textColor="#000000"
+        android:visibility="gone"
+        android:text="No Data"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/activity_loading.xml b/VirtualApp/app/src/main/res/layout/activity_loading.xml
index 99f778be2..43a5f9efe 100644
--- a/VirtualApp/app/src/main/res/layout/activity_loading.xml
+++ b/VirtualApp/app/src/main/res/layout/activity_loading.xml
@@ -2,7 +2,7 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="#77000000">
+    android:background="@color/transparent">
 
     <RelativeLayout
         android:layout_width="match_parent"
diff --git a/VirtualApp/app/src/main/res/layout/activity_location_settings.xml b/VirtualApp/app/src/main/res/layout/activity_location_settings.xml
new file mode 100644
index 000000000..7382b36a6
--- /dev/null
+++ b/VirtualApp/app/src/main/res/layout/activity_location_settings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <ListView
+        android:id="@+id/appdata_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/activity_marker.xml b/VirtualApp/app/src/main/res/layout/activity_marker.xml
new file mode 100644
index 000000000..285edcedb
--- /dev/null
+++ b/VirtualApp/app/src/main/res/layout/activity_marker.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipToPadding="true"
+    android:fitsSystemWindows="true"
+    >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <include layout="@layout/content_toolbar"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <io.virtualapp.widgets.fittext.FitTextView
+                android:id="@+id/address"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/line_height"
+                android:layout_marginLeft="6dp"
+                android:layout_marginRight="6dp"
+                android:gravity="center_vertical"
+                android:maxLines="2"
+                app:ftMaxTextSize="20sp"
+                app:ftMinTextSize="10sp"
+                tools:text="hello"/>
+
+            <com.tencent.tencentmap.mapsdk.map.MapView
+                android:id="@+id/map"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent" />
+        </LinearLayout>
+    </LinearLayout>
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/activity_splash.xml b/VirtualApp/app/src/main/res/layout/activity_splash.xml
index 5cd0c625c..ba40bcb2d 100644
--- a/VirtualApp/app/src/main/res/layout/activity_splash.xml
+++ b/VirtualApp/app/src/main/res/layout/activity_splash.xml
@@ -1,8 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<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:gravity="bottom"
+    android:orientation="vertical"
     tools:context=".splash.SplashActivity">
 
 
@@ -10,17 +12,10 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="bottom|center"
-        android:layout_marginBottom="90dp"
+        android:layout_marginBottom="20dp"
         android:text="@string/app_name"
         android:textColor="@android:color/white"
-        android:textSize="23sp" />
+        android:textSize="26sp" />
 
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom|center"
-        android:layout_marginBottom="60dp"
-        android:text="@string/splash_desc"
-        android:textSize="13sp" />
 
-</FrameLayout>
+</LinearLayout>
diff --git a/VirtualApp/app/src/main/res/layout/content_toolbar.xml b/VirtualApp/app/src/main/res/layout/content_toolbar.xml
new file mode 100644
index 000000000..0afb9c8bb
--- /dev/null
+++ b/VirtualApp/app/src/main/res/layout/content_toolbar.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.AppBarLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:theme="@style/AppTheme.AppBarOverlay">
+
+    <android.support.v7.widget.Toolbar
+        android:id="@+id/task_top_toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/actionBarSize"
+        android:background="@color/colorPrimary"
+        app:popupTheme="@style/AppTheme.PopupOverlay"
+        app:theme="@style/AppTheme.AppBarOverlay"/>
+</android.support.design.widget.AppBarLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/fragment_list_app.xml b/VirtualApp/app/src/main/res/layout/fragment_list_app.xml
index 8ebd26cf7..923bba26b 100644
--- a/VirtualApp/app/src/main/res/layout/fragment_list_app.xml
+++ b/VirtualApp/app/src/main/res/layout/fragment_list_app.xml
@@ -26,7 +26,17 @@
         android:layout_gravity="bottom|center"
         android:layout_margin="15dp"
         android:background="@drawable/sel_clone_app_btn"
-        android:text="Install to SandBox (1)"
+        android:text="@string/list_app_activity_install"
+        android:textColor="@color/desktopColorB"
         android:textSize="17sp"
         tools:ignore="HardcodedText" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/select_app_from_external"
+        android:src="@drawable/ic_add"
+        android:layout_gravity="bottom|end"
+        android:layout_marginBottom="100dp"
+        android:layout_marginRight="40dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
 </FrameLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/fragment_setup.xml b/VirtualApp/app/src/main/res/layout/fragment_setup.xml
deleted file mode 100644
index e8c1c59a9..000000000
--- a/VirtualApp/app/src/main/res/layout/fragment_setup.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<io.virtualapp.abs.percent.PercentLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                                               xmlns:app="http://schemas.android.com/apk/res-auto"
-                                               android:layout_width="match_parent"
-                                               android:layout_height="match_parent"
-                                               android:gravity="center|top"
-                                               android:orientation="vertical">
-
-    <ImageView
-        android:id="@+id/setup_ic"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_marginTop="30dp"
-        android:src="@mipmap/ic_launcher"
-        app:layout_heightPercent="45%"
-        app:layout_widthPercent="45%"/>
-
-
-    <LinearLayout
-        android:id="@+id/setup_layout_body"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:orientation="vertical">
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:text="@string/app_welcome"
-            android:textSize="25sp"
-            android:textStyle="bold"/>
-
-        <Button
-            android:id="@+id/btn_go_home"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginLeft="30dp"
-            android:layout_marginRight="30dp"
-            android:layout_marginTop="50dp"
-            android:background="@drawable/sel_guide_btn"
-            android:text="@string/setup_enter_new_world"
-            android:textColor="@android:color/white"/>
-
-    </LinearLayout>
-
-
-</io.virtualapp.abs.percent.PercentLinearLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/item_app.xml b/VirtualApp/app/src/main/res/layout/item_app.xml
index dee68d685..ad72836b4 100644
--- a/VirtualApp/app/src/main/res/layout/item_app.xml
+++ b/VirtualApp/app/src/main/res/layout/item_app.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="?attr/selectableItemBackground">
@@ -12,7 +13,9 @@
         <ImageView
             android:id="@+id/item_app_icon"
             android:layout_width="60dp"
-            android:layout_height="60dp" />
+            android:layout_height="60dp"
+            tools:src="@mipmap/ic_launcher"
+            />
 
         <TextView
             android:id="@+id/item_app_name"
@@ -20,6 +23,7 @@
             android:layout_height="match_parent"
             android:layout_marginLeft="20dp"
             android:layout_marginStart="20dp"
+            tools:text="App Label"
             android:gravity="center|start" />
 
     </LinearLayout>
diff --git a/VirtualApp/app/src/main/res/layout/item_app_manage.xml b/VirtualApp/app/src/main/res/layout/item_app_manage.xml
new file mode 100644
index 000000000..cf415bfd0
--- /dev/null
+++ b/VirtualApp/app/src/main/res/layout/item_app_manage.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="60dp"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/item_app_icon"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_marginLeft="20dp"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            tools:src="@mipmap/ic_launcher" />
+
+        <TextView
+            android:id="@+id/item_app_name"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_centerVertical="true"
+            android:layout_marginLeft="20dp"
+            android:layout_marginStart="20dp"
+            android:layout_toRightOf="@+id/item_app_icon"
+            android:gravity="center|left"
+            tools:text="App Label" />
+
+        <ImageView
+            android:id="@+id/item_app_button"
+            android:src="@drawable/ic_more"
+            android:layout_marginRight="10dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:layout_centerVertical="true" />
+    </RelativeLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/item_clone_app.xml b/VirtualApp/app/src/main/res/layout/item_clone_app.xml
index f07194729..9222bc53a 100644
--- a/VirtualApp/app/src/main/res/layout/item_clone_app.xml
+++ b/VirtualApp/app/src/main/res/layout/item_clone_app.xml
@@ -1,8 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:background="@color/desktopColorA">
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
 
     <ImageView
         android:id="@+id/item_app_checked"
@@ -28,31 +27,49 @@
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:gravity="center"
-        android:orientation="vertical"
-        android:paddingBottom="35dp"
-        android:paddingLeft="20dp"
-        android:paddingRight="20dp"
-        android:paddingTop="35dp">
+        android:layout_gravity="start|center"
+        android:gravity="start|center"
+        android:orientation="horizontal"
+        android:paddingBottom="12dp"
+        android:paddingLeft="24dp"
+        android:paddingTop="12dp">
 
         <ImageView
             android:id="@+id/item_app_icon"
-            android:layout_width="60dp"
-            android:layout_height="60dp"
-            android:layout_marginBottom="12dp" />
+            android:layout_width="32dp"
+            android:layout_height="32dp" />
 
-        <io.virtualapp.widgets.MarqueeTextView
-            android:id="@+id/item_app_name"
-            android:layout_width="wrap_content"
+        <LinearLayout
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:ellipsize="marquee"
-            android:focusableInTouchMode="true"
-            android:gravity="center"
-            android:marqueeRepeatLimit="marquee_forever"
-            android:maxLines="1"
-            android:textColor="#ffffff"
-            android:textSize="12sp" />
+            android:gravity="start|center"
+            android:orientation="vertical"
+            android:paddingLeft="10dp">
 
+            <io.virtualapp.widgets.MarqueeTextView
+                android:id="@+id/item_app_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ellipsize="marquee"
+                android:focusableInTouchMode="true"
+                android:gravity="center"
+                android:marqueeRepeatLimit="marquee_forever"
+                android:singleLine="true"
+                android:textColor="@color/desktopColorB"
+                android:textSize="14sp" />
+
+            <io.virtualapp.widgets.MarqueeTextView
+                android:id="@+id/item_app_summary"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ellipsize="marquee"
+                android:focusableInTouchMode="true"
+                android:gravity="center"
+                android:marqueeRepeatLimit="1"
+                android:singleLine="true"
+                android:textSize="12sp"
+                android:visibility="gone" />
+        </LinearLayout>
     </LinearLayout>
 
 </FrameLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/item_launcher_app.xml b/VirtualApp/app/src/main/res/layout/item_launcher_app.xml
deleted file mode 100644
index bf90c6e66..000000000
--- a/VirtualApp/app/src/main/res/layout/item_launcher_app.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:gravity="center"
-    app:cardElevation="10dp">
-
-    <io.virtualapp.widgets.LabelView
-        xmlns:lv="http://schemas.android.com/apk/res-auto"
-        android:id="@+id/item_app_space_idx"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="end"
-        android:visibility="invisible"
-        lv:lv_background_color="#3F9FE0"
-        lv:lv_gravity="TOP_RIGHT"
-        lv:lv_text="2"
-        lv:lv_text_size="12sp"/>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:orientation="vertical"
-        android:paddingBottom="35dp"
-        android:paddingLeft="20dp"
-        android:paddingRight="20dp"
-        android:paddingTop="35dp">
-
-        <io.virtualapp.widgets.LauncherIconView
-            android:id="@+id/item_app_icon"
-            android:layout_width="45dp"
-            android:layout_height="45dp"
-            android:layout_marginBottom="12dp"
-            app:pi_mask_color="#CC233333"
-            app:pi_progress="0"
-            app:pi_radius="40dp"
-            app:pi_stroke="6dp" />
-
-
-        <io.virtualapp.widgets.MarqueeTextView
-            android:id="@+id/item_app_name"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:ellipsize="marquee"
-            android:focusableInTouchMode="true"
-            android:gravity="center"
-            android:marqueeRepeatLimit="marquee_forever"
-            android:maxLines="1"
-            android:textColor="#ffffff"
-            android:textSize="12sp" />
-
-        <View
-            android:id="@+id/item_first_open_dot"
-            android:layout_width="8dp"
-            android:layout_height="8dp"
-            android:layout_marginTop="12dp"
-            android:background="@drawable/blue_circle"
-            android:visibility="invisible" />
-
-    </LinearLayout>
-
-</android.support.v7.widget.CardView>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/item_location_app.xml b/VirtualApp/app/src/main/res/layout/item_location_app.xml
new file mode 100644
index 000000000..d14e7ac6e
--- /dev/null
+++ b/VirtualApp/app/src/main/res/layout/item_location_app.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                xmlns:tools="http://schemas.android.com/tools"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="?attr/selectableItemBackground">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="60dp"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/item_app_icon"
+            android:layout_width="60dp"
+            android:layout_height="60dp"
+            tools:src="@mipmap/ic_launcher"
+            />
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/item_app_name"
+                style="@style/TextAppearance.AppCompat.Body1"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_marginLeft="20dp"
+                android:layout_marginStart="20dp"
+                android:layout_weight="1"
+                android:gravity="center|start"
+                tools:text="App Label"/>
+
+            <TextView
+                android:id="@+id/item_location"
+                style="@style/TextAppearance.AppCompat.Caption"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_marginLeft="20dp"
+                android:layout_marginStart="20dp"
+                android:layout_weight="1"
+                android:gravity="center|start"
+                tools:text="22,114"/>
+
+        </LinearLayout>
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/item_plugin_recommend.xml b/VirtualApp/app/src/main/res/layout/item_plugin_recommend.xml
new file mode 100644
index 000000000..0c669e0a7
--- /dev/null
+++ b/VirtualApp/app/src/main/res/layout/item_plugin_recommend.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="start|center"
+    android:orientation="vertical"
+    android:paddingBottom="14dp"
+    android:paddingLeft="18dp"
+    android:paddingTop="14dp">
+
+    <TextView
+        android:id="@+id/item_plugin_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="4dp"
+        android:ellipsize="marquee"
+        android:gravity="start|center"
+        android:textColor="@color/desktopColorB"
+        android:textSize="16sp" />
+
+    <TextView
+        android:id="@+id/item_plugin_summary"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/item_plugin_name"
+        android:gravity="start|center"
+        android:textSize="12sp" />
+</RelativeLayout>
diff --git a/VirtualApp/app/src/main/res/layout/item_share.xml b/VirtualApp/app/src/main/res/layout/item_share.xml
new file mode 100644
index 000000000..3df021cd2
--- /dev/null
+++ b/VirtualApp/app/src/main/res/layout/item_share.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="10dp"
+    android:background="?attr/selectableItemBackground">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="54dp"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/item_share_icon"
+            android:layout_width="30dp"
+            android:layout_height="30dp"
+            tools:src="@mipmap/ic_launcher" />
+
+        <TextView
+            android:id="@+id/item_share_name"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginLeft="10dp"
+            android:layout_marginStart="10dp"
+            android:gravity="center|start"
+            tools:text="App Label" />
+
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/item_task_manage.xml b/VirtualApp/app/src/main/res/layout/item_task_manage.xml
new file mode 100644
index 000000000..a582a0c1c
--- /dev/null
+++ b/VirtualApp/app/src/main/res/layout/item_task_manage.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="60dp"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/item_app_icon"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_marginLeft="20dp"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            tools:src="@mipmap/ic_launcher" />
+
+        <TextView
+            android:id="@+id/item_app_name"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_centerVertical="true"
+            android:layout_marginLeft="20dp"
+            android:layout_marginStart="20dp"
+            android:layout_toRightOf="@+id/item_app_icon"
+            android:gravity="center|left"
+            tools:text="App Label" />
+
+        <Button
+            android:id="@+id/item_app_button"
+            android:layout_marginRight="10dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:layout_centerVertical="true" />
+    </RelativeLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/layout/item_user.xml b/VirtualApp/app/src/main/res/layout/item_user.xml
index 559943ac6..baa6ac6c4 100644
--- a/VirtualApp/app/src/main/res/layout/item_user.xml
+++ b/VirtualApp/app/src/main/res/layout/item_user.xml
@@ -9,8 +9,7 @@
     <ImageView
         android:id="@+id/iv_icon"
         android:layout_width="60dp"
-        android:layout_height="60dp"
-        android:src="@drawable/ic_user" />
+        android:layout_height="60dp" />
 
     <TextView
         android:id="@+id/tv_title"
diff --git a/VirtualApp/app/src/main/res/menu/app_manage_menu.xml b/VirtualApp/app/src/main/res/menu/app_manage_menu.xml
new file mode 100644
index 000000000..890b9d794
--- /dev/null
+++ b/VirtualApp/app/src/main/res/menu/app_manage_menu.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        android:id="@+id/action_uninstall"
+        android:orderInCategory="100"
+        android:title="@string/app_manage_uninstall"
+        app:showAsAction="always" />
+
+    <item
+        android:id="@+id/action_repair"
+        android:orderInCategory="100"
+        android:title="@string/app_manage_repair"
+        app:showAsAction="always" />
+
+    <item
+        android:id="@+id/action_redirect"
+        android:orderInCategory="100"
+        android:title="@string/app_manage_redirect_on"
+        app:showAsAction="withText" />
+</menu>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/menu/user_menu.xml b/VirtualApp/app/src/main/res/menu/main_menu.xml
similarity index 76%
rename from VirtualApp/app/src/main/res/menu/user_menu.xml
rename to VirtualApp/app/src/main/res/menu/main_menu.xml
index 63ea87612..212449667 100644
--- a/VirtualApp/app/src/main/res/menu/user_menu.xml
+++ b/VirtualApp/app/src/main/res/menu/main_menu.xml
@@ -2,8 +2,9 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto">
     <item
-        android:id="@+id/action_refresh"
+        android:id="@+id/action_settings"
         android:orderInCategory="100"
         app:showAsAction="always"
+        android:title="@string/settings_title"
         android:icon="@drawable/ic_add"/>
 </menu>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/menu/marktet_map.xml b/VirtualApp/app/src/main/res/menu/marktet_map.xml
new file mode 100644
index 000000000..802d69dca
--- /dev/null
+++ b/VirtualApp/app/src/main/res/menu/marktet_map.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        android:id="@+id/action_clear"
+        android:title="Clear"
+        app:showAsAction="always"/>
+    <item
+        android:id="@+id/action_ok"
+        android:title="@android:string/ok"
+        app:showAsAction="always"/>
+</menu>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/mipmap-hdpi/ic_launcher.png b/VirtualApp/app/src/main/res/mipmap-hdpi/ic_launcher.png
index 597fdf14c..5926cf841 100644
Binary files a/VirtualApp/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/VirtualApp/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/VirtualApp/app/src/main/res/mipmap-mdpi/ic_launcher.png b/VirtualApp/app/src/main/res/mipmap-mdpi/ic_launcher.png
index a50e69993..5023022ee 100644
Binary files a/VirtualApp/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/VirtualApp/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/VirtualApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/VirtualApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 527711d5c..ee36bef21 100644
Binary files a/VirtualApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/VirtualApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/VirtualApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/VirtualApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index aa397ea14..7ef4c1197 100644
Binary files a/VirtualApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/VirtualApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/VirtualApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/VirtualApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index fe0198cb0..3ce611c52 100644
Binary files a/VirtualApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/VirtualApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/VirtualApp/app/src/main/res/values-en/strings.xml b/VirtualApp/app/src/main/res/values-en/strings.xml
new file mode 100644
index 000000000..4d528a2e0
--- /dev/null
+++ b/VirtualApp/app/src/main/res/values-en/strings.xml
@@ -0,0 +1,6 @@
+<resources>
+    <!--Avoid to be Override By library-->
+    <string name="app_name">VirtualXposed</string>
+    <string name="versionchecklib_confirm">Upgrade now</string>
+    <string name="list_app_access_external_storage">If you want to install apk from external storage, please give VirtualXposed the permission.</string>
+</resources>
diff --git a/VirtualApp/app/src/main/res/values-es/strings.xml b/VirtualApp/app/src/main/res/values-es/strings.xml
new file mode 100644
index 000000000..7f08c1315
--- /dev/null
+++ b/VirtualApp/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,169 @@
+<resources>
+    <!--<string name="app_name">VirtualApp</string>-->
+    <string name="app_name">VirtualXposed</string>
+    <string name="wait">Espere, por favor…</string>
+    <string name="desktop">Escritorio</string>
+    <string name="add_app">Agregar aplicación</string>
+    <string name="preparing">Abriendo aplicación…</string>
+    <string name="delete">Eliminar</string>
+    <string name="create_shortcut">Crear acceso directo</string>
+    <string name="new_user">Nuevo usuario</string>
+    <string name="enable">Activar</string>
+    <string name="save">Guardar</string>
+    <string name="save_success">¡Se ha guardado con éxito!</string>
+    <string name="manufacturer">Fabricante</string>
+    <string name="brand">Marca</string>
+    <string name="device">Dispositivo</string>
+    <string name="fake_device_info">Información de dispositivo falsa</string>
+    <string name="wifi_status">Estado de Wi-Fi</string>
+    <string name="config_device_info">Información de dispositivo</string>
+    <string name="about">Acerca de…</string>
+    <string name="clone_apps">Clonar aplicaciones</string>
+    <string name="external_storage">Almacenamiento externo</string>
+    <string name="install_d">Instalar (%d)</string>
+    <string name="install_too_much_once_time">¡No se pueden elegir más de 9 aplicaciones al mismo tiempo!</string>
+    <string name="versionchecklib_confirm">Descargar</string>
+    <string name="versionchecklib_cancel">Cancelar</string>
+    <string name="menu_virtual_location">Ubicación virtual</string>
+    <string name="menu_about">Acerca de…</string>
+    <string name="menu_reboot">Reiniciar</string>
+    <string name="installing_tips">Instalando %1$s…</string>
+    <string name="update_success_tips">¡Se actualizó %1$s con éxito!</string>
+    <string name="install_success_tips">El módulo Xposed no entrará en efecto hasta que lo haya activado en la sección Módulos de la aplicación XposedInstaller!</string>
+    <string name="install_fail_tips">La instalación de %1$s falló: %2$s</string>
+    <string name="copy_right" translatable="false">Todos los derechos reservados © %1$d</string>
+    <string name="about_page_description">VirtualXposed es una aplicación que otorga la posibilidad de utilizar un módulo Xposed sin tener root, sin desbloquear el bootloader y sin modificar la imágen del sistema.</string>
+    <string name="about_feedback_qq_title">Grupo QQ</string>
+    <string name="about_feedback_wechat_title">Grupo WeChat</string>
+    <string name="about_feedback_tips">¡El número del grupo ha sido copiado al portapaleles!</string>
+    <string name="about_version_title">Versión: %1$s</string>
+    <string name="about_donate_title">Donar</string>
+    <string name="reboot_tips_1">¡Reinicio completado con éxtio!</string>
+    <string name="reboot_tips_2">¡Reinicio completado con éxtio!</string>
+    <string name="reboot_tips_3">¡Reinicio completado con éxtio!</string>
+    <string name="meizu_device_tips_title">Detectado dispositivo Meizu: </string>
+    <string name="meizu_device_tips_content">En el SO Meizu, solo se puede utilizar la clonación de aplicaciones de VirtualXposed. Por favor instale XposedInstaller primero.</string>
+    <string name="donate_alipay">Alipay</string>
+    <string name="donate_wepay">Wechat Pay</string>
+    <string name="prompt_alipay_not_found">No se encontró Alipay</string>
+    <string name="prompt_wait">Por favor, espero un momento…</string>
+    <string name="donate_dialog_title">Sobre donar…</string>
+    <string name="donate_dialog_content">No dones a menos que seas adinerado :)</string>
+    <string name="donate_dialog_yes">Donar</string>
+    <string name="donate_dialog_no">Cancelar</string>
+    <string name="large_app_install_tips">La instalación puede demorar un tiempo, por favor sé paciente :)</string>
+    <string name="about_icon_title">Acerca de Icon</string>
+    <string name="about_icon_content">Si el nuevo icono no es de tu agrado, por favor, diseña un nuevo icono para VirtualXposed</string>
+    <string name="about_icon_yes">Aceptar</string>
+    <string name="create_shortcut_success">Acceso directo creado con éxito</string>
+    <string name="about_thanks">Gracias</string>
+    <string name="thanks_dialog_title">Gracias</string>
+    <string name="thanks_dialog_content">Gracias Cheney por proporcionar un icono increíble y gracias por el arduo trabajo de Pei, Page y otros diseñadores, que no sé el nombre. Gracias por las ideas y sugerencias que me brindadas por colaboradores. Un agradecimiento especial a YingLin por ayudarme a comprenderme. El diseño pulposo :)</string>
+    <string name="alert_for_doze_mode_title">Consejos</string>
+    <string name="alert_for_doze_mode_content">Por favor, permita que VirtualXposed se ejecute en segundo plano, de lo contrario puede que no reciba notificaciones de alguna aplicación del entorno virtual.</string>
+    <string name="alert_for_doze_mode_yes">Permitir</string>
+    <string name="alert_for_doze_mode_no">Denegar</string>
+    <string name="about_faq_title">Preguntas frecuentes</string>
+    <string name="clear_app">Borrar\ndatos</string>
+    <string name="stop_app">Forzar\ndetención</string>
+    <string name="home_menu_delete_title">Eliminar aplicación</string>
+    <string name="home_menu_delete_content">¿Desea eliminar %1$s ?</string>
+    <string name="home_menu_clear_title">Borrar datos</string>
+    <string name="home_menu_clear_content">¿Desea borrar los datos de %1$s ?</string>
+    <string name="home_menu_kill_title">Forzar detención</string>
+    <string name="home_menu_kill_content">¿Desea forzar la detención de %1$s? Hacer esto puede causar que la aplicación no funcione correctamente.</string>
+    <string name="add_app_loading_tips">Analizando paquete de la aplicación %1$s</string>
+    <string name="add_app_installing_tips">Instalando %1$s</string>
+    <string name="add_app_loading_complete">%1$s se instaló con éxito</string>
+    <string name="list_app_activity_install">Agregar a VirtualXposed</string>
+    <string name="settings_about_text">Acerca de…</string>
+    <string name="settings_task_manage_text">Administrador de tareas</string>
+    <string name="settings_app_manage_text">Administrador de aplicaciones</string>
+    <string name="settings_desktop_text">Configuración de escritorio</string>
+    <string name="app_manage_uninstall">Desinstalar</string>
+    <string name="app_manage_repair">Reparar</string>
+    <string name="settings_title">Configuración</string>
+    <string name="task_manage_uninstall">Matar proceso</string>
+    <string name="settings_reboot_title">Reiniciar VirtualXposed</string>
+    <string name="settings_reboot_content">Esta opción detendrá forzosamente todas las aplicaciones en ejecucuón en VirtualXposed. ¿Desea continuar?</string>
+    <string name="check_update">Comprobar si hay actualizaciones</string>
+    <string name="version_is_latest">Última versión instalada:</string>
+    <string name="new_version_detected">Nueva versión:</string>
+    <string name="multi_version_tip_title">Consejos de instalación</string>
+    <string name="multi_version_tips_content">Ha seleccionado una aplicación existente. ¿Desea clonar una nueva aplicación y actualizar la existente? \n(Versión instalada %1$s, versión seleccionada: %2$s)</string>
+    <string name="multi_version_multi">Clonar nueva</string>
+    <string name="multi_version_cover">Cubrir</string>
+    <string name="multi_version_upgrade">Actualizar</string>
+    <string name="multi_version_downgrade">Degradar</string>
+    <string name="app_manage_repair_failed_tips">Lo siento, la reparación a fallado, por favor, reinstala esta aplicación</string>
+    <string name="app_manage_repairing">Reparando</string>
+    <string name="app_manage_repair_success_title">Consejos de reparación</string>
+    <string name="app_manage_repair_success_content">La reparación se realizó con éxito, por favor, detenga forzozamente VirtualXposed para que los cambios surtan efecto.</string>
+    <string name="app_manage_repair_reboot_now">Reiniciar ahora</string>
+    <string name="wallpaper_too_big_tips">La imágen de fondo es muy grande y hará mas lento el inicio del entorno, por favor, seleccione otra imágen de un tamaño adecuado.</string>
+    <string name="create_shortcut_already_exist">El acceso directo ya existe</string>
+    <string name="start_app_failed">Abrir aplicación: %1$s falló.</string>
+    <string name="app_manage_redirect_on">Redirección de abrir almacenamiento</string>
+    <string name="app_manage_redirect_off">Desactivar redirección de almacenamiento</string>
+    <string name="app_manage_redirect_desc" />
+    <string name="app_manage_redirect_on_confirm">Abrir, confirmar</string>
+    <string name="app_manage_redirect_off_confirm">Cerrar, confirmar</string>
+    <string name="shared_to_vxp">Compartir en aplicación de VirtualXposed</string>
+    <string name="shared_to_vxp_failed">Compartir falló, por favor intentar nuevamente.</string>
+    <string name="app_installer_label">Agregar a VirtualXposed</string>
+    <string name="install_complete">Instalación terminada</string>
+    <string name="install_fail">Fallo en la instalación: : %1$s</string>
+    <string name="install">Instalar</string>
+    <string name="install_package">Instalar nueva aplicación: %s</string>
+    <string name="install_package_version_tips">Ha seleccionado una aplicación existente, ¿desea instalarla? \n(Versión instalada %1$s, versión seleccionada: %2$s)</string>
+    <string name="settings_add_app_summary">Por favor, agregue tanto la aplicación como el módulo Xposed a VirtualXposed primero, de lo contario el módulo Xposed no surtirá efecto.</string>
+    <string name="settings_advance">Configuración avanzada</string>
+    <string name="advance_settings_hide_settings">Ocultar ícono de configuración en escritorio</string>
+    <string name="advance_settings_disable_installer">Desactivar instalación de aplicaciones para el sistema</string>
+    <string name="advance_settings_hide_settings_summary">Si puede ingresar a la configuración a través de un botón menú en la actividad principal, puede ocultarlo, de lo contrario puede que no tenga la posibilidad de ingresar a la configuración.\n(Reiniciar VirtualXposed para que los cambios surtan efecto)</string>
+    <string name="advance_settings_disable_installer_summary">No mostrar el instalador de VirtualXposed cuando se selecciona un archivo apk en el sistema</string>
+    <string name="advance_settings_directly_back">Retorno directo</string>
+    <string name="advance_settings_directly_back_summary">Retornar al lanzador del sistema en vez del lanzador vitual cuando se está en una aplicación virtualizada.\n(Reiniciar VirtualXposed para que los cambios surtan efecto)</string>
+    <string name="install_self_eggs">Chico, tu idea es prometedora :)</string>
+    <string name="about_feedback_tel_title">Grupo de Telegram: %1$s</string>
+    <string name="advance_settings_install_gms">Instalar / Desinstalar servicios de Google</string>
+    <string name="advance_settings_copy_file">Copiar archivo</string>
+    <string name="about_website_title">Sitio web oficial</string>
+    <string name="about_feedback_title">Comentarios</string>
+    <string name="about_feedback_hint">Especifique el modelo, la versión del sistema, el complemento Xposed utilizado y la versión de la aplicación correspondiente, y luego describa con el mayor detalle posible el problema que está experimentando, de lo contrario, se considera como un comentario no válido.\n</string>
+    <string name="settings_plugin_recommend">Módulo Xposed recomendado</string>
+    <string name="advance_settings_disable_resident_notification">Desactivar las notificaciones de residentes</string>
+    <string name="advance_settings_disable_resident_notification_summary">Después de cerrar la barra de notificaciones residentes, VirtualXposed podría ser eliminado fácilmente por el sistema, lo que provocaría problemas como la imposibilidad de recibir mensajes, y si puede asegurarse de que VirtualXposed no muera, puede intentarlo. En resumen, operación cuidadosa! (Efectivo después de reiniciar VirtualXposed)</string>
+    <string name="settings_module_manage">Gestión de módulos</string>
+    <string name="settings_module_manage_summary">Encienda o apague el módulo Xposed (el módulo Xposed debe encenderse manualmente después de la instalación para que tenga efecto)</string>
+    <string name="xposed_installer_not_found">Instalador Xposed no encontrado, por favor agrégalo a VirtualXposed!</string>
+    <string name="advance_settings_allow_fake_signature_summary">Cuando está habilitado, puede instalar apk que no tiene firmas (Generalmente se usa en apk craqueado, Por favor, preste atención a la seguridad)</string>
+    <string name="advance_settings_allow_fake_signature">Permitir la instalación de apk sin firmas.</string>
+    <string name="permission_tip_title">Important Tips:</string>
+    <string name="permission_tips_content">%1$s no admite permisos de tiempo de ejecución, debe otorgar los permisos que necesita de antemano, permita todos los permisos que solicitará en el siguiente paso, de lo contrario, puede que no funcione correctamente.</string>
+    <string name="permission_tips_confirm">Yo se</string>
+    <string name="permission_denied_tips_content">Aplicación: %1$s no otorga el permiso necesario, si no funciona correctamente, vaya a la administración de permisos de su dispositivo y otórguele permisos.</string>
+    <string name="list_app_access_external_storage">Si desea instalar apk desde el almacenamiento externo, dele el permiso a VirtualXposed.</string>
+    <string name="install_gms_title"></string>
+    <string name="install_gms_content">El servicio de Google es compatible con microG, VirtualXposed está a punto de descargar un archivo (2M) y puede consumir más batería, ¿le gustaría instalarlo?</string>
+    <string name="install_gms_fail_title">Instalación fallida</string>
+    <string name="install_gms_fail_content">Instalar Google Service falló automáticamente, también puede instalarlo manualmente.</string>
+    <string name="install_gms_fail_ok">Manual de instalación</string>
+    <string name="uninstall_gms_title">Desinstalar el servicio de Google</string>
+    <string name="uninstall_gms_content">¿Desea desinstalar el servicio de Google? puedes reinstalarlo más tarde.</string>
+    <string name="uninstall_gms_ok">Desinstalar, confirmar</string>
+    <string name="install_gms_alreay_installed">El servicio de Google ha sido instalado.</string>
+    <string name="install_gms_success">¡El servicio de Google se ha instalado con éxito!</string>
+    <string name="uninstall_gms_success">El servicio de Google ha sido desinstalado con éxito.</string>
+    <string name="donate_choose_title">Forma de donar</string>
+    <string name="donate_bitconins_tips">Mi dirección de bitconins ha sido copiada al portapapeles :)</string>
+    <string name="advance_settings_disable_xposed_summary">Cuando está deshabilitado, todo el módulo Xposed no tendrá efecto.</string>
+    <string name="advance_settings_disable_xposed">Deshabilitar Xposed</string>
+    <string name="prepare_xposed_installer">Preparación del entorno Xposed, espere ...</string>
+    <string name="settings_file_manage_text">File Manage</string>
+    <string name="install_file_manager_tips">File Manager es compatible con Amaze File Manager, descarga (aproximadamente 3M) e instálalo ahora.</string>
+    <string name="settings_permission_manage_text">Permiso Administrar</string>
+    <string name="install_permission_manager_tips">Permission Manage es implementado por XPrivacyLua, descarga (aproximadamente 1.7M) e instálalo ahora?</string>
+    <string name="advance_settings_enable_launcher_summary">Enable Launcher</string>
+    <string name="advance_settings_enable_launcher">Enable Launcher</string>
+</resources>
diff --git a/VirtualApp/app/src/main/res/values-fr/strings.xml b/VirtualApp/app/src/main/res/values-fr/strings.xml
new file mode 100644
index 000000000..5a6d6371e
--- /dev/null
+++ b/VirtualApp/app/src/main/res/values-fr/strings.xml
@@ -0,0 +1,166 @@
+<resources>
+    <!--<string name="app_name">VirtualApp</string>-->
+    <string name="app_name">VirtualXposed</string>
+    <string name="wait">Patienter..</string>
+    <string name="add_app">Ajouter appli</string>
+    <string name="preparing">Ouvrir appli…</string>
+    <string name="delete">Supprimer</string>
+    <string name="create_shortcut">Créer raccourcis</string>
+    <string name="new_user">Nouvel utilisateur</string>
+    <string name="enable">Activer</string>
+    <string name="save">Sauvegarder</string>
+    <string name="save_success">Sauvegarde accomplie!</string>
+    <string name="manufacturer">Fabricant</string>
+    <string name="brand">Marque</string>
+    <string name="device">Périphérique</string>
+    <string name="fake_device_info">Infos périphérique trompeur</string>
+    <string name="wifi_status">Status WIFI</string>
+    <string name="config_device_info">Infos périphérique</string>
+    <string name="about">A propos</string>
+    <string name="clone_apps">Cloner Applis</string>
+    <string name="external_storage">Stockage externe</string>
+    <string name="install_d">Installation (%d)</string>
+    <string name="install_too_much_once_time">Pas plus de 9 applis ne peuvent être choisis en même temps!</string>
+    <string name="versionchecklib_confirm">Téléchargement</string>
+    <string name="versionchecklib_cancel">Annuler</string>
+    <string name="menu_virtual_location">Localisation trompeuse</string>
+    <string name="menu_about">A propos</string>
+    <string name="menu_reboot">Redémarrage</string>
+    <string name="installing_tips">Installation %1$s…</string>
+    <string name="update_success_tips">Mise à jour de %1$s réussie!</string>
+    <string name="install_success_tips">Le module Xposed ne prendra pas effet tant que vous ne l\'aurez pas activé dans les réglages de l\'installateur Xposed.</string>
+    <string name="install_fail_tips">Installation de %1$s échouée: %2$s</string>
+    <string name="copy_right" translatable="false" >Droits d\'utilisations © %1$d</string>
+    <string name="about_page_description">VirtualXposed est une appli qui fournie la possibilité d\'utiliser le module Xposed sans avoir besoin d\'accès raçine, débloquer l\'image de démarrage, ou modifier l\'image système.</string>
+    <string name="about_feedback_qq_title">Groupe QQ Group (Toucher pour copier le numéro de groupe)</string>
+    <string name="about_feedback_wechat_title">Groupe WeChat (Toucher pour copier)</string>
+    <string name="about_feedback_tips">Le numéro du groupe à été copié dans le presse-papier!</string>
+    <string name="about_version_title">Version: %1$s</string>
+    <string name="about_donate_title">Don</string>
+    <string name="reboot_tips_1">Redémarrage réussi!</string>
+    <string name="reboot_tips_2">Redémarrage réussi!</string>
+    <string name="reboot_tips_3">Redémarrage réussi!</string>
+    <string name="meizu_device_tips_title">Détecter périphérique Meizu: </string>
+    <string name="donate_alipay">Alipay</string>
+    <string name="donate_wepay">Paiement Wechat</string>
+    <string name="prompt_alipay_not_found">Alipay non trouvé.</string>
+    <string name="prompt_wait">Patienter juste un moment…</string>
+    <string name="donate_dialog_title">A propos des dons</string>
+    <string name="donate_dialog_content">Ne me faites pas de dons si vous n\'êtes pas riche :)</string>
+    <string name="donate_dialog_yes">Don</string>
+    <string name="donate_dialog_no">Annuler</string>
+    <string name="large_app_install_tips">L\'installation peut prendre un certain temps, merci d\'être patient… :)</string>
+    <string name="about_icon_title">A propos de l\'icône</string>
+    <string name="about_icon_content">Si vous n\'aimez pas la nouvelle icône, vous êtes toujours le bienvenue pour produire une nouvelle icône pour VirtualXposed ;)</string>
+    <string name="about_icon_yes">OK</string>
+    <string name="create_shortcut_success">La création du raccourcis a été réussie!</string>
+    <string name="about_thanks">Remerciements</string>
+    <string name="thanks_dialog_title">Merci</string>
+    <string name="thanks_dialog_content" >Merci à Cheney d\'avoir fournis une icône magnifique, et merci à vous Pei, Peggy, et beaucoup d\'autres de la liste, pour votre grand travail. Merci pour vos idés et suggestions que vous avez fournis, et un remerciement tout à fais spécial à YingLin pour avoir pris le temps de m\'aider à achever mon interface :)</string>
+    <string name="alert_for_doze_mode_title">Indices</string>
+    <string name="alert_for_doze_mode_content">Merci de permettre à VirtualXposed de fonctionner en fond de tâche, vous ne pourriez cependant ne pas recevoir de notifications de certaines applis virtuelles.</string>
+    <string name="alert_for_doze_mode_yes">Permettre</string>
+    <string name="alert_for_doze_mode_no">Refuser</string>
+    <string name="about_faq_title">FAQ</string>
+    <string name="clear_app">Clear\nDonnées</string>
+    <string name="stop_app">Force\nStopper</string>
+    <string name="home_menu_delete_title">Supprimer Appli</string>
+    <string name="home_menu_delete_content">Voulez-vous supprimer %1$s ?</string>
+    <string name="home_menu_clear_content" >Voulez-vous éffacer les données de %1$s ?</string>
+    <string name="home_menu_kill_title">Forcer l\'arrêt</string>
+    <string name="home_menu_kill_content">Voulez-vous forcer l\'arrêt de %1$s ? Cela peut entraîner un fonctionnement inattendu.</string>
+    <string name="add_app_loading_tips">Paquet d\'analyse pour %1$s…</string>
+    <string name="add_app_installing_tips">Installation de %1$s…</string>
+    <string name="add_app_loading_complete">%1$s installation accomplie!</string>
+    <string name="list_app_activity_install">Ajouter à VirtualXposed</string>
+    <string name="settings_about_text">A propos de</string>
+    <string name="settings_task_manage_text">Gérrer Tâches</string>
+    <string name="settings_app_manage_text">Gérrer Applis</string>
+    <string name="settings_desktop_text">Réglages Bureau</string>
+    <string name="app_manage_uninstall">Désinstaller</string>
+    <string name="app_manage_repair">Réparrer</string>
+    <string name="settings_title">Réglages</string>
+    <string name="task_manage_uninstall">Tuer Processus</string>
+    <string name="settings_reboot_title">Redémarrer VirtualXposed</string>
+    <string name="settings_reboot_content">Ceci tuera toutes les applications qui s\'exécutent actuellement dans VirtualXposed. Redémarrer quand même?</string>
+    <string name="check_update">Vérifier mises à jours</string>
+    <string name="version_is_latest">La dernière version est installée.</string>
+    <string name="new_version_detected">Nouvelle Version:</string>
+    <string name="multi_version_tip_title">Conseils d\'installation</string>
+    <string name="multi_version_tips_content">Vous avez choisi une application existante. Voulez-vous l\'installer en tant qu\'application séparée ou simplement mettre à jour l\'application existante ? \n(Version installée %1$s, Version sélectionnée : %2$s)</string>
+    <string name="multi_version_multi">En installer une autre</string>
+    <string name="multi_version_cover">Par-dessus</string>
+    <string name="multi_version_upgrade">Mise à jour</string>
+    <string name="multi_version_downgrade">Rétrogradage</string>
+    <string name="app_manage_repair_failed_tips" >Désolé, réparation échouée; veuillez installer à nouveau l\'appli.</string>
+    <string name="app_manage_repairing">Réparration…</string>
+    <string name="app_manage_repair_success_title">Conseils de réparation</string>
+    <string name="app_manage_repair_success_content">Réparration éffectuée avec succès; merci de forcer l\'arrêt de VirtualXposed pour que le changement soit pris en compte.</string>
+    <string name="app_manage_repair_reboot_now">Redémarrer maintenant</string>
+    <string name="wallpaper_too_big_tips">L\'image du fond d\'écran est trop grande et ralentira le temps de démarrage, veuillez choisir une image correcte..</string>
+    <string name="create_shortcut_already_exist">Ce raccourcis éxiste déjà</string>
+    <string name="start_app_failed">Ouvrir appli: %1$s échoué.</string>
+    <string name="app_manage_redirect_on">Réacheminement de stockage ouvert</string>
+    <string name="app_manage_redirect_off">Désactiver le réacheminement du stockage</string>
+    <string name="app_manage_redirect_desc">La redirection de stockage redirige l\'accès d\'une application de la vraie carte SD vers une carte SD virtuelle, ce qui empêchera certaines applications de créer des répertoires dans votre vraie carte SD ; cependant, une fois la redirection de stockage activée, l\'application ne peut plus accéder à la vraie carte SD. Veuillez le sélectionner lorsque vous y êtes invité.</string>
+    <string name="app_manage_redirect_on_confirm">Ouvrir, Confirmer</string>
+    <string name="app_manage_redirect_off_confirm">Fermer, Confirmer</string>
+    <string name="shared_to_vxp">Partager à l\'appli dans VirtualXposed</string>
+    <string name="shared_to_vxp_failed">Échec du partage, merci de réessayer à nouveau.</string>
+    <string name="app_installer_label">Ajouter à VirtualXposed</string>
+    <string name="install_complete">Installation complête.</string>
+    <string name="install_fail">Installation échouée: %1$s</string>
+    <string name="install">Installation</string>
+    <string name="install_package">Installation nouvelle appli: %s</string>
+    <string name="install_package_version_tips">Vous avez choisi une application existante, \n(Version installée %1$s, version sélectionnée : %2$s). L\'installer?</string>
+    <string name="settings_add_app_summary">Veuillez d\'abord ajouter l\'application et le module Xposed à VirtualXposed, sinon le module Xposed ne prendra pas effet.</string>
+    <string name="settings_advance">Réglages avancés</string>
+    <string name="advance_settings_hide_settings">Masquer le boutton réglages sur le bureau</string>
+    <string name="advance_settings_disable_installer">Désactiver l\'installateur apk pour le système</string>
+    <string name="advance_settings_hide_settings_summary">Si vous pouvez saisir les paramètres à l\'aide de la touche de menu de l\'activité principale, vous pouvez les masquer ; sinon, vous ne pourrez peut-être pas saisir les paramètres ! \n(Redémarrer VirtualXposed pour prendre en compte le changement)..)</string>
+    <string name="advance_settings_disable_installer_summary">Ne pas afficher l\'installateur de VirtualXposed lorsque vous choisissez le fichier apk dans le système.</string>
+    <string name="advance_settings_directly_back">Directement en arrière</string>
+    <string name="advance_settings_directly_back_summary">Revenir au lanceur système au lieu du lanceur virtuel dans une application virtuelle.\n(Redémarrer VirtualXposed pour prendre en compte le changement.)</string>
+    <string name="install_self_eggs">Mec, Ton idée est prometteuse. :)</string>
+    <string name="advance_settings_install_gms">Installer / Désinstaller les services Google</string>
+    <string name="about_feedback_tel_title">Group Telegram: %1$s</string>
+    <string name="advance_settings_copy_file">Copier fichier</string>
+    <string name="about_website_title">Site web officiel</string>
+    <string name="about_feedback_title">Retour utilisateurs</string>
+    <string name="about_feedback_hint">Veuillez spécifier le modèle de votre téléphone, la version Android, le plugin Xposed et la version de l\'application correspondante, puis décrivez (avec le plus de détails possible) le problème que vous rencontrez, sinon il est considéré comme un feedback invalide (parce que nous ne pouvons pas résoudre efficacement le problème avec seulement \"cette application plante\"). ;) )</string>
+    <string name="settings_plugin_recommend">Module Xposed recommandé</string>
+    <string name="advance_settings_disable_resident_notification">Désactiver la notification de résident</string>
+    <string name="advance_settings_disable_resident_notification_summary">Désactivez la notification de résident de VirtualXposed. (Lorsqu\'il est désactivé, VirtualXposed peut être fréquemment tué par le système. Soyez prudents !)</string>
+    <string name="settings_module_manage">Gérrer Module Xposed</string>
+    <string name="settings_module_manage_summary">Activer/Désactiver le module Xposed (Vous devez l\'activer manuellement pour qu\'il prenne effet.)</string>
+    <string name="xposed_installer_not_found">Xposed Installer n\'a pas été trouvé, veuillez d\'abord l\'ajouter à VirtualXposed !</string>
+    <string name="advance_settings_allow_fake_signature_summary">Lorsqu\'il est activé, vous pouvez installer apk qui n\'a pas de signatures (Habituellement utilisé dans les cracks d\'apks, veuillez faire attention)</string>
+    <string name="advance_settings_allow_fake_signature">Permettre l\'installation d\'apk sans signatures.</string>
+    <string name="permission_tip_title">Important Tips:</string>
+    <string name="permission_tips_content">%1$s ne supporte pas l\'autorisation d\'exécution, vous devez donner les autorisations dont il a besoin à l\'avance, veuillez autoriser toutes les permissions qu\'il demandera à l\'étape suivante, sinon cela pourrait ne pas fonctionner correctement.</string>
+    <string name="permission_tips_confirm">Je sais</string>
+    <string name="permission_denied_tips_content">App: %1$ s n\'accorde pas l\'autorisation nécessaire, si cela ne fonctionne pas correctement, veuillez aller à la gestion des permissions de votre appareil et lui donner des permissions.</string>
+    <string name="list_app_access_external_storage">Si vous voulez installer apk à partir d\'un espace de stockage externe, veuillez donner à VirtualXposed la permission.</string>
+    <string name="install_gms_title" />
+    <string name="install_gms_content">Le service Google est pris en charge par microG, VirtualXposed est sur le point de télécharger un fichier (2M), et il peut consommer plus de batterie, souhaitez-vous l\'installer?</string>
+    <string name="install_gms_fail_title">Installation échouée</string>
+    <string name="install_gms_fail_content">L\'installation de Google Service a échoué automatiquement, vous pouvez également l\'installer manuellement.</string>
+    <string name="install_gms_fail_ok">Installation manuelle</string>
+    <string name="uninstall_gms_title">Désinstaller le service Google</string>
+    <string name="uninstall_gms_content">Voulez-vous désinstaller Google Service? vous pouvez le réinstaller plus tard.</string>
+    <string name="uninstall_gms_ok">Désinstaller, confirmer</string>
+    <string name="install_gms_alreay_installed">Le service Google a été installé.</string>
+    <string name="install_gms_success">Le service Google a été installé avec succès !!</string>
+    <string name="uninstall_gms_success">Le service Google a été désinstallé avec succès !!</string>
+    <string name="donate_choose_title">Façon de faire un don</string>
+    <string name="donate_bitconins_tips">Mon adresse bitconins a été copiée dans le presse-papiers :)</string>
+    <string name="advance_settings_disable_xposed_summary">Lorsqu\'il est désactivé, tous les modules Xposed ne prennent pas effet.</string>
+    <string name="advance_settings_disable_xposed">Désactiver Xposed</string>
+    <string name="prepare_xposed_installer">Préparation de l\'environnement Xposed, veuillez patienter …</string>
+    <string name="settings_file_manage_text">File Manage</string>
+    <string name="install_file_manager_tips">Gestionnaire de fichiers est pris en charge par Amaze File Manager, télécharger (environ 3M) et l\'installer maintenant?</string>
+    <string name="settings_permission_manage_text">Permission Gérer</string>
+    <string name="install_permission_manager_tips">Permission Manage est mis en œuvre par XPrivacyLua, télécharger (environ 1,7M) et l\'installer maintenant?</string>
+    <string name="advance_settings_enable_launcher_summary">Enable Launcher</string>
+    <string name="advance_settings_enable_launcher">Enable Launcher</string>
+</resources>
diff --git a/VirtualApp/app/src/main/res/values-pt-rBR/strings.xml b/VirtualApp/app/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000..b3892279a
--- /dev/null
+++ b/VirtualApp/app/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,170 @@
+<resources>
+    
+    <string name="app_name">VirtualXposed</string>
+    <string name="vxp">VirtualXposed</string>
+    <string name="wait">Por favor, espere…</string>
+    <string name="desktop">Área de Trabalho</string>
+    <string name="add_app">Adicionar App</string>
+    <string name="preparing">Abrindo app…</string>
+    <string name="delete">Excluir</string>
+    <string name="create_shortcut">Criar atalho</string>
+    <string name="new_user">Novo usuário</string>
+    <string name="enable">Habilitar</string>
+    <string name="save">Salvar</string>
+    <string name="save_success">Salvo com sucesso!</string>
+    <string name="manufacturer">Fabricante</string>
+    <string name="brand">Marca</string>
+    <string name="device">Dispositivo</string>
+    <string name="fake_device_info">Informações falsas do dispositivo</string>
+    <string name="wifi_status">Status Wifi</string>
+    <string name="config_device_info">Informação do dispositivo</string>
+    <string name="about">Sobre</string>
+    <string name="clone_apps">Clone Apps</string>
+    <string name="external_storage">Armazenamento externo</string>
+    <string name="install_d">Instalar (%d)</string>
+    <string name="install_too_much_once_time">Não mais do que 9 aplicativos podem ser escolhidos por vez!</string>
+    <string name="versionchecklib_confirm">Baixar</string>
+    <string name="versionchecklib_cancel">Cancelar</string>
+    <string name="menu_virtual_location">Localização virtual</string>
+    <string name="menu_about">Sobre</string>
+    <string name="menu_reboot">Reinicie</string>
+    <string name="installing_tips">Instalando %1$s…</string>
+    <string name="update_success_tips">Atualização com sucesso do %1$s!</string>
+    <string name="install_success_tips">O módulo Xposed não terá efeito até que você o habilite na configuração do módulo XposedInstaller.</string>
+    <string name="install_fail_tips">A instalação do %1$s falhou: %2$s</string>
+    <string name="copy_right" translatable="false">Copyrights © %1$d</string>
+    <string name="about_page_description">O VirtualXposed é um aplicativo que fornece a capacidade de usar o módulo Xposed sem precisar de acesso root, desbloquear o bootloader ou modificar a imagem do sistema.</string>
+    <string name="about_feedback_qq_title">Grupo QQ (Clique para copiar o número do grupo)</string>
+    <string name="about_feedback_wechat_title">Grupo WeChat (Clique para copiar)</string>
+    <string name="about_feedback_tips">O número do grupo foi copiado para a área de transferência!</string>
+    <string name="about_version_title">Versão: %1$s</string>
+    <string name="about_donate_title">Doar</string>
+    <string name="reboot_tips_1">Reinício com sucesso!</string>
+    <string name="reboot_tips_2">Reinício com sucesso!</string>
+    <string name="reboot_tips_3">Reinício com sucesso!</string>
+    <string name="meizu_device_tips_title">Detectado o dispositivo Meizu:</string>
+    <string name="meizu_device_tips_content">Nos telefones Meizu OS, você só pode adicionar um aplicativo ao VirtualXposed clonando um aplicativo existente já instalado (não é possível instalar um aplicativo no VirtualXposed por meio do sideload de um apk). Você também deve instalar o XposedInstaller manualmente, pois, ao contrário das instalações em outros telefones Android, ele não está embutido no VirtualXposed ao instalar no Meizu OS. Por favor, instale o XposedInstaller primeiro.</string>
+    <string name="donate_alipay">Alipay</string>
+    <string name="donate_wepay">Wechat Pay</string>
+    <string name="prompt_alipay_not_found">Alipay não encontrado.</string>
+    <string name="prompt_wait">Espere só um momento...</string>
+    <string name="donate_dialog_title">Sobre Doar</string>
+    <string name="donate_dialog_content">Não me doe se você não é rico :)</string>
+    <string name="donate_dialog_yes">Doar</string>
+    <string name="donate_dialog_no">Cancelar</string>
+    <string name="large_app_install_tips">A instalação pode demorar um pouco, por favor, seja paciente... :)</string>
+    <string name="about_icon_title">Sobre o ícone</string>
+    <string name="about_icon_content">Se você não gosta do meu novo ícone, você é sempre bem-vindo para projetar um novo ícone para o VirtualXposed ;)</string>
+    <string name="about_icon_yes">Está bem</string>
+    <string name="create_shortcut_success">Atalho criado com sucesso!</string>
+    <string name="about_thanks">Obrigado</string>
+    <string name="thanks_dialog_title">obrigado</string>
+    <string name="thanks_dialog_content">Obrigado Cheney por fornecer o ícone incrível, e obrigado Pei, Peggy, e muitos outros para listar, pelo seu trabalho duro. Obrigado pelas idéias e sugestões que todos vocês forneceram, e agradecimentos especiais à YingLin por dedicar um tempo para me ajudar a alcançar meu design :)</string>
+    <string name="alert_for_doze_mode_title">Dicas</string>
+    <string name="alert_for_doze_mode_content">Por favor, permita que o VirtualXposed seja executado em segundo plano, caso contrário você não poderá receber notificações de alguns aplicativos virtuais.</string>
+    <string name="alert_for_doze_mode_yes">Permitir</string>
+    <string name="alert_for_doze_mode_no">Negar</string>
+    <string name="about_faq_title">Perguntas frequentes</string>
+    <string name="clear_app">Limpar\ndados</string>
+    <string name="stop_app">Força\nParar</string>
+    <string name="home_menu_delete_title">Excluir aplicativo</string>
+    <string name="home_menu_delete_content">Você deseja excluir %1$s?</string>
+    <string name="home_menu_clear_title">Limpar dados do aplicativo</string>
+    <string name="home_menu_clear_content">Deseja limpar dados de %1$s?</string>
+    <string name="home_menu_kill_title">Força Parada</string>
+    <string name="home_menu_kill_content">Você quer forçar a parada do %1$s? Isso pode fazer com que ele funcione inesperadamente.</string>
+    <string name="add_app_loading_tips">Pacote de análise para %1$s…</string>
+    <string name="add_app_installing_tips">Instalando %1$s…</string>
+    <string name="add_app_loading_complete">%1$s instalado com sucesso!</string>
+    <string name="list_app_activity_install">Adicionar ao VirtualXposed</string>
+    <string name="settings_about_text">Sobre</string>
+    <string name="settings_task_manage_text">Gerenciar Tarefas</string>
+    <string name="settings_app_manage_text">Gerenciar aplicativos</string>
+    <string name="settings_desktop_text">Configurações da área de trabalho</string>
+    <string name="app_manage_uninstall">Desinstalar</string>
+    <string name="app_manage_repair">Reparar</string>
+    <string name="settings_title">Configurações</string>
+    <string name="task_manage_uninstall">Matar processo</string>
+    <string name="settings_reboot_title">Reiniciar o VirtualXposed</string>
+    <string name="settings_reboot_content">Isso matará todos os aplicativos atualmente em execução no VirtualXposed. Reinicie mesmo assim?</string>
+    <string name="check_update">Checar atualização</string>
+    <string name="version_is_latest">A versão mais recente está instalada.</string>
+    <string name="new_version_detected">Nova versão:</string>
+    <string name="multi_version_tip_title">Dicas de instalação</string>
+    <string name="multi_version_tips_content">Você escolheu um aplicativo existente. Você quer instalá-lo como um aplicativo separado ou apenas atualizar o existente? \ n (versão instalada%1$s, versão selecionada:% 2 $ s)</string>
+    <string name="multi_version_multi">Instale outro</string>
+    <string name="multi_version_cover">Tampa</string>
+    <string name="multi_version_upgrade">Atualizar</string>
+    <string name="multi_version_downgrade">Downgrade</string>
+    <string name="app_manage_repair_failed_tips">Desculpe, o reparo falhou; por favor, reinstale este aplicativo.</string>
+    <string name="app_manage_repairing">Reparando…</string>
+    <string name="app_manage_repair_success_title">Dicas de reparo</string>
+    <string name="app_manage_repair_success_content">Sucesso de reparo; por favor force-stop VirtualXposed para que ele tenha efeito.</string>
+    <string name="app_manage_repair_reboot_now">Reinicie agora</string>
+    <string name="wallpaper_too_big_tips">A imagem do papel de parede é muito grande para diminuir o tempo de inicialização, por favor, escolha uma adequada.</string>
+    <string name="create_shortcut_already_exist">Este atalho já existe</string>
+    <string name="start_app_failed">Falha ao abrir o aplicativo: %1$s.</string>
+    <string name="app_manage_redirect_on">Ativar Redirecionamento de armazenamento</string>
+    <string name="app_manage_redirect_off">Desativar o redirecionamento de armazenamento</string>
+    <string name="app_manage_redirect_desc">Redirecionamento de armazenamento redireciona o acesso de um aplicativo do cartão SD real para um cartão SD virtual, o que impedirá que certos aplicativos criem diretórios em seu cartão SD real; no entanto, depois que o redirecionamento de armazenamento estiver habilitado, o aplicativo não poderá mais acessar o cartão SD real. Por favor selecione quando solicitado.</string>
+    <string name="app_manage_redirect_on_confirm">Abrir, confirmar</string>
+    <string name="app_manage_redirect_off_confirm">Fechar, confirmar</string>
+    <string name="shared_to_vxp">Compartilhar para o aplicativo no VirtualXposed</string>
+    <string name="shared_to_vxp_failed">Compartilhar falhou, tente novamente.</string>
+    <string name="app_installer_label">Adicionar ao VirtualXposed</string>
+    <string name="install_complete">Instalação completa.</string>
+    <string name="install_fail">A instalação falhou: %1$s</string>
+    <string name="install">Instalar</string>
+    <string name="install_package">Instalar o novo aplicativo: %s</string>
+    <string name="install_package_version_tips">Você escolheu um aplicativo existente,\n(versão instalada %1$s, versão selecionada: %2$s). Instalar?</string>
+    <string name="settings_add_app_summary">Por favor, adicione o aplicativo e o módulo Xposed ao VirtualXposed primeiro, caso contrário o módulo Xposed não terá efeito.</string>
+    <string name="settings_advance">Configurações avançadas</string>
+    <string name="advance_settings_hide_settings">Ocultar o botão de configurações na área de trabalho</string>
+    <string name="advance_settings_disable_installer">Desativar o instalador apk para o sistema</string>
+    <string name="advance_settings_hide_settings_summary">Se você pode inserir configurações pela tecla de menu na atividade principal, você pode ocultar isso; talvez você não consiga inserir as configurações de outra forma! \n (Reinicie o VirtualXposed para entrar em vigor.)</string>
+    <string name="advance_settings_disable_installer_summary">Não mostre o instalador do VirtualXposed quando você escolher o arquivo apk no sistema</string>
+    <string name="advance_settings_directly_back">Diretamente de volta</string>
+    <string name="advance_settings_directly_back_summary">Volte para o ativador do sistema em vez do lançador virtual quando estiver em um aplicativo virtual. \n (Reinicie o VirtualXposed para entrar em vigor.)</string>
+    <string name="install_self_eggs">Rapaz, sua ideia é promissora :)</string>
+    <string name="advance_settings_install_gms">Instalar/desinstalar serviços do Google</string>
+    <string name="about_feedback_tel_title">Grupo de telegramas: %1$s</string>
+    <string name="advance_settings_copy_file">Copiar Arquivo</string>
+    <string name="about_website_title">Website oficial</string>
+    <string name="about_feedback_title">Comentários</string>
+    <string name="about_feedback_hint">Especifique o modelo do seu telefone, a versão Android, o plugin Xposed e a versão do aplicativo correspondente e, em seguida, descreva (o mais detalhadamente possível) o problema que você está enfrentando, caso contrário, será considerado um feedback inválido (porque não podemos efetivamente solucionar problemas e corrigir o problema com apenas \"este aplicativo falha \";))</string>
+    <string name="settings_plugin_recommend">Módulo Xposed Recomendado</string>
+    <string name="advance_settings_disable_resident_notification">Desativar notificação persistente</string>
+    <string name="advance_settings_disable_resident_notification_summary">Desativar a notificação persistente do VirtualXposed. (Quando desativado, o VirtualXposed pode ser morto freqüentemente pelo sistema. Por favor, seja cauteloso!)</string>
+    <string name="settings_module_manage">Gerenciar o módulo Xposed</string>
+    <string name="settings_module_manage_summary">Ativar/desativar o módulo Xposed (você deve ativá-lo manualmente para que ele tenha efeito)</string>
+    <string name="xposed_installer_not_found">Instalador Xposed não encontrado, por favor, adicione-o ao VirtualXposed primeiro!</string>
+    <string name="advance_settings_allow_fake_signature_summary">Quando ativado, você pode instalar apk que não tem assinaturas (geralmente usado em apk crackeado, por favor, tenha cuidado )</string>
+    <string name="advance_settings_allow_fake_signature">Permitir instalar apk sem assinaturas.</string>
+    <string name="permission_tip_title">Dicas importantes:</string>
+    <string name="permission_tips_content">%1$s não suporta a permissão de tempo de execução, você deve conceder permissões que precisa com antecedência, por favor, permita toda a permissão que será solicitada na próxima etapa, caso contrário, ela pode não funcionar corretamente.</string>
+    <string name="permission_tips_confirm">OK, eu sei.</string>
+    <string name="permission_denied_tips_content">App: %1$s não concede a permissão necessária, se ela não funcionar corretamente, acesse o gerenciamento de permissões do seu dispositivo e conceda-lhe permissões.</string>
+    <string name="list_app_access_external_storage">Se você quiser instalar apk de armazenamento externo, por favor, dê VirtualXposed a permissão.</string>
+    <string name="install_gms_title">Instalar o serviço do Google</string>
+    <string name="install_gms_content">O Serviço Google é suportado pelo microG, o VirtualXposed está prestes a baixar algum arquivo (2M), e pode consumir mais bateria, você gostaria de instalá-lo?</string>
+    <string name="install_gms_fail_title">Instalação falhou</string>
+    <string name="install_gms_fail_content">falha ao instalar o serviço do Google automaticamente, você também pode instalá-lo manualmente.</string>
+    <string name="install_gms_fail_ok">Instalação Manual</string>
+    <string name="uninstall_gms_title">Desinstalar o serviço do Google</string>
+    <string name="uninstall_gms_content">Deseja desinstalar o serviço do Google? você pode reinstalá-lo mais tarde.</string>
+    <string name="uninstall_gms_ok">Desinstalar, confirmar</string>
+    <string name="install_gms_alreay_installed">O serviço do Google foi instalado.</string>
+    <string name="install_gms_success">O serviço do Google foi instalado com sucesso !!</string>
+    <string name="uninstall_gms_success">O serviço do Google foi desinstalado com sucesso !!</string>
+    <string name="donate_choose_title">Maneira de doar</string>
+    <string name="donate_bitconins_tips">Meu endereço bitconins foi copiado para a área de transferência :)</string>
+    <string name="advance_settings_disable_xposed_summary">Quando desativado, todo o módulo Xposed não terá efeito.</string>
+    <string name="advance_settings_disable_xposed">Desativar Xposed</string>
+    <string name="prepare_xposed_installer">Preparando o ambiente Xposed, por favor aguarde ...</string>
+    <string name="settings_file_manage_text">Gerenciar arquivos</string>
+    <string name="install_file_manager_tips">Gerenciador de arquivos é suportado pelo Amaze File Manager, download (cerca de 3M) e instalá-lo agora?</string>
+    <string name="settings_permission_manage_text">Gerenciador de permissões</string>
+    <string name="install_permission_manager_tips">Gerenciador de permissões é implementado pelo XPrivacyLua, baixar (cerca de 1.7M) e instalá-lo agora?</string>
+    <string name="advance_settings_enable_launcher_summary">Enable Launcher</string>
+    <string name="advance_settings_enable_launcher">Enable Launcher</string>
+</resources>
diff --git a/VirtualApp/app/src/main/res/values-ru/strings.xml b/VirtualApp/app/src/main/res/values-ru/strings.xml
new file mode 100644
index 000000000..3b492c354
--- /dev/null
+++ b/VirtualApp/app/src/main/res/values-ru/strings.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+	<string name="app_name">VirtualXposed</string>
+	<string name="wait">Подождите…</string>
+	<string name="desktop">Рабочий стол</string>
+	<string name="add_app">Добавить приложение</string>
+	<string name="preparing">Открытие приложения...</string>
+	<string name="delete">Удалить</string>
+	<string name="create_shortcut">Создать ярлык</string>
+	<string name="new_user">Новый пользователь</string>
+	<string name="enable">Включить</string>
+	<string name="save">Сохранить</string>
+	<string name="save_success">Сохранение завершено!</string>
+	<string name="manufacturer">Производитель</string>
+	<string name="brand">Бренд</string>
+	<string name="device">Устройство</string>
+	<string name="fake_device_info">Фиктивная информация об устройстве</string>
+	<string name="wifi_status">Состояние Wifi</string>
+	<string name="config_device_info">Информация об устройстве</string>
+	<string name="about">О приложении</string>
+	<string name="clone_apps">Клонирование приложений</string>
+	<string name="external_storage">Внешнее хранилище</string>
+	<string name="install_d">Установить (%d)</string>
+	<string name="install_too_much_once_time">За один раз можно выбрать не более 9 приложений!</string>
+	<string name="versionchecklib_confirm">Скачать</string>
+	<string name="versionchecklib_cancel">Отмена</string>
+	<string name="menu_virtual_location">Виртуальное местоположение</string>
+	<string name="menu_about">О приложении</string>
+	<string name="menu_reboot">Перезагрузка</string>
+	<string name="installing_tips">Установка %1$s…</string>
+	<string name="update_success_tips">Обновление %1$s успешно!</string>
+	<string name="install_success_tips">Модуль Xposed не будет действовать, пока вы не включите его в настройках модуля XposedInstaller.</string>
+	<string name="install_fail_tips">Установка %1$s ошибка: %2$s</string>
+	<string name="about_page_description">VirtualXposed - это приложение, которое обеспечивает возможность использования модуля Xposed без необходимости доступа к рут, разблокировки загрузчика или изменения образа системы.</string>
+	<string name="about_feedback_qq_title">QQ Group (нажмите, чтобы скопировать номер группы)</string>
+	<string name="about_feedback_wechat_title">WeChat Group (нажмите, чтобы скопировать)</string>
+	<string name="about_feedback_tips">Номер группы скопирован в буфер обмена!</string>
+	<string name="about_version_title">Версия: %1$s</string>
+	<string name="about_donate_title">Пожертвовать</string>
+	<string name="reboot_tips_1">Успешная перезагрузка!</string>
+	<string name="reboot_tips_2">Успешная перезагрузка!</string>
+	<string name="reboot_tips_3">Успешная перезагрузка!</string>
+	<string name="meizu_device_tips_title">Обнаружение устройства Meizu:</string>
+	<string name="meizu_device_tips_content">"В телефонах Meizu OS вы можете добавлять приложение в VirtualXposed, клонируя существующее приложение, которое уже установлено (вы не можете установить приложение в VirtualXposed через загрузку apk). Вы также должны установить XposedInstaller вручную, так как, в отличие от установок на другом Android телефоне, он не встроен в VirtualXposed при установке на Meizu OS.  Сначала установите XposedInstaller."</string>
+	<string name="prompt_alipay_not_found">Alipay не найден.</string>
+	<string name="prompt_wait">Подождите минуту...</string>
+	<string name="donate_dialog_title">О пожертвовании</string>
+	<string name="donate_dialog_content">Не жертвуйте мне, если не богаты :)</string>
+	<string name="donate_dialog_yes">Пожертвовать</string>
+	<string name="donate_dialog_no">Отмена</string>
+	<string name="large_app_install_tips">Установка может занять некоторое время, будьте терпеливы... :)</string>
+	<string name="about_icon_title">О значке</string>
+	<string name="about_icon_content">Если не нравится мой новый значок, вы всегда можете создать новый значок для VirtualXposed;)</string>
+	<string name="about_icon_yes">ОК</string>
+	<string name="create_shortcut_success">Ярлык успешно создан!</string>
+	<string name="about_thanks">Спасибо</string>
+	<string name="thanks_dialog_title">Спасибо</string>
+	<string name="alert_for_doze_mode_title">Подсказки</string>
+	<string name="alert_for_doze_mode_content">Разрешите VirtualXposed работать в фоновом режиме, в противном случае вы не сможете получать уведомления от некоторых виртуальных приложений.</string>
+	<string name="alert_for_doze_mode_yes">Разрешить</string>
+	<string name="alert_for_doze_mode_no">Запретить</string>
+	<string name="clear_app">"Очистить 
+данные"</string>
+	<string name="stop_app">"Принудительная 
+остановка"</string>
+	<string name="home_menu_delete_title">Удалить приложение</string>
+	<string name="home_menu_delete_content">Удалить %1$s ?</string>
+	<string name="home_menu_kill_title">Принудительная остановка</string>
+	<string name="home_menu_kill_content">Хотите принудительно остановить %1$s ? Это может привести к сбоям</string>
+	<string name="add_app_loading_tips">Разбор пакета для %1$s…</string>
+	<string name="add_app_installing_tips">Установка %1$s…</string>
+	<string name="add_app_loading_complete">%1$s успешно установлено!</string>
+	<string name="list_app_activity_install">Добавить в VirtualXposed</string>
+	<string name="settings_about_text">О приложении</string>
+	<string name="settings_task_manage_text">Управление задачами</string>
+	<string name="settings_app_manage_text">Управление приложениями</string>
+	<string name="settings_desktop_text">Настройки</string>
+	<string name="app_manage_uninstall">Удаление</string>
+	<string name="app_manage_repair">Исправить</string>
+	<string name="settings_title">Настройки</string>
+	<string name="task_manage_uninstall">Уничтожить процесс</string>
+	<string name="settings_reboot_title">Перезагрузка VirtualXposed</string>
+	<string name="settings_reboot_content">Это уничтожит все приложения, которые в настоящее время работают в VirtualXposed. Перезагрузить всё равно?</string>
+	<string name="check_update">Проверить обновление</string>
+	<string name="version_is_latest">Установлена ​​последняя версия.</string>
+	<string name="new_version_detected">Новая версия:</string>
+	<string name="multi_version_tip_title">Советы по установке</string>
+	<string name="multi_version_tips_content">"Вы выбрали существующее приложение. Хотите установить его как отдельное приложение или просто обновить существующее?
+(Установленная версия %1$s, Выбранная версия: %2$s)"</string>
+	<string name="multi_version_multi">Установить еще один</string>
+	<string name="multi_version_cover">Обложка</string>
+	<string name="multi_version_upgrade">Обновить</string>
+	<string name="multi_version_downgrade">Понизить версию</string>
+	<string name="app_manage_repairing">Исправление...</string>
+	<string name="app_manage_repair_success_title">Советы по исправлению</string>
+	<string name="app_manage_repair_success_content">Успешное исправление; принудительно остановите VirtualXposed, чтобы оно вступило в силу.</string>
+	<string name="app_manage_repair_reboot_now">Перезагрузить сейчас</string>
+	<string name="wallpaper_too_big_tips">Изображение обоев слишком велико, может замедлить время запуска, выберите подходящее.</string>
+	<string name="create_shortcut_already_exist">Этот ярлык уже существует</string>
+	<string name="start_app_failed">Открыть приложение: %1$s не удалось.</string>
+	<string name="app_manage_redirect_on">Открыть перенаправление хранилища</string>
+	<string name="app_manage_redirect_off">Отключить перенаправление хранилища</string>
+	<string name="app_manage_redirect_desc">Перенаправление хранилища перенаправляет доступ приложения с реальной SD карты на виртуальную SD карту, что не позволит некоторым приложениям создавать каталоги на вашей реальной SD карте; однако после включения перенаправления хранилища приложение больше не может получить доступ к реальной SD карте. Выберите его при появлении запроса.</string>
+	<string name="app_manage_redirect_on_confirm">Открыть, Подтвердить</string>
+	<string name="app_manage_redirect_off_confirm">Закрыть, Подтвердить</string>
+	<string name="shared_to_vxp">Общий доступ к приложению в VirtualXposed</string>
+	<string name="shared_to_vxp_failed">Не удалось выполнить общий доступ, повторите попытку.</string>
+	<string name="app_installer_label">Добавить в VirtualXposed</string>
+	<string name="install_complete">Установка завершена.</string>
+	<string name="install_fail">Ошибка установки: %1$s</string>
+	<string name="install">Установить</string>
+	<string name="install_package">Установить новое приложение: %s</string>
+	<string name="install_package_version_tips">"Вы выбрали существующее приложение,
+(Установленная версия %1$s, выбранная версия: %2$s). Установить? "</string>
+	<string name="settings_add_app_summary">Сначала добавьте приложение и модуль Xposed в VirtualXposed, иначе модуль Xposed не вступит в силу.</string>
+	<string name="settings_advance">Дополнительные настройки</string>
+	<string name="advance_settings_hide_settings">Скрыть кнопку настроек на рабочем столе</string>
+	<string name="advance_settings_disable_installer">Отключить установщик apk для системы</string>
+	<string name="advance_settings_hide_settings_summary">"Если сможете ввести настройки с помощью клавиши меню в основном действии, можете скрыть это: вы не сможете вводить настройки в противном случае!
+(Перезапустите VirtualXposed, чтобы вступило в силу.) "</string>
+	<string name="advance_settings_disable_installer_summary">Не показывать установщик VirtualXposed, когда вы выбираете файл apk в системе</string>
+	<string name="advance_settings_directly_back">Непосредственно назад</string>
+	<string name="advance_settings_directly_back_summary">"Вернитесь к системному лаунчеру вместо виртуального лаунчера, когда находитесь в виртуальном приложении.
+(Перезапустите VirtualXposed, чтобы вступило в силу.) "</string>
+	<string name="install_self_eggs">Твоя идея многообещающая :)</string>
+	<string name="advance_settings_install_gms">Установите Google Services</string>
+	<string name="advance_settings_copy_file">Копировать файл</string>
+	<string name="about_website_title">Официальный сайт</string>
+	<string name="about_feedback_title">Отзывы</string>
+	<string name="about_feedback_hint">Укажите модель телефона, версию Android, плагин Xposed и версию соответствующего приложения, а затем опишите (как можно более подробно) проблему, которую вы испытываете, в противном случае она считается недействительной обратной связью (потому что мы не можем эффективно устранить и исправить указанную проблему только с \"приложение глюкануло\" ;) )</string>
+	<string name="settings_plugin_recommend">Рекомендуемый модуль Xposed</string>
+	<string name="advance_settings_disable_resident_notification">Отключить уведомление резидента</string>
+	<string name="advance_settings_disable_resident_notification_summary">Отключите резидентное уведомление VirtualXposed. (Если отключено, VirtualXposed может быть часто уничтожен системой. Будьте осторожны!)</string>
+	<string name="settings_module_manage">Управление модулем Xposed</string>
+	<string name="settings_module_manage_summary">Включить/Отключить модуль Xposed (вы должны включить его вручную, чтобы он вступил в силу)</string>
+	<string name="xposed_installer_not_found">Инсталлятор Xposed, не найден,сначала добавьте его в VirtualXposed!</string>
+	<string name="advance_settings_allow_fake_signature_summary">Когда включено, вы можете установить apk, у которого нет подписи (обычно используется в взломаных apk, будьте осторожны)</string>
+	<string name="advance_settings_allow_fake_signature">Разрешить установку apk без подписи.</string>
+	<string name="thanks_dialog_content">Спасибо Чейни за предоставление удивительного значка, и спасибо Пей, Пегги ,papasha55 и многим другим, всех не перечислить, за их трудную работу. Спасибо за идеи и предложения, которые вы все предоставили, и особая благодарность YingLin за то, что нашёл время, чтобы помочь мне сделать дизайн :)</string>
+	<string name="home_menu_clear_title">Очистить данные приложения</string>
+	<string name="home_menu_clear_content">Очистить данные %1$s ?</string>
+	<string name="app_manage_repair_failed_tips">Исправление не удалось; переустановите это приложение.</string>
+</resources>
diff --git a/VirtualApp/app/src/main/res/values-w820dp/dimens.xml b/VirtualApp/app/src/main/res/values-w820dp/dimens.xml
deleted file mode 100644
index 63fc81644..000000000
--- a/VirtualApp/app/src/main/res/values-w820dp/dimens.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<resources>
-    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
-         (such as screen margins) for screens with more than 820dp of available width. This
-         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
-    <dimen name="activity_horizontal_margin">64dp</dimen>
-</resources>
diff --git a/VirtualApp/app/src/main/res/values-zh-rCN/strings.xml b/VirtualApp/app/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000..addfd1c43
--- /dev/null
+++ b/VirtualApp/app/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,180 @@
+<resources>
+    <!--<string name="app_name">VirtualApp</string>-->
+    <string name="app_name">VirtualXposed</string>
+    <string name="desktop">桌面</string>
+    <string name="add_app">添加应用</string>
+    <string name="preparing">正在打开App,请稍等…</string>
+    <string name="delete">删除</string>
+    <string name="create_shortcut">创建快捷方式</string>
+    <string name="new_user">新的用户</string>
+    <string name="enable">开启</string>
+    <string name="save">保存</string>
+    <string name="save_success">保存成功!</string>
+    <string name="manufacturer">制造商</string>
+    <string name="wait">请稍后…</string>
+    <string name="brand">品牌</string>
+    <string name="device">机型</string>
+    <string name="fake_device_info">伪造设备信息</string>
+    <string name="wifi_status">Wifi状态</string>
+    <string name="config_device_info">配置设备信息</string>
+    <string name="about">关于</string>
+    <string name="clone_apps">克隆App</string>
+    <string name="external_storage">外置存储</string>
+    <string name="install_d">安装 (%d)</string>
+    <string name="install_too_much_once_time">不能一次性安装超过9个App!</string>
+    <string name="versionchecklib_confirm">立即更新</string>
+    <string name="versionchecklib_cancel">取消</string>
+    <string name="menu_virtual_location">虚拟定位</string>
+    <string name="menu_about">关于</string>
+    <string name="menu_reboot">重启</string>
+    <string name="installing_tips">正在安装 %1$s…</string>
+    <string name="update_success_tips">%1$s 更新成功!</string>
+    <string name="install_success_tips">模块安装完后需要去XposedInstaller的模块设置里勾选才能生效哦~</string>
+    <string name="install_fail_tips">%1$s 安装失败,错误码: %2$s</string>
+    <string name="about_page_description">VirtualXposed是一个基于VirtualApp的,免Root、免解锁BL、免刷机使用Xposed框架的APP。</string>
+    <string name="about_feedback_qq_title">内测 QQ 群(点击复制群号)</string>
+    <string name="about_feedback_wechat_title">内测微信群(点击复制群号)</string>
+    <string name="about_feedback_tips">群号已经复制到剪切版</string>
+    <string name="about_version_title">版本号: %1$s</string>
+    <string name="about_donate_title">支持我</string>
+    <string name="reboot_tips_1">虽然也许你不太相信,但是VirtualXposed确实重启完成了!</string>
+    <string name="reboot_tips_2">别点了,不骗你,真的重启成功了。</string>
+    <string name="reboot_tips_3">不信是吧,死给你看。。</string>
+    <string name="meizu_device_tips_title">检测到魅族系统:</string>
+    <string name="meizu_device_tips_content">受限于魅族系统的限制,您必须先在系统中安装相应的APP以及Xposed模块,然后再克隆到VirtualXposed中才能正常使用。请首先在安装 XposedInstaller 然后克隆到 VAEposed 中!!</string>
+    <string name="prompt_alipay_not_found">你没有安装支付宝 ~_~</string>
+    <string name="donate_dialog_title">关于打赏</string>
+    <string name="donate_dialog_content">如果你并不富裕,请不要捐赠!去github点赞支持或者告诉身边的朋友就好 ^_^ </string>
+    <string name="donate_dialog_yes">我就要打赏</string>
+    <string name="donate_dialog_no">去github点赞</string>
+    <string name="large_app_install_tips">安装大型APP可能需要花费较长的时间,请耐心等待~</string>
+    <string name="about_icon_title">关于图标</string>
+    <string name="about_icon_content">如果你不喜欢这个图标,赶紧为VirtualXposed设计一个吧!哈哈哈哈哈,我知道你们这些设计师肯定会看不下去的,啊哈哈哈</string>
+    <string name="about_icon_yes">我知道了</string>
+    <string name="create_shortcut_success">创建快捷方式成功!</string>
+    <string name="about_thanks">致谢</string>
+    <string name="thanks_dialog_title">特别鸣谢</string>
+    <string name="thanks_dialog_content">感谢 Cheney 提供超赞的Icon,另外谢谢芑芮、佩奇、以及另外一些我还不知道名字的设计师的辛勤付出;谢谢各位小伙伴提供的创意和建议,特别感谢 YingLin 不厌其烦地帮我实现我的稀烂设计 :)</string>
+    <string name="alert_for_doze_mode_title">关于后台运行</string>
+    <string name="alert_for_doze_mode_content">为了让VirtualXposed内部运行的应用不会频繁被系统杀死,请允许把VirtualXposed加入电池优化白名单;同时,VirtualXposed会尽量保证APP不滥用此权限。请在接下来的系统弹窗中选择"允许" :)</string>
+    <string name="alert_for_doze_mode_yes">朕同意了</string>
+    <string name="alert_for_doze_mode_no">我拒绝</string>
+    <string name="about_faq_title">常见问题</string>
+    <string name="clear_app">清除\n数据</string>
+    <string name="stop_app">强制\n停止</string>
+    <string name="home_menu_delete_title">删除应用</string>
+    <string name="home_menu_delete_content">是否要从 VirtualXposed 删除 %1$s ?</string>
+    <string name="home_menu_clear_title">清除数据</string>
+    <string name="home_menu_clear_content">确定要删除 %1$s 的数据吗?</string>
+    <string name="home_menu_kill_title">强制停止</string>
+    <string name="home_menu_kill_content">是否要强制停止 %1$s 的运行?</string>
+    <string name="add_app_loading_tips">正在解析安装包 %1$s</string>
+    <string name="add_app_installing_tips">正在安装 %1$s </string>
+    <string name="add_app_loading_complete">%1$s 安装成功!</string>
+    <string name="list_app_activity_install">添加到VirtualXposed</string>
+    <string name="settings_about_text">关于</string>
+    <string name="settings_task_manage_text">任务管理</string>
+    <string name="settings_app_manage_text">应用管理</string>
+    <string name="settings_desktop_text">桌面设置</string>
+    <string name="app_manage_uninstall">卸载</string>
+    <string name="app_manage_repair">尝试修复</string>
+    <string name="settings_title">设置</string>
+    <string name="task_manage_uninstall">结束任务</string>
+    <string name="settings_reboot_title">重启 VirtualXposed</string>
+    <string name="settings_reboot_content">这将使所有运行在 VirtualXposed 内部的APP重启,确认要这么做吗?</string>
+    <string name="check_update">检查更新</string>
+    <string name="version_is_latest">当前版本已是最新!</string>
+    <string name="new_version_detected">检测到新版本:</string>
+    <string name="multi_version_tip_title">安装提示</string>
+    <string name="multi_version_tips_content">你选择了一个已经安装了的应用,是安装一个分身还是覆盖已经安装的版本?\n\n已经安装的版本:%1$s\n即将安装的版本:%2$s</string>
+    <string name="multi_version_multi">安装分身</string>
+    <string name="multi_version_cover">覆盖已有版本</string>
+    <string name="multi_version_upgrade">升级安装</string>
+    <string name="multi_version_downgrade">降级安装</string>
+    <string name="app_manage_repair_failed_tips">很抱歉,修复失败了 :( 请尝试重新安装…</string>
+    <string name="app_manage_repairing">正在修复,请耐心等待…</string>
+    <string name="app_manage_repair_success_title">修复提示</string>
+    <string name="app_manage_repair_success_content">修复成功,请立即重启 VirtualXposed 以保证修复生效。</string>
+    <string name="app_manage_repair_reboot_now">立即重启</string>
+    <string name="wallpaper_too_big_tips">壁纸图片过大,严重拖慢启动速度,建议更换或者恢复默认壁纸~</string>
+    <string name="create_shortcut_already_exist">这个快捷方式已经创建过啦~</string>
+    <string name="start_app_failed">打开应用 %1$s 失败 :(</string>
+    <string name="app_manage_redirect_on">打开存储重定向</string>
+    <string name="app_manage_redirect_off">关闭存储重定向</string>
+    <string name="app_manage_redirect_desc">存储重定向会把应用对真实SD卡的访问重定向到虚拟的SD卡中,可以防止某些APP在你的SD中乱创建目录;但是,开启存储重定向之后,应用无法再访问真实的SD卡,请按需选择。</string>
+    <string name="app_manage_redirect_on_confirm">确认打开</string>
+    <string name="app_manage_redirect_off_confirm">确认关闭</string>
+    <string name="shared_to_vxp">分享到VXP内的应用</string>
+    <string name="shared_to_vxp_failed">分享失败 :( 请稍后重试…</string>
+    <string name="app_installer_label">安装到VirtualXposed</string>
+    <string name="install_complete">完成</string>
+    <string name="install_fail">安装失败:%1$s</string>
+    <string name="install">安装</string>
+    <string name="install_package">添加新应用 : %s</string>
+    <string name="install_package_version_tips">你选择了一个已经安装了的应用,\n\n已经安装的版本:%1$s\n即将安装的版本:%2$s,确认安装?</string>
+    <string name="settings_add_app_summary">必须先把应用以及Xposed模块都添加到 VirtualXposed ,否则Xposed模块不会生效。</string>
+    <string name="settings_advance">高级设置</string>
+    <string name="advance_settings_hide_settings">隐藏桌面的设置按钮</string>
+    <string name="advance_settings_disable_installer">禁用 VirtualXposed 的APK安装器</string>
+    <string name="advance_settings_hide_settings_summary">如果你可以在主界面通过菜单键(长按多任务或者虚拟菜单键)进入设置,那么可以隐藏这个按钮;否则你将无法进入设置!!\n(强制停止VirtualXposed后生效)</string>
+    <string name="advance_settings_disable_installer_summary">在系统中安装APK文件的时候,不显示VirtualXposed的安装器</string>
+    <string name="advance_settings_directly_back">直接返回</string>
+    <string name="advance_settings_directly_back_summary">内部APP退出时,直接返回到系统桌面而不是VirtualXposed的虚拟桌面(强制停止VirtualXposed后生效)</string>
+    <string name="install_self_eggs">小伙子,你这个想法很有前途 :)</string>
+    <string name="advance_settings_install_gms">安装/移除Google服务</string>
+    <string name="about_feedback_tel_title">Telegram 群组: %1$s</string>
+    <string name="advance_settings_copy_file">内部文件复制</string>
+    <string name="about_website_title">官网(使用教程/历史版本下载/模块下载)</string>
+    <string name="about_feedback_title">问题反馈</string>
+    <string name="about_feedback_hint">请说明你的机型,系统版本,使用的Xposed插件以及对应APP版本,然后尽可能详细地描述你遇到的问题;否则视为无效反馈。\n</string>
+    <string name="settings_plugin_recommend">常用模块</string>
+    <string name="advance_settings_disable_resident_notification">关闭常驻通知栏</string>
+    <string name="advance_settings_disable_resident_notification_summary">关闭常驻通知栏之后,VirtualXposed有可能极易被系统杀死,导致无法收到消息等问题;如果你能确保 VirtualXposed 不会被杀死,可以尝试。总之,慎重操作!(重启VirtualXposed后生效)</string>
+    <string name="settings_module_manage">模块管理</string>
+    <string name="settings_module_manage_summary">开启或者关闭Xposed模块(Xposed模块安装之后必须手动开启才能生效)</string>
+    <string name="xposed_installer_not_found">没有找到 Xposed Installer, 请先把它添加到 VirtualXposed 中!</string>
+    <string name="advance_settings_allow_fake_signature_summary">启用之后,您可以安装没有签名的APK到 VirtualXposed (通常用于破解版的APP,请注意安全。如果APP有签名校检,依然会无法使用)</string>
+    <string name="advance_settings_allow_fake_signature">允许安装没有签名的应用</string>
+    <string name="permission_tip_title">重要提示:</string>
+    <string name="permission_tips_content">%1$s 不支持动态申请权限, 您必须提前赋予它需要的所有必要权限, 请在它接下来的权限请求中全部选择允许,否则他可能无法正常运行。</string>
+    <string name="permission_tips_confirm">我知道了</string>
+    <string name="permission_denied_tips_content">%1$s 不支持动态申请权限,如果它工作不正常,请去您设备的系统权限管理中赋予 VirtualXposed 必要权限。</string>
+    <string name="list_app_access_external_storage">如果你想把SD中的APK安装到 VirtualXposed,请赋予它外部存储权限。</string>
+    <string name="install_gms_title">安装Google服务</string>
+    <string name="install_gms_content">Google 服务是通过 microG 支持的,需要下载 2M 左右的文件,安装完之后耗电量可能会增加,确认需要安装吗?</string>
+    <string name="install_gms_fail_title">安装失败</string>
+    <string name="install_gms_fail_content">自动安装Google 服务失败,你可以参考教程手动安装。</string>
+    <string name="install_gms_fail_ok">查看教程</string>
+    <string name="uninstall_gms_title">移除Google服务</string>
+    <string name="uninstall_gms_content">确定要移除 Google 服务吗?需要的时候你可以重新安装它。</string>
+    <string name="uninstall_gms_ok">确认移除</string>
+    <string name="install_gms_success">Google 服务已经安装成功!!</string>
+    <string name="uninstall_gms_success">Google服务已经移除成功!!</string>
+    <string name="donate_choose_title">选择打赏方式</string>
+    <string name="donate_bitconins_tips">我的比特币地址已经复制到剪切板 :)</string>
+    <string name="donate_alipay">支付宝</string>
+    <string name="advance_settings_disable_xposed">关闭 Xposed</string>
+    <string name="advance_settings_disable_xposed_summary">关闭Xposed之后,所有的Xposed均不会生效。</string>
+    <string name="prepare_xposed_installer">正在准备 Xposed 环境,请等待…</string>
+    <string name="install_file_manager_tips">文件管理是通过 Amaze File Manager 支持的,是否立即下载(约3M)并安装?</string>
+    <string name="settings_permission_manage_text">权限管理</string>
+    <string name="settings_file_manage_text">文件管理</string>
+    <string name="install_permission_manager_tips">权限管理是通过 XPrivacyLua 实现的,是否立即下载(约2M)并安装?</string>
+    <string name="advance_settings_enable_launcher_summary">开启之后,可以设置 VirtualXposed 作为系统桌面(重启 VitualXposed 生效)</string>
+    <string name="advance_settings_enable_launcher">启用桌面功能</string>
+    <string name="exp_introduce_title">另一种免Root用Xposed的方式</string>
+    <string name="exp_introduce_install">去看看</string>
+    <string name="what_is_exp">什么是太极?</string>
+    <string name="install_choose_way">选择安装方式</string>
+    <string name="install_choose_content" >除了 VirtualXposed 之外,您还可以通过 太极 来实现免Root使用 Xposed 模块。VirtualXposed 支持多开,太极则更加稳定。</string>
+    <string name="install_choose_taichi">太极</string>
+    <string name="install_taichi_not_exist">您没有安装 太极</string>
+    <string name="install_go_to_install_exp">去安装太极</string>
+    <string name="install_taichi_while_old_version">你安装的 太极 版本过低,请使用新版本的太极!</string>
+    <string name="install_go_latest_exp">去安装新版太极</string>
+    <string name="exp_tips">很久之前跟大家透露了 EXposed 的相关信息,经过一段时间的反馈和调整,已经到达一个小的里程碑。可能还有很多小伙伴不知道,今天就给大家介绍一下。\n\n
+
+跟我之前创造的 VirtualXposed 一样,EXposed 也是一个免Root使用Xposed 模块的App;他俩各有千秋,EXposed 由于直接运行在原生系统,不论是性能还是稳定性都会好很多;而VirtualXposed 基于VA,天生支持多开。EXposed 才诞生一个月,其稳定性已经超越发展将近一年的 VirtualXposed。\n\n
+
+目前 EXposed 已经上架应用商店,并且改名为 太极,大家可以在安装尝试一下。</string>
+</resources>
diff --git a/VirtualApp/app/src/main/res/values-zh-rTW/strings.xml b/VirtualApp/app/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..45ca94a62
--- /dev/null
+++ b/VirtualApp/app/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,164 @@
+<resources>
+    <string name="app_name">VirtualXposed</string>
+    <string name="desktop">桌面</string>
+    <string name="add_app">新增程式</string>
+    <string name="preparing">正在打開App,請稍等…</string>
+    <string name="delete">刪除</string>
+    <string name="create_shortcut">建立捷徑</string>
+    <string name="new_user">新的使用者</string>
+    <string name="enable">開啟</string>
+    <string name="save">儲存</string>
+    <string name="save_success">儲存成功!</string>
+    <string name="manufacturer">製造商</string>
+    <string name="wait">請稍後…</string>
+    <string name="brand">品牌</string>
+    <string name="device">機型</string>
+    <string name="fake_device_info">偽造設備訊息</string>
+    <string name="wifi_status">Wifi狀態</string>
+    <string name="config_device_info">設定設備訊息</string>
+    <string name="about">關於</string>
+    <string name="clone_apps">複製App</string>
+    <string name="external_storage">外部儲存</string>
+    <string name="install_d">安裝 (%d)</string>
+    <string name="install_too_much_once_time">不能一次性安裝超過9個App!</string>
+    <string name="versionchecklib_confirm">立即更新</string>
+    <string name="versionchecklib_cancel">取消</string>
+    <string name="menu_virtual_location">虛擬定位</string>
+    <string name="menu_about">關於</string>
+    <string name="menu_reboot">重啟</string>
+    <string name="installing_tips">正在安裝 %1$s…</string>
+    <string name="update_success_tips">%1$s 更新成功!</string>
+    <string name="install_success_tips">模組安裝完後需要去XposedInstaller的模組設定裡勾選才能生效哦~</string>
+    <string name="install_fail_tips">%1$s 安裝失敗,錯誤碼: %2$s</string>
+    <string name="about_page_description">VirtualXposed是一個基於VirtualApp的,免Root、免解鎖BL、免刷機使用Xposed框架的APP。</string>
+    <string name="about_feedback_qq_title">內測 QQ 群(輕觸複製群號)</string>
+    <string name="about_feedback_wechat_title">內測微信群(輕觸複製群號)</string>
+    <string name="about_feedback_tips">群號已經複製到剪貼簿</string>
+    <string name="about_version_title">版本號: %1$s</string>
+    <string name="about_donate_title">支持我</string>
+    <string name="reboot_tips_1">雖然也許你不太相信,但是VirtualXposed確實重啟完成了!</string>
+    <string name="reboot_tips_2">別點了,不騙你,真的重啟成功了。</string>
+    <string name="reboot_tips_3">不信是吧,死給你看。。</string>
+    <string name="meizu_device_tips_title">檢測到魅族系統:</string>
+    <string name="meizu_device_tips_content">受限於魅族系統的限制,您必須先在系統中安裝相應的APP以及Xposed模組,然後再複製到VirtualXposed中才能正常使用。請首先在安裝 XposedInstaller 然後複製到 VAEposed 中!!</string>
+    <string name="prompt_alipay_not_found">你沒有安裝支付寶 ~_~</string>
+    <string name="donate_dialog_title">關於打賞</string>
+    <string name="donate_dialog_content">如果你並不富裕,請不要捐贈!去github按讚支援或者告訴身邊的朋友就好 ^_^ </string>
+    <string name="donate_dialog_yes">我就要打賞</string>
+    <string name="donate_dialog_no">去github按讚</string>
+    <string name="large_app_install_tips">安裝大型APP可能需要花費較長的時間,請耐心等待~</string>
+    <string name="about_icon_title">關於圖示</string>
+    <string name="about_icon_content">如果你不喜歡這個圖示,趕緊為VirtualXposed設計一個吧!哈哈哈哈哈,我知道你們這些設計師肯定會看不下去的,啊哈哈哈</string>
+    <string name="about_icon_yes">我知道了</string>
+    <string name="create_shortcut_success">建立捷徑成功!</string>
+    <string name="about_thanks">致謝</string>
+    <string name="thanks_dialog_title">特別鳴謝</string>
+    <string name="thanks_dialog_content">感謝 Cheney 提供超讚的Icon,另外謝謝芑芮、佩奇、以及另外一些我還不知道名字的設計師的辛勤付出;謝謝各位小伙伴提供的創意和建議,特別感謝 YingLin 不厭其煩地幫我實現我的稀爛設計 :)</string>
+    <string name="alert_for_doze_mode_title">關於後台執行</string>
+    <string name="alert_for_doze_mode_content">為了讓VirtualXposed內部執行的程式不會頻繁被系統殺死,請允許把VirtualXposed加入電池最佳化白名單;同時,VirtualXposed會儘量保證APP不濫用此權限。請在接下來的系統跳出視窗中選擇"允許" :)</string>
+    <string name="alert_for_doze_mode_yes">朕同意了</string>
+    <string name="alert_for_doze_mode_no">我拒絕</string>
+    <string name="about_faq_title">常見問題</string>
+    <string name="clear_app">清除\n資料</string>
+    <string name="stop_app">強制\n停止</string>
+    <string name="home_menu_delete_title">刪除程式</string>
+    <string name="home_menu_delete_content">是否要從 VirtualXposed 刪除 %1$s ?</string>
+    <string name="home_menu_clear_title">清除資料</string>
+    <string name="home_menu_clear_content">確定要刪除 %1$s 的資料嗎?</string>
+    <string name="home_menu_kill_title">強制停止</string>
+    <string name="home_menu_kill_content">是否要強制停止 %1$s 的執行?</string>
+    <string name="add_app_loading_tips">正在解析安裝包 %1$s</string>
+    <string name="add_app_installing_tips">正在安裝 %1$s </string>
+    <string name="add_app_loading_complete">%1$s 安裝成功!</string>
+    <string name="list_app_activity_install">新增到VirtualXposed</string>
+    <string name="settings_about_text">關於</string>
+    <string name="settings_task_manage_text">任務管理</string>
+    <string name="settings_app_manage_text">程式管理</string>
+    <string name="settings_desktop_text">桌面設定</string>
+    <string name="app_manage_uninstall">移除</string>
+    <string name="app_manage_repair">嘗試修復</string>
+    <string name="settings_title">設定</string>
+    <string name="task_manage_uninstall">結束任務</string>
+    <string name="settings_reboot_title">重啟 VirtualXposed</string>
+    <string name="settings_reboot_content">這將使所有執行在 VirtualXposed 內部的APP重啟,確認要這麼做嗎?</string>
+    <string name="check_update">檢查更新</string>
+    <string name="version_is_latest">目前版本已是最新!</string>
+    <string name="new_version_detected">檢測到新版本:</string>
+    <string name="multi_version_tip_title">安裝提示</string>
+    <string name="multi_version_tips_content">你選擇了一個已經安裝了的程式,是安裝一個分身還是覆蓋已經安裝的版本?\n\n已經安裝的版本:%1$s\n即將安裝的版本:%2$s</string>
+    <string name="multi_version_multi">安裝分身</string>
+    <string name="multi_version_cover">覆蓋已有版本</string>
+    <string name="multi_version_upgrade">升級安裝</string>
+    <string name="multi_version_downgrade">降級安裝</string>
+    <string name="app_manage_repair_failed_tips">很抱歉,修復失敗了 :( 請嘗試重新安裝…</string>
+    <string name="app_manage_repairing">正在修復,請耐心等待…</string>
+    <string name="app_manage_repair_success_title">修復提示</string>
+    <string name="app_manage_repair_success_content">修復成功,請立即重啟 VirtualXposed 以保證修復生效。</string>
+    <string name="app_manage_repair_reboot_now">立即重啟</string>
+    <string name="wallpaper_too_big_tips">壁紙圖片過大,嚴重拖慢啟動速度,建議更換或者恢復預設壁紙~</string>
+    <string name="create_shortcut_already_exist">這個捷徑已經建立過啦~</string>
+    <string name="start_app_failed">打開程式 %1$s 失敗 :(</string>
+    <string name="app_manage_redirect_on">打開儲存重定向</string>
+    <string name="app_manage_redirect_off">關閉儲存重定向</string>
+    <string name="app_manage_redirect_desc">儲存重定向會把程式對真實SD卡的訪問重定向到虛擬的SD卡中,可以防止某些APP在你的SD中亂建立目錄;但是,開啟儲存重定向之後,程式無法再訪問真實的SD卡,請按需選擇。</string>
+    <string name="app_manage_redirect_on_confirm">確認打開</string>
+    <string name="app_manage_redirect_off_confirm">確認關閉</string>
+    <string name="shared_to_vxp">分享到VXP內的程式</string>
+    <string name="shared_to_vxp_failed">分享失敗 :( 請稍後重試…</string>
+    <string name="app_installer_label">安裝到VirtualXposed</string>
+    <string name="install_complete">完成</string>
+    <string name="install_fail">安裝失敗:%1$s</string>
+    <string name="install">安裝</string>
+    <string name="install_package">安裝新程式 : %s</string>
+    <string name="install_package_version_tips">你選擇了一個已經安裝了的程式,\n\n已經安裝的版本:%1$s\n即將安裝的版本:%2$s,確認安裝?</string>
+    <string name="settings_add_app_summary">必須先把程式以及Xposed模組都新增到 VirtualXposed ,否則Xposed模組不會生效。</string>
+    <string name="settings_advance">進階設定</string>
+    <string name="advance_settings_hide_settings">隱藏桌面的設定按鈕</string>
+    <string name="advance_settings_disable_installer">禁用 VirtualXposed 的APK安裝器</string>
+    <string name="advance_settings_hide_settings_summary">如果你可以在主介面透過選單鍵(長按多任務或者虛擬選單鍵)進入設定,那麼可以隱藏這個按鈕;否則你將無法進入設定!!\n(強制停止VirtualXposed後生效)</string>
+    <string name="advance_settings_disable_installer_summary">在系統中安裝APK檔案的時候,不顯示VirtualXposed的安裝器</string>
+    <string name="advance_settings_directly_back">直接返回</string>
+    <string name="advance_settings_directly_back_summary">內部APP退出時,直接返回到系統桌面而不是VirtualXposed的虛擬桌面(強制停止VirtualXposed後生效)</string>
+    <string name="install_self_eggs">小伙子,你這個想法很有前途 :)</string>
+    <string name="advance_settings_install_gms">安裝/移除Google服務</string>
+    <string name="about_feedback_tel_title">Telegram 群組: %1$s</string>
+    <string name="advance_settings_copy_file">內部檔案複製</string>
+    <string name="about_website_title">官網(使用教學/歷史版本下載/模組下載)</string>
+    <string name="about_feedback_title">問題回饋</string>
+    <string name="about_feedback_hint">請說明你的機型,系統版本,使用的Xposed插件以及對應APP版本,然後儘可能詳細地描述你遇到的問題;否則視為無效回饋。\n</string>
+    <string name="settings_plugin_recommend">常用模組</string>
+    <string name="advance_settings_disable_resident_notification">關閉常駐通知欄</string>
+    <string name="advance_settings_disable_resident_notification_summary">關閉常駐通知欄之後,VirtualXposed有可能極易被系統殺死,導致無法收到消息等問題;如果你能確保 VirtualXposed 不會被殺死,可以嘗試。總之,慎重操作!(重啟VirtualXposed後生效)</string>
+    <string name="settings_module_manage">模組管理</string>
+    <string name="settings_module_manage_summary">開啟或者關閉Xposed模組(Xposed模組安裝之後必須手動開啟才能生效)</string>
+    <string name="xposed_installer_not_found">沒有找到 Xposed Installer, 請先把它新增到 VirtualXposed 中!</string>
+    <string name="advance_settings_allow_fake_signature_summary">啟用之後,您可以安裝沒有簽名的APK到 VirtualXposed (通常用於破解版的APP,請注意安全。如果APP有簽名校檢,依然會無法使用)</string>
+    <string name="advance_settings_allow_fake_signature">允許安裝沒有簽名的程式</string>
+    <string name="permission_tip_title">重要提示:</string>
+    <string name="permission_tips_content">%1$s 不支援動態申請權限, 您必須提前賦予它需要的所有必要權限, 請在它接下來的權限請求中全部選擇允許,否則它將無法執行。</string>
+    <string name="permission_tips_confirm">我知道了</string>
+    <string name="permission_denied_tips_content">%1$s 不支持動態申請權限,如果它工作不正常,請去您設備的系統權限管理中賦予 VirtualXposed 必要權限。</string>
+    <string name="list_app_access_external_storage">如果你想把SD中的APK安裝到 VirtualXposed,請賦予它外部儲存權限。</string>
+    <string name="install_gms_title">安裝Google服務</string>
+    <string name="install_gms_content">Google 服務是透過 microG 支援的,需要下載 2M 左右的檔案,安裝完之後耗電量可能會增加,確認需要安裝嗎?</string>
+    <string name="install_gms_fail_title">安裝失敗</string>
+    <string name="install_gms_fail_content">自動安裝Google 服務失敗,你可以參考教學手動安裝。</string>
+    <string name="install_gms_fail_ok">查看教學</string>
+    <string name="uninstall_gms_title">移除Google服務</string>
+    <string name="uninstall_gms_content">確定要移除 Google 服務嗎?需要的時候你可以重新安裝它。</string>
+    <string name="uninstall_gms_ok">確認移除</string>
+    <string name="install_gms_success">Google 服務已經安裝成功!!</string>
+    <string name="uninstall_gms_success">Google服務已經移除成功!!</string>
+    <string name="donate_choose_title">選擇打賞方式</string>
+    <string name="donate_bitconins_tips">我的比特幣地址已經複製到剪貼簿 :)</string>
+    <string name="donate_alipay">支付寶</string>
+    <string name="advance_settings_disable_xposed">關閉 Xposed</string>
+    <string name="advance_settings_disable_xposed_summary">關閉Xposed之後,所有的Xposed均不會生效。</string>
+    <string name="prepare_xposed_installer">正在準備 Xposed 環境,請等待…</string>
+    <string name="settings_file_manage_text">文件管理</string>
+    <string name="install_file_manager_tips">文件管理是通過 Amaze File Manager 支持的,是否立即下載(約3M)並安裝?</string>
+    <string name="settings_permission_manage_text">權限管理</string>
+    <string name="install_permission_manager_tips">權限管理由XPrivacyLua實現,下載(約1.7M)並立即安裝?</string>
+    <string name="advance_settings_enable_launcher_summary">Enable Launcher</string>
+    <string name="advance_settings_enable_launcher">Enable Launcher</string>
+</resources>
diff --git a/VirtualApp/app/src/main/res/values/attrs.xml b/VirtualApp/app/src/main/res/values/attrs.xml
index 047498fe1..cdbf70077 100644
--- a/VirtualApp/app/src/main/res/values/attrs.xml
+++ b/VirtualApp/app/src/main/res/values/attrs.xml
@@ -27,36 +27,6 @@
         <attr name="dsrv_autoScrollHotspot_offsetBottom" format="dimension" />
     </declare-styleable>
 
-    <declare-styleable name="LoadingIndicatorView">
-        <attr name="minWidth" format="dimension" />
-        <attr name="maxWidth" format="dimension" />
-        <attr name="minHeight" format="dimension" />
-        <attr name="maxHeight" format="dimension" />
-        <attr name="indicatorName" format="string" />
-        <attr name="indicatorColor" format="color" />
-    </declare-styleable>
-
-    <declare-styleable name="RippleButton">
-        <attr name="rippleColor" format="color" />
-        <attr name="alphaFactor" format="float" />
-        <attr name="hover" format="boolean" />
-    </declare-styleable>
-
-    <declare-styleable name="MaterialRippleLayout">
-        <attr name="mrl_rippleColor" format="color" localization="suggested" />
-        <attr name="mrl_rippleDimension" format="dimension" localization="suggested" />
-        <attr name="mrl_rippleOverlay" format="boolean" localization="suggested" />
-        <attr name="mrl_rippleAlpha" format="float" localization="suggested" />
-        <attr name="mrl_rippleDuration" format="integer" localization="suggested" />
-        <attr name="mrl_rippleFadeDuration" format="integer" localization="suggested" />
-        <attr name="mrl_rippleHover" format="boolean" localization="suggested" />
-        <attr name="mrl_rippleBackground" format="color" localization="suggested" />
-        <attr name="mrl_rippleDelayClick" format="boolean" localization="suggested" />
-        <attr name="mrl_ripplePersistent" format="boolean" localization="suggested" />
-        <attr name="mrl_rippleInAdapter" format="boolean" localization="suggested" />
-        <attr name="mrl_rippleRoundedCorners" format="dimension" localization="suggested" />
-    </declare-styleable>
-
     <declare-styleable name="LabelView">
         <!-- 设置文字内容 -->
         <attr name="lv_text" format="string"/>
diff --git a/VirtualApp/app/src/main/res/values/colors.xml b/VirtualApp/app/src/main/res/values/colors.xml
index 44794929d..014d83af7 100644
--- a/VirtualApp/app/src/main/res/values/colors.xml
+++ b/VirtualApp/app/src/main/res/values/colors.xml
@@ -10,6 +10,7 @@
     <color name="desktopColorB">#314155</color>
     <color name="desktopColorC">#324257</color>
     <color name="desktopColorD">#2a3646</color>
+    <color name="desktopColorE">#11000000</color>
 
     <color name="holo_blue_dark">#33cccc</color>
     <color name="holo_yellow_dark">#ff9640</color>
diff --git a/VirtualApp/app/src/main/res/values/dimens.xml b/VirtualApp/app/src/main/res/values/dimens.xml
index e70dede94..58b660afb 100644
--- a/VirtualApp/app/src/main/res/values/dimens.xml
+++ b/VirtualApp/app/src/main/res/values/dimens.xml
@@ -19,4 +19,6 @@
     <dimen name="item_height">60dp</dimen>
 
     <dimen name="dsrv_defaultHotspotHeight">56dp</dimen>
+    <dimen name="line_height">40dp</dimen>
+
 </resources>
diff --git a/VirtualApp/app/src/main/res/values/fitTextView.xml b/VirtualApp/app/src/main/res/values/fitTextView.xml
new file mode 100644
index 000000000..db02b0ced
--- /dev/null
+++ b/VirtualApp/app/src/main/res/values/fitTextView.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <declare-styleable name="FitTextView">
+        <attr name="ftMinTextSize" format="dimension"/>
+        <attr name="ftMaxTextSize" format="dimension"/>
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/VirtualApp/app/src/main/res/values/strings.xml b/VirtualApp/app/src/main/res/values/strings.xml
index 63ee7c445..80600c2c1 100644
--- a/VirtualApp/app/src/main/res/values/strings.xml
+++ b/VirtualApp/app/src/main/res/values/strings.xml
@@ -1,12 +1,186 @@
 <resources>
-    <string name="app_name">VirtualApp</string>
-    <string name="app_welcome">Welcome to Virtual App</string>
-    <string name="setup_enter_new_world">Enter new world</string>
+    <!--<string name="app_name">VirtualApp</string>-->
+    <string name="app_name">VirtualXposed</string>
+    <string name="vxp">VirtualXposed</string>
     <string name="wait">Please wait…</string>
     <string name="desktop">Desktop</string>
     <string name="add_app">Add App</string>
-    <string name="preparing">Opening the app…</string>
+    <string name="preparing">Opening app…</string>
     <string name="delete">Delete</string>
     <string name="create_shortcut">Create shortcut</string>
-    <string name="splash_desc">Install your app to the SandBox</string>
+    <string name="new_user">New User</string>
+    <string name="enable">Enable</string>
+    <string name="save">Save</string>
+    <string name="save_success">Save successful!</string>
+    <string name="manufacturer">Manufacturer</string>
+    <string name="brand">Brand</string>
+    <string name="device">Device</string>
+    <string name="fake_device_info">Fake Device Info</string>
+    <string name="wifi_status">Wifi Status</string>
+    <string name="config_device_info">Device Info</string>
+    <string name="about">About</string>
+    <string name="clone_apps">Clone Apps</string>
+    <string name="external_storage">External Storage</string>
+    <string name="install_d">Install (%d)</string>
+    <string name="install_too_much_once_time">You may choose no more than 9 apps at once!</string>
+    <string name="versionchecklib_confirm">Download</string>
+    <string name="versionchecklib_cancel">Cancel</string>
+    <string name="menu_virtual_location">Virtual Location</string>
+    <string name="menu_about">About</string>
+    <string name="menu_reboot">Reboot</string>
+    <string name="installing_tips">Installing %1$s…</string>
+    <string name="update_success_tips">Update %1$s success!</string>
+    <string name="install_success_tips">The Xposed module will not take effect until you enable it in XposedInstaller\'s module setting.</string>
+    <string name="install_fail_tips">Install %1$s failed: %2$s</string>
+    <string name="copy_right" translatable="false" >Copyright © %1$d</string>
+    <string name="about_page_description">VirtualXposed is an app that provides the ability to use the Xposed module without needing root access, unlocking the bootloader, or modifying the system image.</string>
+    <string name="about_feedback_qq_title">QQ Group (Click to copy group number)</string>
+    <string name="about_feedback_wechat_title">WeChat Group (Click to copy)</string>
+    <string name="about_feedback_tips">Group number has been copied to clipboard!</string>
+    <string name="about_version_title">Version: %1$s</string>
+    <string name="about_donate_title">Donate</string>
+    <string name="reboot_tips_1">Reboot successfull!</string>
+    <string name="reboot_tips_2">Reboot successfull!</string>
+    <string name="reboot_tips_3">Reboot successfull!</string>
+    <string name="meizu_device_tips_title">Detect Meizu Device: </string>
+    <string name="meizu_device_tips_content">In Meizu OS phones, you can only add an app to VirtualXposed by cloning an existing app that\'s already installed (you cannot install an app in VirtualXposed via sideloading an apk). You must also install XposedInstaller manually, as, unlike installations on other Android phones, it isn\'t built in to VirtualXposed when installing on Meizu OS. Please install XposedInstaller first.</string>
+    <string name="donate_alipay">Alipay</string>
+    <string name="donate_wepay">Wechat Pay</string>
+    <string name="prompt_alipay_not_found">Alipay not found.</string>
+    <string name="prompt_wait">Wait just a moment…</string>
+    <string name="donate_dialog_title">About Donate</string>
+    <string name="donate_dialog_content">Don\'t donate to me if you aren\'t rich :)</string>
+    <string name="donate_dialog_yes">Donate</string>
+    <string name="donate_dialog_no">Cancel</string>
+    <string name="large_app_install_tips">Installation may take a while, please be patient… :)</string>
+    <string name="about_icon_title">About Icon</string>
+    <string name="about_icon_content">If you do not like my new icon, you are always welcome to design a new icon for VirtualXposed ;)</string>
+    <string name="about_icon_yes">OK</string>
+    <string name="create_shortcut_success">Shortcut created!</string>
+    <string name="about_thanks">Credits</string>
+    <string name="thanks_dialog_title">Credits</string>
+    <string name="thanks_dialog_content" >Thanks Cheney for providing the awesome icon, and thank you Pei, Peggy, and too many others to list, for your hard work. Thanks for the ideas and suggestions you all provided, and special thanks to YingLin for taking the time to help me achieve my design :)</string>
+    <string name="alert_for_doze_mode_title">Tips</string>
+    <string name="alert_for_doze_mode_content">Please allow VirtualXposed to run in the background, otherwise you may not receive notifications from some virtual apps.</string>
+    <string name="alert_for_doze_mode_yes">Allow</string>
+    <string name="alert_for_doze_mode_no">Deny</string>
+    <string name="about_faq_title">FAQ</string>
+    <string name="clear_app">Clear\nData</string>
+    <string name="stop_app">Force\nStop</string>
+    <string name="home_menu_delete_title">Delete App</string>
+    <string name="home_menu_delete_content">Do you want to delete %1$s ?</string>
+    <string name="home_menu_clear_title" >Clear App Data</string>
+    <string name="home_menu_clear_content" >Do you want to clear data of %1$s ?</string>
+    <string name="home_menu_kill_title">Force Stop</string>
+    <string name="home_menu_kill_content">Do you want to force stop %1$s ? This may cause unexpected app behaviours. </string>
+    <string name="add_app_loading_tips">Parsing package for %1$s…</string>
+    <string name="add_app_installing_tips">Installing %1$s…</string>
+    <string name="add_app_loading_complete">%1$s successfully installed. </string>
+    <string name="list_app_activity_install">Add to VirtualXposed</string>
+    <string name="settings_about_text">About</string>
+    <string name="settings_task_manage_text">Manage Tasks</string>
+    <string name="settings_app_manage_text">Manage Apps</string>
+    <string name="settings_desktop_text">Desktop Settings</string>
+    <string name="app_manage_uninstall">Uninstall</string>
+    <string name="app_manage_repair">Repair</string>
+    <string name="settings_title">Settings</string>
+    <string name="task_manage_uninstall">Kill Process</string>
+    <string name="settings_reboot_title">Reboot VirtualXposed</string>
+    <string name="settings_reboot_content">This will kill all apps currently running in VirtualXposed. Reboot anyway?</string>
+    <string name="check_update">Check Update</string>
+    <string name="version_is_latest">Latest version is installed.</string>
+    <string name="new_version_detected">New Version:</string>
+    <string name="multi_version_tip_title">Install tips</string>
+    <string name="multi_version_tips_content">You have chosen an existing app. Do you want to install it as a separate app, or just update the existing one? \n(Installed version %1$s, Selected version: %2$s)</string>
+    <string name="multi_version_multi">Install another one</string>
+    <string name="multi_version_cover">Cover</string>
+    <string name="multi_version_upgrade">Upgrade</string>
+    <string name="multi_version_downgrade">Downgrade</string>
+    <string name="app_manage_repair_failed_tips" >Sorry, repair failed; please re-install this app.</string>
+    <string name="app_manage_repairing">Repairing…</string>
+    <string name="app_manage_repair_success_title">Repair tips</string>
+    <string name="app_manage_repair_success_content">Repair success; please force-stop VirtualXposed for it to take effect.</string>
+    <string name="app_manage_repair_reboot_now">Reboot now</string>
+    <string name="wallpaper_too_big_tips">The wallpaper choosen is too large and may increase the startup time, please choose a proper one.</string>
+    <string name="create_shortcut_already_exist">This shortcut already exists</string>
+    <string name="start_app_failed">Open app: %1$s failed.</string>
+    <string name="app_manage_redirect_on">Open Storage Redirect</string>
+    <string name="app_manage_redirect_off">Disable Storage Redirect</string>
+    <string name="app_manage_redirect_desc">Storage Redirection redirects an application\'s access from the real SD card to a virtual SD card, which will prevent certain apps from creating directories in your real SD card; however, after the storage redirection is enabled, the application can no longer access the real SD card. Please select it when prompted.</string>
+    <string name="app_manage_redirect_on_confirm">Open, Confirm</string>
+    <string name="app_manage_redirect_off_confirm">Close, Confirm</string>
+    <string name="shared_to_vxp">Share to App in VirtualXposed</string>
+    <string name="shared_to_vxp_failed">Share failed, please try again.</string>
+    <string name="app_installer_label">Add to VirtualXposed</string>
+    <string name="install_complete">DONE</string>
+    <string name="install_fail">Install failed: %1$s</string>
+    <string name="install">Install</string>
+    <string name="install_package">Install new app: %s</string>
+    <string name="install_package_version_tips">You have chosen an existing app, \n(Installed version %1$s, selected version: %2$s). Install it?</string>
+    <string name="settings_add_app_summary">Please add both the app and the Xposed module to VirtualXposed first, otherwise the Xposed module won\'t take effect.</string>
+    <string name="settings_advance">Advanced Settings</string>
+    <string name="advance_settings_hide_settings">Hide settings button on desktop</string>
+    <string name="advance_settings_disable_installer">Disable apk installer for system</string>
+    <string name="advance_settings_hide_settings_summary">If you can enter settings by the menu key on main activity, you can hide this; you may not be able to enter settings otherwise! \n(Restart VirtualXposed to take effect.)</string>
+    <string name="advance_settings_disable_installer_summary">Do not show VirtualXposed\'s installer when you choose apk file in system</string>
+    <string name="advance_settings_directly_back">Directly back</string>
+    <string name="advance_settings_directly_back_summary">Go back to the system launcher instead of the virtual launcher when in a virtual app.\n(Restart VirtualXposed to take effect.)</string>
+    <string name="install_self_eggs">Boy, your idea is promising :)</string>
+    <string name="advance_settings_install_gms">Install/Uninstall Google Services</string>
+    <string name="about_feedback_tel_title">Telegram Group: %1$s</string>
+    <string name="advance_settings_copy_file">Copy File</string>
+    <string name="about_website_title">Official website</string>
+    <string name="about_feedback_title">Feedback</string>
+    <string name="about_feedback_hint">Please specify your phone model, Android version, Xposed plugin, and version of the corresponding app, and then describe (in as much detail as possible) the problem you are experiencing, otherwise it is considered invalid feedback (because we can\'t effectively troubleshoot and fix said problem with only \"this app crashes\" ;) )</string>
+    <string name="settings_plugin_recommend">Recommended Xposed Modules</string>
+    <string name="advance_settings_disable_resident_notification">Disable persistent notification</string>
+    <string name="advance_settings_disable_resident_notification_summary">Disable the persistent notification of VirtualXposed. (When disabled, VirtualXposed may be killed by the system frequently. Use with caution!)</string>
+    <string name="settings_module_manage">Manage Xposed Module</string>
+    <string name="settings_module_manage_summary">Enable/Disable Xposed module (You must enable it manually for it to take effect)</string>
+    <string name="xposed_installer_not_found">Xposed Installer not found, please add it to VirtualXposed first!</string>
+    <string name="advance_settings_allow_fake_signature_summary">When enabled, you can install apk which has no signatures(Usually applies to cracked/modded apps, please be careful
+)</string>
+    <string name="advance_settings_allow_fake_signature">Allow to install apk without signatures.</string>
+    <string name="permission_tip_title">Important tips:</string>
+    <string name="permission_tips_content">%1$s  doesn\'t support runtime permission, you must give permissions it needs in advance, please allow all the permission it will request in next step, otherwise it may not work properly.</string>
+    <string name="permission_tips_confirm">OK, I know.</string>
+    <string name="permission_denied_tips_content">App: %1$s doesn\'t grant necessary permission, if it doesn\'t work properly, please go to your device\'s permission management and give it permissions.</string>
+    <string name="list_app_access_external_storage">If you want to install apk from external storage, please give VirtualXposed the permission.</string>
+    <string name="install_gms_title" >Install Google Service</string>
+    <string name="install_gms_content">The Google Service is supported by microG, VirtualXposed is about to download some file(2M), and it may consume more battery, would you like to install it?</string>
+    <string name="install_gms_fail_title">Install failed</string>
+    <string name="install_gms_fail_content">Install Google Service automatically failed, you can also install it manually.</string>
+    <string name="install_gms_fail_ok">Manual Install</string>
+    <string name="uninstall_gms_title">Uninstall Google Service</string>
+    <string name="uninstall_gms_content">Would you like to uninstall Google Service? you can reinstall it later.</string>
+    <string name="uninstall_gms_ok">Uninstall, Confirm</string>
+    <string name="install_gms_alreay_installed">The Google Service has been installed.</string>
+    <string name="install_gms_success">Google Service has been installed successfully!!</string>
+    <string name="uninstall_gms_success">Google Service has been uninstalled successfully!!</string>
+    <string name="donate_choose_title">Way to donate</string>
+    <string name="donate_bitconins_tips">My bitconins address has been copied to clipboard:)</string>
+    <string name="advance_settings_disable_xposed_summary">When disabled, all Xposed module won\'t take effect.</string>
+    <string name="advance_settings_disable_xposed">Disable Xposed</string>
+    <string name="prepare_xposed_installer">Preparing Xposed environment, please wait…</string>
+    <string name="settings_file_manage_text">File Manager</string>
+    <string name="install_file_manager_tips">File Manager is supported by Amaze File Manager, download(about 3M) and install it now?</string>
+    <string name="settings_permission_manage_text">Permission Manager</string>
+    <string name="install_permission_manager_tips">Permission Manager is implemented by XPrivacyLua, download(about 1.7M) and install it now?</string>
+    <string name="exp_tips">A long time ago, I revealed the information about EXposed. After a period of feedback and adjustment, EXposed has reached a milestone. Many users may don\'t know it, let me introduce you today. \n\n
+
+Like the VirtualXposed I created earlier, EXposed is also an app that makes you use Xposed module without root, unlock the bootloader. they both have their own advantages, and EXposed will be much better in terms of performance and stability due to running directly in the native system; while VirtualXposed is based on VitualApp, which is like Parallel Space. EXposed was born a month, and its stability has surpassed VirtualXposed which has been developed for nearly a year. \n\n
+
+Currently, EXposed has been put on the app store and renamed to Tai Chi. You can try it out :)</string>
+    <string name="advance_settings_enable_launcher_summary">When enabled, You can set VirtualXposed be the Launcher of system</string>
+    <string name="advance_settings_enable_launcher">Enable Launcher</string>
+    <string name="exp_introduce_title">An easy way to use Xposed</string>
+    <string name="exp_introduce_install">Have a try</string>
+    <string name="what_is_exp">What is TaiChi?</string>
+    <string name="install_choose_way">Choose way to install</string>
+    <string name="install_choose_content" >You can use Xposed modules through TaiChi except VirtualXposed. VirtualXposed supports Virtual-App,while TaiChi is much more stable.</string>
+    <string name="install_choose_taichi">TaiChi</string>
+    <string name="install_taichi_not_exist">TaiChi is not installed!</string>
+    <string name="install_go_to_install_exp">Go to install TaiChi</string>
+    <string name="install_taichi_while_old_version">The version of TaiChi installed is too old, Please install the latest Taichi!</string>
+    <string name="install_go_latest_exp">Install the latest TaiChi</string>
 </resources>
diff --git a/VirtualApp/app/src/main/res/values/styles.xml b/VirtualApp/app/src/main/res/values/styles.xml
index 182f33032..d59a73286 100644
--- a/VirtualApp/app/src/main/res/values/styles.xml
+++ b/VirtualApp/app/src/main/res/values/styles.xml
@@ -2,7 +2,6 @@
 
     <!-- Base application theme. -->
     <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
-        <!-- Customize your theme here. -->
         <item name="windowNoTitle">true</item>
         <item name="android:windowIsTranslucent">false</item>
         <item name="colorPrimary">@color/colorPrimary</item>
@@ -12,7 +11,6 @@
     </style>
 
     <style name="UITheme" parent="Theme.AppCompat.NoActionBar">
-        <!-- Customize your theme here. -->
         <item name="colorPrimary">@color/colorPrimary</item>
         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
         <item name="colorAccent">@color/colorAccent</item>
@@ -30,11 +28,8 @@
 
     <style name="VAAlertTheme" parent="Theme.AppCompat.Light.Dialog.Alert" />
 
-    <style name="AVLoadingIndicatorView">
-        <item name="minWidth">48dip</item>
-        <item name="maxWidth">48dip</item>
-        <item name="minHeight">48dip</item>
-        <item name="maxHeight">48dip</item>
-        <item name="indicatorName">BallPulseIndicator</item>
-    </style>
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
+
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
+
 </resources>
diff --git a/VirtualApp/app/src/main/res/xml/settings_preferences.xml b/VirtualApp/app/src/main/res/xml/settings_preferences.xml
new file mode 100644
index 000000000..3c9d54974
--- /dev/null
+++ b/VirtualApp/app/src/main/res/xml/settings_preferences.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <Preference
+        android:key="settings_add_app"
+        android:persistent="false"
+        android:summary="@string/settings_add_app_summary"
+        android:title="@string/add_app" />
+
+    <Preference
+        android:key="settings_module_manage"
+        android:persistent="false"
+        android:summary="@string/settings_module_manage_summary"
+        android:title="@string/settings_module_manage" />
+
+    <Preference
+        android:key="settings_plugin_recommend"
+        android:persistent="false"
+        android:title="@string/settings_plugin_recommend" />
+
+    <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+        android:key="settings_advance"
+        android:title="@string/settings_advance">
+
+        <Preference
+            android:key="advance_settings_install_gms"
+            android:persistent="false"
+            android:title="@string/advance_settings_install_gms"/>
+
+        <Preference
+            android:key="settings_file_manage"
+            android:persistent="false"
+            android:title="@string/settings_file_manage_text" />
+
+        <SwitchPreference
+            android:defaultValue="false"
+            android:key="advance_settings_disable_xposed"
+            android:persistent="true"
+            android:summary="@string/advance_settings_disable_xposed_summary"
+            android:title="@string/advance_settings_disable_xposed" />
+
+        <SwitchPreference
+            android:defaultValue="false"
+            android:key="advance_settings_disable_resident_notification"
+            android:persistent="true"
+            android:summary="@string/advance_settings_disable_resident_notification_summary"
+            android:title="@string/advance_settings_disable_resident_notification" />
+
+        <SwitchPreference
+            android:defaultValue="false"
+            android:key="advance_settings_hide_settings"
+            android:persistent="true"
+            android:summary="@string/advance_settings_hide_settings_summary"
+            android:title="@string/advance_settings_hide_settings" />
+
+        <SwitchPreference
+            android:defaultValue="false"
+            android:key="advance_settings_allow_fake_signature"
+            android:persistent="true"
+            android:summary="@string/advance_settings_allow_fake_signature_summary"
+            android:title="@string/advance_settings_allow_fake_signature" />
+
+        <SwitchPreference
+            android:defaultValue="false"
+            android:key="advance_settings_disable_installer"
+            android:persistent="true"
+            android:summary="@string/advance_settings_disable_installer_summary"
+            android:title="@string/advance_settings_disable_installer" />
+
+        <SwitchPreference
+            android:defaultValue="false"
+            android:key="advance_settings_enable_launcher"
+            android:persistent="true"
+            android:summary="@string/advance_settings_enable_launcher_summary"
+            android:title="@string/advance_settings_enable_launcher" />
+
+        <SwitchPreference
+            android:defaultValue="false"
+            android:key="advance_settings_directly_back"
+            android:persistent="true"
+            android:summary="@string/advance_settings_directly_back_summary"
+            android:title="@string/advance_settings_directly_back" />
+
+        <Preference
+            android:key="settings_desktop"
+            android:persistent="false"
+            android:title="@string/settings_desktop_text" />
+    </PreferenceScreen>
+
+    <Preference
+        android:key="settings_permission_manage"
+        android:persistent="false"
+        android:title="@string/settings_permission_manage_text" />
+
+    <Preference
+        android:key="settings_app_manage"
+        android:persistent="false"
+        android:title="@string/settings_app_manage_text" />
+
+    <Preference
+        android:key="settings_task_manage"
+        android:persistent="false"
+        android:title="@string/settings_task_manage_text" />
+
+    <Preference
+        android:key="settings_donate"
+        android:persistent="false"
+        android:title="@string/about_donate_title" />
+
+    <Preference
+        android:key="settings_faq"
+        android:persistent="false"
+        android:title="@string/about_faq_title" />
+
+    <Preference
+        android:key="settings_about"
+        android:persistent="false"
+        android:title="@string/settings_about_text" />
+
+    <Preference
+        android:key="settings_reboot"
+        android:persistent="false"
+        android:title="@string/menu_reboot" />
+</PreferenceScreen>
diff --git a/VirtualApp/build.gradle b/VirtualApp/build.gradle
index 7aa701d03..107923b3e 100644
--- a/VirtualApp/build.gradle
+++ b/VirtualApp/build.gradle
@@ -3,11 +3,20 @@
 buildscript {
     repositories {
         jcenter()
+        maven {
+            url 'https://maven.google.com/'
+            name 'Google'
+        }
+        maven {
+            url 'https://maven.fabric.io/public'
+        }
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.1'
-        classpath 'me.tatarka:gradle-retrolambda:3.6.0'
-        classpath 'com.android.tools.build:gradle-experimental:0.8.0'
+        classpath 'com.android.tools.build:gradle:3.2.1'
+        classpath 'com.android.tools.build:gradle-experimental:0.11.1'
+        // We recommend changing it to the latest version from our changelog:
+        // https://docs.fabric.io/android/changelog.html#fabric-gradle-plugin
+        classpath 'io.fabric.tools:gradle:1.+'
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }
@@ -15,10 +24,15 @@ buildscript {
 
 allprojects {
     repositories {
-        jcenter()
+        mavenLocal()
         maven {
             url "https://jitpack.io"
         }
+        maven {
+            url 'https://maven.google.com/'
+            name 'Google'
+        }
+        jcenter()
     }
 }
 
diff --git a/VirtualApp/gradle/wrapper/gradle-wrapper.properties b/VirtualApp/gradle/wrapper/gradle-wrapper.properties
index 00a91bd00..418abf0eb 100644
--- a/VirtualApp/gradle/wrapper/gradle-wrapper.properties
+++ b/VirtualApp/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sun Jan 15 17:29:32 CST 2017
+#Mon Oct 29 11:05:44 CST 2018
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/VirtualApp/launcher b/VirtualApp/launcher
new file mode 160000
index 000000000..cb60e0b76
--- /dev/null
+++ b/VirtualApp/launcher
@@ -0,0 +1 @@
+Subproject commit cb60e0b76b2d4f7e87e7e98ad014801265d5bc77
diff --git a/VirtualApp/lib/build.gradle b/VirtualApp/lib/build.gradle
index f16af8fac..95263b3a8 100644
--- a/VirtualApp/lib/build.gradle
+++ b/VirtualApp/lib/build.gradle
@@ -1,17 +1,24 @@
 apply plugin: 'com.android.library'
 
+Properties properties = new Properties()
+def localProp = file(project.rootProject.file('local.properties'))
+if (localProp.exists()) {
+    properties.load(localProp.newDataInputStream())
+}
+def exposedVersion = properties.getProperty("exposed.version") ?: "0.3.5"
+
 android {
-    compileSdkVersion 24
-    buildToolsVersion "25.0.2"
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
 
     defaultConfig {
-        minSdkVersion 14
-        targetSdkVersion 25
+        minSdkVersion 21
+        targetSdkVersion 22
         versionCode 1
         versionName "1.0"
         externalNativeBuild {
             ndkBuild {
-                abiFilters "armeabi", "armeabi-v7a", "x86"
+                abiFilters "armeabi-v7a", "x86"
             }
         }
     }
@@ -26,11 +33,23 @@ android {
             path file("src/main/jni/Android.mk")
         }
     }
+    lintOptions {
+        //IJobService need NewApi
+        warning 'NewApi','OnClick'
+        checkReleaseBuilds false
+        // Or, if you prefer, you can continue to check for errors in release builds,
+        // but continue the build even when errors are found:
+        abortOnError false
+    }
 }
 
 
 dependencies {
-    compile fileTree(include: ['*.jar'], dir: 'libs')
-    //    compile 'net.lingala.zip4j:zip4j:1.3.2'
-    compile files('src/main/libs/android-art-interpret-3.0.0.jar')
+    implementation fileTree(include: ['*.jar'], dir: 'libs')
+    api "me.weishu.exposed:exposed-core:${exposedVersion}"
+    implementation "me.weishu:free_reflection:1.2.0"
+}
+
+repositories {
+    maven { url 'https://jitpack.io' }
 }
diff --git a/VirtualApp/lib/src/main/AndroidManifest.xml b/VirtualApp/lib/src/main/AndroidManifest.xml
index 6d89fced3..deb56b2af 100644
--- a/VirtualApp/lib/src/main/AndroidManifest.xml
+++ b/VirtualApp/lib/src/main/AndroidManifest.xml
@@ -225,33 +225,58 @@
     <application>
         <service
             android:name="com.lody.virtual.client.stub.DaemonService"
-            android:process=":x" />
+            android:process="@string/engine_process_name" />
 
         <service
             android:name="com.lody.virtual.client.stub.DaemonService$InnerService"
-            android:process=":x" />
+            android:process="@string/engine_process_name" />
 
         <activity
             android:name="com.lody.virtual.client.stub.ShortcutHandleActivity"
+            android:excludeFromRecents="true"
+            android:taskAffinity="va.task.shortcut"
             android:exported="true"
-            android:process=":x"
+            android:process="@string/engine_process_name"
             android:theme="@android:style/Theme.Translucent.NoTitleBar" />
 
         <activity
             android:name=".client.stub.StubPendingActivity"
-            android:process=":x" />
+            android:process="@string/engine_process_name" />
 
         <service
             android:name=".client.stub.StubPendingService"
-            android:process=":x" />
+            android:process="@string/engine_process_name" />
         <receiver
             android:name=".client.stub.StubPendingReceiver"
-            android:process=":x" />
+            android:process="@string/engine_process_name" />
 
         <service
             android:name=".client.stub.StubJob"
             android:permission="android.permission.BIND_JOB_SERVICE"
-            android:process=":x" />
+            android:process="@string/engine_process_name" />
+
+        <service
+            android:name=".client.stub.DaemonJobService"
+            android:enabled="true"
+            android:exported="true"
+            android:permission="android.permission.BIND_JOB_SERVICE"
+            android:process="@string/engine_process_name" />
+
+        <activity
+            android:name=".client.stub.ChooseAccountTypeActivity"
+            android:configChanges="keyboard|keyboardHidden|orientation"
+            android:excludeFromRecents="true"
+            android:exported="false"
+            android:process="@string/engine_process_name"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name=".client.stub.ChooseTypeAndAccountActivity"
+            android:configChanges="keyboard|keyboardHidden|orientation"
+            android:excludeFromRecents="true"
+            android:exported="false"
+            android:process="@string/engine_process_name"
+            android:screenOrientation="portrait" />
 
         <activity
             android:name=".client.stub.ChooserActivity"
@@ -259,7 +284,7 @@
             android:excludeFromRecents="true"
             android:exported="true"
             android:finishOnCloseSystemDialogs="true"
-            android:process=":x"
+            android:process="@string/engine_process_name"
             android:screenOrientation="portrait"
             android:theme="@style/VAAlertTheme" />
 
@@ -269,7 +294,7 @@
             android:excludeFromRecents="true"
             android:exported="true"
             android:finishOnCloseSystemDialogs="true"
-            android:process=":x"
+            android:process="@string/engine_process_name"
             android:screenOrientation="portrait"
             android:theme="@style/VAAlertTheme" />
 
@@ -277,7 +302,7 @@
             android:name="com.lody.virtual.server.BinderProvider"
             android:authorities="${applicationId}.virtual.service.BinderProvider"
             android:exported="false"
-            android:process=":x" />
+            android:process="@string/engine_process_name" />
 
         <activity
             android:name="com.lody.virtual.client.stub.StubActivity$C0"
@@ -980,302 +1005,702 @@
             android:taskAffinity="com.lody.virtual.vt"
             android:theme="@android:style/Theme.Dialog" />
 
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C0"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p0"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C1"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p1"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C2"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p2"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C3"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p3"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C4"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p4"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C5"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p5"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C6"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p6"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C7"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p7"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C8"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p8"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C9"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p9"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C10"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p10"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C11"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p11"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C12"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p12"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C13"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p13"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C14"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p14"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C15"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p15"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C16"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p16"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C17"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p17"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C18"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p18"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C19"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p19"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C20"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p20"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C21"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p21"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C22"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p22"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C23"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p23"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C24"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p24"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C25"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p25"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C26"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p26"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C27"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p27"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C28"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p28"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C29"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p29"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C30"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p30"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C31"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p31"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C32"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p32"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C33"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p33"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C34"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p34"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C35"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p35"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C36"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p36"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C37"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p37"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C38"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p38"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C39"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p39"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C40"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p40"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C41"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p41"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C42"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p42"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C43"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p43"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C44"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p44"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C45"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p45"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C46"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p46"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C47"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p47"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C48"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p48"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
+        <activity
+            android:name="com.lody.virtual.client.stub.StubExcludeFromRecentActivity$C49"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
+            android:process=":p49"
+            android:excludeFromRecents="true"
+            android:taskAffinity="com.lody.virtual.vt"
+            android:theme="@style/VATheme" />
+
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C0"
+            android:name="com.lody.virtual.client.stub.StubCP$C0"
             android:authorities="${applicationId}.virtual_stub_0"
             android:exported="false"
             android:process=":p0" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C1"
+            android:name="com.lody.virtual.client.stub.StubCP$C1"
             android:authorities="${applicationId}.virtual_stub_1"
             android:exported="false"
             android:process=":p1" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C2"
+            android:name="com.lody.virtual.client.stub.StubCP$C2"
             android:authorities="${applicationId}.virtual_stub_2"
             android:exported="false"
             android:process=":p2" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C3"
+            android:name="com.lody.virtual.client.stub.StubCP$C3"
             android:authorities="${applicationId}.virtual_stub_3"
             android:exported="false"
             android:process=":p3" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C4"
+            android:name="com.lody.virtual.client.stub.StubCP$C4"
             android:authorities="${applicationId}.virtual_stub_4"
             android:exported="false"
             android:process=":p4" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C5"
+            android:name="com.lody.virtual.client.stub.StubCP$C5"
             android:authorities="${applicationId}.virtual_stub_5"
             android:exported="false"
             android:process=":p5" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C6"
+            android:name="com.lody.virtual.client.stub.StubCP$C6"
             android:authorities="${applicationId}.virtual_stub_6"
             android:exported="false"
             android:process=":p6" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C7"
+            android:name="com.lody.virtual.client.stub.StubCP$C7"
             android:authorities="${applicationId}.virtual_stub_7"
             android:exported="false"
             android:process=":p7" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C8"
+            android:name="com.lody.virtual.client.stub.StubCP$C8"
             android:authorities="${applicationId}.virtual_stub_8"
             android:exported="false"
             android:process=":p8" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C9"
+            android:name="com.lody.virtual.client.stub.StubCP$C9"
             android:authorities="${applicationId}.virtual_stub_9"
             android:exported="false"
             android:process=":p9" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C10"
+            android:name="com.lody.virtual.client.stub.StubCP$C10"
             android:authorities="${applicationId}.virtual_stub_10"
             android:exported="false"
             android:process=":p10" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C11"
+            android:name="com.lody.virtual.client.stub.StubCP$C11"
             android:authorities="${applicationId}.virtual_stub_11"
             android:exported="false"
             android:process=":p11" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C12"
+            android:name="com.lody.virtual.client.stub.StubCP$C12"
             android:authorities="${applicationId}.virtual_stub_12"
             android:exported="false"
             android:process=":p12" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C13"
+            android:name="com.lody.virtual.client.stub.StubCP$C13"
             android:authorities="${applicationId}.virtual_stub_13"
             android:exported="false"
             android:process=":p13" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C14"
+            android:name="com.lody.virtual.client.stub.StubCP$C14"
             android:authorities="${applicationId}.virtual_stub_14"
             android:exported="false"
             android:process=":p14" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C15"
+            android:name="com.lody.virtual.client.stub.StubCP$C15"
             android:authorities="${applicationId}.virtual_stub_15"
             android:exported="false"
             android:process=":p15" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C16"
+            android:name="com.lody.virtual.client.stub.StubCP$C16"
             android:authorities="${applicationId}.virtual_stub_16"
             android:exported="false"
             android:process=":p16" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C17"
+            android:name="com.lody.virtual.client.stub.StubCP$C17"
             android:authorities="${applicationId}.virtual_stub_17"
             android:exported="false"
             android:process=":p17" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C18"
+            android:name="com.lody.virtual.client.stub.StubCP$C18"
             android:authorities="${applicationId}.virtual_stub_18"
             android:exported="false"
             android:process=":p18" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C19"
+            android:name="com.lody.virtual.client.stub.StubCP$C19"
             android:authorities="${applicationId}.virtual_stub_19"
             android:exported="false"
             android:process=":p19" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C20"
+            android:name="com.lody.virtual.client.stub.StubCP$C20"
             android:authorities="${applicationId}.virtual_stub_20"
             android:exported="false"
             android:process=":p20" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C21"
+            android:name="com.lody.virtual.client.stub.StubCP$C21"
             android:authorities="${applicationId}.virtual_stub_21"
             android:exported="false"
             android:process=":p21" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C22"
+            android:name="com.lody.virtual.client.stub.StubCP$C22"
             android:authorities="${applicationId}.virtual_stub_22"
             android:exported="false"
             android:process=":p22" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C23"
+            android:name="com.lody.virtual.client.stub.StubCP$C23"
             android:authorities="${applicationId}.virtual_stub_23"
             android:exported="false"
             android:process=":p23" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C24"
+            android:name="com.lody.virtual.client.stub.StubCP$C24"
             android:authorities="${applicationId}.virtual_stub_24"
             android:exported="false"
             android:process=":p24" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C25"
+            android:name="com.lody.virtual.client.stub.StubCP$C25"
             android:authorities="${applicationId}.virtual_stub_25"
             android:exported="false"
             android:process=":p25" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C26"
+            android:name="com.lody.virtual.client.stub.StubCP$C26"
             android:authorities="${applicationId}.virtual_stub_26"
             android:exported="false"
             android:process=":p26" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C27"
+            android:name="com.lody.virtual.client.stub.StubCP$C27"
             android:authorities="${applicationId}.virtual_stub_27"
             android:exported="false"
             android:process=":p27" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C28"
+            android:name="com.lody.virtual.client.stub.StubCP$C28"
             android:authorities="${applicationId}.virtual_stub_28"
             android:exported="false"
             android:process=":p28" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C29"
+            android:name="com.lody.virtual.client.stub.StubCP$C29"
             android:authorities="${applicationId}.virtual_stub_29"
             android:exported="false"
             android:process=":p29" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C30"
+            android:name="com.lody.virtual.client.stub.StubCP$C30"
             android:authorities="${applicationId}.virtual_stub_30"
             android:exported="false"
             android:process=":p30" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C31"
+            android:name="com.lody.virtual.client.stub.StubCP$C31"
             android:authorities="${applicationId}.virtual_stub_31"
             android:exported="false"
             android:process=":p31" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C32"
+            android:name="com.lody.virtual.client.stub.StubCP$C32"
             android:authorities="${applicationId}.virtual_stub_32"
             android:exported="false"
             android:process=":p32" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C33"
+            android:name="com.lody.virtual.client.stub.StubCP$C33"
             android:authorities="${applicationId}.virtual_stub_33"
             android:exported="false"
             android:process=":p33" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C34"
+            android:name="com.lody.virtual.client.stub.StubCP$C34"
             android:authorities="${applicationId}.virtual_stub_34"
             android:exported="false"
             android:process=":p34" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C35"
+            android:name="com.lody.virtual.client.stub.StubCP$C35"
             android:authorities="${applicationId}.virtual_stub_35"
             android:exported="false"
             android:process=":p35" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C36"
+            android:name="com.lody.virtual.client.stub.StubCP$C36"
             android:authorities="${applicationId}.virtual_stub_36"
             android:exported="false"
             android:process=":p36" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C37"
+            android:name="com.lody.virtual.client.stub.StubCP$C37"
             android:authorities="${applicationId}.virtual_stub_37"
             android:exported="false"
             android:process=":p37" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C38"
+            android:name="com.lody.virtual.client.stub.StubCP$C38"
             android:authorities="${applicationId}.virtual_stub_38"
             android:exported="false"
             android:process=":p38" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C39"
+            android:name="com.lody.virtual.client.stub.StubCP$C39"
             android:authorities="${applicationId}.virtual_stub_39"
             android:exported="false"
             android:process=":p39" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C40"
+            android:name="com.lody.virtual.client.stub.StubCP$C40"
             android:authorities="${applicationId}.virtual_stub_40"
             android:exported="false"
             android:process=":p40" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C41"
+            android:name="com.lody.virtual.client.stub.StubCP$C41"
             android:authorities="${applicationId}.virtual_stub_41"
             android:exported="false"
             android:process=":p41" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C42"
+            android:name="com.lody.virtual.client.stub.StubCP$C42"
             android:authorities="${applicationId}.virtual_stub_42"
             android:exported="false"
             android:process=":p42" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C43"
+            android:name="com.lody.virtual.client.stub.StubCP$C43"
             android:authorities="${applicationId}.virtual_stub_43"
             android:exported="false"
             android:process=":p43" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C44"
+            android:name="com.lody.virtual.client.stub.StubCP$C44"
             android:authorities="${applicationId}.virtual_stub_44"
             android:exported="false"
             android:process=":p44" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C45"
+            android:name="com.lody.virtual.client.stub.StubCP$C45"
             android:authorities="${applicationId}.virtual_stub_45"
             android:exported="false"
             android:process=":p45" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C46"
+            android:name="com.lody.virtual.client.stub.StubCP$C46"
             android:authorities="${applicationId}.virtual_stub_46"
             android:exported="false"
             android:process=":p46" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C47"
+            android:name="com.lody.virtual.client.stub.StubCP$C47"
             android:authorities="${applicationId}.virtual_stub_47"
             android:exported="false"
             android:process=":p47" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C48"
+            android:name="com.lody.virtual.client.stub.StubCP$C48"
             android:authorities="${applicationId}.virtual_stub_48"
             android:exported="false"
             android:process=":p48" />
 
         <provider
-            android:name="com.lody.virtual.client.stub.StubContentProvider$C49"
+            android:name="com.lody.virtual.client.stub.StubCP$C49"
             android:authorities="${applicationId}.virtual_stub_49"
             android:exported="false"
             android:process=":p49" />
diff --git a/VirtualApp/lib/src/main/aidl/android/content/ISyncAdapter.aidl b/VirtualApp/lib/src/main/aidl/android/content/ISyncAdapter.aidl
new file mode 100644
index 000000000..2325fffee
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/android/content/ISyncAdapter.aidl
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.accounts.Account;
+import android.os.Bundle;
+import android.content.ISyncContext;
+
+/**
+ * Interface used to control the sync activity on a SyncAdapter
+ */
+interface ISyncAdapter {
+    /**
+     * Initiate a sync for this account. SyncAdapter-specific parameters may
+     * be specified in extras, which is guaranteed to not be null.
+     *
+     * @param syncContext the ISyncContext used to indicate the progress of the sync. When
+     *   the sync is finished (successfully or not) ISyncContext.onFinished() must be called.
+     * @param authority the authority that should be synced
+     * @param account the account that should be synced
+     * @param extras SyncAdapter-specific parameters
+     */
+    void startSync(ISyncContext syncContext, String authority,
+      in Account account, in Bundle extras);
+
+    /**
+     * Cancel the most recently initiated sync. Due to race conditions, this may arrive
+     * after the ISyncContext.onFinished() for that sync was called.
+     * @param syncContext the ISyncContext that was passed to {@link #startSync}
+     */
+    void cancelSync(ISyncContext syncContext);
+
+    /**
+     * Initialize the SyncAdapter for this account and authority.
+     *
+     * @param account the account that should be synced
+     * @param authority the authority that should be synced
+     */
+    void initialize(in Account account, String authority);
+}
diff --git a/VirtualApp/lib/src/main/aidl/android/content/ISyncContext.aidl b/VirtualApp/lib/src/main/aidl/android/content/ISyncContext.aidl
new file mode 100644
index 000000000..6d18a1cec
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/android/content/ISyncContext.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.SyncResult;
+
+/**
+ * Interface used by the SyncAdapter to indicate its progress.
+ * @hide
+ */
+interface ISyncContext {
+    /**
+     * Call to indicate that the SyncAdapter is making progress. E.g., if this SyncAdapter
+     * downloads or sends records to/from the server, this may be called after each record
+     * is downloaded or uploaded.
+     */
+    void sendHeartbeat();
+
+    /**
+     * Signal that the corresponding sync session is completed.
+     * @param result information about this sync session
+     */
+    void onFinished(in SyncResult result);
+}
diff --git a/VirtualApp/lib/src/main/aidl/android/content/ISyncStatusObserver.aidl b/VirtualApp/lib/src/main/aidl/android/content/ISyncStatusObserver.aidl
new file mode 100644
index 000000000..88ea23a02
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/android/content/ISyncStatusObserver.aidl
@@ -0,0 +1,6 @@
+package android.content;
+
+
+interface ISyncStatusObserver {
+    void onStatusChanged(int which);
+}
diff --git a/VirtualApp/lib/src/main/aidl/android/location/ILocationListener.aidl b/VirtualApp/lib/src/main/aidl/android/location/ILocationListener.aidl
new file mode 100644
index 000000000..1571ae26a
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/android/location/ILocationListener.aidl
@@ -0,0 +1,13 @@
+// ILocationListener.aidl
+package android.location;
+
+import android.location.Location;
+import android.os.Bundle;
+
+interface ILocationListener
+{
+    void onLocationChanged(in Location location);
+    void onStatusChanged(String provider, int status, in Bundle extras);
+    void onProviderEnabled(String provider);
+    void onProviderDisabled(String provider);
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/aidl/android/net/IConnectivityManager.aidl b/VirtualApp/lib/src/main/aidl/android/net/IConnectivityManager.aidl
new file mode 100644
index 000000000..c26314e51
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/android/net/IConnectivityManager.aidl
@@ -0,0 +1,18 @@
+package android.net;
+
+import android.net.NetworkInfo;
+import android.net.LinkProperties;
+
+interface IConnectivityManager {
+
+    NetworkInfo getActiveNetworkInfo();
+    NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked);
+
+    NetworkInfo getNetworkInfo(int networkType);
+    NetworkInfo[] getAllNetworkInfo();
+    boolean isActiveNetworkMetered();
+    boolean requestRouteToHostAddress(int networkType, int address);
+    LinkProperties getActiveLinkProperties();
+    LinkProperties getLinkProperties(int networkType);
+
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/BadgerInfo.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/BadgerInfo.aidl
new file mode 100644
index 000000000..0467f81fa
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/BadgerInfo.aidl
@@ -0,0 +1,4 @@
+// BadgerInfo.aidl
+package com.lody.virtual.remote;
+
+parcelable BadgerInfo;
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/VDeviceInfo.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/VDeviceInfo.aidl
new file mode 100644
index 000000000..76f27ca3a
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/VDeviceInfo.aidl
@@ -0,0 +1,4 @@
+// VDeviceInfo.aidl
+package com.lody.virtual.remote;
+
+parcelable VDeviceInfo;
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VCell.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VCell.aidl
new file mode 100644
index 000000000..9bab23382
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VCell.aidl
@@ -0,0 +1,4 @@
+// VCell.aidl
+package com.lody.virtual.remote.vloc;
+
+parcelable VCell;
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VLocation.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VLocation.aidl
new file mode 100644
index 000000000..7bf7cd84d
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VLocation.aidl
@@ -0,0 +1,4 @@
+// VLocation.aidl
+package com.lody.virtual.remote.vloc;
+
+parcelable VLocation;
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VWifi.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VWifi.aidl
new file mode 100644
index 000000000..406b75249
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VWifi.aidl
@@ -0,0 +1,4 @@
+// VWifi.aidl
+package com.lody.virtual.remote.vloc;
+
+parcelable VWifi;
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IActivityManager.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IActivityManager.aidl
index 0b1ba13db..94d8625b5 100644
--- a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IActivityManager.aidl
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IActivityManager.aidl
@@ -5,6 +5,7 @@ import com.lody.virtual.remote.VParceledListSlice;
 import com.lody.virtual.remote.AppTaskInfo;
 import com.lody.virtual.remote.PendingIntentData;
 import com.lody.virtual.remote.PendingResultData;
+import com.lody.virtual.remote.BadgerInfo;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.app.Notification;
@@ -80,7 +81,7 @@ interface IActivityManager {
     boolean stopServiceToken(in ComponentName className, in IBinder token, int startId, int userId);
 
     void setServiceForeground(in ComponentName className, in IBinder token, int id,
-                            in Notification notification, boolean keepNotification, int userId);
+                            in Notification notification, boolean removeNotification, int userId);
 
     int bindService(in IBinder caller, in IBinder token, in Intent service,
                     String resolvedType, in IServiceConnection connection, int flags, int userId);
@@ -110,4 +111,6 @@ interface IActivityManager {
     void processRestarted(in String packageName, in String processName, int userId);
 
     void broadcastFinish(in PendingResultData res);
+
+    void notifyBadgerChange(in BadgerInfo info);
 }
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IAppManager.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IAppManager.aidl
index ca665ed60..ef2a9d366 100644
--- a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IAppManager.aidl
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IAppManager.aidl
@@ -19,6 +19,8 @@ interface IAppManager {
     boolean installPackageAsUser(int userId, String packageName);
     boolean uninstallPackageAsUser(String packageName, int userId);
     boolean uninstallPackage(String packageName);
+    boolean clearPackageAsUser(int userId, String packageName);
+    boolean clearPackage(String packageName);
     List<InstalledAppInfo> getInstalledApps(int flags);
     List<InstalledAppInfo> getInstalledAppsAsUser(int userId, int flags);
     int getInstalledAppCount();
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IDeviceInfoManager.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IDeviceInfoManager.aidl
new file mode 100644
index 000000000..3305d799d
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IDeviceInfoManager.aidl
@@ -0,0 +1,12 @@
+// IDeviceInfoManager.aidl
+package com.lody.virtual.server;
+
+import com.lody.virtual.remote.VDeviceInfo;
+
+interface IDeviceInfoManager {
+
+    VDeviceInfo getDeviceInfo(int userId);
+
+    void updateDeviceInfo(int userId, in VDeviceInfo info);
+
+}
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IJobScheduler.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IJobScheduler.aidl
index 5e230364c..065ec43b8 100644
--- a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IJobScheduler.aidl
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IJobScheduler.aidl
@@ -1,13 +1,16 @@
-package com.lody.virtual.server;
-
-import android.app.job.JobInfo;
-
- /**
-  * IPC interface that supports the app-facing {@link #JobScheduler} api.
-  */
-interface IJobScheduler {
-    int schedule(in JobInfo job);
-    void cancel(int jobId);
-    void cancelAll();
-    List<JobInfo> getAllPendingJobs();
-}
+//package com.lody.virtual.server;
+//
+//import android.app.job.JobInfo;
+//import android.app.job.JobParameters;
+//
+// /**
+//  * IPC interface that supports the app-facing {@link #JobScheduler} api.
+//  */
+//interface IJobScheduler {
+//    int schedule(in JobInfo job);
+//    void cancel(int jobId);
+//    void cancelAll();
+//    List<JobInfo> getAllPendingJobs();
+//    int enqueue(in JobInfo job, in JobParameters work);
+//    JobInfo getPendingJob(int i);
+//}
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IPackageManager.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IPackageManager.aidl
index 9e64f4775..973cbf7ef 100644
--- a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IPackageManager.aidl
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IPackageManager.aidl
@@ -16,6 +16,8 @@ import android.content.pm.PermissionGroupInfo;
 import com.lody.virtual.remote.ReceiverInfo;
 import com.lody.virtual.remote.VParceledListSlice;
 
+import com.lody.virtual.server.IPackageInstaller;
+
 interface IPackageManager {
 
         int getPackageUid(String packageName, int userId);
@@ -69,4 +71,8 @@ interface IPackageManager {
          VParceledListSlice queryContentProviders(in String processName, int vuid, int flags);
 
          List<String> querySharedPackages(in String packageName);
+
+         String getNameForUid(int uid);
+
+         IPackageInstaller getPackageInstaller();
 }
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IVirtualLocationManager.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IVirtualLocationManager.aidl
new file mode 100644
index 000000000..750319aa8
--- /dev/null
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IVirtualLocationManager.aidl
@@ -0,0 +1,30 @@
+// IVirtualLocationManager.aidl
+package com.lody.virtual.server;
+
+import com.lody.virtual.remote.vloc.VCell;
+import com.lody.virtual.remote.vloc.VWifi;
+import com.lody.virtual.remote.vloc.VLocation;
+
+interface IVirtualLocationManager {
+
+    int getMode(int userId, in String pkg);
+    void setMode(int userId, in String pkg, int mode);
+
+    void setCell(in int userId, in String pkg, in VCell cell);
+    void setAllCell(in int userId, in String pkg, in List<VCell> cell);
+    void setNeighboringCell(in int userId, in String pkg, in List<VCell> cell);
+
+    void setGlobalCell(in VCell cell);
+    void setGlobalAllCell(in List<VCell> cell);
+    void setGlobalNeighboringCell(in List<VCell> cell);
+
+    VCell getCell(in int userId, in String pkg);
+    List<VCell> getAllCell(in int userId, in String pkg);
+    List<VCell> getNeighboringCell(in int userId, in String pkg);
+
+    void setLocation(in int userId, in String pkg, in VLocation loc);
+    VLocation getLocation(in int userId, in String pkg);
+
+    void setGlobalLocation(in VLocation loc);
+    VLocation getGlobalLocation();
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/interfaces/IUiCallback.aidl b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/interfaces/IUiCallback.aidl
index 8eb88364e..01e12ba81 100644
--- a/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/interfaces/IUiCallback.aidl
+++ b/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/interfaces/IUiCallback.aidl
@@ -3,4 +3,5 @@ package com.lody.virtual.server.interfaces;
 
 interface IUiCallback {
     void onAppOpened(in String packageName, in int userId);
+    void onOpenFailed(in String packageName, in int userId);
 }
diff --git a/VirtualApp/lib/src/main/java/android/app/ActivityThread.java b/VirtualApp/lib/src/main/java/android/app/ActivityThread.java
new file mode 100644
index 000000000..82ea1677c
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/app/ActivityThread.java
@@ -0,0 +1,9 @@
+package android.app;
+
+/**
+ * @author weishu
+ * @date 2018/8/7.
+ */
+public class ActivityThread {
+    public static class ActivityClientRecord {}
+}
diff --git a/VirtualApp/lib/src/main/java/android/app/ClientTransactionHandler.java b/VirtualApp/lib/src/main/java/android/app/ClientTransactionHandler.java
new file mode 100644
index 000000000..c50fe82ea
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/app/ClientTransactionHandler.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.PendingTransactionActions;
+import android.app.servertransaction.TransactionExecutor;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.util.MergedConfiguration;
+
+import java.util.List;
+
+/**
+ * Defines operations that a {@link ClientTransaction} or its items
+ * can perform on client.
+ * @hide
+ */
+public abstract class ClientTransactionHandler {
+
+    // Schedule phase related logic and handlers.
+    /** Prepare and schedule transaction for execution. */
+    void scheduleTransaction(ClientTransaction transaction) {
+
+    }
+    /**
+     * Execute transaction immediately without scheduling it. This is used for local requests, so
+     * it will also recycle the transaction.
+     */
+    public void executeTransaction(ClientTransaction transaction) {
+    }
+
+    /**
+     * Get the {@link TransactionExecutor} that will be performing lifecycle transitions and
+     * callbacks for activities.
+     */
+    abstract TransactionExecutor getTransactionExecutor();
+    abstract void sendMessage(int what, Object obj);
+    // Prepare phase related logic and handlers. Methods that inform about about pending changes or
+    // do other internal bookkeeping.
+    /** Set pending config in case it will be updated by other transaction item. */
+    public abstract void updatePendingConfiguration(Configuration config);
+    /** Set current process state. */
+    public abstract void updateProcessState(int processState, boolean fromIpc);
+    // Execute phase related logic and handlers. Methods here execute actual lifecycle transactions
+    // and deliver callbacks.
+    /** Destroy the activity. */
+    public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+            boolean getNonConfigInstance, String reason);
+    /** Pause the activity. */
+    public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+            int configChanges, PendingTransactionActions pendingActions, String reason);
+    /**
+     * Resume the activity.
+     * @param token Target activity token.
+     * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
+     *                          request for a transaction.
+     * @param isForward Flag indicating if next transition is forward.
+     * @param reason Reason for performing this operation.
+     */
+    public abstract void handleResumeActivity(IBinder token, boolean finalStateRequest,
+            boolean isForward, String reason);
+    /**
+     * Stop the activity.
+     * @param token Target activity token.
+     * @param show Flag indicating whether activity is still shown.
+     * @param configChanges Activity configuration changes.
+     * @param pendingActions Pending actions to be used on this or later stages of activity
+     *                       transaction.
+     * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
+     *                          request for a transaction.
+     * @param reason Reason for performing this operation.
+     */
+    public abstract void handleStopActivity(IBinder token, boolean show, int configChanges,
+            PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
+    /** Report that activity was stopped to server. */
+    public abstract void reportStop(PendingTransactionActions pendingActions);
+    /** Restart the activity after it was stopped. */
+    public abstract void performRestartActivity(IBinder token, boolean start);
+    /** Deliver activity (override) configuration change. */
+    public abstract void handleActivityConfigurationChanged(IBinder activityToken,
+            Configuration overrideConfig, int displayId);
+    /** Deliver result from another activity. */
+    public abstract void handleSendResult(IBinder token, List results, String reason);
+    /** Deliver multi-window mode change notification. */
+    public abstract void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
+            Configuration overrideConfig);
+    /** Deliver new intent. */
+    public abstract void handleNewIntent(IBinder token, List intents,
+            boolean andPause);
+    /** Deliver picture-in-picture mode change notification. */
+    public abstract void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
+            Configuration overrideConfig);
+    /** Update window visibility. */
+    public abstract void handleWindowVisibility(IBinder token, boolean show);
+    /** Perform activity launch. */
+    public abstract Activity handleLaunchActivity(ActivityThread.ActivityClientRecord r,
+            PendingTransactionActions pendingActions, Intent customIntent);
+    /** Perform activity start. */
+    public abstract void handleStartActivity(ActivityThread.ActivityClientRecord r,
+            PendingTransactionActions pendingActions);
+    /** Get package info. */
+    public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
+                                                    CompatibilityInfo compatInfo);
+    /** Deliver app configuration change notification. */
+    public abstract void handleConfigurationChanged(Configuration config);
+    /**
+     * Get {@link ActivityThread.ActivityClientRecord} instance that corresponds to the
+     * provided token.
+     */
+    public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token);
+    /**
+     * Prepare activity relaunch to update internal bookkeeping. This is used to track multiple
+     * relaunch and config update requests.
+     * @param token Activity token.
+     * @param pendingResults Activity results to be delivered.
+     * @param pendingNewIntents New intent messages to be delivered.
+     * @param configChanges Mask of configuration changes that have occurred.
+     * @param config New configuration applied to the activity.
+     * @param preserveWindow Whether the activity should try to reuse the window it created,
+     *                        including the decor view after the relaunch.
+     * @return An initialized instance of {@link ActivityThread.ActivityClientRecord} to use during
+     *         relaunch, or {@code null} if relaunch cancelled.
+     */
+    public abstract ActivityThread.ActivityClientRecord prepareRelaunchActivity(IBinder token,
+            List pendingResults, List pendingNewIntents,
+            int configChanges, MergedConfiguration config, boolean preserveWindow);
+    /**
+     * Perform activity relaunch.
+     * @param r Activity client record prepared for relaunch.
+     * @param pendingActions Pending actions to be used on later stages of activity transaction.
+     * */
+    public abstract void handleRelaunchActivity(ActivityThread.ActivityClientRecord r,
+            PendingTransactionActions pendingActions);
+    /**
+     * Report that relaunch request was handled.
+     * @param token Target activity token.
+     * @param pendingActions Pending actions initialized on earlier stages of activity transaction.
+     *                       Used to check if we should report relaunch to WM.
+     * */
+    public abstract void reportRelaunch(IBinder token, PendingTransactionActions pendingActions);
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/android/app/LoadedApk.java b/VirtualApp/lib/src/main/java/android/app/LoadedApk.java
new file mode 100644
index 000000000..805a5f098
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/app/LoadedApk.java
@@ -0,0 +1,8 @@
+package android.app;
+
+/**
+ * @author weishu
+ * @date 2018/8/7.
+ */
+public class LoadedApk {
+}
diff --git a/VirtualApp/lib/src/main/java/android/app/TransactionHandlerProxy.java b/VirtualApp/lib/src/main/java/android/app/TransactionHandlerProxy.java
new file mode 100644
index 000000000..84107e5f4
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/app/TransactionHandlerProxy.java
@@ -0,0 +1,207 @@
+package android.app;
+
+import android.app.ActivityThread.ActivityClientRecord;
+import android.app.servertransaction.PendingTransactionActions;
+import android.app.servertransaction.TransactionExecutor;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.util.Log;
+import android.util.MergedConfiguration;
+
+import com.lody.virtual.client.VClientImpl;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.ipc.VActivityManager;
+import com.lody.virtual.helper.utils.ComponentUtils;
+import com.lody.virtual.remote.InstalledAppInfo;
+import com.lody.virtual.remote.StubActivityRecord;
+
+import java.util.List;
+
+import mirror.android.app.ActivityManagerNative;
+import mirror.android.app.IActivityManager;
+
+
+/**
+ * @author weishu
+ * @date 2018/8/7.
+ */
+public class TransactionHandlerProxy extends ClientTransactionHandler {
+
+    private static final String TAG = "TransactionHandlerProxy";
+
+    private ClientTransactionHandler originalHandler;
+
+    public TransactionHandlerProxy(ClientTransactionHandler originalHandler) {
+        this.originalHandler = originalHandler;
+    }
+
+    @Override
+    TransactionExecutor getTransactionExecutor() {
+        return originalHandler.getTransactionExecutor();
+    }
+
+    @Override
+    void sendMessage(int what, Object obj) {
+        originalHandler.sendMessage(what, obj);
+    }
+
+    @Override
+    public void updatePendingConfiguration(Configuration config) {
+        originalHandler.updatePendingConfiguration(config);
+    }
+
+    @Override
+    public void updateProcessState(int processState, boolean fromIpc) {
+        originalHandler.updateProcessState(processState, fromIpc);
+    }
+
+    @Override
+    public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
+        originalHandler.handleDestroyActivity(token, finishing, configChanges, getNonConfigInstance, reason);
+    }
+
+    @Override
+    public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, PendingTransactionActions pendingActions, String reason) {
+        originalHandler.handlePauseActivity(token, finished, userLeaving, configChanges, pendingActions, reason);
+    }
+
+    @Override
+    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
+        originalHandler.handleResumeActivity(token, finalStateRequest, isForward, reason);
+    }
+
+    @Override
+    public void handleStopActivity(IBinder token, boolean show, int configChanges, PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
+        originalHandler.handleStopActivity(token, show, configChanges, pendingActions, finalStateRequest, reason);
+    }
+
+    @Override
+    public void reportStop(PendingTransactionActions pendingActions) {
+        originalHandler.reportStop(pendingActions);
+    }
+
+    @Override
+    public void performRestartActivity(IBinder token, boolean start) {
+        originalHandler.performRestartActivity(token, start);
+    }
+
+    @Override
+    public void handleActivityConfigurationChanged(IBinder activityToken, Configuration overrideConfig, int displayId) {
+        originalHandler.handleActivityConfigurationChanged(activityToken, overrideConfig, displayId);
+    }
+
+    @Override
+    public void handleSendResult(IBinder token, List results, String reason) {
+        originalHandler.handleSendResult(token, results, reason);
+    }
+
+    @Override
+    public void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode, Configuration overrideConfig) {
+        originalHandler.handleMultiWindowModeChanged(token, isInMultiWindowMode, overrideConfig);
+    }
+
+    @Override
+    public void handleNewIntent(IBinder token, List intents, boolean andPause) {
+        originalHandler.handleNewIntent(token, intents, andPause);
+    }
+
+    @Override
+    public void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode, Configuration overrideConfig) {
+        originalHandler.handlePictureInPictureModeChanged(token, isInPipMode, overrideConfig);
+    }
+
+    @Override
+    public void handleWindowVisibility(IBinder token, boolean show) {
+        originalHandler.handleWindowVisibility(token, show);
+    }
+
+    @Override
+    public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
+
+        Intent stubIntent = mirror.android.app.ActivityThread.ActivityClientRecord.intent.get(r);
+        StubActivityRecord saveInstance = new StubActivityRecord(stubIntent);
+        if (saveInstance.intent == null) {
+            Log.i(TAG, "save instance intent is null, return");
+            return null;
+        }
+        Intent intent = saveInstance.intent;
+        ComponentName caller = saveInstance.caller;
+        IBinder token = mirror.android.app.ActivityThread.ActivityClientRecord.token.get(r);
+        ActivityInfo info = saveInstance.info;
+        if (VClientImpl.get().getToken() == null) {
+            InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(info.packageName, 0);
+            if (installedAppInfo == null) {
+                Log.i(TAG, "install app info is null, return");
+                return null;
+            }
+            VActivityManager.get().processRestarted(info.packageName, info.processName, saveInstance.userId);
+            // getH().sendMessageAtFrontOfQueue(Message.obtain(msg));
+            Log.i(TAG, "restart process, return");
+            return handleLaunchActivity(r, pendingActions, customIntent);
+        }
+        if (!VClientImpl.get().isBound()) {
+            VClientImpl.get().bindApplicationForActivity(info.packageName, info.processName, intent);
+            // getH().sendMessageAtFrontOfQueue(Message.obtain(msg));
+            Log.i(TAG, "rebound application, return");
+            return handleLaunchActivity(r, pendingActions, customIntent);
+        }
+        int taskId = IActivityManager.getTaskForActivity.call(
+                ActivityManagerNative.getDefault.call(),
+                token,
+                false
+        );
+
+        Object packageInfo = mirror.android.app.ActivityThread.ActivityClientRecord.packageInfo.get(r);
+
+        mirror.android.app.ActivityThread.ActivityClientRecord.packageInfo.set(r, null);
+
+        VActivityManager.get().onActivityCreate(ComponentUtils.toComponentName(info), caller, token, info, intent, ComponentUtils.getTaskAffinity(info), taskId, info.launchMode, info.flags);
+        ClassLoader appClassLoader = VClientImpl.get().getClassLoader(info.applicationInfo);
+        intent.setExtrasClassLoader(appClassLoader);
+        mirror.android.app.ActivityThread.ActivityClientRecord.intent.set(r, intent);
+        mirror.android.app.ActivityThread.ActivityClientRecord.activityInfo.set(r, info);
+
+        return originalHandler.handleLaunchActivity(r, pendingActions, customIntent);
+    }
+
+    @Override
+    public void handleStartActivity(ActivityClientRecord r, PendingTransactionActions pendingActions) {
+        originalHandler.handleStartActivity(r, pendingActions);
+    }
+
+    @Override
+    public LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) {
+        return originalHandler.getPackageInfoNoCheck(ai, compatInfo);
+    }
+
+    @Override
+    public void handleConfigurationChanged(Configuration config) {
+        originalHandler.handleConfigurationChanged(config);
+    }
+
+    @Override
+    public ActivityClientRecord getActivityClient(IBinder token) {
+        Log.i(TAG, "getActivityClient : " + token);
+        return originalHandler.getActivityClient(token);
+    }
+
+    @Override
+    public ActivityClientRecord prepareRelaunchActivity(IBinder token, List pendingResults, List pendingNewIntents, int configChanges, MergedConfiguration config, boolean preserveWindow) {
+        return originalHandler.prepareRelaunchActivity(token, pendingResults, pendingNewIntents, configChanges, config, preserveWindow);
+    }
+
+    @Override
+    public void handleRelaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions) {
+        originalHandler.handleRelaunchActivity(r, pendingActions);
+    }
+
+    @Override
+    public void reportRelaunch(IBinder token, PendingTransactionActions pendingActions) {
+        originalHandler.reportRelaunch(token, pendingActions);
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/android/app/servertransaction/ClientTransaction.java b/VirtualApp/lib/src/main/java/android/app/servertransaction/ClientTransaction.java
new file mode 100644
index 000000000..39bd7d5fe
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/app/servertransaction/ClientTransaction.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.servertransaction;
+/**
+ * A container that holds a sequence of messages, which may be sent to a client.
+ * This includes a list of callbacks and a final lifecycle state.
+ *
+ * @see com.android.server.am.ClientLifecycleManager
+ * @see ClientTransactionItem
+ * @see ActivityLifecycleItem
+ * @hide
+ */
+public class ClientTransaction {
+
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/android/app/servertransaction/PendingTransactionActions.java b/VirtualApp/lib/src/main/java/android/app/servertransaction/PendingTransactionActions.java
new file mode 100644
index 000000000..22244df10
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/app/servertransaction/PendingTransactionActions.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.servertransaction;
+/**
+ * Container that has data pending to be used at later stages of
+ * {@link ClientTransaction}.
+ * An instance of this class is passed to each individual transaction item, so it can use some
+ * information from previous steps or add some for the following steps.
+ *
+ * @hide
+ */
+public class PendingTransactionActions {
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/android/app/servertransaction/TransactionExecutor.java b/VirtualApp/lib/src/main/java/android/app/servertransaction/TransactionExecutor.java
new file mode 100644
index 000000000..719080b71
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/app/servertransaction/TransactionExecutor.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.servertransaction;
+
+/**
+ * Class that manages transaction execution in the correct order.
+ * @hide
+ */
+public class TransactionExecutor {
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/android/content/SyncStatusInfo.java b/VirtualApp/lib/src/main/java/android/content/SyncStatusInfo.java
new file mode 100644
index 000000000..20c00afbc
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/content/SyncStatusInfo.java
@@ -0,0 +1,174 @@
+package android.content;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class SyncStatusInfo implements Parcelable {
+    static final int VERSION = 2;
+
+    public final int authorityId;
+    public long totalElapsedTime;
+    public int numSyncs;
+    public int numSourcePoll;
+    public int numSourceServer;
+    public int numSourceLocal;
+    public int numSourceUser;
+    public int numSourcePeriodic;
+    public long lastSuccessTime;
+    public int lastSuccessSource;
+    public long lastFailureTime;
+    public int lastFailureSource;
+    public String lastFailureMesg;
+    public long initialFailureTime;
+    public boolean pending;
+    public boolean initialize;
+    
+  // Warning: It is up to the external caller to ensure there are
+  // no race conditions when accessing this list
+  private ArrayList<Long> periodicSyncTimes;
+
+    private static final String TAG = "Sync";
+
+    public SyncStatusInfo(int authorityId) {
+        this.authorityId = authorityId;
+    }
+
+    public int getLastFailureMesgAsInt(int def) {
+        return 0;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(VERSION);
+        parcel.writeInt(authorityId);
+        parcel.writeLong(totalElapsedTime);
+        parcel.writeInt(numSyncs);
+        parcel.writeInt(numSourcePoll);
+        parcel.writeInt(numSourceServer);
+        parcel.writeInt(numSourceLocal);
+        parcel.writeInt(numSourceUser);
+        parcel.writeLong(lastSuccessTime);
+        parcel.writeInt(lastSuccessSource);
+        parcel.writeLong(lastFailureTime);
+        parcel.writeInt(lastFailureSource);
+        parcel.writeString(lastFailureMesg);
+        parcel.writeLong(initialFailureTime);
+        parcel.writeInt(pending ? 1 : 0);
+        parcel.writeInt(initialize ? 1 : 0);
+        if (periodicSyncTimes != null) {
+            parcel.writeInt(periodicSyncTimes.size());
+            for (long periodicSyncTime : periodicSyncTimes) {
+                parcel.writeLong(periodicSyncTime);
+            }
+        } else {
+            parcel.writeInt(-1);
+        }
+    }
+
+    public SyncStatusInfo(Parcel parcel) {
+        int version = parcel.readInt();
+        if (version != VERSION && version != 1) {
+            Log.w("SyncStatusInfo", "Unknown version: " + version);
+        }
+        authorityId = parcel.readInt();
+        totalElapsedTime = parcel.readLong();
+        numSyncs = parcel.readInt();
+        numSourcePoll = parcel.readInt();
+        numSourceServer = parcel.readInt();
+        numSourceLocal = parcel.readInt();
+        numSourceUser = parcel.readInt();
+        lastSuccessTime = parcel.readLong();
+        lastSuccessSource = parcel.readInt();
+        lastFailureTime = parcel.readLong();
+        lastFailureSource = parcel.readInt();
+        lastFailureMesg = parcel.readString();
+        initialFailureTime = parcel.readLong();
+        pending = parcel.readInt() != 0;
+        initialize = parcel.readInt() != 0;
+        if (version == 1) {
+            periodicSyncTimes = null;
+        } else {
+            int N = parcel.readInt();
+            if (N < 0) {
+                periodicSyncTimes = null;
+            } else {
+                periodicSyncTimes = new ArrayList<Long>();
+                for (int i=0; i<N; i++) {
+                    periodicSyncTimes.add(parcel.readLong());
+                }
+            }
+        }
+    }
+
+    public SyncStatusInfo(SyncStatusInfo other) {
+        authorityId = other.authorityId;
+        totalElapsedTime = other.totalElapsedTime;
+        numSyncs = other.numSyncs;
+        numSourcePoll = other.numSourcePoll;
+        numSourceServer = other.numSourceServer;
+        numSourceLocal = other.numSourceLocal;
+        numSourceUser = other.numSourceUser;
+        numSourcePeriodic = other.numSourcePeriodic;
+        lastSuccessTime = other.lastSuccessTime;
+        lastSuccessSource = other.lastSuccessSource;
+        lastFailureTime = other.lastFailureTime;
+        lastFailureSource = other.lastFailureSource;
+        lastFailureMesg = other.lastFailureMesg;
+        initialFailureTime = other.initialFailureTime;
+        pending = other.pending;
+        initialize = other.initialize;
+        if (other.periodicSyncTimes != null) {
+            periodicSyncTimes = new ArrayList<Long>(other.periodicSyncTimes);
+        }
+    }
+
+    public void setPeriodicSyncTime(int index, long when) {
+        // The list is initialized lazily when scheduling occurs so we need to make sure
+        // we initialize elements < index to zero (zero is ignore for scheduling purposes)
+        ensurePeriodicSyncTimeSize(index);
+        periodicSyncTimes.set(index, when);
+    }
+
+    public long getPeriodicSyncTime(int index) {
+        if (periodicSyncTimes != null && index < periodicSyncTimes.size()) {
+            return periodicSyncTimes.get(index);
+        } else {
+            return 0;
+        }
+    }
+
+    public void removePeriodicSyncTime(int index) {
+        if (periodicSyncTimes != null && index < periodicSyncTimes.size()) {
+            periodicSyncTimes.remove(index);
+        }
+    }
+
+    public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() {
+        public SyncStatusInfo createFromParcel(Parcel in) {
+            return new SyncStatusInfo(in);
+        }
+
+        public SyncStatusInfo[] newArray(int size) {
+            return new SyncStatusInfo[size];
+        }
+    };
+
+    private void ensurePeriodicSyncTimeSize(int index) {
+        if (periodicSyncTimes == null) {
+            periodicSyncTimes = new ArrayList<>(0);
+        }
+
+        final int requiredSize = index + 1;
+        if (periodicSyncTimes.size() < requiredSize) {
+            for (int i = periodicSyncTimes.size(); i < requiredSize; i++) {
+                periodicSyncTimes.add((long) 0);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/android/content/res/CompatibilityInfo.java b/VirtualApp/lib/src/main/java/android/content/res/CompatibilityInfo.java
new file mode 100644
index 000000000..4fa366f3f
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/content/res/CompatibilityInfo.java
@@ -0,0 +1,26 @@
+
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.res;
+/**
+ * CompatibilityInfo class keeps the information about compatibility mode that the application is
+ * running under.
+ * 
+ *  {@hide} 
+ */
+public class CompatibilityInfo {
+
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/android/location/LocationRequest.java b/VirtualApp/lib/src/main/java/android/location/LocationRequest.java
new file mode 100644
index 000000000..6737f2f4b
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/location/LocationRequest.java
@@ -0,0 +1,33 @@
+package android.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public final class LocationRequest implements Parcelable {
+
+    public String getProvider() {
+        return null;
+    }
+
+
+    public static final Creator<LocationRequest> CREATOR = new Creator<LocationRequest>() {
+        @Override
+        public LocationRequest createFromParcel(Parcel in) {
+            return null;
+        }
+
+        @Override
+        public LocationRequest[] newArray(int size) {
+            return null;
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/android/util/MergedConfiguration.java b/VirtualApp/lib/src/main/java/android/util/MergedConfiguration.java
new file mode 100644
index 000000000..d07fbbca0
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/android/util/MergedConfiguration.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package android.util;
+/**
+ * Container that holds global and override config and their merge product.
+ * Merged configuration updates automatically whenever global or override configs are updated via
+ * setters.
+ *
+ * {@hide}
+ */
+public class MergedConfiguration {
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/Build.java b/VirtualApp/lib/src/main/java/com/lody/virtual/Build.java
index b83335dc9..c6d5a5ebf 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/Build.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/Build.java
@@ -10,7 +10,7 @@
 
 public class Build {
 
-    public static final String VERSION_NAME = "Build-822-03";
+    public static final String VERSION_NAME = "Build-823-01";
 
-    public static final int VERSION_CODE = 8220003;
+    public static final int VERSION_CODE = 8230001;
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/DelegateApplication64Bit.java b/VirtualApp/lib/src/main/java/com/lody/virtual/DelegateApplication64Bit.java
new file mode 100644
index 000000000..70a9d62a9
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/DelegateApplication64Bit.java
@@ -0,0 +1,204 @@
+package com.lody.virtual;
+
+import android.annotation.TargetApi;
+import android.app.Application;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * @author Lody
+ *         <p>
+ *         <p>
+ *         Copy the file to your Project.
+ */
+@TargetApi(Build.VERSION_CODES.M)
+public abstract class DelegateApplication64Bit extends Application {
+
+    private Application mTarget;
+
+    protected abstract String get32BitPackageName();
+
+
+    private static Field findField(Object instance, String name) throws NoSuchFieldException {
+        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
+            try {
+                Field field = clazz.getDeclaredField(name);
+
+
+                if (!field.isAccessible()) {
+                    field.setAccessible(true);
+                }
+
+                return field;
+            } catch (NoSuchFieldException e) {
+                // ignore and search next
+            }
+        }
+
+        throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
+    }
+
+
+    private static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
+            throws NoSuchMethodException {
+        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
+            try {
+                Method method = clazz.getDeclaredMethod(name, parameterTypes);
+
+
+                if (!method.isAccessible()) {
+                    method.setAccessible(true);
+                }
+
+                return method;
+            } catch (NoSuchMethodException e) {
+                // ignore and search next
+            }
+        }
+
+        throw new NoSuchMethodException("Method " + name + " with parameters " +
+                Arrays.asList(parameterTypes) + " not found in " + instance.getClass());
+    }
+
+
+    private static void expandFieldArray(Object instance, String fieldName,
+                                         Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
+            IllegalAccessException {
+        Field jlrField = findField(instance, fieldName);
+        Object[] original = (Object[]) jlrField.get(instance);
+        Object[] combined = (Object[]) Array.newInstance(
+                original.getClass().getComponentType(), original.length + extraElements.length);
+        System.arraycopy(original, 0, combined, 0, original.length);
+        System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
+        jlrField.set(instance, combined);
+    }
+
+
+    private static void expandFieldList(Object instance, String fieldName, Object[] extraElements) throws NoSuchFieldException, IllegalAccessException {
+        Field field = findField(instance, fieldName);
+        Object[] original = ((List) field.get(instance)).toArray();
+        Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + 1);
+        System.arraycopy(original, 0, combined, 0, original.length);
+        System.arraycopy(extraElements, 0, combined, original.length, 1);
+        field.set(instance, Arrays.asList(combined));
+    }
+
+    private static Object[] makeDexElements(
+            Object dexPathList, ArrayList<File> files,
+            ArrayList<IOException> suppressedExceptions)
+            throws IllegalAccessException, InvocationTargetException,
+            NoSuchMethodException {
+        Method makeDexElements;
+        if (Build.VERSION.SDK_INT >= 23) {
+            makeDexElements = findMethod(dexPathList, "makePathElements", List.class, File.class, List.class);
+        } else {
+            makeDexElements = findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
+        }
+        return (Object[]) makeDexElements.invoke(dexPathList, files, null,
+                suppressedExceptions);
+
+    }
+
+    protected void attachBaseContext(Context context) {
+        super.attachBaseContext(context);
+        try {
+            ApplicationInfo ai = getPackageManager().getApplicationInfo(get32BitPackageName(), 0);
+            ClassLoader classLoader = getClassLoader();
+            Object dexPathList = findField(classLoader, "pathList").get(classLoader);
+            ArrayList<IOException> suppressedExceptions = new ArrayList<>();
+            ArrayList<File> dexFiles = new ArrayList<>();
+            dexFiles.add(new File(ai.publicSourceDir));
+            ArrayList<File> nativeLibs = new ArrayList<>();
+            nativeLibs.add(new File(ai.nativeLibraryDir));
+            if (Build.VERSION.SDK_INT > 25) {
+                expandFieldList(dexPathList, "nativeLibraryDirectories", new File[]{new File(ai.nativeLibraryDir)});
+                expandFieldArray(dexPathList, "nativeLibraryPathElements",
+                        (Object[]) findMethod(dexPathList, "makePathElements", List.class).invoke(dexPathList, nativeLibs));
+            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                expandFieldList(dexPathList, "nativeLibraryDirectories", new File[]{new File(ai.nativeLibraryDir)});
+                expandFieldArray(dexPathList, "nativeLibraryPathElements", makeDexElements(dexPathList, nativeLibs, suppressedExceptions));
+            } else {
+                expandFieldArray(dexPathList, "nativeLibraryDirectories", new File[]{new File(ai.nativeLibraryDir)});
+            }
+            expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, dexFiles, suppressedExceptions));
+            if (suppressedExceptions.size() > 0) {
+                for (IOException e : suppressedExceptions) {
+                    Log.w(getClass().getSimpleName(), "Exception in makeDexElement", e);
+                }
+                Field suppressedExceptionsField =
+                        findField(classLoader, "dexElementsSuppressedExceptions");
+                IOException[] dexElementsSuppressedExceptions =
+                        (IOException[]) suppressedExceptionsField.get(classLoader);
+
+                if (dexElementsSuppressedExceptions == null) {
+                    dexElementsSuppressedExceptions =
+                            suppressedExceptions.toArray(
+                                    new IOException[suppressedExceptions.size()]);
+                } else {
+                    IOException[] combined =
+                            new IOException[suppressedExceptions.size() +
+                                    dexElementsSuppressedExceptions.length];
+                    suppressedExceptions.toArray(combined);
+                    System.arraycopy(dexElementsSuppressedExceptions, 0, combined,
+                            suppressedExceptions.size(), dexElementsSuppressedExceptions.length);
+                    dexElementsSuppressedExceptions = combined;
+                }
+                suppressedExceptionsField.set(classLoader, dexElementsSuppressedExceptions);
+            }
+            mTarget = (Application) classLoader.loadClass(ai.className).newInstance();
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    public void onConfigurationChanged(Configuration configuration) {
+        super.onConfigurationChanged(configuration);
+        if (mTarget != null) {
+            mTarget.onConfigurationChanged(configuration);
+        }
+    }
+
+    public void onCreate() {
+        super.onCreate();
+        if (mTarget != null) {
+            mTarget.onCreate();
+        }
+    }
+
+    public void onLowMemory() {
+        super.onLowMemory();
+        if (mTarget != null) {
+            mTarget.onLowMemory();
+        }
+    }
+
+    public void onTerminate() {
+        super.onTerminate();
+        if (mTarget != null) {
+            mTarget.onTerminate();
+        }
+    }
+
+    public void onTrimMemory(int i) {
+        super.onTrimMemory(i);
+        if (mTarget != null) {
+            mTarget.onTrimMemory(i);
+        }
+    }
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java b/VirtualApp/lib/src/main/java/com/lody/virtual/GmsSupport.java
similarity index 71%
rename from VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java
rename to VirtualApp/lib/src/main/java/com/lody/virtual/GmsSupport.java
index 7fe0a1c74..e97666bb5 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/GmsSupport.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/GmsSupport.java
@@ -1,11 +1,10 @@
-package com.lody.virtual.client.hook.secondary;
+package com.lody.virtual;
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 
 import com.lody.virtual.client.core.InstallStrategy;
 import com.lody.virtual.client.core.VirtualCore;
-import com.lody.virtual.server.pm.VAppManagerService;
 
 import java.util.Arrays;
 import java.util.List;
@@ -15,14 +14,14 @@
  */
 public class GmsSupport {
 
-    private static final List<String> GOOGLE_APP = Arrays.asList(
+    public static final List<String> GOOGLE_APP = Arrays.asList(
             "com.android.vending",
             "com.google.android.play.games",
             "com.google.android.wearable.app",
             "com.google.android.wearable.app.cn"
     );
 
-    private static final List<String> GOOGLE_SERVICE = Arrays.asList(
+    public static final List<String> GOOGLE_SERVICE = Arrays.asList(
             "com.google.android.gsf",
             "com.google.android.gms",
             "com.google.android.gsf.login",
@@ -37,7 +36,6 @@ public class GmsSupport {
             "com.google.android.syncadapters.calendar"
     );
 
-
     public static boolean isGmsFamilyPackage(String packageName) {
         return packageName.equals("com.android.vending")
                 || packageName.equals("com.google.android.gms");
@@ -47,10 +45,14 @@ public static boolean isGoogleFrameworkInstalled() {
         return VirtualCore.get().isAppInstalled("com.google.android.gms");
     }
 
+    public static boolean isOutsideGoogleFrameworkExist() {
+        return VirtualCore.get().isOutsideInstalled("com.google.android.gms");
+    }
+
     private static void installPackages(List<String> list, int userId) {
-        VAppManagerService service = VAppManagerService.get();
+        VirtualCore core = VirtualCore.get();
         for (String packageName : list) {
-            if (service.isAppInstalledAsUser(userId, packageName)) {
+            if (core.isAppInstalledAsUser(userId, packageName)) {
                 continue;
             }
             ApplicationInfo info = null;
@@ -63,15 +65,23 @@ private static void installPackages(List<String> list, int userId) {
                 continue;
             }
             if (userId == 0) {
-                service.installPackage(info.sourceDir, InstallStrategy.DEPEND_SYSTEM_IF_EXIST, false);
+                core.installPackage(info.sourceDir, InstallStrategy.DEPEND_SYSTEM_IF_EXIST);
             } else {
-                service.installPackageAsUser(userId, packageName);
+                core.installPackageAsUser(userId, packageName);
             }
         }
     }
 
-    public static void installGms(int userId) {
+    public static void installGApps(int userId) {
         installPackages(GOOGLE_SERVICE, userId);
         installPackages(GOOGLE_APP, userId);
     }
-}
+
+    public static void installGoogleService(int userId) {
+        installPackages(GOOGLE_SERVICE, userId);
+    }
+
+    public static void installGoogleApp(int userId) {
+        installPackages(GOOGLE_APP, userId);
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/NativeEngine.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/NativeEngine.java
index 927d2d144..1a35d687e 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/NativeEngine.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/NativeEngine.java
@@ -8,6 +8,8 @@
 import com.lody.virtual.client.env.VirtualRuntime;
 import com.lody.virtual.client.ipc.VActivityManager;
 import com.lody.virtual.client.natives.NativeMethods;
+import com.lody.virtual.helper.compat.BuildCompat;
+import com.lody.virtual.helper.utils.DeviceUtil;
 import com.lody.virtual.helper.utils.VLog;
 import com.lody.virtual.os.VUserHandle;
 import com.lody.virtual.remote.InstalledAppInfo;
@@ -23,14 +25,17 @@
  * VirtualApp Native Project
  */
 public class NativeEngine {
-
     private static final String TAG = NativeEngine.class.getSimpleName();
 
+    private static final String VESCAPE = "/6decacfa7aad11e8a718985aebe4663a";
+
     private static Map<String, InstalledAppInfo> sDexOverrideMap;
 
+    private static boolean sFlag = false;
+
     static {
         try {
-            System.loadLibrary("va-native");
+            System.loadLibrary("va++");
         } catch (Throwable e) {
             VLog.e(TAG, VLog.getStackTraceString(e));
         }
@@ -53,18 +58,18 @@ public static void startDexOverride() {
         }
     }
 
-    public static String getRedirectedPath(String origPath) {
+    public static String getRedirectedPath(String redirectPath) {
         try {
-            return nativeGetRedirectedPath(origPath);
+            return nativeGetRedirectedPath(redirectPath);
         } catch (Throwable e) {
             VLog.e(TAG, VLog.getStackTraceString(e));
         }
-        return origPath;
+        return redirectPath;
     }
 
-    public static String restoreRedirectedPath(String origPath) {
+    public static String resverseRedirectedPath(String origPath) {
         try {
-            return nativeRestoreRedirectedPath(origPath);
+            return nativeReverseRedirectedPath(origPath);
         } catch (Throwable e) {
             VLog.e(TAG, VLog.getStackTraceString(e));
         }
@@ -79,12 +84,23 @@ public static void redirectDirectory(String origPath, String newPath) {
             newPath = newPath + "/";
         }
         try {
-            nativeRedirect(origPath, newPath);
+            nativeIORedirect(origPath, newPath);
         } catch (Throwable e) {
             VLog.e(TAG, VLog.getStackTraceString(e));
         }
     }
 
+    public static String getEscapePath(String path) {
+        if (path == null) {
+            return null;
+        }
+        File file = new File(path);
+        if (file.exists()) {
+            return file.getAbsolutePath();
+        }
+        return new File(VESCAPE, path).getAbsolutePath();
+    }
+
     public static void redirectFile(String origPath, String newPath) {
         if (origPath.endsWith("/")) {
             origPath = origPath.substring(0, origPath.length() - 1);
@@ -94,36 +110,60 @@ public static void redirectFile(String origPath, String newPath) {
         }
 
         try {
-            nativeRedirect(origPath, newPath);
+            nativeIORedirect(origPath, newPath);
+        } catch (Throwable e) {
+            VLog.e(TAG, VLog.getStackTraceString(e));
+        }
+    }
+
+    public static void whitelist(String path, boolean directory) {
+        if (directory && !path.endsWith("/")) {
+            path = path + "/";
+        } else if (!directory && path.endsWith("/")) {
+            path = path.substring(0, path.length() - 1);
+        }
+        try {
+            nativeIOWhitelist(path);
         } catch (Throwable e) {
             VLog.e(TAG, VLog.getStackTraceString(e));
         }
     }
 
-    public static void readOnly(String path) {
+    public static void forbid(String path) {
+        if (!path.endsWith("/")) {
+            path = path + "/";
+        }
         try {
-            nativeReadOnly(path);
+            nativeIOForbid(path);
         } catch (Throwable e) {
             VLog.e(TAG, VLog.getStackTraceString(e));
         }
     }
 
-    public static void hook() {
+    public static void enableIORedirect() {
         try {
-            int previewSdkInt = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? Build.VERSION.PREVIEW_SDK_INT : 0;
-            nativeStartUniformer(Build.VERSION.SDK_INT, previewSdkInt);
+            String soPath = String.format("/data/data/%s/lib/libva++.so", VirtualCore.get().getHostPkg());
+            if (!new File(soPath).exists()) {
+                throw new RuntimeException("io redirect failed.");
+            }
+            redirectDirectory(VESCAPE, "/");
+            nativeEnableIORedirect(soPath, Build.VERSION.SDK_INT, BuildCompat.getPreviewSDKInt());
         } catch (Throwable e) {
             VLog.e(TAG, VLog.getStackTraceString(e));
         }
     }
 
-    static void hookNative() {
+    static void launchEngine() {
+        if (sFlag) {
+            return;
+        }
         Method[] methods = {NativeMethods.gOpenDexFileNative, NativeMethods.gCameraNativeSetup, NativeMethods.gAudioRecordNativeCheckPermission};
         try {
-            nativeHookNative(methods, VirtualCore.get().getHostPkg(), VirtualRuntime.isArt(), Build.VERSION.SDK_INT, NativeMethods.gCameraMethodType);
+            nativeLaunchEngine(methods, VirtualCore.get().getHostPkg(), VirtualRuntime.isArt(), Build.VERSION.SDK_INT, NativeMethods.gCameraMethodType);
         } catch (Throwable e) {
             VLog.e(TAG, VLog.getStackTraceString(e));
         }
+        sFlag = true;
     }
 
     public static void onKillProcess(int pid, int signal) {
@@ -145,7 +185,7 @@ public static int onGetCallingUid(int originUid) {
         if (vuid != -1) {
             return VUserHandle.getAppId(vuid);
         }
-        VLog.d(TAG, "Unknown uid: " + callingPid);
+        VLog.w(TAG, String.format("Unknown uid: %s", callingPid));
         return VClientImpl.get().getBaseVUid();
     }
 
@@ -156,7 +196,7 @@ public static void onOpenDexFileNative(String[] params) {
         try {
             String canonical = new File(dexOrJarPath).getCanonicalPath();
             InstalledAppInfo info = sDexOverrideMap.get(canonical);
-            if (info != null && !info.dependSystem) {
+            if (info != null && !info.dependSystem || info != null && DeviceUtil.isMeizuBelowN() && params[1] == null) {
                 outputPath = info.getOdexFile().getPath();
                 params[1] = outputPath;
             }
@@ -166,19 +206,23 @@ public static void onOpenDexFileNative(String[] params) {
     }
 
 
-    private static native void nativeHookNative(Object method, String hostPackageName, boolean isArt, int apiLevel, int cameraMethodType);
+    private static native void nativeLaunchEngine(Object[] method, String hostPackageName, boolean isArt, int apiLevel, int cameraMethodType);
 
     private static native void nativeMark();
 
-    private static native String nativeRestoreRedirectedPath(String redirectedPath);
+    private static native String nativeReverseRedirectedPath(String redirectedPath);
 
     private static native String nativeGetRedirectedPath(String orgPath);
 
-    private static native void nativeRedirect(String origPath, String newPath);
+    private static native void nativeIORedirect(String origPath, String newPath);
+
+    private static native void nativeIOWhitelist(String path);
+
+    private static native void nativeIOForbid(String path);
 
-    private static native void nativeReadOnly(String path);
+    private static native void nativeEnableIORedirect(String selfSoPath, int apiLevel, int previewApiLevel);
 
-    private static native void nativeStartUniformer(int apiLevel, int previewApiLevel);
+    public static native void disableJit(int apiLevel);
 
     public static int onGetUid(int uid) {
         return VClientImpl.get().getBaseVUid();
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/VClientImpl.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/VClientImpl.java
index 67efdaecb..7550a37ad 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/VClientImpl.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/VClientImpl.java
@@ -12,9 +12,11 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
+import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Build;
 import android.os.ConditionVariable;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IInterface;
@@ -23,7 +25,8 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
-import android.util.Log;
+import android.system.ErrnoException;
+import android.system.Os;
 
 import com.lody.virtual.client.core.CrashHandler;
 import com.lody.virtual.client.core.InvocationStubManager;
@@ -36,9 +39,10 @@
 import com.lody.virtual.client.hook.proxies.am.HCallbackStub;
 import com.lody.virtual.client.hook.secondary.ProxyServiceFactory;
 import com.lody.virtual.client.ipc.VActivityManager;
+import com.lody.virtual.client.ipc.VDeviceManager;
 import com.lody.virtual.client.ipc.VPackageManager;
 import com.lody.virtual.client.ipc.VirtualStorageManager;
-import com.lody.virtual.client.stub.StubManifest;
+import com.lody.virtual.client.stub.VASettings;
 import com.lody.virtual.helper.compat.BuildCompat;
 import com.lody.virtual.helper.compat.StorageManagerCompat;
 import com.lody.virtual.helper.utils.VLog;
@@ -46,23 +50,33 @@
 import com.lody.virtual.os.VUserHandle;
 import com.lody.virtual.remote.InstalledAppInfo;
 import com.lody.virtual.remote.PendingResultData;
-import com.taobao.android.dex.interpret.ARTUtils;
+import com.lody.virtual.remote.VDeviceInfo;
+import com.lody.virtual.server.interfaces.IUiCallback;
 
 import java.io.File;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
+import me.weishu.exposed.ExposedBridge;
 import mirror.android.app.ActivityThread;
 import mirror.android.app.ActivityThreadNMR1;
 import mirror.android.app.ContextImpl;
+import mirror.android.app.ContextImplKitkat;
 import mirror.android.app.IActivityManager;
 import mirror.android.app.LoadedApk;
+import mirror.android.app.LoadedApkICS;
+import mirror.android.app.LoadedApkKitkat;
 import mirror.android.content.ContentProviderHolderOreo;
+import mirror.android.content.res.CompatibilityInfo;
 import mirror.android.providers.Settings;
 import mirror.android.renderscript.RenderScriptCacheDir;
+import mirror.android.view.CompatibilityInfoHolder;
+import mirror.android.view.DisplayAdjustments;
 import mirror.android.view.HardwareRenderer;
 import mirror.android.view.RenderScript;
 import mirror.android.view.ThreadedRenderer;
@@ -89,9 +103,11 @@ public final class VClientImpl extends IVClient.Stub {
     private Instrumentation mInstrumentation = AppInstrumentation.getDefault();
     private IBinder token;
     private int vuid;
+    private VDeviceInfo deviceInfo;
     private AppBindData mBoundApplication;
     private Application mInitialApplication;
     private CrashHandler crashHandler;
+    private IUiCallback mUiCallback;
 
     public static VClientImpl get() {
         return gClient;
@@ -101,16 +117,28 @@ public boolean isBound() {
         return mBoundApplication != null;
     }
 
+    public VDeviceInfo getDeviceInfo() {
+        if (deviceInfo == null) {
+            synchronized (this) {
+                if (deviceInfo == null) {
+                    deviceInfo = VDeviceManager.get().getDeviceInfo(getUserId(vuid));
+                }
+            }
+        }
+        return deviceInfo;
+    }
+
     public Application getCurrentApplication() {
         return mInitialApplication;
     }
 
     public String getCurrentPackage() {
-        return mBoundApplication != null ? mBoundApplication.appInfo.packageName : null;
+        return mBoundApplication != null ?
+                mBoundApplication.appInfo.packageName : VPackageManager.get().getNameForUid(getVUid());
     }
 
     public ApplicationInfo getCurrentApplicationInfo() {
-        return mInitialApplication != null ? mInitialApplication.getApplicationInfo() : null;
+        return mBoundApplication != null ? mBoundApplication.appInfo : null;
     }
 
     public CrashHandler getCrashHandler() {
@@ -134,6 +162,11 @@ public ClassLoader getClassLoader(ApplicationInfo appInfo) {
         return context.getClassLoader();
     }
 
+    public ClassLoader getClassLoader(String packageName) {
+        Context context = createPackageContext(packageName);
+        return context.getClassLoader();
+    }
+
     private void sendMessage(int what, Object obj) {
         Message msg = Message.obtain();
         msg.what = what;
@@ -178,6 +211,11 @@ private void handleNewIntent(NewIntentData data) {
         }
     }
 
+    public void bindApplicationForActivity(final String packageName, final String processName, final Intent intent) {
+        mUiCallback = VirtualCore.getUiCallback(intent);
+        bindApplication(packageName, processName);
+    }
+
     public void bindApplication(final String packageName, final String processName) {
         if (Looper.getMainLooper() == Looper.myLooper()) {
             bindApplicationNoCheck(packageName, processName, new ConditionVariable());
@@ -195,6 +233,10 @@ public void run() {
     }
 
     private void bindApplicationNoCheck(String packageName, String processName, ConditionVariable lock) {
+        VDeviceInfo deviceInfo = getDeviceInfo();
+        if (processName == null) {
+            processName = packageName;
+        }
         mTempLock = lock;
         try {
             setupUncaughtHandler();
@@ -206,6 +248,8 @@ private void bindApplicationNoCheck(String packageName, String processName, Cond
         } catch (Throwable e) {
             e.printStackTrace();
         }
+        mirror.android.os.Build.SERIAL.set(deviceInfo.serial);
+        mirror.android.os.Build.DEVICE.set(Build.DEVICE.replace(" ", "_"));
         ActivityThread.mInitialApplication.set(
                 VirtualCore.mainThread(),
                 null
@@ -217,14 +261,11 @@ private void bindApplicationNoCheck(String packageName, String processName, Cond
             Process.killProcess(0);
             System.exit(0);
         }
-        if (!info.dependSystem && info.artFlyMode) {
-            ARTUtils.init(VirtualCore.get().getContext());
-            ARTUtils.setIsDex2oatEnabled(false);
-        }
         data.appInfo = VPackageManager.get().getApplicationInfo(packageName, 0, getUserId(vuid));
         data.processName = processName;
+        data.appInfo.processName = processName;
         data.providers = VPackageManager.get().queryContentProviders(processName, getVUid(), PackageManager.GET_META_DATA);
-        Log.i(TAG, "Binding application " + data.appInfo.packageName + " (" + data.processName + ")");
+        VLog.i(TAG, String.format("Binding application %s, (%s)", data.appInfo.packageName, data.processName));
         mBoundApplication = data;
         VirtualRuntime.setupRuntime(data.processName, data.appInfo);
         int targetSdkVersion = data.appInfo.targetSdkVersion;
@@ -232,28 +273,33 @@ private void bindApplicationNoCheck(String packageName, String processName, Cond
             StrictMode.ThreadPolicy newPolicy = new StrictMode.ThreadPolicy.Builder(StrictMode.getThreadPolicy()).permitNetwork().build();
             StrictMode.setThreadPolicy(newPolicy);
         }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            if (mirror.android.os.StrictMode.sVmPolicyMask != null) {
-                mirror.android.os.StrictMode.sVmPolicyMask.set(0);
-            }
-        }
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
             mirror.android.os.Message.updateCheckRecycle.call(targetSdkVersion);
         }
-        if (StubManifest.ENABLE_IO_REDIRECT) {
+        if (VASettings.ENABLE_IO_REDIRECT) {
             startIOUniformer();
         }
-        NativeEngine.hookNative();
+        NativeEngine.launchEngine();
         Object mainThread = VirtualCore.mainThread();
         NativeEngine.startDexOverride();
         Context context = createPackageContext(data.appInfo.packageName);
-        System.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
+        try {
+            // anti-virus, fuck ESET-NOD32: a variant of Android/AdDisplay.AdLock.AL potentially unwanted
+            // we can make direct call... use reflect to bypass.
+            // System.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
+            System.class.getDeclaredMethod("setProperty", String.class, String.class)
+                    .invoke(null, "java.io.tmpdir", context.getCacheDir().getAbsolutePath());
+        } catch (Throwable ignored) {
+            VLog.e(TAG, "set tmp dir error:", ignored);
+        }
+
         File codeCacheDir;
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             codeCacheDir = context.getCodeCacheDir();
         } else {
             codeCacheDir = context.getCacheDir();
         }
+
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
             if (HardwareRenderer.setupDiskCache != null) {
                 HardwareRenderer.setupDiskCache.call(codeCacheDir);
@@ -277,13 +323,51 @@ private void bindApplicationNoCheck(String packageName, String processName, Cond
         mirror.android.app.ActivityThread.AppBindData.info.set(boundApp, data.info);
         VMRuntime.setTargetSdkVersion.call(VMRuntime.getRuntime.call(), data.appInfo.targetSdkVersion);
 
+        Configuration configuration = context.getResources().getConfiguration();
+        Object compatInfo = CompatibilityInfo.ctor.newInstance(data.appInfo, configuration.screenLayout, configuration.smallestScreenWidthDp, false);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+                DisplayAdjustments.setCompatibilityInfo.call(ContextImplKitkat.mDisplayAdjustments.get(context), compatInfo);
+            }
+            DisplayAdjustments.setCompatibilityInfo.call(LoadedApkKitkat.mDisplayAdjustments.get(mBoundApplication.info), compatInfo);
+        } else {
+            CompatibilityInfoHolder.set.call(LoadedApkICS.mCompatibilityInfo.get(mBoundApplication.info), compatInfo);
+        }
+
         boolean conflict = SpecialComponentList.isConflictingInstrumentation(packageName);
         if (!conflict) {
             InvocationStubManager.getInstance().checkEnv(AppInstrumentation.class);
         }
+
+        ApplicationInfo applicationInfo = LoadedApk.mApplicationInfo.get(data.info);
+        if (Build.VERSION.SDK_INT >= 26 && applicationInfo.splitNames == null) {
+            applicationInfo.splitNames = new String[1];
+        }
+
+
+        boolean enableXposed = VirtualCore.get().isXposedEnabled();
+        if (enableXposed) {
+            VLog.i(TAG, "Xposed is enabled.");
+            ClassLoader originClassLoader = context.getClassLoader();
+            ExposedBridge.initOnce(context, data.appInfo, originClassLoader);
+            List<InstalledAppInfo> modules = VirtualCore.get().getInstalledApps(0);
+            for (InstalledAppInfo module : modules) {
+                ExposedBridge.loadModule(module.apkPath, module.getOdexFile().getParent(), module.libPath,
+                        data.appInfo, originClassLoader);
+            }
+        } else {
+            VLog.w(TAG, "Xposed is disable..");
+        }
+
         mInitialApplication = LoadedApk.makeApplication.call(data.info, false, null);
+
+        // ExposedBridge.patchAppClassLoader(context);
+
         mirror.android.app.ActivityThread.mInitialApplication.set(mainThread, mInitialApplication);
         ContextFixer.fixContext(mInitialApplication);
+        if (Build.VERSION.SDK_INT >= 24 && "com.tencent.mm:recovery".equals(processName)) {
+            fixWeChatRecovery(mInitialApplication);
+        }
         if (data.providers != null) {
             installContentProviders(mInitialApplication, data.providers);
         }
@@ -291,6 +375,7 @@ private void bindApplicationNoCheck(String packageName, String processName, Cond
             lock.open();
             mTempLock = null;
         }
+        VirtualCore.get().getComponentDelegate().beforeApplicationCreate(mInitialApplication);
         try {
             mInstrumentation.callApplicationOnCreate(mInitialApplication);
             InvocationStubManager.getInstance().checkEnv(HCallbackStub.class);
@@ -303,12 +388,37 @@ private void bindApplicationNoCheck(String packageName, String processName, Cond
             }
         } catch (Exception e) {
             if (!mInstrumentation.onException(mInitialApplication, e)) {
+                // 1. tell ui that do not need wait use now.
+                if (mUiCallback != null) {
+                    try {
+                        mUiCallback.onOpenFailed(packageName, VUserHandle.myUserId());
+                    } catch (RemoteException ignored) {
+                    }
+                }
+                // 2. tell vams that launch finish.
+                VActivityManager.get().appDoneExecuting();
+
+                // 3. rethrow
                 throw new RuntimeException(
-                        "Unable to create application " + mInitialApplication.getClass().getName()
+                        "Unable to create application " + (mInitialApplication == null ? " [null application] " : mInitialApplication.getClass().getName())
                                 + ": " + e.toString(), e);
             }
         }
         VActivityManager.get().appDoneExecuting();
+        VirtualCore.get().getComponentDelegate().afterApplicationCreate(mInitialApplication);
+    }
+
+    private void fixWeChatRecovery(Application app) {
+        try {
+            Field field = app.getClassLoader().loadClass("com.tencent.recovery.Recovery").getField("context");
+            field.setAccessible(true);
+            if (field.get(null) != null) {
+                return;
+            }
+            field.set(null, app.getBaseContext());
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
     }
 
     private void setupUncaughtHandler() {
@@ -328,6 +438,9 @@ private void setupUncaughtHandler() {
                 groups.add(newRoot);
                 mirror.java.lang.ThreadGroup.groups.set(root, groups);
                 for (ThreadGroup group : newGroups) {
+                    if (group == newRoot) {
+                        continue;
+                    }
                     mirror.java.lang.ThreadGroup.parent.set(group, newRoot);
                 }
             }
@@ -339,6 +452,9 @@ private void setupUncaughtHandler() {
                 ThreadGroupN.groups.set(newRoot, newGroups);
                 ThreadGroupN.groups.set(root, new ThreadGroup[]{newRoot});
                 for (Object group : newGroups) {
+                    if (group == newRoot) {
+                        continue;
+                    }
                     ThreadGroupN.parent.set(group, newRoot);
                 }
                 ThreadGroupN.ngroups.set(root, 1);
@@ -350,31 +466,95 @@ private void setupUncaughtHandler() {
     private void startIOUniformer() {
         ApplicationInfo info = mBoundApplication.appInfo;
         int userId = VUserHandle.myUserId();
+        String wifiMacAddressFile = deviceInfo.getWifiFile(userId).getPath();
+        NativeEngine.redirectDirectory("/sys/class/net/wlan0/address", wifiMacAddressFile);
+        NativeEngine.redirectDirectory("/sys/class/net/eth0/address", wifiMacAddressFile);
+        NativeEngine.redirectDirectory("/sys/class/net/wifi/address", wifiMacAddressFile);
+
         NativeEngine.redirectDirectory("/data/data/" + info.packageName, info.dataDir);
         NativeEngine.redirectDirectory("/data/user/0/" + info.packageName, info.dataDir);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             NativeEngine.redirectDirectory("/data/user_de/0/" + info.packageName, info.dataDir);
         }
-        String libPath = new File(VEnvironment.getDataAppPackageDirectory(info.packageName), "lib").getAbsolutePath();
-        String userLibPath = new File(VEnvironment.getUserSystemDirectory(userId), "lib").getAbsolutePath();
+        String libPath = VEnvironment.getAppLibDirectory(info.packageName).getAbsolutePath();
+        String userLibPath = new File(VEnvironment.getUserSystemDirectory(userId), info.packageName + "/lib").getAbsolutePath();
         NativeEngine.redirectDirectory(userLibPath, libPath);
         NativeEngine.redirectDirectory("/data/data/" + info.packageName + "/lib/", libPath);
         NativeEngine.redirectDirectory("/data/user/0/" + info.packageName + "/lib/", libPath);
 
-        NativeEngine.readOnly(VEnvironment.getDataAppDirectory().getPath());
+        File dataUserLib = new File(VEnvironment.getDataUserPackageDirectory(userId, info.packageName), "lib");
+        if (!dataUserLib.exists()) {
+            try {
+                Os.symlink(libPath, dataUserLib.getPath());
+            } catch (ErrnoException e) {
+                VLog.w(TAG, "symlink error", e);
+            }
+        }
+
+        setupVirtualStorage(info, userId);
+
+        NativeEngine.enableIORedirect();
+    }
+
+    private void setupVirtualStorage(ApplicationInfo info, int userId) {
         VirtualStorageManager vsManager = VirtualStorageManager.get();
-        String vsPath = vsManager.getVirtualStorage(info.packageName, userId);
         boolean enable = vsManager.isVirtualStorageEnable(info.packageName, userId);
-        if (enable && vsPath != null) {
-            File vsDirectory = new File(vsPath);
-            if (vsDirectory.exists() || vsDirectory.mkdirs()) {
-                HashSet<String> mountPoints = getMountPoints();
-                for (String mountPoint : mountPoints) {
-                    NativeEngine.redirectDirectory(mountPoint, vsPath);
-                }
+        if (!enable) {
+            // There are lots of situation to deal, I am tired, disable it now.
+            // such as: FileProvider.
+            return;
+        }
+
+        File vsDir = VEnvironment.getVirtualStorageDir(info.packageName, userId);
+        if (vsDir == null || !vsDir.exists() || !vsDir.isDirectory()) {
+            return;
+        }
+
+        HashSet<String> storageRoots = getMountPoints();
+        storageRoots.add(Environment.getExternalStorageDirectory().getAbsolutePath());
+
+        Set<String> whiteList = new HashSet<>();
+        whiteList.add(Environment.DIRECTORY_PODCASTS);
+        whiteList.add(Environment.DIRECTORY_RINGTONES);
+        whiteList.add(Environment.DIRECTORY_ALARMS);
+        whiteList.add(Environment.DIRECTORY_NOTIFICATIONS);
+        whiteList.add(Environment.DIRECTORY_PICTURES);
+        whiteList.add(Environment.DIRECTORY_MOVIES);
+        whiteList.add(Environment.DIRECTORY_DOWNLOADS);
+        whiteList.add(Environment.DIRECTORY_DCIM);
+        whiteList.add("Android/obb");
+        if (Build.VERSION.SDK_INT >= 19) {
+            whiteList.add(Environment.DIRECTORY_DOCUMENTS);
+        }
+
+        // ensure virtual storage white directory exists.
+        for (String whiteDir : whiteList) {
+            File originalDir = new File(Environment.getExternalStorageDirectory(), whiteDir);
+            File virtualDir = new File(vsDir, whiteDir);
+            if (!originalDir.exists()) {
+                continue;
             }
+            //noinspection ResultOfMethodCallIgnored
+            virtualDir.mkdirs();
+        }
+
+        String vsPath = vsDir.getAbsolutePath();
+        NativeEngine.whitelist(vsPath, true);
+        String privatePath = VEnvironment.getVirtualPrivateStorageDir(userId).getAbsolutePath();
+        NativeEngine.whitelist(privatePath, true);
+
+        for (String storageRoot : storageRoots) {
+            for (String whiteDir : whiteList) {
+                // white list, do not redirect
+                String whitePath = new File(storageRoot, whiteDir).getAbsolutePath();
+                NativeEngine.whitelist(whitePath, true);
+            }
+
+            // redirect xxx/Android/data/ -> /xxx/Android/data/<host>/virtual/<user>
+            NativeEngine.redirectDirectory(new File(storageRoot, "Android/data/").getAbsolutePath(), privatePath);
+            // redirect /sdcard/ -> vsdcard
+            NativeEngine.redirectDirectory(storageRoot, vsPath);
         }
-        NativeEngine.hook();
     }
 
     @SuppressLint("SdCardPath")
@@ -395,6 +575,7 @@ private Context createPackageContext(String packageName) {
             Context hostContext = VirtualCore.get().getContext();
             return hostContext.createPackageContext(packageName, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
         } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
             VirtualRuntime.crash(new RemoteException());
         }
         throw new RuntimeException();
@@ -418,8 +599,10 @@ private void installContentProviders(Context app, List<ProviderInfo> providers)
         Object mainThread = VirtualCore.mainThread();
         try {
             for (ProviderInfo cpi : providers) {
-                if (cpi.enabled) {
+                try {
                     ActivityThread.installProvider(mainThread, app, cpi, null);
+                } catch (Throwable e) {
+                    e.printStackTrace();
                 }
             }
         } finally {
@@ -447,7 +630,7 @@ public IBinder acquireProviderClient(ProviderInfo info) {
                 client = resolver.acquireContentProviderClient(authority);
             }
         } catch (Throwable e) {
-            e.printStackTrace();
+            VLog.e(TAG, "", e);
         }
         if (client != null) {
             provider = mirror.android.content.ContentProviderClient.mContentProvider.get(client);
@@ -467,7 +650,7 @@ private void fixInstalledProviders() {
                     continue;
                 }
                 ProviderInfo info = ContentProviderHolderOreo.info.get(holder);
-                if (!info.authority.startsWith(StubManifest.STUB_CP_AUTHORITY)) {
+                if (!info.authority.startsWith(VASettings.STUB_CP_AUTHORITY)) {
                     provider = ProviderHook.createProxy(true, info.authority, provider);
                     ActivityThread.ProviderClientRecordJB.mProvider.set(clientRecord, provider);
                     ContentProviderHolderOreo.provider.set(holder, provider);
@@ -479,7 +662,7 @@ private void fixInstalledProviders() {
                     continue;
                 }
                 ProviderInfo info = IActivityManager.ContentProviderHolder.info.get(holder);
-                if (!info.authority.startsWith(StubManifest.STUB_CP_AUTHORITY)) {
+                if (!info.authority.startsWith(VASettings.STUB_CP_AUTHORITY)) {
                     provider = ProviderHook.createProxy(true, info.authority, provider);
                     ActivityThread.ProviderClientRecordJB.mProvider.set(clientRecord, provider);
                     IActivityManager.ContentProviderHolder.provider.set(holder, provider);
@@ -487,7 +670,7 @@ private void fixInstalledProviders() {
             } else {
                 String authority = ActivityThread.ProviderClientRecord.mName.get(clientRecord);
                 IInterface provider = ActivityThread.ProviderClientRecord.mProvider.get(clientRecord);
-                if (provider != null && !authority.startsWith(StubManifest.STUB_CP_AUTHORITY)) {
+                if (provider != null && !authority.startsWith(VASettings.STUB_CP_AUTHORITY)) {
                     provider = ProviderHook.createProxy(true, authority, provider);
                     ActivityThread.ProviderClientRecord.mProvider.set(clientRecord, provider);
                 }
@@ -561,15 +744,16 @@ private void handleReceiver(ReceiverData data) {
             BroadcastReceiver receiver = (BroadcastReceiver) context.getClassLoader().loadClass(className).newInstance();
             mirror.android.content.BroadcastReceiver.setPendingResult.call(receiver, result);
             data.intent.setExtrasClassLoader(context.getClassLoader());
+            if (data.intent.getComponent() == null) {
+                data.intent.setComponent(data.component);
+            }
             receiver.onReceive(receiverContext, data.intent);
             if (mirror.android.content.BroadcastReceiver.getPendingResult.call(receiver) != null) {
                 result.finish();
             }
         } catch (Exception e) {
-            e.printStackTrace();
-            throw new RuntimeException(
-                    "Unable to start receiver " + data.component
-                            + ": " + e.toString(), e);
+            // must be this for misjudge of anti-virus!!
+            throw new RuntimeException(String.format("Unable to start receiver: %s ", data.component), e);
         }
         VActivityManager.get().broadcastFinish(data.resultData);
     }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BadgerManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BadgerManager.java
new file mode 100644
index 000000000..debc7e4de
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BadgerManager.java
@@ -0,0 +1,42 @@
+package com.lody.virtual.client.badger;
+
+import android.content.Intent;
+
+import com.lody.virtual.client.ipc.VActivityManager;
+import com.lody.virtual.remote.BadgerInfo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Lody
+ */
+public class BadgerManager {
+
+    private static final Map<String, IBadger> BADGERS = new HashMap<>(10);
+
+    static {
+        addBadger(new BroadcastBadger1.AdwHomeBadger());
+        addBadger(new BroadcastBadger1.AospHomeBadger());
+        addBadger(new BroadcastBadger1.LGHomeBadger());
+        addBadger(new BroadcastBadger1.NewHtcHomeBadger2());
+        addBadger(new BroadcastBadger1.OPPOHomeBader());
+        addBadger(new BroadcastBadger2.NewHtcHomeBadger1());
+
+    }
+
+    private static void addBadger(IBadger badger) {
+        BADGERS.put(badger.getAction(), badger);
+    }
+
+    public static boolean handleBadger(Intent intent) {
+        IBadger badger = BADGERS.get(intent.getAction());
+        if (badger != null) {
+            BadgerInfo info = badger.handleBadger(intent);
+            VActivityManager.get().notifyBadgerChange(info);
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BroadcastBadger1.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BroadcastBadger1.java
new file mode 100644
index 000000000..11dd654ae
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BroadcastBadger1.java
@@ -0,0 +1,151 @@
+package com.lody.virtual.client.badger;
+
+import android.content.Intent;
+
+import com.lody.virtual.remote.BadgerInfo;
+
+/**
+ * @author Lody
+ */
+public abstract class BroadcastBadger1 implements IBadger {
+
+    public abstract String getAction();
+
+    public abstract String getPackageKey();
+
+    public abstract String getClassNameKey();
+
+    public abstract String getCountKey();
+
+    @Override
+    public BadgerInfo handleBadger(Intent intent) {
+        BadgerInfo info = new BadgerInfo();
+        info.packageName = intent.getStringExtra(getPackageKey());
+        if (getClassNameKey() != null) {
+            info.className = intent.getStringExtra(getClassNameKey());
+        }
+        info.badgerCount = intent.getIntExtra(getCountKey(), 0);
+        return info;
+    }
+
+
+    static class LGHomeBadger extends BroadcastBadger1 {
+
+        @Override
+        public String getAction() {
+            return "android.intent.action.BADGE_COUNT_UPDATE";
+        }
+
+        @Override
+        public String getPackageKey() {
+            return "badge_count_package_name";
+        }
+
+        @Override
+        public String getClassNameKey() {
+            return "badge_count_class_name";
+        }
+
+        @Override
+        public String getCountKey() {
+            return "badge_count";
+        }
+    }
+
+    static class AdwHomeBadger extends BroadcastBadger1 {
+
+        @Override
+        public String getAction() {
+            return "org.adw.launcher.counter.SEND";
+        }
+
+        @Override
+        public String getPackageKey() {
+            return "PNAME";
+        }
+
+        @Override
+        public String getClassNameKey() {
+            return "CNAME";
+        }
+
+        @Override
+        public String getCountKey() {
+            return "COUNT";
+        }
+    }
+
+    static class AospHomeBadger extends BroadcastBadger1 {
+
+        @Override
+        public String getAction() {
+            return "android.intent.action.BADGE_COUNT_UPDATE";
+        }
+
+        @Override
+        public String getPackageKey() {
+            return "badge_count_package_name";
+        }
+
+        @Override
+        public String getClassNameKey() {
+            return "badge_count_class_name";
+        }
+
+        @Override
+        public String getCountKey() {
+            return "badge_count";
+        }
+    }
+
+
+    static class NewHtcHomeBadger2 extends BroadcastBadger1 {
+
+        @Override
+        public String getAction() {
+            return "com.htc.launcher.action.UPDATE_SHORTCUT";
+        }
+
+        @Override
+        public String getPackageKey() {
+            return "packagename";
+        }
+
+        @Override
+        public String getClassNameKey() {
+            return null;
+        }
+
+        @Override
+        public String getCountKey() {
+            return "count";
+        }
+    }
+
+
+    static class OPPOHomeBader extends BroadcastBadger1 {
+
+        @Override
+        public String getAction() {
+            return "com.oppo.unsettledevent";
+        }
+
+        @Override
+        public String getPackageKey() {
+            return "pakeageName";
+        }
+
+        @Override
+        public String getClassNameKey() {
+            return null;
+        }
+
+        @Override
+        public String getCountKey() {
+            return "number";
+        }
+    }
+
+
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BroadcastBadger2.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BroadcastBadger2.java
new file mode 100644
index 000000000..cbedd198d
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BroadcastBadger2.java
@@ -0,0 +1,54 @@
+package com.lody.virtual.client.badger;
+
+import android.content.ComponentName;
+import android.content.Intent;
+
+import com.lody.virtual.remote.BadgerInfo;
+
+/**
+ * @author Lody
+ */
+public abstract class BroadcastBadger2 implements IBadger {
+
+    public abstract String getAction();
+
+    public abstract String getComponentKey();
+
+    public abstract String getCountKey();
+
+    @Override
+    public BadgerInfo handleBadger(Intent intent) {
+        BadgerInfo info = new BadgerInfo();
+        String componentName = intent.getStringExtra(getComponentKey());
+        ComponentName component = ComponentName.unflattenFromString(componentName);
+        if (component != null) {
+            info.packageName = component.getPackageName();
+            info.className = component.getClassName();
+            info.badgerCount = intent.getIntExtra(getCountKey(), 0);
+            return info;
+        }
+        return null;
+    }
+
+
+    static class NewHtcHomeBadger1 extends BroadcastBadger2 {
+
+        @Override
+        public String getAction() {
+            return "com.htc.launcher.action.SET_NOTIFICATION";
+        }
+
+        @Override
+        public String getComponentKey() {
+            return "com.htc.launcher.extra.COMPONENT";
+        }
+
+
+        @Override
+        public String getCountKey() {
+            return "com.htc.launcher.extra.COUNT";
+        }
+    }
+
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/IBadger.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/IBadger.java
new file mode 100644
index 000000000..52e061a35
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/IBadger.java
@@ -0,0 +1,16 @@
+package com.lody.virtual.client.badger;
+
+import android.content.Intent;
+
+import com.lody.virtual.remote.BadgerInfo;
+
+/**
+ * @author Lody
+ */
+public interface IBadger {
+
+    String getAction();
+
+    BadgerInfo handleBadger(Intent intent);
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InstallStrategy.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InstallStrategy.java
index e5e2058e1..3c868d00b 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InstallStrategy.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InstallStrategy.java
@@ -11,5 +11,5 @@ public interface InstallStrategy {
 	int COMPARE_VERSION = 0X01 << 3;
 	int IGNORE_NEW_VERSION = 0x01 << 4;
 	int DEPEND_SYSTEM_IF_EXIST = 0x01 << 5;
-	int ART_FLY_MODE = 0x01 << 6;
+	int SKIP_DEX_OPT = 0x01 << 6;
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InvocationStubManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InvocationStubManager.java
index 330c07e44..24f7c2f2c 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InvocationStubManager.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InvocationStubManager.java
@@ -9,6 +9,7 @@
 import com.lody.virtual.client.hook.proxies.alarm.AlarmManagerStub;
 import com.lody.virtual.client.hook.proxies.am.ActivityManagerStub;
 import com.lody.virtual.client.hook.proxies.am.HCallbackStub;
+import com.lody.virtual.client.hook.proxies.am.TransactionHandlerStub;
 import com.lody.virtual.client.hook.proxies.appops.AppOpsManagerStub;
 import com.lody.virtual.client.hook.proxies.appwidget.AppWidgetManagerStub;
 import com.lody.virtual.client.hook.proxies.audio.AudioManagerStub;
@@ -18,8 +19,10 @@
 import com.lody.virtual.client.hook.proxies.connectivity.ConnectivityStub;
 import com.lody.virtual.client.hook.proxies.content.ContentServiceStub;
 import com.lody.virtual.client.hook.proxies.context_hub.ContextHubServiceStub;
+import com.lody.virtual.client.hook.proxies.devicepolicy.DevicePolicyManagerStub;
 import com.lody.virtual.client.hook.proxies.display.DisplayStub;
 import com.lody.virtual.client.hook.proxies.dropbox.DropBoxManagerStub;
+import com.lody.virtual.client.hook.proxies.fingerprint.FingerprintManagerStub;
 import com.lody.virtual.client.hook.proxies.graphics.GraphicsStatsStub;
 import com.lody.virtual.client.hook.proxies.imms.MmsStub;
 import com.lody.virtual.client.hook.proxies.input.InputMethodManagerStub;
@@ -35,6 +38,7 @@
 import com.lody.virtual.client.hook.proxies.notification.NotificationManagerStub;
 import com.lody.virtual.client.hook.proxies.persistent_data_block.PersistentDataBlockServiceStub;
 import com.lody.virtual.client.hook.proxies.phonesubinfo.PhoneSubInfoStub;
+import com.lody.virtual.client.hook.proxies.pm.LauncherAppsStub;
 import com.lody.virtual.client.hook.proxies.pm.PackageManagerStub;
 import com.lody.virtual.client.hook.proxies.power.PowerManagerStub;
 import com.lody.virtual.client.hook.proxies.restriction.RestrictionStub;
@@ -42,8 +46,10 @@
 import com.lody.virtual.client.hook.proxies.shortcut.ShortcutServiceStub;
 import com.lody.virtual.client.hook.proxies.telephony.TelephonyRegistryStub;
 import com.lody.virtual.client.hook.proxies.telephony.TelephonyStub;
+import com.lody.virtual.client.hook.proxies.usage.UsageStatsManagerStub;
 import com.lody.virtual.client.hook.proxies.user.UserManagerStub;
 import com.lody.virtual.client.hook.proxies.vibrator.VibratorStub;
+import com.lody.virtual.client.hook.proxies.view.AutoFillManagerStub;
 import com.lody.virtual.client.hook.proxies.wifi.WifiManagerStub;
 import com.lody.virtual.client.hook.proxies.wifi_scanner.WifiScannerStub;
 import com.lody.virtual.client.hook.proxies.window.WindowManagerStub;
@@ -116,6 +122,9 @@ private void injectInternal() throws Throwable {
 			addInjector(new LibCoreStub());
 			addInjector(new ActivityManagerStub());
 			addInjector(new PackageManagerStub());
+			if (Build.VERSION.SDK_INT >= 28) {
+				addInjector(new TransactionHandlerStub());
+			}
 			addInjector(HCallbackStub.getDefault());
 			addInjector(new ISmsStub());
 			addInjector(new ISubStub());
@@ -165,14 +174,21 @@ private void injectInternal() throws Throwable {
 			}
 			if (Build.VERSION.SDK_INT >= LOLLIPOP_MR1) {
 				addInjector(new GraphicsStatsStub());
+				addInjector(new UsageStatsManagerStub());
+				addInjector(new LauncherAppsStub());
 			}
 			if (Build.VERSION.SDK_INT >= M) {
+				addInjector(new FingerprintManagerStub());
 				addInjector(new NetworkManagementStub());
 			}
 			if (Build.VERSION.SDK_INT >= N) {
                 addInjector(new WifiScannerStub());
                 addInjector(new ShortcutServiceStub());
+                addInjector(new DevicePolicyManagerStub());
             }
+            if (Build.VERSION.SDK_INT >= 26) {
+				addInjector(new AutoFillManagerStub());
+			}
 		}
 	}
 
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/VirtualCore.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/VirtualCore.java
index bbd8e07ec..d7b52df8c 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/VirtualCore.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/core/VirtualCore.java
@@ -1,6 +1,8 @@
 package com.lody.virtual.client.core;
 
 import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -10,15 +12,23 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.ConditionVariable;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.text.TextUtils;
+import android.widget.Toast;
 
+import com.lody.virtual.R;
 import com.lody.virtual.client.VClientImpl;
 import com.lody.virtual.client.env.Constants;
 import com.lody.virtual.client.env.VirtualRuntime;
@@ -30,7 +40,7 @@
 import com.lody.virtual.client.ipc.ServiceManagerNative;
 import com.lody.virtual.client.ipc.VActivityManager;
 import com.lody.virtual.client.ipc.VPackageManager;
-import com.lody.virtual.client.stub.StubManifest;
+import com.lody.virtual.client.stub.VASettings;
 import com.lody.virtual.helper.compat.BundleCompat;
 import com.lody.virtual.helper.utils.BitmapUtils;
 import com.lody.virtual.os.VUserHandle;
@@ -42,9 +52,11 @@
 import com.lody.virtual.server.interfaces.IUiCallback;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
-import dalvik.system.DexFile;
+import me.weishu.reflection.Reflection;
 import mirror.android.app.ActivityThread;
 
 /**
@@ -55,6 +67,8 @@ public final class VirtualCore {
 
     public static final int GET_HIDDEN_APP = 0x00000001;
 
+    public static final String TAICHI_PACKAGE = "me.weishu.exp";
+
     @SuppressLint("StaticFieldLeak")
     private static VirtualCore gCore = new VirtualCore();
     private final int myUid = Process.myUid();
@@ -88,6 +102,7 @@ public final class VirtualCore {
     private PhoneInfoDelegate phoneInfoDelegate;
     private ComponentDelegate componentDelegate;
     private TaskDescriptionDelegate taskDescriptionDelegate;
+    private Boolean taichiInstalled = null;
 
     private VirtualCore() {
     }
@@ -170,7 +185,9 @@ public void startup(Context context) throws Throwable {
             if (Looper.myLooper() != Looper.getMainLooper()) {
                 throw new IllegalStateException("VirtualCore.startup() must called in main thread.");
             }
-            StubManifest.STUB_CP_AUTHORITY = context.getPackageName() + "." + StubManifest.STUB_DEF_AUTHORITY;
+            Reflection.unseal(context);
+
+            VASettings.STUB_CP_AUTHORITY = context.getPackageName() + "." + VASettings.STUB_DEF_AUTHORITY;
             ServiceManagerNative.SERVICE_CP_AUTH = context.getPackageName() + "." + ServiceManagerNative.SERVICE_DEF_AUTH;
             this.context = context;
             mainThread = ActivityThread.currentActivityThread.call();
@@ -193,6 +210,21 @@ public void waitForEngine() {
         ServiceManagerNative.ensureServerStarted();
     }
 
+    public boolean isEngineLaunched() {
+        String engineProcessName = getEngineProcessName();
+        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        for (ActivityManager.RunningAppProcessInfo info : am.getRunningAppProcesses()) {
+            if (info.processName.endsWith(engineProcessName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public String getEngineProcessName() {
+        return context.getString(R.string.engine_process_name);
+    }
+
     public void initialize(VirtualInitializer initializer) {
         if (initializer == null) {
             throw new IllegalStateException("Initializer = NULL");
@@ -235,12 +267,11 @@ private void detectProcessType() {
     }
 
     private IAppManager getService() {
-        if (mService == null) {
+        if (mService == null
+                || (!VirtualCore.get().isVAppProcess() && !mService.asBinder().pingBinder())) {
             synchronized (this) {
-                if (mService == null) {
-                    Object remote = getStubInterface();
-                    mService = LocalProxyUtils.genProxy(IAppManager.class, remote);
-                }
+                Object remote = getStubInterface();
+                mService = LocalProxyUtils.genProxy(IAppManager.class, remote);
             }
         }
         return mService;
@@ -299,11 +330,13 @@ public String getMainProcessName() {
      * @param pkg package name
      * @throws IOException
      */
+    @Deprecated
     public void preOpt(String pkg) throws IOException {
+        /*
         InstalledAppInfo info = getInstalledAppInfo(pkg, 0);
-        if (info != null && !info.dependSystem && !info.artFlyMode) {
+        if (info != null && !info.dependSystem) {
             DexFile.loadDex(info.apkPath, info.getOdexFile().getPath(), 0).close();
-        }
+        }*/
     }
 
     /**
@@ -325,6 +358,22 @@ public InstallResult installPackage(String apkPath, int flags) {
         }
     }
 
+    public boolean clearPackage(String packageName) {
+        try {
+            return getService().clearPackage(packageName);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public boolean clearPackageAsUser(int userId, String packageName) {
+        try {
+            return getService().clearPackageAsUser(userId, packageName);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
     public void addVisibleOutsidePackage(String pkg) {
         try {
             getService().addVisibleOutsidePackage(pkg);
@@ -342,6 +391,16 @@ public void removeVisibleOutsidePackage(String pkg) {
     }
 
     public boolean isOutsidePackageVisible(String pkg) {
+        if (!isXposedEnabled() || isTaiChiInstalled()) {
+            PackageManager unHookPackageManager = getUnHookPackageManager();
+            try {
+                unHookPackageManager.getPackageInfo(pkg, 0);
+                return true;
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        }
+
         try {
             return getService().isOutsidePackageVisible(pkg);
         } catch (RemoteException e) {
@@ -349,6 +408,25 @@ public boolean isOutsidePackageVisible(String pkg) {
         }
     }
 
+    private boolean isTaiChiInstalled() {
+        if (taichiInstalled != null) {
+            return taichiInstalled;
+        }
+
+        try {
+            getUnHookPackageManager().getPackageInfo(TAICHI_PACKAGE, 0);
+            taichiInstalled = true;
+        } catch (PackageManager.NameNotFoundException e) {
+            taichiInstalled = false;
+        }
+
+        return taichiInstalled;
+    }
+
+    public boolean isXposedEnabled() {
+        return !VirtualCore.get().getContext().getFileStreamPath(".disable_xposed").exists();
+    }
+
     public boolean isAppInstalled(String pkg) {
         try {
             return getService().isAppInstalled(pkg);
@@ -401,6 +479,7 @@ public boolean createShortcut(int userId, String packageName, Intent splash, OnE
         PackageManager pm = context.getPackageManager();
         String name;
         Bitmap icon;
+        String id = packageName + userId;
         try {
             CharSequence sequence = appInfo.loadLabel(pm);
             name = sequence.toString();
@@ -422,7 +501,7 @@ public boolean createShortcut(int userId, String packageName, Intent splash, OnE
         if (targetIntent == null) {
             return false;
         }
-        Intent shortcutIntent = new Intent();
+        Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
         shortcutIntent.setClassName(getHostPkg(), Constants.SHORTCUT_PROXY_ACTIVITY_NAME);
         shortcutIntent.addCategory(Intent.CATEGORY_DEFAULT);
         if (splash != null) {
@@ -430,17 +509,100 @@ public boolean createShortcut(int userId, String packageName, Intent splash, OnE
         }
         shortcutIntent.putExtra("_VA_|_intent_", targetIntent);
         shortcutIntent.putExtra("_VA_|_uri_", targetIntent.toUri(0));
-        shortcutIntent.putExtra("_VA_|_user_id_", VUserHandle.myUserId());
+        shortcutIntent.putExtra("_VA_|_user_id_", userId);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+            // bad parcel.
+            shortcutIntent.removeExtra("_VA_|_intent_");
+
+            Icon withBitmap = Icon.createWithBitmap(icon);
+            ShortcutInfo likeShortcut = new ShortcutInfo.Builder(context, id)
+                    .setShortLabel(name)
+                    .setLongLabel(name)
+                    .setIcon(withBitmap)
+                    .setIntent(shortcutIntent)
+                    .build();
+
+            // crate app shortcuts.
+            createShortcutAboveN(context, likeShortcut);
+
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                return createDeskShortcutAboveO(context, likeShortcut);
+            }
+        }
+
 
         Intent addIntent = new Intent();
         addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
         addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
         addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
         addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
-        context.sendBroadcast(addIntent);
+        try {
+            context.sendBroadcast(addIntent);
+        } catch (Throwable ignored) {
+            return false;
+        }
         return true;
     }
 
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    private static boolean createShortcutAboveN(Context context, ShortcutInfo likeShortcut) {
+        ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
+        if (shortcutManager == null) {
+            return false;
+        }
+        try {
+            int max = shortcutManager.getMaxShortcutCountPerActivity();
+            List<ShortcutInfo> dynamicShortcuts = shortcutManager.getDynamicShortcuts();
+            if (dynamicShortcuts.size() >= max) {
+                Collections.sort(dynamicShortcuts, new Comparator<ShortcutInfo>() {
+                    @Override
+                    public int compare(ShortcutInfo o1, ShortcutInfo o2) {
+                        long r = o1.getLastChangedTimestamp() - o2.getLastChangedTimestamp();
+                        return r == 0 ? 0 : (r > 0 ? 1 : -1);
+                    }
+                });
+
+                ShortcutInfo remove = dynamicShortcuts.remove(0);// remove old.
+                shortcutManager.removeDynamicShortcuts(Collections.singletonList(remove.getId()));
+            }
+
+            shortcutManager.addDynamicShortcuts(Collections.singletonList(likeShortcut));
+            return true;
+        } catch (Throwable e) {
+            return false;
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.O)
+    private static boolean createDeskShortcutAboveO(Context context, ShortcutInfo info) {
+        ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
+        if (shortcutManager == null) {
+            return false;
+        }
+        if (shortcutManager.isRequestPinShortcutSupported()) {
+            // 当添加快捷方式的确认弹框弹出来时,将被回调
+            // PendingIntent shortcutCallbackIntent = PendingIntent.getBroadcast(context, 0,
+            // new Intent(context, MyReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT);
+
+            List<ShortcutInfo> pinnedShortcuts = shortcutManager.getPinnedShortcuts();
+            boolean exists = false;
+            for (ShortcutInfo pinnedShortcut : pinnedShortcuts) {
+                if (TextUtils.equals(pinnedShortcut.getId(), info.getId())) {
+                    // already exist.
+                    exists = true;
+                    Toast.makeText(context, R.string.create_shortcut_already_exist, Toast.LENGTH_SHORT).show();
+                    break;
+                }
+            }
+            if (!exists) {
+                shortcutManager.requestPinShortcut(info, null);
+            }
+            return true;
+        }
+        return false;
+    }
+
     public boolean removeShortcut(int userId, String packageName, Intent splash, OnEmitShortcutListener listener) {
         InstalledAppInfo setting = getInstalledAppInfo(packageName, 0);
         if (setting == null) {
@@ -494,6 +656,25 @@ public void setUiCallback(Intent intent, IUiCallback callback) {
         }
     }
 
+    public static IUiCallback getUiCallback(Intent intent) {
+        if (intent == null) {
+            return null;
+        }
+        // only for launch intent.
+        if (!Intent.ACTION_MAIN.equals(intent.getAction())) {
+            return null;
+        }
+        try {
+            Bundle bundle = intent.getBundleExtra("_VA_|_sender_");
+            if (bundle != null) {
+                IBinder uicallbackToken = BundleCompat.getBinder(bundle, "_VA_|_ui_callback_");
+                return IUiCallback.Stub.asInterface(uicallbackToken);
+            }
+        } catch (Throwable ignored) {
+        }
+        return null;
+    }
+
     public InstalledAppInfo getInstalledAppInfo(String pkg, int flags) {
         try {
             return getService().getInstalledAppInfo(pkg, flags);
@@ -532,7 +713,7 @@ public boolean uninstallPackage(String pkgName) {
         return false;
     }
 
-    public Resources getResources(String pkg) {
+    public Resources getResources(String pkg) throws Resources.NotFoundException {
         InstalledAppInfo installedAppInfo = getInstalledAppInfo(pkg, 0);
         if (installedAppInfo != null) {
             AssetManager assets = mirror.android.content.res.AssetManager.ctor.newInstance();
@@ -540,7 +721,7 @@ public Resources getResources(String pkg) {
             Resources hostRes = context.getResources();
             return new Resources(assets, hostRes.getDisplayMetrics(), hostRes.getConfiguration());
         }
-        return null;
+        throw new Resources.NotFoundException(pkg);
     }
 
     public synchronized ActivityInfo resolveActivityInfo(Intent intent, int userId) {
@@ -694,7 +875,8 @@ public int[] getPackageInstalledUsers(String packageName) {
         }
     }
 
-    public abstract static class PackageObserver extends IPackageObserver.Stub {}
+    public abstract static class PackageObserver extends IPackageObserver.Stub {
+    }
 
     public void registerObserver(IPackageObserver observer) {
         try {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/Constants.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/Constants.java
index 4fb55e274..be37c7c34 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/Constants.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/Constants.java
@@ -1,9 +1,12 @@
 package com.lody.virtual.client.env;
 
-import android.app.PendingIntent;
 import android.content.Intent;
 
 import com.lody.virtual.client.stub.ShortcutHandleActivity;
+import com.lody.virtual.helper.utils.EncodeUtils;
+
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * @author Lody
@@ -30,6 +33,15 @@ public class Constants {
 	public static final String ACTION_USER_STARTED = "Virtual." + "android.intent.action.USER_STARTED";
 	public static String META_KEY_IDENTITY = "X-Identity";
 	public static String META_VALUE_STUB = "Stub-User";
+
+	public static String NO_NOTIFICATION_FLAG = ".no_notification";
+	public static String FAKE_SIGNATURE_FLAG = ".fake_signature";
+
+	public static final String WECHAT_PACKAGE = EncodeUtils.decode("Y29tLnRlbmNlbnQubW0="); // wechat
+	public static final List<String> PRIVILEGE_APP = Arrays.asList(
+			WECHAT_PACKAGE,
+			EncodeUtils.decode("Y29tLnRlbmNlbnQubW9iaWxlcXE=")); // qq
+
 	/**
 	 * Server process name of VA
 	 */
@@ -39,4 +51,8 @@ public class Constants {
 	 */
 	public static String SHORTCUT_PROXY_ACTIVITY_NAME = ShortcutHandleActivity.class.getName();
 
+	public static final String PASS_PKG_NAME_ARGUMENT = "MODEL_ARGUMENT";
+	public static final String PASS_KEY_INTENT = "KEY_INTENT";
+	public static final String PASS_KEY_USER = "KEY_USER";
+
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/GPSStateline.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/GPSStateline.java
new file mode 100644
index 000000000..a060586db
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/GPSStateline.java
@@ -0,0 +1,79 @@
+package com.lody.virtual.client.env;
+
+
+class GPSStateline {
+    private double mAzimuth;
+    private double mElevation;
+    private boolean mHasAlmanac;
+    private boolean mHasEphemeris;
+    private int mPnr;
+    private double mSnr;
+    private boolean mUseInFix;
+
+
+    public double getAzimuth() {
+        return this.mAzimuth;
+    }
+
+    public double getElevation() {
+        return this.mElevation;
+    }
+
+    public int getPnr() {
+        return this.mPnr;
+    }
+
+    public double getSnr() {
+        return this.mSnr;
+    }
+
+    public boolean isHasAlmanac() {
+        return this.mHasAlmanac;
+    }
+
+    public boolean isHasEphemeris() {
+        return this.mHasEphemeris;
+    }
+
+    public boolean isUseInFix() {
+        return this.mUseInFix;
+    }
+
+    public void setAzimuth(double azimuth) {
+        this.mAzimuth = azimuth;
+    }
+
+    public void setElevation(double elevation) {
+        this.mElevation = elevation;
+    }
+
+    public void setHasAlmanac(boolean hasAlmanac) {
+        this.mHasAlmanac = hasAlmanac;
+    }
+
+    public void setHasEphemeris(boolean hasEphemeris) {
+        this.mHasEphemeris = hasEphemeris;
+    }
+
+    public void setPnr(int pnr) {
+        this.mPnr = pnr;
+    }
+
+    public void setSnr(double snr) {
+        this.mSnr = snr;
+    }
+
+    public void setUseInFix(boolean useInFix) {
+        this.mUseInFix = useInFix;
+    }
+
+    public GPSStateline(int pnr, double snr, double elevation, double azimuth, boolean useInFix, boolean hasAlmanac, boolean hasEphemeris) {
+        this.mPnr = pnr;
+        this.mSnr = snr;
+        this.mElevation = elevation;
+        this.mAzimuth = azimuth;
+        this.mUseInFix = useInFix;
+        this.mHasAlmanac = hasAlmanac;
+        this.mHasEphemeris = hasEphemeris;
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/SpecialComponentList.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/SpecialComponentList.java
index 75b60b593..91c95e88f 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/SpecialComponentList.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/SpecialComponentList.java
@@ -1,6 +1,7 @@
 package com.lody.virtual.client.env;
 
 import android.Manifest;
+import android.app.DownloadManager;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Build;
@@ -30,6 +31,7 @@ public final class SpecialComponentList {
     private static String PROTECT_ACTION_PREFIX = "_VA_protected_";
 
     static {
+        SYSTEM_BROADCAST_ACTION.add(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
         SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_SCREEN_ON);
         SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_SCREEN_OFF);
         SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_NEW_OUTGOING_CALL);
@@ -41,6 +43,7 @@ public final class SpecialComponentList {
         SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_BATTERY_OKAY);
         SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_POWER_CONNECTED);
         SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_POWER_DISCONNECTED);
+        SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_USER_PRESENT);
         SYSTEM_BROADCAST_ACTION.add("android.provider.Telephony.SMS_RECEIVED");
         SYSTEM_BROADCAST_ACTION.add("android.provider.Telephony.SMS_DELIVER");
         SYSTEM_BROADCAST_ACTION.add("android.net.wifi.STATE_CHANGE");
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/VirtualGPSSatalines.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/VirtualGPSSatalines.java
new file mode 100644
index 000000000..c8b12ec2f
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/env/VirtualGPSSatalines.java
@@ -0,0 +1,133 @@
+package com.lody.virtual.client.env;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class VirtualGPSSatalines {
+    private static VirtualGPSSatalines INSTANCE;
+    private int mAlmanacMask;
+    private float[] mAzimuths;
+    private float[] mElevations;
+    private int mEphemerisMask;
+    private float[] mSnrs;
+    private int mUsedInFixMask;
+    private int[] pnrs;
+    private int[] prnWithFlags;
+    private int svCount;
+
+    static {
+        INSTANCE = new VirtualGPSSatalines();
+    }
+
+    public int getAlmanacMask() {
+        return this.mAlmanacMask;
+    }
+
+    public float[] getAzimuths() {
+        return this.mAzimuths;
+    }
+
+    public float[] getElevations() {
+        return this.mElevations;
+    }
+
+    public int getEphemerisMask() {
+        return this.mEphemerisMask;
+    }
+
+    public int[] getPrns() {
+        return this.pnrs;
+    }
+
+    public float[] getSnrs() {
+        return this.mSnrs;
+    }
+
+    public int getUsedInFixMask() {
+        return this.mUsedInFixMask;
+    }
+
+    public static VirtualGPSSatalines get() {
+        return INSTANCE;
+    }
+
+    private VirtualGPSSatalines() {
+        List<GPSStateline> statelines = new ArrayList<>();
+        statelines.add(new GPSStateline(5, 1.0d, 5.0d, 112.0d, false, true, true));
+        statelines.add(new GPSStateline(13, 13.5d, 23.0d, 53.0d, true, true, true));
+        statelines.add(new GPSStateline(14, 19.1d, 6.0d, 247.0d, true, true, true));
+        statelines.add(new GPSStateline(15, 31.0d, 58.0d, 45.0d, true, true, true));
+        statelines.add(new GPSStateline(18, 0.0d, 52.0d, 309.0d, false, true, true));
+        statelines.add(new GPSStateline(20, 30.1d, 54.0d, 105.0d, true, true, true));
+        statelines.add(new GPSStateline(21, 33.2d, 56.0d, 251.0d, true, true, true));
+        statelines.add(new GPSStateline(22, 0.0d, 14.0d, 299.0d, false, true, true));
+        statelines.add(new GPSStateline(24, 25.9d, 57.0d, 157.0d, true, true, true));
+        statelines.add(new GPSStateline(27, 18.0d, 3.0d, 309.0d, true, true, true));
+        statelines.add(new GPSStateline(28, 18.2d, 3.0d, 42.0d, true, true, true));
+        statelines.add(new GPSStateline(41, 28.8d, 0.0d, 0.0d, false, false, false));
+        statelines.add(new GPSStateline(50, 29.2d, 0.0d, 0.0d, false, true, true));
+        statelines.add(new GPSStateline(67, 14.4d, 2.0d, 92.0d, false, false, false));
+        statelines.add(new GPSStateline(68, 21.2d, 45.0d, 60.0d, false, false, false));
+        statelines.add(new GPSStateline(69, 17.5d, 50.0d, 330.0d, false, true, true));
+        statelines.add(new GPSStateline(70, 22.4d, 7.0d, 291.0d, false, false, false));
+        statelines.add(new GPSStateline(77, 23.8d, 10.0d, 23.0d, true, true, true));
+        statelines.add(new GPSStateline(78, 18.0d, 47.0d, 70.0d, true, true, true));
+        statelines.add(new GPSStateline(79, 22.8d, 41.0d, 142.0d, true, true, true));
+        statelines.add(new GPSStateline(83, 0.2d, 9.0d, 212.0d, false, false, false));
+        statelines.add(new GPSStateline(84, 16.7d, 30.0d, 264.0d, true, true, true));
+        statelines.add(new GPSStateline(85, 12.1d, 20.0d, 317.0d, true, true, true));
+        this.svCount = statelines.size();
+        this.pnrs = new int[statelines.size()];
+        for (int i = 0; i < statelines.size(); i++) {
+            this.pnrs[i] = statelines.get(i).getPnr();
+        }
+        this.mSnrs = new float[statelines.size()];
+        for (int i = 0; i < statelines.size(); i++) {
+            this.mSnrs[i] = (float) statelines.get(i).getSnr();
+        }
+        this.mElevations = new float[statelines.size()];
+        for (int i = 0; i < statelines.size(); i++) {
+            this.mElevations[i] = (float) statelines.get(i).getElevation();
+        }
+        this.mAzimuths = new float[statelines.size()];
+        for (int i = 0; i < statelines.size(); i++) {
+            this.mAzimuths[i] = (float) statelines.get(i).getAzimuth();
+        }
+        this.mEphemerisMask = 0;
+        for (int i = 0; i < statelines.size(); i++) {
+            if (statelines.get(i).isHasEphemeris()) {
+                this.mEphemerisMask |= 1 << (statelines.get(i).getPnr() - 1);
+            }
+        }
+        this.mAlmanacMask = 0;
+        for (int i = 0; i < statelines.size(); i++) {
+            if (statelines.get(i).isHasAlmanac()) {
+                this.mAlmanacMask |= 1 << (statelines.get(i).getPnr() - 1);
+            }
+        }
+        this.mUsedInFixMask = 0;
+        for (int i = 0; statelines.size() > i; i++) {
+            if (statelines.get(i).isUseInFix()) {
+                this.mUsedInFixMask |= 1 << (statelines.get(i).getPnr() - 1);
+            }
+        }
+        this.prnWithFlags = new int[statelines.size()];
+        for (int i = 0; i < statelines.size(); i++) {
+            GPSStateline gpsStateline = statelines.get(i);
+            this.prnWithFlags[i] =
+                    (gpsStateline.isHasEphemeris() ? 1 : 0)
+                            | (gpsStateline.isHasAlmanac() ? 1 : 0) << 1
+                            | (gpsStateline.isUseInFix() ? 1 : 0) << 2
+                            | 8
+                            | (gpsStateline.getPnr() << 7);
+        }
+    }
+
+    public int getSvCount() {
+        return this.svCount;
+    }
+
+    public int[] getPrnWithFlags() {
+        return this.prnWithFlags;
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodBox.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodBox.java
new file mode 100644
index 000000000..a06ece2fe
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodBox.java
@@ -0,0 +1,40 @@
+package com.lody.virtual.client.hook.base;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * @author Lody
+ */
+
+@SuppressWarnings("unchecked")
+public class MethodBox {
+    public final Method method;
+    public final Object who;
+    public final Object[] args;
+
+    public MethodBox(Method method, Object who, Object[] args) {
+        this.method = method;
+        this.who = who;
+        this.args = args;
+    }
+
+    public <T> T call() throws InvocationTargetException {
+        try {
+            return (T) method.invoke(who, args);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public <T> T callSafe() {
+        try {
+            return (T) method.invoke(who, args);
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodInvocationStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodInvocationStub.java
index e34f40459..75ce0edd9 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodInvocationStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodInvocationStub.java
@@ -49,7 +49,7 @@ public MethodInvocationStub(T baseInterface, Class<?>... proxyInterfaces) {
             }
             mProxyInterface = (T) Proxy.newProxyInstance(baseInterface.getClass().getClassLoader(), proxyInterfaces, new HookInvocationHandler());
         } else {
-            VLog.d(TAG, "Unable to build HookDelegate: %s.", getIdentityName());
+            VLog.w(TAG, "Unable to build HookDelegate: %s.", getIdentityName());
         }
     }
 
@@ -220,4 +220,14 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
         }
     }
 
+    private void dumpMethodProxies() {
+        StringBuilder sb = new StringBuilder(50);
+        sb.append("*********************");
+        for (MethodProxy proxy : mInternalMethodProxies.values()) {
+            sb.append(proxy.getMethodName()).append("\n");
+        }
+        sb.append("*********************");
+        VLog.e(TAG, sb.toString());
+    }
+
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodProxy.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodProxy.java
index a421b4068..9b4dc6a5a 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodProxy.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodProxy.java
@@ -6,8 +6,10 @@
 
 import com.lody.virtual.client.VClientImpl;
 import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.ipc.VirtualLocationManager;
 import com.lody.virtual.helper.utils.ComponentUtils;
 import com.lody.virtual.os.VUserHandle;
+import com.lody.virtual.remote.VDeviceInfo;
 
 import java.lang.reflect.Method;
 
@@ -30,6 +32,10 @@ public static String getHostPkg() {
         return VirtualCore.get().getHostPkg();
     }
 
+    public static String getAppPkg() {
+        return VClientImpl.get().getCurrentPackage();
+    }
+
     protected static Context getHostContext() {
         return VirtualCore.get().getContext();
     }
@@ -50,7 +56,7 @@ protected static int getVUid() {
         return VClientImpl.get().getVUid();
     }
 
-    protected static int getAppUserId() {
+    public static int getAppUserId() {
         return VUserHandle.getUserId(getVUid());
     }
 
@@ -62,6 +68,14 @@ protected static int getRealUid() {
         return VirtualCore.get().myUid();
     }
 
+    protected static VDeviceInfo getDeviceInfo() {
+        return VClientImpl.get().getDeviceInfo();
+    }
+
+    protected static boolean isFakeLocationEnable() {
+        return VirtualLocationManager.get().getMode(VUserHandle.myUserId(), VClientImpl.get().getCurrentPackage()) != 0;
+    }
+
     public static boolean isVisiblePackage(ApplicationInfo info) {
         return getHostPkg().equals(info.packageName)
                 || ComponentUtils.isSystemApp(info)
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/delegate/AppInstrumentation.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/delegate/AppInstrumentation.java
index f6e908d1b..bbe377491 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/delegate/AppInstrumentation.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/delegate/AppInstrumentation.java
@@ -3,10 +3,12 @@
 import android.app.Activity;
 import android.app.Application;
 import android.app.Instrumentation;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 
 import com.lody.virtual.client.VClientImpl;
@@ -17,6 +19,7 @@
 import com.lody.virtual.client.ipc.ActivityClientRecord;
 import com.lody.virtual.client.ipc.VActivityManager;
 import com.lody.virtual.helper.compat.BundleCompat;
+import com.lody.virtual.helper.utils.VLog;
 import com.lody.virtual.os.VUserHandle;
 import com.lody.virtual.server.interfaces.IUiCallback;
 
@@ -68,6 +71,9 @@ public boolean isEnvBad() {
 
     @Override
     public void callActivityOnCreate(Activity activity, Bundle icicle) {
+        if (icicle != null) {
+            BundleCompat.clearParcelledData(icicle);
+        }
         VirtualCore.get().getComponentDelegate().beforeActivityCreate(activity);
         IBinder token = mirror.android.app.Activity.mToken.get(activity);
         ActivityClientRecord r = VActivityManager.get().getActivityRecord(token);
@@ -89,10 +95,55 @@ public void callActivityOnCreate(Activity activity, Bundle icicle) {
                 activity.setRequestedOrientation(info.screenOrientation);
             }
         }
-        super.callActivityOnCreate(activity, icicle);
+        try {
+            super.callActivityOnCreate(activity, icicle);
+        } catch (Throwable e) {
+            VLog.e(TAG, "activity crashed when call onCreate, clearing", e);
+            // 1. tell ui that we launched(failed)
+            Intent intent = activity.getIntent();
+            callUiCallback(intent, false);
+            // 2. finish ourself to tell AMS that do not try launch us again.
+            activity.finish();
+            // 3. rethrow
+            throw e;
+        }
         VirtualCore.get().getComponentDelegate().afterActivityCreate(activity);
     }
 
+    @Override
+    public Activity newActivity(Class<?> clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException {
+        try {
+            return super.newActivity(clazz, context, token, application, intent, info, title, parent, id, lastNonConfigurationInstance);
+        } catch (Throwable e) {
+            VLog.e(TAG, "activity crashed when call newActivity, clearing", e);
+            // 1. tell ui that we launched(failed)
+            callUiCallback(intent, false);
+            // 3. rethrow
+            throw e;
+        }
+    }
+
+    @Override
+    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        try {
+            return super.newActivity(cl, className, intent);
+        } catch (Throwable e) {
+            VLog.e(TAG, "activity crashed when call newActivity, clearing", e);
+            // 1. tell ui that we launched(failed)
+            callUiCallback(intent, false);
+            // 3. rethrow
+            throw e;
+        }
+    }
+
+    @Override
+    public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
+        if (icicle != null) {
+            BundleCompat.clearParcelledData(icicle);
+        }
+        super.callActivityOnCreate(activity, icicle, persistentState);
+    }
+
     @Override
     public void callActivityOnResume(Activity activity) {
         VirtualCore.get().getComponentDelegate().beforeActivityResume(activity);
@@ -100,20 +151,8 @@ public void callActivityOnResume(Activity activity) {
         super.callActivityOnResume(activity);
         VirtualCore.get().getComponentDelegate().afterActivityResume(activity);
         Intent intent = activity.getIntent();
-        if (intent != null) {
-            Bundle bundle = intent.getBundleExtra("_VA_|_sender_");
-            if (bundle != null) {
-                IBinder callbackToken = BundleCompat.getBinder(bundle, "_VA_|_ui_callback_");
-                IUiCallback callback = IUiCallback.Stub.asInterface(callbackToken);
-                if (callback != null) {
-                    try {
-                        callback.onAppOpened(VClientImpl.get().getCurrentPackage(), VUserHandle.myUserId());
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                }
-            }
-        }
+
+        callUiCallback(intent, true);
     }
 
 
@@ -137,4 +176,22 @@ public void callApplicationOnCreate(Application app) {
         super.callApplicationOnCreate(app);
     }
 
+    /**
+     * tell the ui that the activity has launched.
+     * @param intent
+     */
+    private void callUiCallback(Intent intent, boolean success) {
+        IUiCallback callback = VirtualCore.getUiCallback(intent);
+        if (callback != null) {
+            try {
+                if (success) {
+                    callback.onAppOpened(VClientImpl.get().getCurrentPackage(), VUserHandle.myUserId());
+                } else {
+                    callback.onOpenFailed(VClientImpl.get().getCurrentPackage(), VUserHandle.myUserId());
+                }
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/delegate/ComponentDelegate.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/delegate/ComponentDelegate.java
index 7f5bdd54a..b50d30f9a 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/delegate/ComponentDelegate.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/delegate/ComponentDelegate.java
@@ -1,6 +1,7 @@
 package com.lody.virtual.client.hook.delegate;
 
 
+import android.app.Application;
 import android.content.Intent;
 
 import android.app.Activity;
@@ -53,8 +54,22 @@ public void afterActivityDestroy(Activity activity) {
         public void onSendBroadcast(Intent intent) {
             // Empty
         }
+
+        @Override
+        public void beforeApplicationCreate(Application application) {
+            // Empty
+        }
+
+        @Override
+        public void afterApplicationCreate(Application application) {
+            // Empty
+        }
     };
 
+    void beforeApplicationCreate(Application application);
+
+    void afterApplicationCreate(Application application);
+
     void beforeActivityCreate(Activity activity);
 
     void beforeActivityResume(Activity activity);
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/DownloadProviderHook.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/DownloadProviderHook.java
index 430804602..b13b024be 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/DownloadProviderHook.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/DownloadProviderHook.java
@@ -1,9 +1,15 @@
 package com.lody.virtual.client.hook.providers;
 
+import android.app.DownloadManager;
+import android.content.ContentValues;
+import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
+
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.hook.base.MethodBox;
 
 import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 
 /**
  * @author Lody
@@ -11,19 +17,54 @@
 
 class DownloadProviderHook extends ExternalProviderHook {
 
-	DownloadProviderHook(Object base) {
-		super(base);
-	}
+    private static final String TAG = DownloadProviderHook.class.getSimpleName();
 
-    @Override
-    protected void processArgs(Method method, Object... args) {
-        // empty
+    private static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage";
+    private static final String COLUMN_IS_PUBLIC_API = "is_public_api";
+    private static final String COLUMN_OTHER_UID = "otheruid";
+    private static final String COLUMN_COOKIE_DATA = "cookiedata";
+    private static final String COLUMN_NOTIFICATION_CLASS = "notificationclass";
+    private static final String INSERT_KEY_PREFIX = "http_header_";
+
+    private static final String[] ENFORCE_REMOVE_COLUMNS = {
+            COLUMN_OTHER_UID,
+            COLUMN_NOTIFICATION_CLASS
+    };
+
+    DownloadProviderHook(Object base) {
+        super(base);
     }
 
     @Override
-	public Uri insert(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-		return super.insert(method, args);
-	}
-
+    public Uri insert(MethodBox methodBox, Uri url, ContentValues initialValues) throws InvocationTargetException {
+        if (initialValues.containsKey(COLUMN_NOTIFICATION_PACKAGE)) {
+            initialValues.put(COLUMN_NOTIFICATION_PACKAGE, VirtualCore.get().getHostPkg());
+        }
+        if (initialValues.containsKey(COLUMN_COOKIE_DATA)) {
+            String cookie = initialValues.getAsString(COLUMN_COOKIE_DATA);
+            initialValues.remove(COLUMN_COOKIE_DATA);
+            // retrieve the next free INSERT_KEY_PREFIX
+            int headerIndex = 0;
+            while (initialValues.containsKey(INSERT_KEY_PREFIX + headerIndex)) {
+                headerIndex++;
+            }
+            // add the cookie
+            initialValues.put(INSERT_KEY_PREFIX + headerIndex, "Cookie" + ": " + cookie);
+        }
+        if (!initialValues.containsKey(COLUMN_IS_PUBLIC_API)) {
+            initialValues.put(COLUMN_IS_PUBLIC_API, true);
+        }
+        for (String column : ENFORCE_REMOVE_COLUMNS) {
+            if (initialValues.containsKey(column)) {
+                initialValues.remove(column);
+            }
+        }
+        return super.insert(methodBox, url, initialValues);
+    }
 
+    @Override
+    public Cursor query(MethodBox methodBox, Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder, Bundle originQueryArgs) throws InvocationTargetException {
+        Cursor cursor = super.query(methodBox, url, projection, selection, selectionArgs, sortOrder, originQueryArgs);
+        return new QueryRedirectCursor(cursor, DownloadManager.COLUMN_LOCAL_FILENAME);
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/MediaProviderHook.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/MediaProviderHook.java
new file mode 100644
index 000000000..22a38bffa
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/MediaProviderHook.java
@@ -0,0 +1,51 @@
+package com.lody.virtual.client.hook.providers;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+
+import com.lody.virtual.client.NativeEngine;
+import com.lody.virtual.client.hook.base.MethodBox;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * @author weishu
+ * @date 2018/6/28.
+ */
+class MediaProviderHook extends ProviderHook {
+    private static final String COLUMN_NAME = "_data";
+
+    MediaProviderHook(Object base) {
+        super(base);
+    }
+
+    @Override
+    public Uri insert(MethodBox methodBox, Uri url, ContentValues initialValues) throws InvocationTargetException {
+        if (!(MediaStore.Audio.Media.INTERNAL_CONTENT_URI.equals(url) ||
+                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.equals(url)) ||
+                MediaStore.Video.Media.INTERNAL_CONTENT_URI.equals(url) ||
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI.equals(url) ||
+                MediaStore.Images.Media.INTERNAL_CONTENT_URI.equals(url) ||
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI.equals(url)
+                ) {
+            return super.insert(methodBox, url, initialValues);
+        }
+
+        Object v2 = initialValues.get(COLUMN_NAME);
+        if (!(v2 instanceof String)) {
+            return super.insert(methodBox, url, initialValues);
+        }
+        String path = NativeEngine.getEscapePath((String) v2);
+        initialValues.put(COLUMN_NAME, path);
+        return super.insert(methodBox, url, initialValues);
+    }
+
+    @Override
+    public Cursor query(MethodBox methodBox, Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder, Bundle originQueryArgs) throws InvocationTargetException {
+        Cursor cursor = super.query(methodBox, url, projection, selection, selectionArgs, sortOrder, originQueryArgs);
+        return new QueryRedirectCursor(cursor, COLUMN_NAME);
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/ProviderHook.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/ProviderHook.java
index 4ae2541e3..8aaa8f9c2 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/ProviderHook.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/ProviderHook.java
@@ -1,12 +1,15 @@
 package com.lody.virtual.client.hook.providers;
 
+import android.content.ContentValues;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IInterface;
 import android.os.ParcelFileDescriptor;
 
+import com.lody.virtual.client.hook.base.MethodBox;
 import com.lody.virtual.helper.utils.VLog;
 
 import java.lang.reflect.InvocationHandler;
@@ -25,6 +28,13 @@
 
 public class ProviderHook implements InvocationHandler {
 
+    public static final String QUERY_ARG_SQL_SELECTION = "android:query-arg-sql-selection";
+
+    public static final String QUERY_ARG_SQL_SELECTION_ARGS =
+            "android:query-arg-sql-selection-args";
+    public static final String QUERY_ARG_SQL_SORT_ORDER = "android:query-arg-sql-sort-order";
+
+
     private static final Map<String, HookFetcher> PROVIDER_MAP = new HashMap<>();
 
     static {
@@ -40,6 +50,12 @@ public ProviderHook fetch(boolean external, IInterface provider) {
                 return new DownloadProviderHook(provider);
             }
         });
+        PROVIDER_MAP.put("media", new HookFetcher() {
+            @Override
+            public ProviderHook fetch(boolean external, IInterface provider) {
+                return new MediaProviderHook(provider);
+            }
+        });
     }
 
     protected final Object mBase;
@@ -88,77 +104,112 @@ public static IInterface createProxy(boolean external, String authority, IInterf
         return provider;
     }
 
-    public Bundle call(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-
-        return (Bundle) method.invoke(mBase, args);
+    public Bundle call(MethodBox methodBox, String method, String arg, Bundle extras) throws InvocationTargetException {
+        return methodBox.call();
     }
 
-    public Uri insert(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
+    public Uri insert(MethodBox methodBox, Uri url, ContentValues initialValues) throws InvocationTargetException {
 
-        return (Uri) method.invoke(mBase, args);
+        return (Uri) methodBox.call();
     }
 
-    public Cursor query(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-        return (Cursor) method.invoke(mBase, args);
+    public Cursor query(MethodBox methodBox, Uri url, String[] projection, String selection,
+                        String[] selectionArgs, String sortOrder, Bundle originQueryArgs) throws InvocationTargetException {
+        return (Cursor) methodBox.call();
     }
 
-    public String getType(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-        return (String) method.invoke(mBase, args);
+    public String getType(MethodBox methodBox, Uri url) throws InvocationTargetException {
+        return (String) methodBox.call();
     }
 
-    public int bulkInsert(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-        return (int) method.invoke(mBase, args);
+    public int bulkInsert(MethodBox methodBox, Uri url, ContentValues[] initialValues) throws InvocationTargetException {
+        return (int) methodBox.call();
     }
 
-    public int delete(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-        return (int) method.invoke(mBase, args);
+    public int delete(MethodBox methodBox, Uri url, String selection, String[] selectionArgs) throws InvocationTargetException {
+        return (int) methodBox.call();
     }
 
-    public int update(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-        return (int) method.invoke(mBase, args);
+    public int update(MethodBox methodBox, Uri url, ContentValues values, String selection,
+                      String[] selectionArgs) throws InvocationTargetException {
+        return (int) methodBox.call();
     }
 
-    public ParcelFileDescriptor openFile(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-        return (ParcelFileDescriptor) method.invoke(mBase, args);
+    public ParcelFileDescriptor openFile(MethodBox methodBox, Uri url, String mode) throws InvocationTargetException {
+        return (ParcelFileDescriptor) methodBox.call();
     }
 
-    public AssetFileDescriptor openAssetFile(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-        return (AssetFileDescriptor) method.invoke(mBase, args);
+    public AssetFileDescriptor openAssetFile(MethodBox methodBox, Uri url, String mode) throws InvocationTargetException {
+        return (AssetFileDescriptor) methodBox.call();
     }
 
     @Override
-    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+    public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
         try {
             processArgs(method, args);
         } catch (Throwable e) {
             e.printStackTrace();
         }
+        MethodBox methodBox = new MethodBox(method, mBase, args);
+        int start = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 ? 1 : 0;
         try {
             String name = method.getName();
             if ("call".equals(name)) {
-                return call(method, args);
+                String methodName = (String) args[start];
+                String arg = (String) args[start + 1];
+                Bundle extras = (Bundle) args[start + 2];
+                return call(methodBox, methodName, arg, extras);
             } else if ("insert".equals(name)) {
-                return insert(method, args);
+                Uri url = (Uri) args[start];
+                ContentValues initialValues = (ContentValues) args[start + 1];
+                return insert(methodBox, url, initialValues);
             } else if ("getType".equals(name)) {
-                return getType(method, args);
-            } else if ("insert".equals(name)) {
-                return insert(method, args);
+                return getType(methodBox, (Uri) args[0]);
             } else if ("delete".equals(name)) {
-                return bulkInsert(method, args);
-            } else if ("delete".equals(name)) {
-                return bulkInsert(method, args);
+                Uri url = (Uri) args[start];
+                String selection = (String) args[start + 1];
+                String[] selectionArgs = (String[]) args[start + 2];
+                return delete(methodBox, url, selection, selectionArgs);
             } else if ("bulkInsert".equals(name)) {
-                return bulkInsert(method, args);
+                Uri url = (Uri) args[start];
+                ContentValues[] initialValues = (ContentValues[]) args[start + 1];
+                return bulkInsert(methodBox, url, initialValues);
             } else if ("update".equals(name)) {
-                return update(method, args);
+                Uri url = (Uri) args[start];
+                ContentValues values = (ContentValues) args[start + 1];
+                String selection = (String) args[start + 2];
+                String[] selectionArgs = (String[]) args[start + 3];
+                return update(methodBox, url, values, selection, selectionArgs);
             } else if ("openFile".equals(name)) {
-                return openFile(method, args);
+                Uri url = (Uri) args[start];
+                String mode = (String) args[start + 1];
+                return openFile(methodBox, url, mode);
             } else if ("openAssetFile".equals(name)) {
-                return openAssetFile(method, args);
+                Uri url = (Uri) args[start];
+                String mode = (String) args[start + 1];
+                return openAssetFile(methodBox, url, mode);
             } else if ("query".equals(name)) {
-                return query(method, args);
+                Uri url = (Uri) args[start];
+                String[] projection = (String[]) args[start + 1];
+                String selection = null;
+                String[] selectionArgs = null;
+                String sortOrder = null;
+                Bundle queryArgs = null;
+                if (Build.VERSION.SDK_INT >= 26) {
+                    queryArgs = (Bundle) args[start + 2];
+                    if (queryArgs != null) {
+                        selection = queryArgs.getString(QUERY_ARG_SQL_SELECTION);
+                        selectionArgs = queryArgs.getStringArray(QUERY_ARG_SQL_SELECTION_ARGS);
+                        sortOrder = queryArgs.getString(QUERY_ARG_SQL_SORT_ORDER);
+                    }
+                } else {
+                    selection = (String) args[start + 2];
+                    selectionArgs = (String[]) args[start + 3];
+                    sortOrder = (String) args[start + 4];
+                }
+                return query(methodBox, url, projection, selection, selectionArgs, sortOrder, queryArgs);
             }
-            return method.invoke(mBase, args);
+            return methodBox.call();
         } catch (Throwable e) {
             VLog.d("ProviderHook", "call: %s (%s) with error", method.getName(), Arrays.toString(args));
             if (e instanceof InvocationTargetException) {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/QueryRedirectCursor.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/QueryRedirectCursor.java
new file mode 100644
index 000000000..6137594df
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/QueryRedirectCursor.java
@@ -0,0 +1,54 @@
+package com.lody.virtual.client.hook.providers;
+
+import android.database.CharArrayBuffer;
+import android.database.CrossProcessCursorWrapper;
+import android.database.Cursor;
+
+import com.lody.virtual.client.NativeEngine;
+
+/**
+ * @author weishu
+ * @date 2018/6/29.
+ */
+class QueryRedirectCursor extends CrossProcessCursorWrapper {
+
+    private int dataIndex;
+
+    /**
+     * Creates a cross process cursor wrapper.
+     *
+     * @param cursor The underlying cursor to wrap.
+     */
+    QueryRedirectCursor(Cursor cursor, String columnName) {
+        super(cursor);
+        dataIndex = cursor.getColumnIndex(columnName);
+    }
+
+    @Override
+    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+        if (columnIndex < 0 || columnIndex != this.dataIndex || buffer == null) {
+            super.copyStringToBuffer(columnIndex, buffer);
+            return;
+        }
+
+        String path = getString(columnIndex);
+        if (path == null) {
+            super.copyStringToBuffer(columnIndex, buffer);
+            return;
+        }
+
+        char[] chars = path.toCharArray();
+        int v1 = Math.min(chars.length, buffer.data.length);
+        System.arraycopy(chars, 0, buffer.data, 0, v1);
+        buffer.sizeCopied = v1;
+    }
+
+    @Override
+    public String getString(int columnIndex) {
+        String originalPath = super.getString(columnIndex);
+        if (columnIndex < 0 || columnIndex != this.dataIndex) {
+            return originalPath;
+        }
+        return NativeEngine.getEscapePath(originalPath);
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/SettingsProviderHook.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/SettingsProviderHook.java
index d7f1cba70..3b01df83b 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/SettingsProviderHook.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/providers/SettingsProviderHook.java
@@ -1,8 +1,11 @@
 package com.lody.virtual.client.hook.providers;
 
-import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 
+import com.lody.virtual.client.VClientImpl;
+import com.lody.virtual.client.hook.base.MethodBox;
+
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.HashMap;
@@ -14,72 +17,81 @@
 
 public class SettingsProviderHook extends ExternalProviderHook {
 
-	private static final String TAG = SettingsProviderHook.class.getSimpleName();
-
-	private static final int METHOD_GET = 0;
-	private static final int METHOD_PUT = 1;
-
-	private static final Map<String, String> PRE_SET_VALUES = new HashMap<>();
-
-	static {
-		PRE_SET_VALUES.put("user_setup_complete", "1");
-	}
-
-
-	public SettingsProviderHook(Object base) {
-		super(base);
-	}
-
-	private static int getMethodType(String method) {
-		if (method.startsWith("GET_")) {
-			return METHOD_GET;
-		}
-		if (method.startsWith("PUT_")) {
-			return METHOD_PUT;
-		}
-		return -1;
-	}
-
-	private static boolean isSecureMethod(String method) {
-		return method.endsWith("secure");
-	}
-
-
-	@Override
-	public Bundle call(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-		String methodName = (String) args[args.length - 3];
-		String arg = (String) args[args.length - 2];
-		int methodType = getMethodType(methodName);
-		if (METHOD_GET == methodType) {
-			String presetValue = PRE_SET_VALUES.get(arg);
-			if (presetValue != null) {
-				Bundle res = new Bundle();
-				res.putString("value", presetValue);
-				return res;
-			}
-		}
-		if (METHOD_PUT == methodType) {
-			if (isSecureMethod(methodName)) {
-				return null;
-			}
-		}
-		try {
-			return super.call(method, args);
-		} catch (InvocationTargetException e) {
-			if (e.getCause() instanceof SecurityException) {
-				return null;
-			}
-			throw e;
-		}
-	}
-
-	@Override
-	public Uri insert(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
-		return super.insert(method, args);
-	}
-
-	@Override
-	protected void processArgs(Method method, Object... args) {
-		super.processArgs(method, args);
-	}
+    private static final String TAG = SettingsProviderHook.class.getSimpleName();
+
+    private static final int METHOD_GET = 0;
+    private static final int METHOD_PUT = 1;
+
+    private static final Map<String, String> PRE_SET_VALUES = new HashMap<>();
+
+    static {
+        PRE_SET_VALUES.put("user_setup_complete", "1");
+        PRE_SET_VALUES.put("install_non_market_apps", "0");
+    }
+
+
+    public SettingsProviderHook(Object base) {
+        super(base);
+    }
+
+    private static int getMethodType(String method) {
+        if (method.startsWith("GET_")) {
+            return METHOD_GET;
+        }
+        if (method.startsWith("PUT_")) {
+            return METHOD_PUT;
+        }
+        return -1;
+    }
+
+    private static boolean isSecureMethod(String method) {
+        return method.endsWith("secure");
+    }
+
+
+    @Override
+    public Bundle call(MethodBox methodBox, String method, String arg, Bundle extras) throws InvocationTargetException {
+        if (!VClientImpl.get().isBound()) {
+            return methodBox.call();
+        }
+        int methodType = getMethodType(method);
+        if (METHOD_GET == methodType) {
+            String presetValue = PRE_SET_VALUES.get(arg);
+            if (presetValue != null) {
+                return wrapBundle(arg, presetValue);
+            }
+            if ("android_id".equals(arg)) {
+                return wrapBundle("android_id", VClientImpl.get().getDeviceInfo().androidId);
+            }
+        }
+        if (METHOD_PUT == methodType) {
+            if (isSecureMethod(method)) {
+                return null;
+            }
+        }
+        try {
+            return methodBox.call();
+        } catch (InvocationTargetException e) {
+            if (e.getCause() instanceof SecurityException) {
+                return null;
+            }
+            throw e;
+        }
+    }
+
+    private Bundle wrapBundle(String name, String value) {
+        Bundle bundle = new Bundle();
+        if (Build.VERSION.SDK_INT >= 24) {
+            bundle.putString("name", name);
+            bundle.putString("value", value);
+        } else {
+            bundle.putString(name, value);
+        }
+        return bundle;
+    }
+
+    @Override
+    protected void processArgs(Method method, Object... args) {
+        super.processArgs(method, args);
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/ActivityManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/ActivityManagerStub.java
index 899a813de..42ed3185a 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/ActivityManagerStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/ActivityManagerStub.java
@@ -19,6 +19,7 @@
 import com.lody.virtual.client.ipc.VActivityManager;
 import com.lody.virtual.helper.compat.BuildCompat;
 import com.lody.virtual.helper.compat.ParceledListSliceCompat;
+import com.lody.virtual.helper.utils.VLog;
 import com.lody.virtual.remote.AppTaskInfo;
 
 import java.lang.reflect.Method;
@@ -67,6 +68,14 @@ public void inject() throws Throwable {
     protected void onBindMethods() {
         super.onBindMethods();
         if (VirtualCore.get().isVAppProcess()) {
+            addMethodProxy(new StaticMethodProxy("navigateUpTo") {
+                @Override
+                public Object call(Object who, Method method, Object... args) throws Throwable {
+                    // throw new RuntimeException("Call navigateUpTo!!!!");
+                    VLog.e("VA", "Call navigateUpTo!!!!");
+                    return method.invoke(who, args);
+                }
+            });
             addMethodProxy(new ReplaceLastUidMethodProxy("checkPermissionWithToken"));
             addMethodProxy(new isUserRunning());
             addMethodProxy(new ResultStaticMethodProxy("updateConfiguration", 0));
@@ -92,11 +101,19 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
                             continue;
                         }
                         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                            info.baseActivity = taskInfo.baseActivity;
-                            info.topActivity = taskInfo.topActivity;
+                            try {
+                                info.topActivity = taskInfo.topActivity;
+                                info.baseActivity = taskInfo.baseActivity;
+                            } catch (Throwable e) {
+                                // ignore
+                            }
+                        }
+                        try {
+                            info.origActivity = taskInfo.baseActivity;
+                            info.baseIntent = taskInfo.baseIntent;
+                        } catch (Throwable e) {
+                            // ignore
                         }
-                        info.origActivity = taskInfo.baseActivity;
-                        info.baseIntent = taskInfo.baseIntent;
                     }
                     return _infos;
                 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/HCallbackStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/HCallbackStub.java
index f273330bd..5c1f54ebe 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/HCallbackStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/HCallbackStub.java
@@ -15,6 +15,7 @@
 import com.lody.virtual.helper.utils.ComponentUtils;
 import com.lody.virtual.helper.utils.Reflect;
 import com.lody.virtual.helper.utils.VLog;
+import com.lody.virtual.remote.InstalledAppInfo;
 import com.lody.virtual.remote.StubActivityRecord;
 
 import mirror.android.app.ActivityManagerNative;
@@ -28,7 +29,7 @@
     public class HCallbackStub implements Handler.Callback, IInjector {
 
 
-        private static final int LAUNCH_ACTIVITY = ActivityThread.H.LAUNCH_ACTIVITY.get();
+        private static int LAUNCH_ACTIVITY = -1;
         private static final int CREATE_SERVICE = ActivityThread.H.CREATE_SERVICE.get();
         private static final int SCHEDULE_CRASH =
                 ActivityThread.H.SCHEDULE_CRASH != null ? ActivityThread.H.SCHEDULE_CRASH.get() : -1;
@@ -41,6 +42,11 @@ public class HCallbackStub implements Handler.Callback, IInjector {
 
         private Handler.Callback otherCallback;
 
+        static {
+            if (android.os.Build.VERSION.SDK_INT < 28) {
+                LAUNCH_ACTIVITY = ActivityThread.H.LAUNCH_ACTIVITY.get();
+            }
+        }
         private HCallbackStub() {
         }
 
@@ -106,12 +112,16 @@ private boolean handleLaunchActivity(Message msg) {
             IBinder token = ActivityThread.ActivityClientRecord.token.get(r);
             ActivityInfo info = saveInstance.info;
             if (VClientImpl.get().getToken() == null) {
+                InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(info.packageName, 0);
+                if(installedAppInfo == null){
+                    return true;
+                }
                 VActivityManager.get().processRestarted(info.packageName, info.processName, saveInstance.userId);
                 getH().sendMessageAtFrontOfQueue(Message.obtain(msg));
                 return false;
             }
             if (!VClientImpl.get().isBound()) {
-                VClientImpl.get().bindApplication(info.packageName, info.processName);
+                VClientImpl.get().bindApplicationForActivity(info.packageName, info.processName, intent);
                 getH().sendMessageAtFrontOfQueue(Message.obtain(msg));
                 return false;
             }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/MethodProxies.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/MethodProxies.java
index 2e870e897..8b8c89232 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/MethodProxies.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/MethodProxies.java
@@ -4,7 +4,8 @@
 import android.app.ActivityManager;
 import android.app.Application;
 import android.app.IServiceConnection;
-import android.app.PendingIntent;
+import android.app.Notification;
+import android.app.Service;
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
 import android.content.Intent;
@@ -19,38 +20,47 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.RemoteException;
+import android.provider.MediaStore;
 import android.text.TextUtils;
 import android.util.TypedValue;
 
+import com.lody.virtual.client.NativeEngine;
 import com.lody.virtual.client.VClientImpl;
+import com.lody.virtual.client.badger.BadgerManager;
 import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.env.Constants;
 import com.lody.virtual.client.env.SpecialComponentList;
 import com.lody.virtual.client.hook.base.MethodProxy;
+import com.lody.virtual.client.hook.base.ReplaceLastPkgMethodProxy;
 import com.lody.virtual.client.hook.delegate.TaskDescriptionDelegate;
 import com.lody.virtual.client.hook.providers.ProviderHook;
 import com.lody.virtual.client.hook.secondary.ServiceConnectionDelegate;
 import com.lody.virtual.client.hook.utils.MethodParameterUtils;
 import com.lody.virtual.client.ipc.ActivityClientRecord;
 import com.lody.virtual.client.ipc.VActivityManager;
+import com.lody.virtual.client.ipc.VNotificationManager;
 import com.lody.virtual.client.ipc.VPackageManager;
 import com.lody.virtual.client.stub.ChooserActivity;
-import com.lody.virtual.client.stub.StubManifest;
 import com.lody.virtual.client.stub.StubPendingActivity;
 import com.lody.virtual.client.stub.StubPendingReceiver;
 import com.lody.virtual.client.stub.StubPendingService;
+import com.lody.virtual.client.stub.VASettings;
 import com.lody.virtual.helper.compat.ActivityManagerCompat;
 import com.lody.virtual.helper.compat.BuildCompat;
 import com.lody.virtual.helper.utils.ArrayUtils;
 import com.lody.virtual.helper.utils.BitmapUtils;
 import com.lody.virtual.helper.utils.ComponentUtils;
 import com.lody.virtual.helper.utils.DrawableUtils;
+import com.lody.virtual.helper.utils.EncodeUtils;
+import com.lody.virtual.helper.utils.FileUtils;
+import com.lody.virtual.helper.utils.Reflect;
 import com.lody.virtual.helper.utils.VLog;
 import com.lody.virtual.os.VUserHandle;
 import com.lody.virtual.os.VUserInfo;
@@ -285,19 +295,16 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
             String[] resolvedTypes = (String[]) args[6];
             int type = (int) args[0];
             int flags = (int) args[7];
-            if ((PendingIntent.FLAG_UPDATE_CURRENT & flags) != 0) {
-                flags = (flags & ~(PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_NO_CREATE)) | PendingIntent.FLAG_CANCEL_CURRENT;
-            }
             if (args[5] instanceof Intent[]) {
                 Intent[] intents = (Intent[]) args[5];
-                if (intents.length > 0) {
-                    Intent intent = intents[intents.length - 1];
-                    if (resolvedTypes != null && resolvedTypes.length > 0) {
-                        intent.setDataAndType(intent.getData(), resolvedTypes[resolvedTypes.length - 1]);
+                for (int i = 0; i < intents.length; i++) {
+                    Intent intent = intents[i];
+                    if (resolvedTypes != null && i < resolvedTypes.length) {
+                        intent.setDataAndType(intent.getData(), resolvedTypes[i]);
                     }
                     Intent targetIntent = redirectIntentSender(type, creator, intent);
                     if (targetIntent != null) {
-                        args[5] = new Intent[]{targetIntent};
+                        intents[i] = targetIntent;
                     }
                 }
             }
@@ -354,10 +361,30 @@ public boolean isEnable() {
     }
 
 
+    static class OverridePendingTransition extends MethodProxy {
+
+        @Override
+        public String getMethodName() {
+            return "overridePendingTransition";
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            String packageName = (String) args[1];
+            if (Constants.WECHAT_PACKAGE.equals(packageName)) {
+                // 解决微信界面跳转狂闪的问题
+                return null;
+            } else {
+                return super.call(who, method, args);
+            }
+        }
+    }
+
     static class StartActivity extends MethodProxy {
 
         private static final String SCHEME_FILE = "file";
         private static final String SCHEME_PACKAGE = "package";
+        private static final String SCHEME_CONTENT = "content";
 
         @Override
         public String getMethodName() {
@@ -394,6 +421,10 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
                 if (handleUninstallRequest(intent)) {
                     return 0;
                 }
+            } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intent.getAction()) ||
+                    MediaStore.ACTION_VIDEO_CAPTURE.equals(intent.getAction()) ||
+                    MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(intent.getAction())) {
+                handleMediaCaptureRequest(intent);
             }
 
             String resultWho = null;
@@ -416,10 +447,15 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                 args[intentIndex - 1] = getHostPkg();
             }
+            if (intent.getScheme() != null && intent.getScheme().equals(SCHEME_PACKAGE) && intent.getData() != null) {
+                if (intent.getAction() != null && intent.getAction().startsWith("android.settings.")) {
+                    intent.setData(Uri.parse("package:" + getHostPkg()));
+                }
+            }
 
             ActivityInfo activityInfo = VirtualCore.get().resolveActivityInfo(intent, userId);
             if (activityInfo == null) {
-                VLog.e("VActivityManager", "Unable to resolve activityInfo : " + intent);
+                VLog.e("VActivityManager", "Unable to resolve activityInfo : %s", intent);
                 if (intent.getPackage() != null && isAppPkg(intent.getPackage())) {
                     return ActivityManagerCompat.START_INTENT_NOT_RESOLVED;
                 }
@@ -460,16 +496,13 @@ private boolean handleInstallRequest(Intent intent) {
             IAppRequestListener listener = VirtualCore.get().getAppRequestListener();
             if (listener != null) {
                 Uri packageUri = intent.getData();
-                if (SCHEME_FILE.equals(packageUri.getScheme())) {
-                    File sourceFile = new File(packageUri.getPath());
-                    try {
-                        listener.onRequestInstall(sourceFile.getPath());
-                        return true;
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
+                String sourcePath = FileUtils.getFileFromUri(getHostContext(), packageUri);
+                try {
+                    listener.onRequestInstall(sourcePath);
+                    return true;
+                } catch (RemoteException e) {
+                    e.printStackTrace();
                 }
-
             }
             return false;
         }
@@ -492,6 +525,21 @@ private boolean handleUninstallRequest(Intent intent) {
             return false;
         }
 
+        private void handleMediaCaptureRequest(Intent intent) {
+            Uri uri = intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
+            if (uri == null || !SCHEME_FILE.equals(uri.getScheme())) {
+                return;
+            }
+            String path = uri.getPath();
+            String newPath = NativeEngine.getRedirectedPath(path);
+            if (newPath == null) {
+                return;
+            }
+            File realFile = new File(newPath);
+            Uri newUri = Uri.fromFile(realFile);
+            intent.putExtra(MediaStore.EXTRA_OUTPUT, newUri);
+        }
+
     }
 
     static class StartActivities extends MethodProxy {
@@ -511,9 +559,13 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
                 token = (IBinder) args[tokenIndex];
             }
             Bundle options = ArrayUtils.getFirst(args, Bundle.class);
-
             return VActivityManager.get().startActivities(intents, resolvedTypes, token, options, VUserHandle.myUserId());
         }
+
+        @Override
+        public boolean isEnable() {
+            return isAppProcess();
+        }
     }
 
 
@@ -669,6 +721,37 @@ public String getMethodName() {
 
         @Override
         public Object call(Object who, Method method, Object... args) throws Throwable {
+            ComponentName component = (ComponentName) args[0];
+            IBinder token = (IBinder) args[1];
+            int id = (int) args[2];
+            Notification notification = (Notification) args[3];
+            boolean removeNotification = false;
+            if (args[4] instanceof Boolean) {
+                removeNotification = (boolean) args[4];
+            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && args[4] instanceof Integer) {
+                int flags = (int) args[4];
+                removeNotification = (flags & Service.STOP_FOREGROUND_REMOVE) != 0;
+            } else {
+                VLog.e(getClass().getSimpleName(), "Unknown flag : " + args[4]);
+            }
+            VNotificationManager.get().dealNotification(id, notification, getAppPkg());
+
+            /**
+             * `BaseStatusBar#updateNotification` aosp will use use
+             * `new StatusBarIcon(...notification.getSmallIcon()...)`
+             *  while in samsung SystemUI.apk ,the corresponding code comes as
+             * `new StatusBarIcon(...pkgName,notification.icon...)`
+             * the icon comes from `getSmallIcon.getResource`
+             * which will throw an exception on :x process thus crash the application
+             */
+            if (notification != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+                    (Build.BRAND.equalsIgnoreCase("samsung") || Build.MANUFACTURER.equalsIgnoreCase("samsung"))) {
+                notification.icon = getHostContext().getApplicationInfo().icon;
+                Icon icon = Icon.createWithResource(getHostPkg(), notification.icon);
+                Reflect.on(notification).call("setSmallIcon", icon);
+            }
+
+            VActivityManager.get().setServiceForeground(component, token, id, notification, removeNotification);
             return 0;
         }
 
@@ -824,6 +907,9 @@ && getHostPkg().equals(service.getComponent().getPackageName())) {
             service.setDataAndType(service.getData(), resolvedType);
             ServiceInfo serviceInfo = VirtualCore.get().resolveServiceInfo(service, VUserHandle.myUserId());
             if (serviceInfo != null) {
+                if (isFiltered(service)) {
+                    return service.getComponent();
+                }
                 return VActivityManager.get().startService(appThread, service, resolvedType, userId);
             }
             return method.invoke(who, args);
@@ -833,6 +919,16 @@ && getHostPkg().equals(service.getComponent().getPackageName())) {
         public boolean isEnable() {
             return isAppProcess() || isServerProcess();
         }
+
+        private boolean isFiltered(Intent service) {
+            // disable tinker.
+            if (service != null && service.getComponent() != null
+                    && EncodeUtils.decode("Y29tLnRlbmNlbnQudGlua2VyLmxpYi5zZXJ2aWMuVGlua2VyUGF0Y2hTZXJ2aWNl") // com.tencent.tinker.lib.service.TinkerPatchService
+                    .equals(service.getComponent().getClassName())) {
+                return true;
+            }
+            return false;
+        }
     }
 
     static class StartActivityAndWait extends StartActivity {
@@ -1271,7 +1367,7 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
                 if (targetVPid == -1) {
                     return null;
                 }
-                args[nameIdx] = StubManifest.getStubAuthority(targetVPid);
+                args[nameIdx] = VASettings.getStubAuthority(targetVPid);
                 Object holder = method.invoke(who, args);
                 if (holder == null) {
                     return null;
@@ -1463,18 +1559,48 @@ private Intent handleIntent(final Intent intent) {
             if ("android.intent.action.CREATE_SHORTCUT".equals(action)
                     || "com.android.launcher.action.INSTALL_SHORTCUT".equals(action)) {
 
-                return StubManifest.ENABLE_INNER_SHORTCUT ? handleInstallShortcutIntent(intent) : null;
+                return VASettings.ENABLE_INNER_SHORTCUT ? handleInstallShortcutIntent(intent) : null;
 
             } else if ("com.android.launcher.action.UNINSTALL_SHORTCUT".equals(action)) {
 
                 handleUninstallShortcutIntent(intent);
 
+            } else if (BadgerManager.handleBadger(intent)) {
+                return null;
+            } else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)) {
+                // intent send to system, do not modify it's action(may have other same intent)
+                return handleMediaScannerIntent(intent);
             } else {
                 return ComponentUtils.redirectBroadcastIntent(intent, VUserHandle.myUserId());
             }
             return intent;
         }
 
+        private Intent handleMediaScannerIntent(Intent intent) {
+            if (intent == null) {
+                return null;
+            }
+            Uri data = intent.getData();
+            if (data == null) {
+                return intent;
+            }
+            String scheme = data.getScheme();
+            if (!"file".equalsIgnoreCase(scheme)) {
+                return intent;
+            }
+            String path = data.getPath();
+            if (path == null) {
+                return intent;
+            }
+            String newPath = NativeEngine.getRedirectedPath(path);
+            File newFile = new File(newPath);
+            if (!newFile.exists()) {
+                return intent;
+            }
+            intent.setData(Uri.fromFile(newFile));
+            return intent;
+        }
+
         private Intent handleInstallShortcutIntent(Intent intent) {
             Intent shortcut = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
             if (shortcut != null) {
@@ -1494,16 +1620,14 @@ private Intent handleInstallShortcutIntent(Intent intent) {
                     if (icon != null && !TextUtils.equals(icon.packageName, getHostPkg())) {
                         try {
                             Resources resources = VirtualCore.get().getResources(pkg);
-                            if (resources != null) {
-                                int resId = resources.getIdentifier(icon.resourceName, "drawable", pkg);
-                                if (resId > 0) {
-                                    //noinspection deprecation
-                                    Drawable iconDrawable = resources.getDrawable(resId);
-                                    Bitmap newIcon = BitmapUtils.drawableToBitmap(iconDrawable);
-                                    if (newIcon != null) {
-                                        intent.removeExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
-                                        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, newIcon);
-                                    }
+                            int resId = resources.getIdentifier(icon.resourceName, "drawable", pkg);
+                            if (resId > 0) {
+                                //noinspection deprecation
+                                Drawable iconDrawable = resources.getDrawable(resId);
+                                Bitmap newIcon = BitmapUtils.drawableToBitmap(iconDrawable);
+                                if (newIcon != null) {
+                                    intent.removeExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+                                    intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, newIcon);
                                 }
                             }
                         } catch (Throwable e) {
@@ -1601,4 +1725,10 @@ public boolean isEnable() {
             return isAppProcess();
         }
     }
+
+    static class GetPackageProcessState extends ReplaceLastPkgMethodProxy {
+        public GetPackageProcessState() {
+            super("getPackageProcessState");
+        }
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/TransactionHandlerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/TransactionHandlerStub.java
new file mode 100644
index 000000000..bb3dd3362
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/am/TransactionHandlerStub.java
@@ -0,0 +1,39 @@
+package com.lody.virtual.client.hook.proxies.am;
+
+import android.app.ClientTransactionHandler;
+import android.app.TransactionHandlerProxy;
+import android.util.Log;
+
+import com.lody.virtual.client.interfaces.IInjector;
+
+import java.lang.reflect.Field;
+
+import mirror.android.app.ActivityThread;
+
+/**
+ * @author weishu
+ * @date 2018/8/7.
+ */
+public class TransactionHandlerStub implements IInjector {
+    private static final String TAG = "TransactionHandlerStub";
+
+    @Override
+    public void inject() throws Throwable {
+        Log.i(TAG, "inject transaction handler.");
+        Object activityThread = ActivityThread.currentActivityThread.call();
+        Object transactionExecutor = ActivityThread.mTransactionExecutor.get(activityThread);
+
+        Field mTransactionHandlerField = transactionExecutor.getClass().getDeclaredField("mTransactionHandler");
+        mTransactionHandlerField.setAccessible(true);
+        ClientTransactionHandler original = (ClientTransactionHandler) mTransactionHandlerField.get(transactionExecutor);
+        TransactionHandlerProxy proxy = new TransactionHandlerProxy(original);
+
+        mTransactionHandlerField.set(transactionExecutor, proxy);
+        Log.i(TAG, "executor's handler: " + mTransactionHandlerField.get(transactionExecutor));
+    }
+
+    @Override
+    public boolean isEnvBad() {
+        return false;
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/appops/AppOpsManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/appops/AppOpsManagerStub.java
index d46cee55c..b86168426 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/appops/AppOpsManagerStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/appops/AppOpsManagerStub.java
@@ -5,6 +5,7 @@
 import android.os.Build;
 
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.client.hook.base.MethodProxy;
 import com.lody.virtual.client.hook.base.ReplaceLastPkgMethodProxy;
 import com.lody.virtual.client.hook.base.StaticMethodProxy;
 
@@ -14,54 +15,63 @@
 
 /**
  * @author Lody
- *
- * Fuck the AppOpsService.
- *
+ *         <p>
+ *         Fuck the AppOpsService.
  * @see android.app.AppOpsManager
  */
 @TargetApi(Build.VERSION_CODES.KITKAT)
 public class AppOpsManagerStub extends BinderInvocationProxy {
 
-	public AppOpsManagerStub() {
-		super(IAppOpsService.Stub.asInterface, Context.APP_OPS_SERVICE);
-	}
+    public AppOpsManagerStub() {
+        super(IAppOpsService.Stub.asInterface, Context.APP_OPS_SERVICE);
+    }
 
-	@Override
-	protected void onBindMethods() {
-		super.onBindMethods();
-		addMethodProxy(new BaseMethodProxy("checkOperation", 1, 2));
-		addMethodProxy(new BaseMethodProxy("noteOperation", 1, 2));
-		addMethodProxy(new BaseMethodProxy("startOperation", 2, 3));
-		addMethodProxy(new BaseMethodProxy("finishOperation", 2, 3));
-		addMethodProxy(new BaseMethodProxy("startWatchingMode", -1, 1));
-		addMethodProxy(new BaseMethodProxy("checkPackage", 0, 1));
-		addMethodProxy(new BaseMethodProxy("getOpsForPackage", 0, 1));
-		addMethodProxy(new BaseMethodProxy("setMode", 1, 2));
-		addMethodProxy(new BaseMethodProxy("checkAudioOperation", 2, 3));
-		addMethodProxy(new BaseMethodProxy("setAudioRestriction", 2, -1));
-		addMethodProxy(new BaseMethodProxy("noteProxyOperation", 2, 3));
-		addMethodProxy(new ReplaceLastPkgMethodProxy("resetAllModes"));
-	}
+    @Override
+    protected void onBindMethods() {
+        super.onBindMethods();
+        addMethodProxy(new BaseMethodProxy("checkOperation", 1, 2));
+        addMethodProxy(new BaseMethodProxy("noteOperation", 1, 2));
+        addMethodProxy(new BaseMethodProxy("startOperation", 2, 3));
+        addMethodProxy(new BaseMethodProxy("finishOperation", 2, 3));
+        addMethodProxy(new BaseMethodProxy("startWatchingMode", -1, 1));
+        addMethodProxy(new BaseMethodProxy("checkPackage", 0, 1));
+        addMethodProxy(new BaseMethodProxy("getOpsForPackage", 0, 1));
+        addMethodProxy(new BaseMethodProxy("setMode", 1, 2));
+        addMethodProxy(new BaseMethodProxy("checkAudioOperation", 2, 3));
+        addMethodProxy(new BaseMethodProxy("setAudioRestriction", 2, -1));
+        addMethodProxy(new ReplaceLastPkgMethodProxy("resetAllModes"));
+        addMethodProxy(new MethodProxy() {
+            @Override
+            public String getMethodName() {
+                return "noteProxyOperation";
+            }
 
-	private class BaseMethodProxy extends StaticMethodProxy {
-		final int pkgIndex;
-		final int uidIndex;
+            @Override
+            public Object call(Object who, Method method, Object... args) throws Throwable {
+                return 0;
+            }
+        });
+    }
 
-		BaseMethodProxy(String name, int uidIndex, int pkgIndex) {
-			super(name);
-			this.pkgIndex = pkgIndex;
-			this.uidIndex = uidIndex;
-		}
+    private class BaseMethodProxy extends StaticMethodProxy {
+        final int pkgIndex;
+        final int uidIndex;
 
-		@Override
-		public boolean beforeCall(Object who, Method method, Object... args) {
-			if (pkgIndex != -1 && args.length > pkgIndex && args[pkgIndex] instanceof String) {
-				args[pkgIndex] = getHostPkg();
-			}
-			if (uidIndex != -1 && args[uidIndex] instanceof Integer) {
-				args[uidIndex] = getRealUid();
-			}
-			return true;
-		}
-	}
+        BaseMethodProxy(String name, int uidIndex, int pkgIndex) {
+            super(name);
+            this.pkgIndex = pkgIndex;
+            this.uidIndex = uidIndex;
+        }
+
+        @Override
+        public boolean beforeCall(Object who, Method method, Object... args) {
+            if (pkgIndex != -1 && args.length > pkgIndex && args[pkgIndex] instanceof String) {
+                args[pkgIndex] = getHostPkg();
+            }
+            if (uidIndex != -1 && args[uidIndex] instanceof Integer) {
+                args[uidIndex] = getRealUid();
+            }
+            return true;
+        }
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/bluetooth/BluetoothStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/bluetooth/BluetoothStub.java
index 22102a6a6..9590cab43 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/bluetooth/BluetoothStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/bluetooth/BluetoothStub.java
@@ -2,9 +2,9 @@
 
 import android.os.Build;
 
-import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
 import com.lody.virtual.client.hook.base.StaticMethodProxy;
+import com.lody.virtual.helper.utils.marks.FakeDeviceMark;
 
 import java.lang.reflect.Method;
 
@@ -28,6 +28,7 @@ protected void onBindMethods() {
         addMethodProxy(new GetAddress());
     }
 
+    @FakeDeviceMark("fake MAC")
     private static class GetAddress extends StaticMethodProxy {
 
         GetAddress() {
@@ -35,14 +36,8 @@ private static class GetAddress extends StaticMethodProxy {
         }
 
         @Override
-        public Object afterCall(Object who, Method method, Object[] args, Object result) throws Throwable {
-            if (VirtualCore.get().getPhoneInfoDelegate() != null) {
-                String res = VirtualCore.get().getPhoneInfoDelegate().getBluetoothAddress((String) result, getAppUserId());
-                if (res != null) {
-                    return res;
-                }
-            }
-            return super.afterCall(who, method, args, result);
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            return getDeviceInfo().bluetoothMac;
         }
     }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/clipboard/ClipBoardStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/clipboard/ClipBoardStub.java
index 7744b3276..c94ca87f5 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/clipboard/ClipBoardStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/clipboard/ClipBoardStub.java
@@ -8,6 +8,7 @@
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
 import com.lody.virtual.client.hook.base.ReplaceLastPkgMethodProxy;
 import com.lody.virtual.helper.compat.BuildCompat;
+import com.lody.virtual.helper.utils.DeviceUtil;
 
 import mirror.android.content.ClipboardManager;
 import mirror.android.content.ClipboardManagerOreo;
@@ -23,7 +24,7 @@ public ClipBoardStub() {
     }
 
     private static IInterface getInterface() {
-        if (BuildCompat.isOreo()) {
+        if (isOreo()) {
             android.content.ClipboardManager cm = (android.content.ClipboardManager)
                     VirtualCore.get().getContext().getSystemService(Context.CLIPBOARD_SERVICE);
             return ClipboardManagerOreo.mService.get(cm);
@@ -49,7 +50,7 @@ protected void onBindMethods() {
     @Override
     public void inject() throws Throwable {
         super.inject();
-        if (BuildCompat.isOreo()) {
+        if (isOreo()) {
             android.content.ClipboardManager cm = (android.content.ClipboardManager)
                     VirtualCore.get().getContext().getSystemService(Context.CLIPBOARD_SERVICE);
             ClipboardManagerOreo.mService.set(cm, getInvocationStub().getProxyInterface());
@@ -57,4 +58,10 @@ public void inject() throws Throwable {
             ClipboardManager.sService.set(getInvocationStub().getProxyInterface());
         }
     }
+
+    private static boolean isOreo() {
+        return BuildCompat.isOreo() &&
+                !DeviceUtil.isSamsung()
+                || ClipboardManager.getService == null;
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/connectivity/ConnectivityStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/connectivity/ConnectivityStub.java
index 1a81fef3c..116a53ea6 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/connectivity/ConnectivityStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/connectivity/ConnectivityStub.java
@@ -3,7 +3,12 @@
 import android.content.Context;
 
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.client.hook.base.MethodProxy;
 import com.lody.virtual.client.hook.base.ReplaceLastPkgMethodProxy;
+import com.lody.virtual.client.hook.base.StaticMethodProxy;
+import com.lody.virtual.client.ipc.ServiceManagerNative;
+
+import java.lang.reflect.Method;
 
 import mirror.android.net.IConnectivityManager;
 
@@ -19,6 +24,5 @@ public ConnectivityStub() {
     @Override
     protected void onBindMethods() {
         super.onBindMethods();
-        addMethodProxy(new ReplaceLastPkgMethodProxy("getActiveNetworkInfo"));
     }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/content/ContentServiceStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/content/ContentServiceStub.java
index a0b876237..d2c7dabd0 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/content/ContentServiceStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/content/ContentServiceStub.java
@@ -1,15 +1,15 @@
 package com.lody.virtual.client.hook.proxies.content;
 
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.client.hook.base.Inject;
 
 import mirror.android.content.IContentService;
 
 /**
  * @author Lody
- *
  * @see IContentService
  */
-
+@Inject(MethodProxies.class)
 public class ContentServiceStub extends BinderInvocationProxy {
 
     public ContentServiceStub() {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/content/MethodProxies.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/content/MethodProxies.java
new file mode 100644
index 000000000..2cf30245e
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/content/MethodProxies.java
@@ -0,0 +1,72 @@
+package com.lody.virtual.client.hook.proxies.content;
+
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+
+import com.lody.virtual.client.VClientImpl;
+import com.lody.virtual.client.hook.base.MethodProxy;
+
+import java.lang.reflect.Method;
+
+/**
+ * author: weishu on 18/3/13.
+ */
+class MethodProxies {
+
+    static class NotifyChange extends MethodProxy {
+
+        @Override
+        public String getMethodName() {
+            return "notifyChange";
+        }
+
+        @Override
+        public boolean beforeCall(Object who, Method method, Object... args) {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+                return super.beforeCall(who, method, args);
+            }
+            ApplicationInfo currentApplicationInfo = VClientImpl.get().getCurrentApplicationInfo();
+            if (currentApplicationInfo == null) {
+                return super.beforeCall(who, method, args);
+            }
+            int targetSdkVersion = currentApplicationInfo.targetSdkVersion;
+
+            int length = args.length;
+            int index = -1;
+            for (int i = 0; i < length; i++) {
+                Object obj = args[length - 1];
+                if (obj != null && obj.getClass() == Integer.class) {
+                    if ((int) obj == targetSdkVersion) {
+                        index = i;
+                    }
+                }
+            }
+            /*
+            In ContentService, it contains this code:
+
+            if (targetSdkVersion >= Build.VERSION_CODES.O) {
+                throw new SecurityException(msg);
+            } else {
+                if (msg.startsWith("Failed to find provider")) {
+                    // Sigh, we need to quietly let apps targeting older API
+                    // levels notify on non-existent providers.
+                } else {
+                    Log.w(TAG, "Ignoring notify for " + uri + " from " + uid + ": " + msg);
+                    return;
+                }
+            }
+            we just modify the targetSdkVersion dynamic to fake it.
+            */
+            if (index != -1) {
+                args[index] = Build.VERSION_CODES.N_MR1;
+            }
+
+            return super.beforeCall(who, method, args);
+        }
+
+        @Override
+        public boolean isEnable() {
+            return isAppProcess();
+        }
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/context_hub/ContextHubServiceStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/context_hub/ContextHubServiceStub.java
index eae9e781d..afcaf2760 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/context_hub/ContextHubServiceStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/context_hub/ContextHubServiceStub.java
@@ -1,5 +1,7 @@
 package com.lody.virtual.client.hook.proxies.context_hub;
 
+import android.os.Build;
+
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
 import com.lody.virtual.client.hook.base.ResultStaticMethodProxy;
 
@@ -8,7 +10,11 @@
 public class ContextHubServiceStub extends BinderInvocationProxy {
 
     public ContextHubServiceStub() {
-        super(IContextHubService.Stub.asInterface, "contexthub_service");
+        super(IContextHubService.Stub.asInterface, getServiceName());
+    }
+
+    private static String getServiceName() {
+        return Build.VERSION.SDK_INT >= 26 ? "contexthub" : "contexthub_service";
     }
 
     @Override
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/devicepolicy/DevicePolicyManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/devicepolicy/DevicePolicyManagerStub.java
new file mode 100644
index 000000000..802e9050c
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/devicepolicy/DevicePolicyManagerStub.java
@@ -0,0 +1,42 @@
+package com.lody.virtual.client.hook.proxies.devicepolicy;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.client.hook.base.MethodProxy;
+
+import java.lang.reflect.Method;
+
+import mirror.android.app.admin.IDevicePolicyManager;
+
+/**
+ * Created by wy on 2017/10/20.
+ */
+
+public class DevicePolicyManagerStub extends BinderInvocationProxy{
+    public DevicePolicyManagerStub() {
+        super(IDevicePolicyManager.Stub.asInterface, Context.DEVICE_POLICY_SERVICE);
+    }
+
+    @Override
+    protected void onBindMethods() {
+        super.onBindMethods();
+        addMethodProxy(new GetStorageEncryptionStatus());
+    }
+
+    private static class GetStorageEncryptionStatus extends MethodProxy {
+
+        @Override
+        public String getMethodName() {
+            return "getStorageEncryptionStatus";
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            args[0] = VirtualCore.get().getHostPkg();
+            return method.invoke(who, args);
+        }
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/fingerprint/FingerprintManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/fingerprint/FingerprintManagerStub.java
new file mode 100644
index 000000000..7e90799d8
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/fingerprint/FingerprintManagerStub.java
@@ -0,0 +1,31 @@
+package com.lody.virtual.client.hook.proxies.fingerprint;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+
+import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.client.hook.base.ReplaceLastPkgMethodProxy;
+
+import mirror.android.hardware.fingerprint.IFingerprintService;
+
+/**
+ * Created by natsuki on 12/10/2017.
+ */
+
+@TargetApi(Build.VERSION_CODES.M)
+public class FingerprintManagerStub extends BinderInvocationProxy {
+    public FingerprintManagerStub() {
+        super(IFingerprintService.Stub.asInterface, Context.FINGERPRINT_SERVICE);
+    }
+
+    @Override
+    protected void onBindMethods() {
+        addMethodProxy(new ReplaceLastPkgMethodProxy("isHardwareDetected"));
+        addMethodProxy(new ReplaceLastPkgMethodProxy("hasEnrolledFingerprints"));
+        addMethodProxy(new ReplaceLastPkgMethodProxy("authenticate"));
+        addMethodProxy(new ReplaceLastPkgMethodProxy("cancelAuthentication"));
+        addMethodProxy(new ReplaceLastPkgMethodProxy("getEnrolledFingerprints"));
+        addMethodProxy(new ReplaceLastPkgMethodProxy("getAuthenticatorId"));
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/input/MethodProxies.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/input/MethodProxies.java
index ddef30dd2..81e47a07b 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/input/MethodProxies.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/input/MethodProxies.java
@@ -13,48 +13,40 @@
 
 class MethodProxies {
 
-    static class StartInput extends MethodProxy {
+    static class StartInput extends StartInputOrWindowGainedFocus {
 
         @Override
         public String getMethodName() {
             return "startInput";
         }
+    }
+
+    static class WindowGainedFocus extends StartInputOrWindowGainedFocus {
 
         @Override
-        public Object call(Object who, Method method, Object... args) throws Throwable {
-            if (args.length > 2 && args[2] instanceof EditorInfo) {
-                EditorInfo attribute = (EditorInfo) args[2];
-                attribute.packageName = getHostPkg();
-            }
-            return method.invoke(who, args);
+        public String getMethodName() {
+            return "windowGainedFocus";
         }
 
+
     }
 
-    static class WindowGainedFocus extends MethodProxy {
+    static class StartInputOrWindowGainedFocus extends MethodProxy {
 
-        private Boolean noEditorInfo = null;
-        private int editorInfoIndex = -1;
 
         @Override
         public String getMethodName() {
-            return "windowGainedFocus";
+            return "startInputOrWindowGainedFocus";
         }
 
         @Override
         public Object call(Object who, Method method, Object... args) throws Throwable {
-            if (noEditorInfo == null) {
-                editorInfoIndex = ArrayUtils.indexOfFirst(args, EditorInfo.class);
-                noEditorInfo = editorInfoIndex == -1;
-            }
-            if (!noEditorInfo) {
+            int editorInfoIndex = ArrayUtils.indexOfFirst(args, EditorInfo.class);
+            if (editorInfoIndex != -1) {
                 EditorInfo attribute = (EditorInfo) args[editorInfoIndex];
-                if (attribute != null) {
-                    attribute.packageName = getHostPkg();
-                }
+                attribute.packageName = getHostPkg();
             }
             return method.invoke(who, args);
         }
-
     }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/job/JobServiceStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/job/JobServiceStub.java
index 2682c4032..7e947cef9 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/job/JobServiceStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/job/JobServiceStub.java
@@ -3,15 +3,18 @@
 import android.annotation.TargetApi;
 import android.app.job.JobInfo;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Build;
 
-import com.lody.virtual.client.hook.base.MethodProxy;
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.client.hook.base.MethodProxy;
 import com.lody.virtual.client.ipc.VJobScheduler;
+import com.lody.virtual.helper.utils.ComponentUtils;
 
 import java.lang.reflect.Method;
 
 import mirror.android.app.job.IJobScheduler;
+import mirror.android.app.job.JobWorkItem;
 
 /**
  * @author Lody
@@ -32,8 +35,51 @@ protected void onBindMethods() {
 		addMethodProxy(new getAllPendingJobs());
 		addMethodProxy(new cancelAll());
 		addMethodProxy(new cancel());
+
+		if (Build.VERSION.SDK_INT >= 24) {
+			addMethodProxy(new getPendingJob());
+		}
+		if (Build.VERSION.SDK_INT >= 26) {
+			addMethodProxy(new enqueue());
+		}
 	}
 
+	private class getPendingJob extends MethodProxy {
+		private getPendingJob() {
+		}
+		public Object call(Object who, Method method, Object... args) throws Throwable {
+			return VJobScheduler.get().getPendingJob((Integer) args[0]);
+		}
+		public String getMethodName() {
+			return "getPendingJob";
+		}
+	}
+
+	private class enqueue extends MethodProxy {
+		private enqueue() {
+		}
+		public Object call(Object who, Method method, Object... args) throws Throwable {
+			return VJobScheduler.get().enqueue(
+					(JobInfo) args[0],
+					JobServiceStub.this.redirect(args[1], MethodProxy.getAppPkg())
+			);
+		}
+		public String getMethodName() {
+			return "enqueue";
+		}
+	}
+
+	private Object redirect(Object item, String pkg) {
+		if (item == null) {
+			return null;
+		}
+		Intent redirectIntentSender = ComponentUtils.redirectIntentSender(4, pkg, (Intent) JobWorkItem.getIntent.call(item, new Object[0]), null);
+		Object newInstance = JobWorkItem.ctor.newInstance(redirectIntentSender);
+		JobWorkItem.mWorkId.set(newInstance, JobWorkItem.mWorkId.get(item));
+		JobWorkItem.mGrants.set(newInstance, JobWorkItem.mGrants.get(item));
+		JobWorkItem.mDeliveryCount.set(newInstance, JobWorkItem.mDeliveryCount.get(item));
+		return newInstance;
+	}
 
 	private class schedule extends MethodProxy {
 
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/GPSListenerThread.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/GPSListenerThread.java
new file mode 100644
index 000000000..1f8554274
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/GPSListenerThread.java
@@ -0,0 +1,161 @@
+package com.lody.virtual.client.hook.proxies.location;
+
+import android.location.Location;
+import android.os.Build.VERSION;
+import android.os.Handler;
+
+import com.lody.virtual.client.ipc.VirtualLocationManager;
+import com.lody.virtual.remote.vloc.VLocation;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import mirror.android.location.LocationManager;
+
+
+public class GPSListenerThread extends TimerTask {
+    private static GPSListenerThread INSTANCE;
+    private Handler handler = new Handler();
+    private boolean isRunning = false;
+    private HashMap<Object, Long> listeners = new HashMap<>();
+    private Timer timer = new Timer();
+
+    static {
+        INSTANCE = new GPSListenerThread();
+    }
+
+    private void notifyGPSStatus(Map listeners) {
+        if (listeners != null && !listeners.isEmpty()) {
+            //noinspection unchecked
+            Set<Map.Entry> entries = listeners.entrySet();
+            for (Map.Entry entry : entries) {
+                try {
+                    Object value = entry.getValue();
+                    if (value != null) {
+                        MockLocationHelper.invokeSvStatusChanged(value);
+                    }
+                } catch (Throwable e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    private void notifyLocation(Map listeners) {
+        if (listeners != null) {
+            try {
+                if (!listeners.isEmpty()) {
+                    VLocation vLocation = VirtualLocationManager.get().getLocation();
+                    if (vLocation != null) {
+                        Location location = vLocation.toSysLocation();
+                        //noinspection unchecked
+                        Set<Map.Entry> entries = listeners.entrySet();
+                        for (Map.Entry entry : entries) {
+                            Object value = entry.getValue();
+                            if (value != null) {
+                                try {
+                                    LocationManager.ListenerTransport.onLocationChanged.call(value, location);
+                                } catch (Throwable e) {
+                                    e.printStackTrace();
+                                }
+                            }
+                        }
+                    }
+                }
+            } catch (Throwable e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void notifyMNmeaListener(Map listeners) {
+        if (listeners != null && !listeners.isEmpty()) {
+            //noinspection unchecked
+            Set<Map.Entry> entries = listeners.entrySet();
+            for (Map.Entry entry : entries) {
+                try {
+                    Object value = entry.getValue();
+                    if (value != null) {
+                        MockLocationHelper.invokeNmeaReceived(value);
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    public void addListenerTransport(Object transport) {
+        this.listeners.put(transport, System.currentTimeMillis());
+        if (!isRunning) {
+            synchronized (this) {
+                if (!isRunning) {
+                    isRunning = true;
+                    timer.schedule(this, 1000, 1000);
+                }
+            }
+        }
+    }
+
+    public void removeListenerTransport(Object transport) {
+        if (transport != null) {
+            listeners.remove(transport);
+        }
+    }
+
+    public void run() {
+        if (!listeners.isEmpty()) {
+            if (VirtualLocationManager.get().getMode() == VirtualLocationManager.MODE_CLOSE) {
+                listeners.clear();
+                return;
+            }
+            for (Map.Entry entry : this.listeners.entrySet()) {
+                try {
+                    Object transport = entry.getKey();
+                    Map gpsStatusListeners;
+                    if (VERSION.SDK_INT >= 24) {
+                        Map nmeaListeners = LocationManager.mGnssNmeaListeners.get(transport);
+                        notifyGPSStatus(LocationManager.mGnssStatusListeners.get(transport));
+                        notifyMNmeaListener(nmeaListeners);
+                        gpsStatusListeners = LocationManager.mGpsStatusListeners.get(transport);
+                        notifyGPSStatus(gpsStatusListeners);
+                        notifyMNmeaListener(LocationManager.mGpsNmeaListeners.get(transport));
+                    } else {
+                        gpsStatusListeners = LocationManager.mGpsStatusListeners.get(transport);
+                        notifyGPSStatus(gpsStatusListeners);
+                        notifyMNmeaListener(LocationManager.mNmeaListeners.get(transport));
+                    }
+                    final Map listeners = LocationManager.mListeners.get(transport);
+                    if (gpsStatusListeners != null && !gpsStatusListeners.isEmpty()) {
+                        if (listeners == null || listeners.isEmpty()) {
+                            // listeners not ready
+                            handler.postDelayed(new Runnable() {
+                                public void run() {
+                                    GPSListenerThread.this.notifyLocation(listeners);
+                                }
+                            }, 100);
+                        } else {
+                            notifyLocation(listeners);
+                        }
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    public void stop() {
+        this.timer.cancel();
+    }
+
+    public static GPSListenerThread get() {
+        return INSTANCE;
+    }
+
+    private GPSListenerThread() {
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/GPSStatusListenerThread.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/GPSStatusListenerThread.java
new file mode 100644
index 000000000..27779a47c
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/GPSStatusListenerThread.java
@@ -0,0 +1,61 @@
+package com.lody.virtual.client.hook.proxies.location;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class GPSStatusListenerThread extends TimerTask {
+    private static GPSStatusListenerThread INSTANCE;
+    private boolean isRunning = false;
+    private Map<Object, Long> listeners = new HashMap<>();
+    private Timer timer = new Timer();
+
+    static {
+        INSTANCE = new GPSStatusListenerThread();
+    }
+
+    public void addListenerTransport(Object transport) {
+        if (!isRunning) {
+            synchronized (this) {
+                if (!isRunning) {
+                    isRunning = true;
+                    timer.schedule(this, 100, 800);
+                }
+            }
+        }
+        listeners.put(transport, System.currentTimeMillis());
+    }
+
+    public void removeListenerTransport(Object obj) {
+        if (obj != null) {
+            listeners.remove(obj);
+        }
+    }
+
+    public void run() {
+        if (!listeners.isEmpty()) {
+            for (Entry entry : listeners.entrySet()) {
+                try {
+                    Object transport = entry.getKey();
+                    MockLocationHelper.invokeSvStatusChanged(transport);
+                    MockLocationHelper.invokeNmeaReceived(transport);
+                } catch (Throwable e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    public void stop() {
+        timer.cancel();
+    }
+
+    public static GPSStatusListenerThread get() {
+        return INSTANCE;
+    }
+
+    private GPSStatusListenerThread() {
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/LocationManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/LocationManagerStub.java
index 6aac7935a..36c4e6cec 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/LocationManagerStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/LocationManagerStub.java
@@ -5,80 +5,74 @@
 import android.text.TextUtils;
 
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.client.hook.base.Inject;
+import com.lody.virtual.client.hook.base.LogInvocation;
 import com.lody.virtual.client.hook.base.ReplaceLastPkgMethodProxy;
+import com.lody.virtual.client.stub.VASettings;
 
 import java.lang.reflect.Method;
 
 import mirror.android.location.ILocationManager;
-import mirror.android.location.LocationRequestL;
 
 /**
  * @author Lody
- *
  * @see android.location.LocationManager
  */
+@LogInvocation(LogInvocation.Condition.ALWAYS)
+@Inject(MethodProxies.class)
 public class LocationManagerStub extends BinderInvocationProxy {
-	public LocationManagerStub() {
-		super(ILocationManager.Stub.asInterface, Context.LOCATION_SERVICE);
-	}
+    public LocationManagerStub() {
+        super(ILocationManager.Stub.asInterface, Context.LOCATION_SERVICE);
+    }
 
-	private static class BaseMethodProxy extends ReplaceLastPkgMethodProxy {
+    @Override
+    protected void onBindMethods() {
+        super.onBindMethods();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            addMethodProxy(new ReplaceLastPkgMethodProxy("addTestProvider"));
+            addMethodProxy(new ReplaceLastPkgMethodProxy("removeTestProvider"));
+            addMethodProxy(new ReplaceLastPkgMethodProxy("setTestProviderLocation"));
+            addMethodProxy(new ReplaceLastPkgMethodProxy("clearTestProviderLocation"));
+            addMethodProxy(new ReplaceLastPkgMethodProxy("setTestProviderEnabled"));
+            addMethodProxy(new ReplaceLastPkgMethodProxy("clearTestProviderEnabled"));
+            addMethodProxy(new ReplaceLastPkgMethodProxy("setTestProviderStatus"));
+            addMethodProxy(new ReplaceLastPkgMethodProxy("clearTestProviderStatus"));
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            addMethodProxy(new FakeReplaceLastPkgMethodProxy("addGpsMeasurementsListener", true));
+            addMethodProxy(new FakeReplaceLastPkgMethodProxy("addGpsNavigationMessageListener", true));
+            addMethodProxy(new FakeReplaceLastPkgMethodProxy("removeGpsMeasurementListener", 0));
+            addMethodProxy(new FakeReplaceLastPkgMethodProxy("removeGpsNavigationMessageListener", 0));
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            addMethodProxy(new FakeReplaceLastPkgMethodProxy("requestGeofence", 0));
+            addMethodProxy(new FakeReplaceLastPkgMethodProxy("removeGeofence", 0));
+        }
 
-		public BaseMethodProxy(String name) {
-			super(name);
-		}
-		@Override
-		public Object call(Object who, Method method, Object... args) throws Throwable {
-			if (args.length > 0) {
-				Object request = args[0];
-				if (LocationRequestL.mHideFromAppOps != null) {
-					LocationRequestL.mHideFromAppOps.set(request, false);
-				}
-				if (LocationRequestL.mWorkSource != null) {
-					LocationRequestL.mWorkSource.set(request, null);
-				}
-			}
-			return super.call(who, method, args);
-		}
-	}
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
+            addMethodProxy(new FakeReplaceLastPkgMethodProxy("addProximityAlert", 0));
+        }
 
-	@Override
-	protected void onBindMethods() {
-		super.onBindMethods();
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-			addMethodProxy(new ReplaceLastPkgMethodProxy("addTestProvider"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("removeTestProvider"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("setTestProviderLocation"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("clearTestProviderLocation"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("setTestProviderEnabled"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("clearTestProviderEnabled"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("setTestProviderStatus"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("clearTestProviderStatus"));
-		}
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-			addMethodProxy(new ReplaceLastPkgMethodProxy("addGpsMeasurementsListener"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("addGpsNavigationMessageListener"));
-		}
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            addMethodProxy(new FakeReplaceLastPkgMethodProxy("addNmeaListener", 0));
+            addMethodProxy(new FakeReplaceLastPkgMethodProxy("removeNmeaListener", 0));
+        }
+    }
 
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
-			addMethodProxy(new ReplaceLastPkgMethodProxy("addGpsStatusListener"));
-		}
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-			addMethodProxy(new BaseMethodProxy("requestLocationUpdates"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("removeUpdates"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("requestGeofence"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("removeGeofence"));
-			addMethodProxy(new BaseMethodProxy("getLastLocation"));
-		}
+    private static class FakeReplaceLastPkgMethodProxy extends ReplaceLastPkgMethodProxy {
+        private Object mDefValue;
 
-		if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN
-				&& TextUtils.equals(Build.VERSION.RELEASE, "4.1.2")) {
-			addMethodProxy(new ReplaceLastPkgMethodProxy("requestLocationUpdates"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("requestLocationUpdatesPI"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("removeUpdates"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("removeUpdatesPI"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("addProximityAlert"));
-			addMethodProxy(new ReplaceLastPkgMethodProxy("getLastKnownLocation"));
-		}
-	}
+        private FakeReplaceLastPkgMethodProxy(String name, Object def) {
+            super(name);
+            mDefValue = def;
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                return mDefValue;
+            }
+            return super.call(who, method, args);
+        }
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/MethodProxies.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/MethodProxies.java
new file mode 100644
index 000000000..9edefbb3e
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/MethodProxies.java
@@ -0,0 +1,314 @@
+package com.lody.virtual.client.hook.proxies.location;
+
+import android.location.LocationManager;
+import android.location.LocationRequest;
+import android.os.Build;
+
+import com.lody.virtual.client.hook.base.MethodProxy;
+import com.lody.virtual.client.hook.base.ReplaceLastPkgMethodProxy;
+import com.lody.virtual.client.ipc.VirtualLocationManager;
+import com.lody.virtual.helper.utils.ArrayUtils;
+import com.lody.virtual.helper.utils.Reflect;
+import com.lody.virtual.remote.vloc.VLocation;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+import mirror.android.location.LocationRequestL;
+
+/**
+ * @author Lody
+ */
+@SuppressWarnings("ALL")
+public class MethodProxies {
+
+    private static void fixLocationRequest(LocationRequest request) {
+        if (request != null) {
+            if (LocationRequestL.mHideFromAppOps != null) {
+                LocationRequestL.mHideFromAppOps.set(request, false);
+            }
+            if (LocationRequestL.mWorkSource != null) {
+                LocationRequestL.mWorkSource.set(request, null);
+            }
+        }
+    }
+
+    static class AddGpsStatusListener extends ReplaceLastPkgMethodProxy {
+
+        public AddGpsStatusListener() {
+            super("addGpsStatusListener");
+        }
+
+        public AddGpsStatusListener(String name) {
+            super(name);
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                Object transport = ArrayUtils.getFirst(args, mirror.android.location.LocationManager.GpsStatusListenerTransport.TYPE);
+                Object locationManager = mirror.android.location.LocationManager.GpsStatusListenerTransport.this$0.get(transport);
+                mirror.android.location.LocationManager.GpsStatusListenerTransport.onGpsStarted.call(transport);
+                mirror.android.location.LocationManager.GpsStatusListenerTransport.onFirstFix.call(transport, 0);
+                if (mirror.android.location.LocationManager.GpsStatusListenerTransport.mListener.get(transport) != null) {
+                    MockLocationHelper.invokeSvStatusChanged(transport);
+                } else {
+                    MockLocationHelper.invokeNmeaReceived(transport);
+                }
+                GPSListenerThread.get().addListenerTransport(locationManager);
+                return true;
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+    static class RequestLocationUpdates extends ReplaceLastPkgMethodProxy {
+
+        public RequestLocationUpdates() {
+            super("requestLocationUpdates");
+        }
+
+        @Override
+        public Object call(final Object who, Method method, Object... args) throws Throwable {
+            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
+                LocationRequest request = (LocationRequest) args[0];
+                fixLocationRequest(request);
+            }
+            if (isFakeLocationEnable()) {
+                Object transport = ArrayUtils.getFirst(args, mirror.android.location.LocationManager.ListenerTransport.TYPE);
+                if (transport != null) {
+                    Object locationManager = mirror.android.location.LocationManager.ListenerTransport.this$0.get(transport);
+                    MockLocationHelper.setGpsStatus(locationManager);
+                    GPSListenerThread.get().addListenerTransport(locationManager);
+                }
+                return 0;
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+    static class RemoveUpdates extends ReplaceLastPkgMethodProxy {
+
+        public RemoveUpdates() {
+            super("removeUpdates");
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                // TODO
+                return 0;
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+    static class GetLastLocation extends ReplaceLastPkgMethodProxy {
+
+        public GetLastLocation() {
+            super("getLastLocation");
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (!(args[0] instanceof String)) {
+                LocationRequest request = (LocationRequest) args[0];
+                fixLocationRequest(request);
+            }
+            if (isFakeLocationEnable()) {
+                VLocation loc = VirtualLocationManager.get().getLocation();
+                if (loc != null) {
+                    return loc.toSysLocation();
+                } else {
+                    return null;
+                }
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+    static class GetLastKnownLocation extends GetLastLocation {
+        @Override
+        public String getMethodName() {
+            return "getLastKnownLocation";
+        }
+    }
+
+    static class getProviders extends MethodProxy {
+
+        static List PROVIDERS = Arrays.asList(
+                LocationManager.GPS_PROVIDER,
+                LocationManager.PASSIVE_PROVIDER,
+                LocationManager.NETWORK_PROVIDER
+        );
+
+        @Override
+        public String getMethodName() {
+            return "getProviders";
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            return PROVIDERS;
+        }
+    }
+
+    static class IsProviderEnabled extends MethodProxy {
+        @Override
+        public String getMethodName() {
+            return "isProviderEnabled";
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                String provider = (String) args[0];
+                if (LocationManager.PASSIVE_PROVIDER.equals(provider)) {
+                    return true;
+                }
+                if (LocationManager.GPS_PROVIDER.equals(provider)) {
+                    return true;
+                }
+                if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
+                    return true;
+                }
+                return false;
+
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+    static class getAllProviders extends getProviders {
+
+        @Override
+        public String getMethodName() {
+            return "getAllProviders";
+        }
+    }
+
+    static class GetBestProvider extends MethodProxy {
+        @Override
+        public String getMethodName() {
+            return "getBestProvider";
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                return LocationManager.GPS_PROVIDER;
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+
+    static class RemoveGpsStatusListener extends ReplaceLastPkgMethodProxy {
+        public RemoveGpsStatusListener() {
+            super("removeGpsStatusListener");
+        }
+
+        public RemoveGpsStatusListener(String name) {
+            super(name);
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                return 0;
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+    static class sendExtraCommand extends MethodProxy {
+
+        @Override
+        public String getMethodName() {
+            return "sendExtraCommand";
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                return true;
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+
+    static class UnregisterGnssStatusCallback extends RemoveGpsStatusListener {
+        public UnregisterGnssStatusCallback() {
+            super("unregisterGnssStatusCallback");
+        }
+    }
+
+    static class RegisterGnssStatusCallback extends MethodProxy {
+
+        @Override
+        public String getMethodName() {
+            return "registerGnssStatusCallback";
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (!isFakeLocationEnable()) {
+                return super.call(who, method, args);
+            }
+            Object transport = ArrayUtils.getFirst(args, mirror.android.location.LocationManager.GnssStatusListenerTransport.TYPE);
+            if (transport != null) {
+                mirror.android.location.LocationManager.GnssStatusListenerTransport.onGnssStarted.call(transport, new Object[0]);
+                if (mirror.android.location.LocationManager.GnssStatusListenerTransport.mGpsListener.get(transport) != null) {
+                    MockLocationHelper.invokeSvStatusChanged(transport);
+                } else {
+                    MockLocationHelper.invokeNmeaReceived(transport);
+                }
+                mirror.android.location.LocationManager.GnssStatusListenerTransport.onFirstFix.call(transport, Integer.valueOf(0));
+                Object locationManager = mirror.android.location.LocationManager.GnssStatusListenerTransport.this$0.get(transport);
+                GPSListenerThread.get().addListenerTransport(locationManager);
+            }
+            return true;
+        }
+    }
+
+    static class getProviderProperties extends MethodProxy {
+
+        @Override
+        public String getMethodName() {
+            return "getProviderProperties";
+        }
+
+        @Override
+        public Object afterCall(Object who, Method method, Object[] args, Object result) throws Throwable {
+            if (!isFakeLocationEnable()) {
+                return super.afterCall(who, method, args, result);
+            }
+            try {
+                Reflect.on(result).set("mRequiresNetwork", false);
+                Reflect.on(result).set("mRequiresCell", false);
+            } catch (Throwable e) {
+                e.printStackTrace();
+            }
+            return result;
+        }
+    }
+
+    static class locationCallbackFinished extends MethodProxy {
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                return true;
+            }
+            return super.call(who, method, args);
+        }
+
+        @Override
+        public String getMethodName() {
+            return "locationCallbackFinished";
+        }
+    }
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/MockLocationHelper.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/MockLocationHelper.java
new file mode 100644
index 000000000..615cbd985
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/location/MockLocationHelper.java
@@ -0,0 +1,207 @@
+package com.lody.virtual.client.hook.proxies.location;
+
+import android.util.Log;
+
+import com.lody.virtual.client.env.VirtualGPSSatalines;
+import com.lody.virtual.client.ipc.VirtualLocationManager;
+import com.lody.virtual.helper.utils.Reflect;
+import com.lody.virtual.remote.vloc.VLocation;
+
+import java.lang.reflect.Method;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import mirror.android.location.LocationManager;
+
+/**
+ * @author Lody
+ */
+public class MockLocationHelper {
+
+    public static void invokeNmeaReceived(Object listener) {
+        if (listener != null) {
+            VirtualGPSSatalines satalines = VirtualGPSSatalines.get();
+            try {
+                VLocation location = VirtualLocationManager.get().getLocation();
+                if (location != null) {
+                    String date = new SimpleDateFormat("HHmmss:SS", Locale.US).format(new Date());
+                    String lat = getGPSLat(location.latitude);
+                    String lon = getGPSLat(location.longitude);
+                    String latNW = getNorthWest(location);
+                    String lonSE = getSouthEast(location);
+                    String $GPGGA = checksum(String.format("$GPGGA,%s,%s,%s,%s,%s,1,%s,692,.00,M,.00,M,,,", date, lat, latNW, lon, lonSE, satalines.getSvCount()));
+                    String $GPRMC = checksum(String.format("$GPRMC,%s,A,%s,%s,%s,%s,0,0,260717,,,A,", date, lat, latNW, lon, lonSE));
+                    if (LocationManager.GnssStatusListenerTransport.onNmeaReceived != null) {
+                        LocationManager.GnssStatusListenerTransport.onNmeaReceived.call(listener, System.currentTimeMillis(), "$GPGSV,1,1,04,12,05,159,36,15,41,087,15,19,38,262,30,31,56,146,19,*73");
+                        LocationManager.GnssStatusListenerTransport.onNmeaReceived.call(listener, System.currentTimeMillis(), $GPGGA);
+                        LocationManager.GnssStatusListenerTransport.onNmeaReceived.call(listener, System.currentTimeMillis(), "$GPVTG,0,T,0,M,0,N,0,K,A,*25");
+                        LocationManager.GnssStatusListenerTransport.onNmeaReceived.call(listener, System.currentTimeMillis(), $GPRMC);
+                        LocationManager.GnssStatusListenerTransport.onNmeaReceived.call(listener, System.currentTimeMillis(), "$GPGSA,A,2,12,15,19,31,,,,,,,,,604,712,986,*27");
+                    } else if (LocationManager.GpsStatusListenerTransport.onNmeaReceived != null) {
+                        LocationManager.GpsStatusListenerTransport.onNmeaReceived.call(listener, System.currentTimeMillis(), "$GPGSV,1,1,04,12,05,159,36,15,41,087,15,19,38,262,30,31,56,146,19,*73");
+                        LocationManager.GpsStatusListenerTransport.onNmeaReceived.call(listener, System.currentTimeMillis(), $GPGGA);
+                        LocationManager.GpsStatusListenerTransport.onNmeaReceived.call(listener, System.currentTimeMillis(), "$GPVTG,0,T,0,M,0,N,0,K,A,*25");
+                        LocationManager.GpsStatusListenerTransport.onNmeaReceived.call(listener, System.currentTimeMillis(), $GPRMC);
+                        LocationManager.GpsStatusListenerTransport.onNmeaReceived.call(listener, System.currentTimeMillis(), "$GPGSA,A,2,12,15,19,31,,,,,,,,,604,712,986,*27");
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public static void setGpsStatus(Object locationManager) {
+
+        VirtualGPSSatalines satalines = VirtualGPSSatalines.get();
+        Method setStatus = null;
+        int svCount = satalines.getSvCount();
+        float[] snrs = satalines.getSnrs();
+        int[] prns = satalines.getPrns();
+        float[] elevations = satalines.getElevations();
+        float[] azimuths = satalines.getAzimuths();
+        Object mGpsStatus = Reflect.on(locationManager).get("mGpsStatus");
+        try {
+            setStatus = mGpsStatus.getClass().getDeclaredMethod("setStatus", Integer.TYPE, int[].class, float[].class, float[].class, float[].class, Integer.TYPE, Integer.TYPE, Integer.TYPE);
+            setStatus.setAccessible(true);
+            int ephemerisMask = satalines.getEphemerisMask();
+            int almanacMask = satalines.getAlmanacMask();
+            int usedInFixMask = satalines.getUsedInFixMask();
+            setStatus.invoke(mGpsStatus, svCount, prns, snrs, elevations, azimuths, ephemerisMask, almanacMask, usedInFixMask);
+        } catch (Exception e) {
+            // ignore
+        }
+        if (setStatus == null) {
+            try {
+                setStatus = mGpsStatus.getClass().getDeclaredMethod("setStatus", Integer.TYPE, int[].class, float[].class, float[].class, float[].class, int[].class, int[].class, int[].class);
+                setStatus.setAccessible(true);
+                svCount = satalines.getSvCount();
+                int length = satalines.getPrns().length;
+                elevations = satalines.getElevations();
+                azimuths = satalines.getAzimuths();
+                int[] ephemerisMask = new int[length];
+                for (int i = 0; i < length; i++) {
+                    ephemerisMask[i] = satalines.getEphemerisMask();
+                }
+                int[] almanacMask = new int[length];
+                for (int i = 0; i < length; i++) {
+                    almanacMask[i] = satalines.getAlmanacMask();
+                }
+                int[] usedInFixMask = new int[length];
+                for (int i = 0; i < length; i++) {
+                    usedInFixMask[i] = satalines.getUsedInFixMask();
+                }
+                setStatus.invoke(mGpsStatus, svCount, prns, snrs, elevations, azimuths, ephemerisMask, almanacMask, usedInFixMask);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public static void invokeSvStatusChanged(Object transport) {
+        if (transport != null) {
+            VirtualGPSSatalines satalines = VirtualGPSSatalines.get();
+            try {
+                Class<?> aClass = transport.getClass();
+                int svCount;
+                float[] snrs;
+                float[] elevations;
+                float[] azimuths;
+                if (aClass == LocationManager.GnssStatusListenerTransport.TYPE) {
+                    svCount = satalines.getSvCount();
+                    int[] prnWithFlags = satalines.getPrnWithFlags();
+                    snrs = satalines.getSnrs();
+                    elevations = satalines.getElevations();
+                    azimuths = satalines.getAzimuths();
+                    LocationManager.GnssStatusListenerTransport.onSvStatusChanged.call(transport, svCount, prnWithFlags, snrs, elevations, azimuths);
+                } else if (aClass == LocationManager.GpsStatusListenerTransport.TYPE) {
+                    svCount = satalines.getSvCount();
+                    int[] prns = satalines.getPrns();
+                    snrs = satalines.getSnrs();
+                    elevations = satalines.getElevations();
+                    azimuths = satalines.getAzimuths();
+                    int ephemerisMask = satalines.getEphemerisMask();
+                    int almanacMask = satalines.getAlmanacMask();
+                    int usedInFixMask = satalines.getUsedInFixMask();
+                    if (LocationManager.GpsStatusListenerTransport.onSvStatusChanged != null) {
+                        LocationManager.GpsStatusListenerTransport.onSvStatusChanged.call(transport, svCount, prns, snrs, elevations, azimuths, ephemerisMask, almanacMask, usedInFixMask);
+                    } else if (LocationManager.GpsStatusListenerTransportVIVO.onSvStatusChanged != null) {
+                        LocationManager.GpsStatusListenerTransportVIVO.onSvStatusChanged.call(transport, svCount, prns, snrs, elevations, azimuths, ephemerisMask, almanacMask, usedInFixMask, new long[svCount]);
+                    } else if (LocationManager.GpsStatusListenerTransportSumsungS5.onSvStatusChanged != null) {
+                        LocationManager.GpsStatusListenerTransportSumsungS5.onSvStatusChanged.call(transport, svCount, prns, snrs, elevations, azimuths, ephemerisMask, almanacMask, usedInFixMask, new int[svCount]);
+                    } else if (LocationManager.GpsStatusListenerTransportOPPO_R815T.onSvStatusChanged != null) {
+                        int len = prns.length;
+                        int[] ephemerisMasks = new int[len];
+                        for (int i = 0; i < len; i++) {
+                            ephemerisMasks[i] = satalines.getEphemerisMask();
+                        }
+                        int[] almanacMasks = new int[len];
+                        for (int i = 0; i < len; i++) {
+                            almanacMasks[i] = satalines.getAlmanacMask();
+                        }
+                        int[] usedInFixMasks = new int[len];
+                        for (int i = 0; i < len; i++) {
+                            usedInFixMasks[i] = satalines.getUsedInFixMask();
+                        }
+                        LocationManager.GpsStatusListenerTransportOPPO_R815T.onSvStatusChanged.call(transport, svCount, prns, snrs, elevations, azimuths, ephemerisMasks, almanacMasks, usedInFixMasks, svCount);
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private static String getSouthEast(VLocation location) {
+        if (location.longitude > 0.0d) {
+            return "E";
+        }
+        return "W";
+    }
+
+    private static String getNorthWest(VLocation location) {
+        if (location.latitude > 0.0d) {
+            return "N";
+        }
+        return "S";
+    }
+
+    public static String getGPSLat(double v) {
+        int du = (int) v;
+        double fen = (v - (double) du) * 60.0d;
+        return du + leftZeroPad((int) fen, 2) + ":" + String.valueOf(fen).substring(2);
+    }
+
+    private static String leftZeroPad(int num, int size) {
+        return leftZeroPad(String.valueOf(num), size);
+    }
+
+    private static String leftZeroPad(String num, int size) {
+        StringBuilder sb = new StringBuilder(size);
+        int i;
+        if (num == null) {
+            for (i = 0; i < size; i++) {
+                sb.append('0');
+            }
+        } else {
+            for (i = 0; i < size - num.length(); i++) {
+                sb.append('0');
+            }
+            sb.append(num);
+        }
+        return sb.toString();
+    }
+
+    public static String checksum(String nema) {
+        String checkStr = nema;
+        if (nema.startsWith("$")) {
+            checkStr = nema.substring(1);
+        }
+        int sum = 0;
+        for (int i = 0; i < checkStr.length(); i++) {
+            sum ^= (byte) checkStr.charAt(i);
+        }
+        return nema + "*" + String.format("%02X", sum).toLowerCase();
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/mount/MountServiceStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/mount/MountServiceStub.java
index 13d1c4173..3c4518e1a 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/mount/MountServiceStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/mount/MountServiceStub.java
@@ -1,9 +1,14 @@
 package com.lody.virtual.client.hook.proxies.mount;
 
+import android.os.IInterface;
+
 import com.lody.virtual.client.hook.base.Inject;
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.helper.compat.BuildCompat;
 
+import mirror.RefStaticMethod;
 import mirror.android.os.mount.IMountService;
+import mirror.android.os.storage.IStorageManager;
 
 /**
  * @author Lody
@@ -11,7 +16,15 @@
 @Inject(MethodProxies.class)
 public class MountServiceStub extends BinderInvocationProxy {
 
-	public MountServiceStub() {
-		super(IMountService.Stub.asInterface, "mount");
-	}
+    public MountServiceStub() {
+        super(getInterfaceMethod(), "mount");
+    }
+
+    private static RefStaticMethod<IInterface> getInterfaceMethod() {
+        if (BuildCompat.isOreo()) {
+            return IStorageManager.Stub.asInterface;
+        } else {
+            return IMountService.Stub.asInterface;
+        }
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/notification/MethodProxies.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/notification/MethodProxies.java
index fe6dc5bdf..f4975a391 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/notification/MethodProxies.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/notification/MethodProxies.java
@@ -8,6 +8,7 @@
 import com.lody.virtual.client.hook.utils.MethodParameterUtils;
 import com.lody.virtual.client.ipc.VNotificationManager;
 import com.lody.virtual.helper.utils.ArrayUtils;
+import com.lody.virtual.helper.utils.VLog;
 
 import java.lang.reflect.Method;
 
@@ -55,16 +56,6 @@ public String getMethodName() {
 
         @Override
         public Object call(Object who, Method method, Object... args) throws Throwable {
-            //15 enqueueNotificationWithTag(pkg, tag, id, notification, idOut);
-            //16 enqueueNotificationWithTag(pkg, tag, id, notification, idOut);
-            //17 enqueueNotificationWithTag(pkg, tag, id, notification, idOut, UserHandle.myUserId());
-            //18 enqueueNotificationWithTag(pkg, mContext.getBasePackageName(), tag, id, notification, idOut, UserHandle.myUserId());
-            //19 enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, notification, idOut, UserHandle.myUserId());
-            //21 enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, notification, idOut, UserHandle.myUserId());
-            //22 enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, notification, idOut, UserHandle.myUserId());
-            //23 enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, notification, idOut, UserHandle.myUserId());
-            //24 enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, notification, idOut, user.getIdentifier());
-            //25 enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, notification, idOut, user.getIdentifier());
             String pkg = (String) args[0];
             if (getHostPkg().equals(pkg)) {
                 return method.invoke(who, args);
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/notification/NotificationManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/notification/NotificationManagerStub.java
index 886ce6d10..6704b1f4e 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/notification/NotificationManagerStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/notification/NotificationManagerStub.java
@@ -7,6 +7,7 @@
 import com.lody.virtual.client.hook.base.MethodInvocationProxy;
 import com.lody.virtual.client.hook.base.MethodInvocationStub;
 import com.lody.virtual.client.hook.base.ReplaceCallingPkgMethodProxy;
+import com.lody.virtual.helper.utils.DeviceUtil;
 
 import mirror.android.app.NotificationManager;
 import mirror.android.widget.Toast;
@@ -36,7 +37,18 @@ protected void onBindMethods() {
             addMethodProxy(new ReplaceCallingPkgMethodProxy("getNotificationPolicy"));
             addMethodProxy(new ReplaceCallingPkgMethodProxy("isNotificationPolicyAccessGrantedForPackage"));
         }
-        if ("samsung".equalsIgnoreCase(Build.BRAND) || "samsung".equalsIgnoreCase(Build.MANUFACTURER)) {
+
+        // http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/java/android/app/INotificationManager.aidl
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            addMethodProxy(new ReplaceCallingPkgMethodProxy("createNotificationChannelGroups"));
+            addMethodProxy(new ReplaceCallingPkgMethodProxy("getNotificationChannelGroups"));
+            addMethodProxy(new ReplaceCallingPkgMethodProxy("deleteNotificationChannelGroup"));
+            addMethodProxy(new ReplaceCallingPkgMethodProxy("createNotificationChannels"));
+            addMethodProxy(new ReplaceCallingPkgMethodProxy("getNotificationChannels"));
+            addMethodProxy(new ReplaceCallingPkgMethodProxy("getNotificationChannel"));
+            addMethodProxy(new ReplaceCallingPkgMethodProxy("deleteNotificationChannel"));
+        }
+        if (DeviceUtil.isSamsung()) {
             addMethodProxy(new ReplaceCallingPkgMethodProxy("removeEdgeNotification"));
         }
     }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/phonesubinfo/MethodProxies.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/phonesubinfo/MethodProxies.java
index ebcd3f881..543788c73 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/phonesubinfo/MethodProxies.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/phonesubinfo/MethodProxies.java
@@ -1,50 +1,58 @@
 package com.lody.virtual.client.hook.proxies.phonesubinfo;
 
-import com.lody.virtual.client.core.VirtualCore;
-import com.lody.virtual.client.hook.base.ReplaceCallingPkgMethodProxy;
+import com.lody.virtual.client.hook.base.MethodProxy;
+import com.lody.virtual.helper.utils.marks.FakeDeviceMark;
 
 import java.lang.reflect.Method;
 
 /**
  * @author Lody
  */
-
+@SuppressWarnings("ALL")
 class MethodProxies {
 
-    static class GetDeviceId extends ReplaceCallingPkgMethodProxy {
+    @FakeDeviceMark("fake device id")
+    static class GetDeviceId extends MethodProxy {
 
-        public GetDeviceId() {
-            super("getDeviceId");
+        @Override
+        public String getMethodName() {
+            return "getDeviceId";
         }
 
         @Override
-        public Object afterCall(Object who, Method method, Object[] args, Object result) throws Throwable {
-            if (VirtualCore.get().getPhoneInfoDelegate() != null) {
-                String res = VirtualCore.get().getPhoneInfoDelegate().getDeviceId((String) result, getAppUserId());
-                if (res != null) {
-                    return res;
-                }
-            }
-            return super.afterCall(who, method, args, result);
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            return getDeviceInfo().deviceId;
+        }
+    }
+
+    static class GetDeviceIdForSubscriber extends GetDeviceId {
+
+        @Override
+        public String getMethodName() {
+            return "getDeviceIdForSubscriber";
         }
+
     }
 
-    static class GetDeviceIdForSubscriber extends ReplaceCallingPkgMethodProxy {
+    @FakeDeviceMark("fake iccid")
+    static class GetIccSerialNumber extends MethodProxy {
 
-        // just for letv eui
-        public GetDeviceIdForSubscriber() {
-            super("getDeviceIdForSubscriber");
+        @Override
+        public String getMethodName() {
+            return "getIccSerialNumber";
         }
 
         @Override
-        public Object afterCall(Object who, Method method, Object[] args, Object result) throws Throwable {
-            if (VirtualCore.get().getPhoneInfoDelegate() != null) {
-                String res = VirtualCore.get().getPhoneInfoDelegate().getDeviceId((String) result, getAppUserId());
-                if (res != null) {
-                    return res;
-                }
-            }
-            return super.afterCall(who, method, args, result);
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            return getDeviceInfo().iccId;
+        }
+    }
+
+
+    static class getIccSerialNumberForSubscriber extends GetIccSerialNumber {
+        @Override
+        public String getMethodName() {
+            return "getIccSerialNumberForSubscriber";
         }
     }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/phonesubinfo/PhoneSubInfoStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/phonesubinfo/PhoneSubInfoStub.java
index fbc5ce0c9..03ab9aa3e 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/phonesubinfo/PhoneSubInfoStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/phonesubinfo/PhoneSubInfoStub.java
@@ -37,9 +37,6 @@ protected void onBindMethods() {
 		addMethodProxy(new ReplaceLastPkgMethodProxy("getVoiceMailNumberForSubscriber"));
 		addMethodProxy(new ReplaceCallingPkgMethodProxy("getVoiceMailAlphaTag"));
 		addMethodProxy(new ReplaceLastPkgMethodProxy("getVoiceMailAlphaTagForSubscriber"));
-		// The following method maybe need to fake
-		//addHook(new ReplaceCallingPkgHook("getDeviceId"));
-		addMethodProxy(new ReplaceCallingPkgMethodProxy("getIccSerialNumber"));
-		addMethodProxy(new ReplaceLastPkgMethodProxy("getIccSerialNumberForSubscriber"));
 	}
+
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/LauncherAppsStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/LauncherAppsStub.java
new file mode 100644
index 000000000..ba73f8c18
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/LauncherAppsStub.java
@@ -0,0 +1,53 @@
+package com.lody.virtual.client.hook.proxies.pm;
+
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.os.IInterface;
+
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.client.hook.base.ReplaceCallingPkgMethodProxy;
+
+/**
+ * @author weishu
+ * @date 2018/7/4.
+ */
+public class LauncherAppsStub extends BinderInvocationProxy {
+
+    public LauncherAppsStub() {
+        super(getInterface(), Context.LAUNCHER_APPS_SERVICE);
+    }
+
+    private static IInterface getInterface() {
+        LauncherApps cm = (LauncherApps) VirtualCore.get().getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
+        return mirror.android.content.pm.LauncherApps.mService.get(cm);
+    }
+
+    @Override
+    public void inject() throws Throwable {
+        super.inject();
+        LauncherApps cm = (LauncherApps) VirtualCore.get().getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
+        mirror.android.content.pm.LauncherApps.mService.set(cm, getInvocationStub().getProxyInterface());
+    }
+
+    @Override
+    protected void onBindMethods() {
+        super.onBindMethods();
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("addOnAppsChangedListener"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getLauncherActivities"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("resolveActivity"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("startActivityAsUser"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("showAppDetailsAsUser"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("isPackageEnabled"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("isActivityEnabled"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getApplicationInfo"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getShortcuts"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("pinShortcuts"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("startShortcut"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getShortcutIconResId"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getShortcutIconFd"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("hasShortcutHostPermission"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getShortcutConfigActivities"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getShortcutConfigActivityIntent"));
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/MethodProxies.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/MethodProxies.java
index 7f1abcf3c..93863747f 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/MethodProxies.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/MethodProxies.java
@@ -8,13 +8,16 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver2;
+import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
+import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IInterface;
@@ -27,7 +30,11 @@
 import com.lody.virtual.helper.collection.ArraySet;
 import com.lody.virtual.helper.compat.ParceledListSliceCompat;
 import com.lody.virtual.helper.utils.ArrayUtils;
+import com.lody.virtual.helper.utils.EncodeUtils;
 import com.lody.virtual.os.VUserHandle;
+import com.lody.virtual.server.IPackageInstaller;
+import com.lody.virtual.server.pm.installer.SessionInfo;
+import com.lody.virtual.server.pm.installer.SessionParams;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
@@ -242,6 +249,11 @@ public String getMethodName() {
 
     static class GetPackageInstaller extends MethodProxy {
 
+        @Override
+        public boolean isEnable() {
+            return isAppProcess();
+        }
+
         @Override
         public String getMethodName() {
             return "getPackageInstaller";
@@ -250,18 +262,73 @@ public String getMethodName() {
         @Override
         public Object call(final Object who, Method method, Object... args) throws Throwable {
             final IInterface installer = (IInterface) method.invoke(who, args);
-
+            final IPackageInstaller vInstaller = VPackageManager.get().getPackageInstaller();
             return Proxy.newProxyInstance(installer.getClass().getClassLoader(), installer.getClass().getInterfaces(),
                     new InvocationHandler() {
                         @Override
                         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-                            String name = method.getName();
-                            if (name.equals("getMySessions")) {
-                                MethodParameterUtils.replaceFirstAppPkg(args);
-                            } else if (name.equals("createSession")) {
-                                MethodParameterUtils.replaceFirstAppPkg(args);
+                            switch (method.getName()) {
+                                case "createSession": {
+                                    SessionParams params = SessionParams.create((PackageInstaller.SessionParams) args[0]);
+                                    String installerPackageName = (String) args[1];
+                                    return vInstaller.createSession(params, installerPackageName, VUserHandle.myUserId());
+                                }
+                                case "updateSessionAppIcon": {
+                                    int sessionId = (int) args[0];
+                                    Bitmap appIcon = (Bitmap) args[1];
+                                    vInstaller.updateSessionAppIcon(sessionId, appIcon);
+                                    return 0;
+                                }
+                                case "updateSessionAppLabel": {
+                                    int sessionId = (int) args[0];
+                                    String appLabel = (String) args[1];
+                                    vInstaller.updateSessionAppLabel(sessionId, appLabel);
+                                    return 0;
+                                }
+                                case "abandonSession": {
+                                    vInstaller.abandonSession((Integer) args[0]);
+                                    return 0;
+                                }
+                                case "openSession": {
+                                    return vInstaller.openSession((Integer) args[0]);
+                                }
+                                case "getSessionInfo": {
+                                    SessionInfo info = vInstaller.getSessionInfo((Integer) args[0]);
+                                    if (info != null) {
+                                        return info.alloc();
+                                    }
+                                    return null;
+                                }
+                                case "getAllSessions": {
+                                    return ParceledListSliceCompat.create(
+                                            vInstaller.getAllSessions((Integer) args[0]).getList()
+                                    );
+                                }
+                                case "getMySessions": {
+                                    String installerPackageName = (String) args[0];
+                                    int userId = (int) args[1];
+                                    return ParceledListSliceCompat.create(
+                                            vInstaller.getMySessions(installerPackageName, userId).getList()
+                                    );
+                                }
+                                case "registerCallback": {
+                                    IPackageInstallerCallback callback = (IPackageInstallerCallback) args[0];
+                                    vInstaller.registerCallback(callback, VUserHandle.myUserId());
+                                    return 0;
+                                }
+                                case "unregisterCallback": {
+                                    IPackageInstallerCallback callback = (IPackageInstallerCallback) args[0];
+                                    vInstaller.unregisterCallback(callback);
+                                    return 0;
+                                }
+                                case "setPermissionsResult": {
+                                    int sessionId = (int) args[0];
+                                    boolean accepted = (boolean) args[1];
+                                    vInstaller.setPermissionsResult(sessionId, accepted);
+                                    return 0;
+                                }
                             }
-                            return method.invoke(installer, args);
+                            throw new RuntimeException("Not support PackageInstaller method : " + method.getName());
                         }
                     });
         }
@@ -383,7 +450,7 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
             if (_hostResult != null) {
                 List<ResolveInfo> hostResult = slice ? ParceledListSlice.getList.call(_hostResult)
                         : (List) _hostResult;
-                if(hostResult != null) {
+                if (hostResult != null) {
                     Iterator<ResolveInfo> iterator = hostResult.iterator();
                     while (iterator.hasNext()) {
                         ResolveInfo info = iterator.next();
@@ -448,21 +515,6 @@ public String getMethodName() {
 
     }
 
-
-    static class AddOnPermissionsChangeListener extends MethodProxy {
-
-        @Override
-        public String getMethodName() {
-            return "addOnPermissionsChangeListener";
-        }
-
-        @Override
-        public Object call(Object who, Method method, Object... args) throws Throwable {
-            return 0;
-        }
-    }
-
-
     @SuppressWarnings("unchecked")
     static class QueryIntentActivities extends MethodProxy {
 
@@ -481,7 +533,7 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
             if (_hostResult != null) {
                 List<ResolveInfo> hostResult = slice ? ParceledListSlice.getList.call(_hostResult)
                         : (List) _hostResult;
-                if(hostResult != null) {
+                if (hostResult != null) {
                     Iterator<ResolveInfo> iterator = hostResult.iterator();
                     while (iterator.hasNext()) {
                         ResolveInfo info = iterator.next();
@@ -737,8 +789,9 @@ public String getMethodName() {
         @Override
         public Object call(Object who, Method method, Object... args) throws Throwable {
             String processName = (String) args[0];
+            int uid = (int) args[1];
             int flags = (int) args[2];
-            List<ProviderInfo> infos = VPackageManager.get().queryContentProviders(processName, flags, 0);
+            List<ProviderInfo> infos = VPackageManager.get().queryContentProviders(processName, uid, flags);
             if (ParceledListSliceCompat.isReturnParceledListSlice(method)) {
                 return ParceledListSliceCompat.create(infos);
             }
@@ -799,7 +852,6 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
                         if (ArrayUtils.isEmpty(two)) {
                             return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
                         } else {
-                            // 走到了这里说明两个包的签名都在
                             if (Arrays.equals(one, two)) {
                                 return PackageManager.SIGNATURE_MATCH;
                             } else {
@@ -816,6 +868,36 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
         }
     }
 
+    static class checkUidSignatures extends MethodProxy {
+
+        @Override
+        public String getMethodName() {
+            return "checkUidSignatures";
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            int uid1 = (int) args[0];
+            int uid2 = (int) args[1];
+            // TODO: verify the signatures by uid.
+            return PackageManager.SIGNATURE_MATCH;
+        }
+    }
+
+    static class getNameForUid extends MethodProxy {
+
+        @Override
+        public String getMethodName() {
+            return "getNameForUid";
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            int uid = (int) args[0];
+            return VPackageManager.get().getNameForUid(uid);
+        }
+    }
+
 
     static class DeletePackage extends MethodProxy {
 
@@ -944,7 +1026,9 @@ static class SetComponentEnabledSetting extends MethodProxy {
 
         @Override
         public String getMethodName() {
-            return "setComponentEnabledSetting";
+            // return "setComponentEnabledSetting";
+            // anti-virus, fuck ESET-NOD32: a variant of Android/AdDisplay.AdLock.AL potentially unwanted
+            return EncodeUtils.decode("c2V0Q29tcG9uZW50RW5hYmxlZFNldHRpbmc=");
         }
 
         @Override
@@ -1022,7 +1106,7 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
             Object _hostResult = method.invoke(who, args);
             List<ResolveInfo> hostResult = slice ? ParceledListSlice.getList.call(_hostResult)
                     : (List) _hostResult;
-            if(hostResult != null) {
+            if (hostResult != null) {
                 Iterator<ResolveInfo> iterator = hostResult.iterator();
                 while (iterator.hasNext()) {
                     ResolveInfo info = iterator.next();
@@ -1126,7 +1210,7 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
             Object _hostResult = method.invoke(who, args);
             List<ResolveInfo> hostResult = slice ? ParceledListSlice.getList.call(_hostResult)
                     : (List) _hostResult;
-            if(hostResult != null) {
+            if (hostResult != null) {
                 Iterator<ResolveInfo> iterator = hostResult.iterator();
                 while (iterator.hasNext()) {
                     ResolveInfo info = iterator.next();
@@ -1149,19 +1233,6 @@ public boolean isEnable() {
     }
 
 
-    static class RemoveOnPermissionsChangeListener extends MethodProxy {
-
-        @Override
-        public String getMethodName() {
-            return "removeOnPermissionsChangeListener";
-        }
-
-        @Override
-        public Object call(Object who, Method method, Object... args) throws Throwable {
-            return 0;
-        }
-    }
-
     static class GetApplicationBlockedSettingAsUser extends MethodProxy {
 
         @Override
@@ -1175,4 +1246,5 @@ public Object call(Object who, Method method, Object... args) throws Throwable {
             return method.invoke(who, args);
         }
     }
+
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/PackageManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/PackageManagerStub.java
index 61f2b734d..04e67f670 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/PackageManagerStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/pm/PackageManagerStub.java
@@ -1,11 +1,12 @@
 package com.lody.virtual.client.hook.proxies.pm;
 
+import android.os.Build;
 import android.os.IInterface;
 
 import com.lody.virtual.client.hook.base.BinderInvocationStub;
-import com.lody.virtual.client.hook.base.MethodInvocationStub;
 import com.lody.virtual.client.hook.base.Inject;
 import com.lody.virtual.client.hook.base.MethodInvocationProxy;
+import com.lody.virtual.client.hook.base.MethodInvocationStub;
 import com.lody.virtual.client.hook.base.ResultStaticMethodProxy;
 import com.lody.virtual.helper.compat.BuildCompat;
 
@@ -17,38 +18,43 @@
 @Inject(MethodProxies.class)
 public final class PackageManagerStub extends MethodInvocationProxy<MethodInvocationStub<IInterface>> {
 
-	public PackageManagerStub() {
-		super(new MethodInvocationStub<IInterface>(ActivityThread.sPackageManager.get()));
-	}
-
-	@Override
-	protected void onBindMethods() {
-		super.onBindMethods();
-		addMethodProxy(new ResultStaticMethodProxy("addPermissionAsync", true));
-		addMethodProxy(new ResultStaticMethodProxy("addPermission", true));
-		addMethodProxy(new ResultStaticMethodProxy("performDexOpt", true));
-		addMethodProxy(new ResultStaticMethodProxy("performDexOptIfNeeded", false));
-		addMethodProxy(new ResultStaticMethodProxy("performDexOptSecondary", true));
-		if (BuildCompat.isOreo()) {
-			addMethodProxy(new ResultStaticMethodProxy("notifyDexLoad", 0));
-			addMethodProxy(new ResultStaticMethodProxy("notifyPackageUse", 0));
-			addMethodProxy(new ResultStaticMethodProxy("setInstantAppCookie", false));
-			addMethodProxy(new ResultStaticMethodProxy("isInstantApp", false));
-		}
-	}
-
-	@Override
-	public void inject() throws Throwable {
-		final IInterface hookedPM = getInvocationStub().getProxyInterface();
-		ActivityThread.sPackageManager.set(hookedPM);
-
-		BinderInvocationStub pmHookBinder = new BinderInvocationStub(getInvocationStub().getBaseInterface());
-		pmHookBinder.copyMethodProxies(getInvocationStub());
-		pmHookBinder.replaceService("package");
-	}
-
-	@Override
-	public boolean isEnvBad() {
-		return getInvocationStub().getProxyInterface() != ActivityThread.sPackageManager.get();
-	}
+    public PackageManagerStub() {
+        super(new MethodInvocationStub<>(ActivityThread.sPackageManager.get()));
+    }
+
+    @Override
+    protected void onBindMethods() {
+        super.onBindMethods();
+        addMethodProxy(new ResultStaticMethodProxy("addPermissionAsync", true));
+        addMethodProxy(new ResultStaticMethodProxy("addPermission", true));
+        addMethodProxy(new ResultStaticMethodProxy("performDexOpt", true));
+        addMethodProxy(new ResultStaticMethodProxy("performDexOptIfNeeded", false));
+        addMethodProxy(new ResultStaticMethodProxy("performDexOptSecondary", true));
+        addMethodProxy(new ResultStaticMethodProxy("addOnPermissionsChangeListener", 0));
+        addMethodProxy(new ResultStaticMethodProxy("removeOnPermissionsChangeListener", 0));
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            addMethodProxy(new ResultStaticMethodProxy("checkPackageStartable", 0));
+        }
+        if (BuildCompat.isOreo()) {
+            addMethodProxy(new ResultStaticMethodProxy("notifyDexLoad", 0));
+            addMethodProxy(new ResultStaticMethodProxy("notifyPackageUse", 0));
+            addMethodProxy(new ResultStaticMethodProxy("setInstantAppCookie", false));
+            addMethodProxy(new ResultStaticMethodProxy("isInstantApp", false));
+        }
+
+    }
+
+    @Override
+    public void inject() throws Throwable {
+        final IInterface hookedPM = getInvocationStub().getProxyInterface();
+        ActivityThread.sPackageManager.set(hookedPM);
+        BinderInvocationStub pmHookBinder = new BinderInvocationStub(getInvocationStub().getBaseInterface());
+        pmHookBinder.copyMethodProxies(getInvocationStub());
+        pmHookBinder.replaceService("package");
+    }
+
+    @Override
+    public boolean isEnvBad() {
+        return getInvocationStub().getProxyInterface() != ActivityThread.sPackageManager.get();
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/shortcut/ShortcutServiceStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/shortcut/ShortcutServiceStub.java
index 4e2793a28..d57656a84 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/shortcut/ShortcutServiceStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/shortcut/ShortcutServiceStub.java
@@ -1,9 +1,25 @@
 package com.lody.virtual.client.hook.proxies.shortcut;
 
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Build;
+import android.os.PersistableBundle;
+
+import com.lody.virtual.client.env.Constants;
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
 import com.lody.virtual.client.hook.base.ReplaceCallingPkgMethodProxy;
+import com.lody.virtual.helper.compat.ParceledListSliceCompat;
+
+import java.lang.reflect.Method;
+import java.util.List;
 
-import mirror.android.content.pm.ILauncherApps;
+import mirror.android.content.pm.IShortcutService;
+import mirror.android.content.pm.ParceledListSlice;
 
 /**
  * @author Lody
@@ -12,7 +28,7 @@ public class ShortcutServiceStub extends BinderInvocationProxy {
 
 
     public ShortcutServiceStub() {
-        super(ILauncherApps.Stub.asInterface, "shortcut");
+        super(IShortcutService.Stub.asInterface, "shortcut");
     }
 
     @Override
@@ -24,8 +40,9 @@ public void inject() throws Throwable {
     protected void onBindMethods() {
         super.onBindMethods();
         addMethodProxy(new ReplaceCallingPkgMethodProxy("getManifestShortcuts"));
+        // TODO: 18/3/3 Support dynamic shortcut ?
         addMethodProxy(new ReplaceCallingPkgMethodProxy("getDynamicShortcuts"));
-        addMethodProxy(new ReplaceCallingPkgMethodProxy("setDynamicShortcuts"));
+        addMethodProxy(new ReplacePkgAndShortcutListMethodProxy("setDynamicShortcuts"));
         addMethodProxy(new ReplaceCallingPkgMethodProxy("addDynamicShortcuts"));
         addMethodProxy(new ReplaceCallingPkgMethodProxy("createShortcutResultIntent"));
         addMethodProxy(new ReplaceCallingPkgMethodProxy("disableShortcuts"));
@@ -36,5 +53,116 @@ protected void onBindMethods() {
         addMethodProxy(new ReplaceCallingPkgMethodProxy("getMaxShortcutCountPerActivity"));
         addMethodProxy(new ReplaceCallingPkgMethodProxy("reportShortcutUsed"));
         addMethodProxy(new ReplaceCallingPkgMethodProxy("onApplicationActive"));
+
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getPinnedShortcuts"));
+        addMethodProxy(new ReplacePkgAndShortcutMethodProxy("requestPinShortcut"));
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    private static void replaceShortcutInfo(ShortcutInfo shortcutInfo, String hostPackage, PackageManager pm) {
+        if (shortcutInfo == null) {
+            return;
+        }
+
+        mirror.android.content.pm.ShortcutInfo.mPackageName.set(shortcutInfo, hostPackage);
+        try {
+            Drawable applicationIcon = pm.getApplicationIcon(hostPackage);
+            Icon icon = Icon.createWithBitmap(((BitmapDrawable) applicationIcon).getBitmap());
+            mirror.android.content.pm.ShortcutInfo.mIcon.set(shortcutInfo, icon);
+        } catch (Throwable ignored) {
+        }
+
+        Intent[] intents = mirror.android.content.pm.ShortcutInfo.mIntents.get(shortcutInfo);
+
+        if (intents != null) {
+            int length = intents.length;
+            Intent[] swap = new Intent[length];
+
+            PersistableBundle[] persistableBundles = mirror.android.content.pm.ShortcutInfo.mIntentPersistableExtrases.get(shortcutInfo);
+            if (persistableBundles == null) {
+                persistableBundles = new PersistableBundle[length];
+            }
+
+            for (int i = 0; i < length; i++) {
+                Intent intent = intents[i];
+                PersistableBundle persistableBundle = persistableBundles[i];
+                if (persistableBundle == null) {
+                    persistableBundle = new PersistableBundle();
+                }
+
+                Intent shortcutIntent = new Intent();
+                shortcutIntent.setClassName(hostPackage, Constants.SHORTCUT_PROXY_ACTIVITY_NAME);
+                shortcutIntent.addCategory(Intent.CATEGORY_DEFAULT);
+
+                persistableBundle.putString("_VA_|_uri_", intent.toUri(0));
+                persistableBundle.putInt("_VA_|_user_id_", 0);
+                swap[i] = shortcutIntent;
+            }
+
+            System.arraycopy(swap, 0, intents, 0, length);
+            mirror.android.content.pm.ShortcutInfo.mIntentPersistableExtrases.set(shortcutInfo, persistableBundles);
+        }
+    }
+
+    private static class ReplacePkgAndShortcutListMethodProxy extends ReplaceCallingPkgMethodProxy {
+        ReplacePkgAndShortcutListMethodProxy(String name) {
+            super(name);
+        }
+
+        @Override
+        public boolean beforeCall(Object who, Method method, Object... args) {
+
+            List<ShortcutInfo> shortcutList = findFirstShortcutList(args);
+            if (shortcutList != null) {
+                String hostPkg = getHostPkg();
+                for (ShortcutInfo shortcutInfo : shortcutList) {
+                    replaceShortcutInfo(shortcutInfo, hostPkg, getPM());
+                }
+            }
+
+            return super.beforeCall(who, method, args);
+        }
+
+        @TargetApi(Build.VERSION_CODES.N_MR1)
+        private List<ShortcutInfo> findFirstShortcutList(Object... args) {
+            if (args == null) {
+                return null;
+            }
+            for (Object arg : args) {
+                if (arg.getClass().isAssignableFrom(ParceledListSlice.TYPE)) {
+                    return ParceledListSliceCompat.getList(arg);
+                }
+            }
+            return null;
+        }
+    }
+
+    private static class ReplacePkgAndShortcutMethodProxy extends ReplaceCallingPkgMethodProxy {
+
+        ReplacePkgAndShortcutMethodProxy(String name) {
+            super(name);
+        }
+
+        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+        @Override
+        public boolean beforeCall(Object who, Method method, Object... args) {
+            ShortcutInfo shortcutInfo = findFirstShortcutInfo(args);
+            replaceShortcutInfo(shortcutInfo, getHostPkg(), getPM());
+
+            return super.beforeCall(who, method, args);
+        }
+
+        @TargetApi(Build.VERSION_CODES.N_MR1)
+        private ShortcutInfo findFirstShortcutInfo(Object[] args) {
+            if (args == null) {
+                return null;
+            }
+            for (Object arg : args) {
+                if (arg.getClass() == mirror.android.content.pm.ShortcutInfo.TYPE) {
+                    return (ShortcutInfo) arg;
+                }
+            }
+            return null;
+        }
     }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/MethodProxies.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/MethodProxies.java
index fda1ae109..90053223b 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/MethodProxies.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/MethodProxies.java
@@ -1,31 +1,191 @@
 package com.lody.virtual.client.hook.proxies.telephony;
 
-import com.lody.virtual.client.core.VirtualCore;
+import android.os.Bundle;
+import android.telephony.CellIdentityCdma;
+import android.telephony.CellIdentityGsm;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoCdma;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellSignalStrengthCdma;
+import android.telephony.CellSignalStrengthGsm;
+import android.telephony.NeighboringCellInfo;
+import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.gsm.GsmCellLocation;
+
+import com.lody.virtual.client.hook.base.MethodProxy;
 import com.lody.virtual.client.hook.base.ReplaceCallingPkgMethodProxy;
+import com.lody.virtual.client.hook.base.StaticMethodProxy;
+import com.lody.virtual.client.ipc.VirtualLocationManager;
+import com.lody.virtual.helper.utils.marks.FakeDeviceMark;
+import com.lody.virtual.helper.utils.marks.FakeLocMark;
+import com.lody.virtual.remote.vloc.VCell;
 
 import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @author Lody
  */
-
+@SuppressWarnings("ALL")
 class MethodProxies {
 
-    static class GetDeviceId extends ReplaceCallingPkgMethodProxy {
+    @FakeDeviceMark("fake device id.")
+    static class GetDeviceId extends StaticMethodProxy {
 
         public GetDeviceId() {
             super("getDeviceId");
         }
 
         @Override
-        public Object afterCall(Object who, Method method, Object[] args, Object result) throws Throwable {
-            if (VirtualCore.get().getPhoneInfoDelegate() != null) {
-                String res = VirtualCore.get().getPhoneInfoDelegate().getDeviceId((String) result, getAppUserId());
-                if (res != null) {
-                    return res;
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            return getDeviceInfo().deviceId;
+        }
+    }
+
+    @FakeLocMark("cell location")
+    static class GetCellLocation extends ReplaceCallingPkgMethodProxy {
+
+        public GetCellLocation() {
+            super("getCellLocation");
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                VCell cell = VirtualLocationManager.get().getCell(getAppUserId(), getAppPkg());
+                if (cell != null) {
+                    return getCellLocationInternal(cell);
+                }
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+    static class getAllCellInfoUsingSubId extends ReplaceCallingPkgMethodProxy {
+
+        public getAllCellInfoUsingSubId() {
+            super("getAllCellInfoUsingSubId");
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                return null;
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+    @FakeLocMark("cell location")
+    static class GetAllCellInfo extends ReplaceCallingPkgMethodProxy {
+
+        public GetAllCellInfo() {
+            super("getAllCellInfo");
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                List<VCell> cells = VirtualLocationManager.get().getAllCell(getAppUserId(), getAppPkg());
+                if (cells != null) {
+                    List<CellInfo> result = new ArrayList<CellInfo>();
+                    for (VCell cell : cells) {
+                        result.add(createCellInfo(cell));
+                    }
+                    return result;
+                }
+
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+    @FakeLocMark("neb cell location")
+    static class GetNeighboringCellInfo extends ReplaceCallingPkgMethodProxy {
+
+        public GetNeighboringCellInfo() {
+            super("getNeighboringCellInfo");
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            if (isFakeLocationEnable()) {
+                List<VCell> cells = VirtualLocationManager.get().getNeighboringCell(getAppUserId(), getAppPkg());
+                if (cells != null) {
+                    List<NeighboringCellInfo> infos = new ArrayList<>();
+                    for (VCell cell : cells) {
+                        NeighboringCellInfo info = new NeighboringCellInfo();
+                        mirror.android.telephony.NeighboringCellInfo.mLac.set(info, cell.lac);
+                        mirror.android.telephony.NeighboringCellInfo.mCid.set(info, cell.cid);
+                        mirror.android.telephony.NeighboringCellInfo.mRssi.set(info, 6);
+                        infos.add(info);
+                    }
+                    return infos;
                 }
             }
-            return super.afterCall(who, method, args, result);
+            return super.call(who, method, args);
         }
     }
+
+    private static Bundle getCellLocationInternal(VCell cell) {
+        if (cell != null) {
+            Bundle cellData = new Bundle();
+            if (cell.type == 2) {
+                try {
+                    CdmaCellLocation cellLoc = new CdmaCellLocation();
+                    cellLoc.setCellLocationData(cell.baseStationId, Integer.MAX_VALUE, Integer.MAX_VALUE, cell.systemId, cell.networkId);
+                    cellLoc.fillInNotifierBundle(cellData);
+                } catch (Throwable e) {
+                    cellData.putInt("baseStationId", cell.baseStationId);
+                    cellData.putInt("baseStationLatitude", Integer.MAX_VALUE);
+                    cellData.putInt("baseStationLongitude", Integer.MAX_VALUE);
+                    cellData.putInt("systemId", cell.systemId);
+                    cellData.putInt("networkId", cell.networkId);
+                }
+            } else {
+                try {
+                    GsmCellLocation cellLoc = new GsmCellLocation();
+                    cellLoc.setLacAndCid(cell.lac, cell.cid);
+                    cellLoc.fillInNotifierBundle(cellData);
+                } catch (Throwable e) {
+                    cellData.putInt("lac", cell.lac);
+                    cellData.putInt("cid", cell.cid);
+                    cellData.putInt("psc", cell.psc);
+                }
+            }
+            return cellData;
+        }
+        return null;
+    }
+
+
+    private static CellInfo createCellInfo(VCell cell) {
+        if (cell.type == 2) { // CDMA
+            CellInfoCdma cdma = mirror.android.telephony.CellInfoCdma.ctor.newInstance();
+            CellIdentityCdma identityCdma = mirror.android.telephony.CellInfoCdma.mCellIdentityCdma.get(cdma);
+            CellSignalStrengthCdma strengthCdma = mirror.android.telephony.CellInfoCdma.mCellSignalStrengthCdma.get(cdma);
+            mirror.android.telephony.CellIdentityCdma.mNetworkId.set(identityCdma, cell.networkId);
+            mirror.android.telephony.CellIdentityCdma.mSystemId.set(identityCdma, cell.systemId);
+            mirror.android.telephony.CellIdentityCdma.mBasestationId.set(identityCdma, cell.baseStationId);
+            mirror.android.telephony.CellSignalStrengthCdma.mCdmaDbm.set(strengthCdma, -74);
+            mirror.android.telephony.CellSignalStrengthCdma.mCdmaEcio.set(strengthCdma, -91);
+            mirror.android.telephony.CellSignalStrengthCdma.mEvdoDbm.set(strengthCdma, -64);
+            mirror.android.telephony.CellSignalStrengthCdma.mEvdoSnr.set(strengthCdma, 7);
+            return cdma;
+        } else { // GSM
+            CellInfoGsm gsm = mirror.android.telephony.CellInfoGsm.ctor.newInstance();
+            CellIdentityGsm identityGsm = mirror.android.telephony.CellInfoGsm.mCellIdentityGsm.get(gsm);
+            CellSignalStrengthGsm strengthGsm = mirror.android.telephony.CellInfoGsm.mCellSignalStrengthGsm.get(gsm);
+            mirror.android.telephony.CellIdentityGsm.mMcc.set(identityGsm, cell.mcc);
+            mirror.android.telephony.CellIdentityGsm.mMnc.set(identityGsm, cell.mnc);
+            mirror.android.telephony.CellIdentityGsm.mLac.set(identityGsm, cell.lac);
+            mirror.android.telephony.CellIdentityGsm.mCid.set(identityGsm, cell.cid);
+            mirror.android.telephony.CellSignalStrengthGsm.mSignalStrength.set(strengthGsm, 20);
+            mirror.android.telephony.CellSignalStrengthGsm.mBitErrorRate.set(strengthGsm, 0);
+            return gsm;
+        }
+    }
+
+
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/TelephonyRegistryStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/TelephonyRegistryStub.java
index 55b6a0706..e0dbe2a86 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/TelephonyRegistryStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/TelephonyRegistryStub.java
@@ -1,9 +1,13 @@
 package com.lody.virtual.client.hook.proxies.telephony;
 
+import android.telephony.PhoneStateListener;
+
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
 import com.lody.virtual.client.hook.base.ReplaceCallingPkgMethodProxy;
 import com.lody.virtual.client.hook.base.ReplaceSequencePkgMethodProxy;
 
+import java.lang.reflect.Method;
+
 import mirror.com.android.internal.telephony.ITelephonyRegistry;
 
 public class TelephonyRegistryStub extends BinderInvocationProxy {
@@ -16,6 +20,24 @@ public TelephonyRegistryStub() {
 	protected void onBindMethods() {
 		super.onBindMethods();
 		addMethodProxy(new ReplaceCallingPkgMethodProxy("listen"));
-		addMethodProxy(new ReplaceSequencePkgMethodProxy("listenForSubscriber", 1));
+		addMethodProxy(new ReplaceSequencePkgMethodProxy("listenForSubscriber", 1) {
+			@Override
+			public boolean beforeCall(Object who, Method method, Object... args) {
+				if (android.os.Build.VERSION.SDK_INT >= 17) {
+					if (isFakeLocationEnable()) {
+						for (int i = args.length - 1; i > 0; i--) {
+							if (args[i] instanceof Integer) {
+								int events = (Integer) args[i];
+								events ^= PhoneStateListener.LISTEN_CELL_INFO;
+								events ^= PhoneStateListener.LISTEN_CELL_LOCATION;
+								args[i] = events;
+								break;
+							}
+						}
+					}
+				}
+				return super.beforeCall(who, method, args);
+			}
+		});
 	}
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/TelephonyStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/TelephonyStub.java
index f3268b225..ebdbbef34 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/TelephonyStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/telephony/TelephonyStub.java
@@ -22,9 +22,6 @@ public TelephonyStub() {
 	@Override
 	protected void onBindMethods() {
 		super.onBindMethods();
-		addMethodProxy(new ReplaceCallingPkgMethodProxy("getNeighboringCellInfo"));
-		addMethodProxy(new ReplaceCallingPkgMethodProxy("getAllCellInfo"));
-		addMethodProxy(new ReplaceCallingPkgMethodProxy("getCellLocation"));
 		addMethodProxy(new ReplaceCallingPkgMethodProxy("isOffhook"));
 		addMethodProxy(new ReplaceLastPkgMethodProxy("getLine1NumberForDisplay"));
 		addMethodProxy(new ReplaceLastPkgMethodProxy("isOffhookForSubscriber"));
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/usage/UsageStatsManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/usage/UsageStatsManagerStub.java
new file mode 100644
index 000000000..17d803a4f
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/usage/UsageStatsManagerStub.java
@@ -0,0 +1,29 @@
+package com.lody.virtual.client.hook.proxies.usage;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+
+import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.client.hook.base.ReplaceLastPkgMethodProxy;
+
+import mirror.android.app.IUsageStatsManager;
+
+/**
+ * Created by caokai on 2017/9/8.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
+public class UsageStatsManagerStub extends BinderInvocationProxy {
+
+    public UsageStatsManagerStub() {
+        super(IUsageStatsManager.Stub.asInterface, Context.USAGE_STATS_SERVICE);
+    }
+    @Override
+    protected void onBindMethods() {
+        super.onBindMethods();
+        addMethodProxy(new ReplaceLastPkgMethodProxy("queryUsageStats"));
+        addMethodProxy(new ReplaceLastPkgMethodProxy("queryConfigurations"));
+        addMethodProxy(new ReplaceLastPkgMethodProxy("queryEvents"));
+    }
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/user/UserManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/user/UserManagerStub.java
index 54e6ac9df..b54456233 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/user/UserManagerStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/user/UserManagerStub.java
@@ -10,34 +10,34 @@
 
 import java.util.Collections;
 
+import mirror.android.content.pm.UserInfo;
 import mirror.android.os.IUserManager;
 
 /**
  * @author Lody
- *
  */
 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
 public class UserManagerStub extends BinderInvocationProxy {
 
-	public UserManagerStub() {
-		super(IUserManager.Stub.asInterface, Context.USER_SERVICE);
-	}
+    public UserManagerStub() {
+        super(IUserManager.Stub.asInterface, Context.USER_SERVICE);
+    }
 
-	@Override
-	protected void onBindMethods() {
-		super.onBindMethods();
-		addMethodProxy(new ReplaceCallingPkgMethodProxy("setApplicationRestrictions"));
-		addMethodProxy(new ReplaceCallingPkgMethodProxy("getApplicationRestrictions"));
-		addMethodProxy(new ReplaceCallingPkgMethodProxy("getApplicationRestrictionsForUser"));
-		addMethodProxy(new ResultStaticMethodProxy("getProfileParent", null));
-		addMethodProxy(new ResultStaticMethodProxy("getUserIcon", null));
-		addMethodProxy(new ResultStaticMethodProxy("getUserInfo", null));
-		addMethodProxy(new ResultStaticMethodProxy("getDefaultGuestRestrictions", null));
-		addMethodProxy(new ResultStaticMethodProxy("setDefaultGuestRestrictions", null));
-		addMethodProxy(new ResultStaticMethodProxy("removeRestrictions", null));
-		addMethodProxy(new ResultStaticMethodProxy("getUsers", Collections.EMPTY_LIST));
-		addMethodProxy(new ResultStaticMethodProxy("createUser", null));
-		addMethodProxy(new ResultStaticMethodProxy("createProfileForUser", null));
-		addMethodProxy(new ResultStaticMethodProxy("getProfiles", Collections.EMPTY_LIST));
-	}
+    @Override
+    protected void onBindMethods() {
+        super.onBindMethods();
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("setApplicationRestrictions"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getApplicationRestrictions"));
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getApplicationRestrictionsForUser"));
+        addMethodProxy(new ResultStaticMethodProxy("getProfileParent", null));
+        addMethodProxy(new ResultStaticMethodProxy("getUserIcon", null));
+        addMethodProxy(new ResultStaticMethodProxy("getUserInfo", UserInfo.ctor.newInstance(0, "Admin", UserInfo.FLAG_PRIMARY.get())));
+        addMethodProxy(new ResultStaticMethodProxy("getDefaultGuestRestrictions", null));
+        addMethodProxy(new ResultStaticMethodProxy("setDefaultGuestRestrictions", null));
+        addMethodProxy(new ResultStaticMethodProxy("removeRestrictions", null));
+        addMethodProxy(new ResultStaticMethodProxy("getUsers", Collections.EMPTY_LIST));
+        addMethodProxy(new ResultStaticMethodProxy("createUser", null));
+        addMethodProxy(new ResultStaticMethodProxy("createProfileForUser", null));
+        addMethodProxy(new ResultStaticMethodProxy("getProfiles", Collections.EMPTY_LIST));
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/view/AutoFillManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/view/AutoFillManagerStub.java
new file mode 100644
index 000000000..494327fef
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/view/AutoFillManagerStub.java
@@ -0,0 +1,80 @@
+package com.lody.virtual.client.hook.proxies.view;
+
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.util.Log;
+
+import com.lody.virtual.client.hook.base.BinderInvocationProxy;
+import com.lody.virtual.client.hook.base.ReplaceLastPkgMethodProxy;
+import com.lody.virtual.helper.utils.ArrayUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import mirror.android.view.IAutoFillManager;
+
+/**
+ * @author 陈磊.
+ */
+
+public class AutoFillManagerStub extends BinderInvocationProxy {
+
+    private static final String TAG = "AutoFillManagerStub";
+
+    private static final String AUTO_FILL_NAME = "autofill";
+    public AutoFillManagerStub() {
+        super(IAutoFillManager.Stub.asInterface, AUTO_FILL_NAME);
+    }
+
+    @SuppressLint("WrongConstant")
+    @Override
+    public void inject() throws Throwable {
+        super.inject();
+        try {
+            Object AutoFillManagerInstance = getContext().getSystemService(AUTO_FILL_NAME);
+            if (AutoFillManagerInstance == null) {
+                throw new NullPointerException("AutoFillManagerInstance is null.");
+            }
+            Object AutoFillManagerProxy = getInvocationStub().getProxyInterface();
+            if (AutoFillManagerProxy == null) {
+                throw new NullPointerException("AutoFillManagerProxy is null.");
+            }
+            Field AutoFillManagerServiceField = AutoFillManagerInstance.getClass().getDeclaredField("mService");
+            AutoFillManagerServiceField.setAccessible(true);
+            AutoFillManagerServiceField.set(AutoFillManagerInstance, AutoFillManagerProxy);
+        } catch (Throwable tr) {
+            Log.e(TAG, "AutoFillManagerStub inject error.", tr);
+            return;
+        }
+
+        addMethodProxy(new ReplacePkgAndComponentProxy("startSession"));
+        addMethodProxy(new ReplacePkgAndComponentProxy("updateOrRestartSession"));
+        addMethodProxy(new ReplaceLastPkgMethodProxy("isServiceEnabled"));
+    }
+
+    static class ReplacePkgAndComponentProxy extends ReplaceLastPkgMethodProxy {
+
+        ReplacePkgAndComponentProxy(String name) {
+            super(name);
+        }
+
+        @Override
+        public boolean beforeCall(Object who, Method method, Object... args) {
+            replaceLastAppComponent(args, getHostPkg());
+            return super.beforeCall(who, method, args);
+        }
+
+        static ComponentName replaceLastAppComponent(Object[] args, String hostPkg) {
+            int index = ArrayUtils.indexOfLast(args, ComponentName.class);
+            if (index != -1) {
+                ComponentName orig = (ComponentName) args[index];
+                ComponentName newComponent = new ComponentName(hostPkg, orig.getClassName());
+                args[index] = newComponent;
+                return newComponent;
+            }
+            return null;
+        }
+    }
+
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/wifi/MethodProxies.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/wifi/MethodProxies.java
deleted file mode 100644
index 6131d4738..000000000
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/wifi/MethodProxies.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.lody.virtual.client.hook.proxies.wifi;
-
-import com.lody.virtual.client.hook.base.MethodProxy;
-import com.lody.virtual.client.hook.utils.MethodParameterUtils;
-
-import java.lang.reflect.Method;
-
-/**
- * @author Lody
- */
-
-class MethodProxies {
-
-    static class GetBatchedScanResults extends MethodProxy {
-
-        @Override
-        public String getMethodName() {
-            return "getBatchedScanResults";
-        }
-
-        @Override
-        public Object call(Object who, Method method, Object... args) throws Throwable {
-            MethodParameterUtils.replaceFirstAppPkg(args);
-            return method.invoke(who, args);
-        }
-    }
-
-    static class GetScanResults extends MethodProxy {
-
-        @Override
-        public String getMethodName() {
-            return "getScanResults";
-        }
-
-        @Override
-        public Object call(Object who, Method method, Object... args) throws Throwable {
-            MethodParameterUtils.replaceFirstAppPkg(args);
-            return method.invoke(who, args);
-        }
-    }
-
-    static class SetWifiEnabled extends MethodProxy {
-
-        @Override
-        public String getMethodName() {
-            return "setWifiEnabled";
-        }
-
-        @Override
-        public Object call(Object who, Method method, Object... args) throws Throwable {
-            MethodParameterUtils.replaceFirstAppPkg(args);
-            return method.invoke(who, args);
-        }
-    }
-}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/wifi/WifiManagerStub.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/wifi/WifiManagerStub.java
index dc64b32bc..299257107 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/wifi/WifiManagerStub.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/proxies/wifi/WifiManagerStub.java
@@ -1,27 +1,63 @@
 package com.lody.virtual.client.hook.proxies.wifi;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
+import android.net.DhcpInfo;
+import android.net.wifi.ScanResult;
+import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiInfo;
-import android.text.TextUtils;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.WorkSource;
 
-import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.hook.base.BinderInvocationProxy;
-import com.lody.virtual.client.hook.base.Inject;
 import com.lody.virtual.client.hook.base.MethodProxy;
-import com.lody.virtual.client.hook.delegate.PhoneInfoDelegate;
+import com.lody.virtual.client.hook.base.ReplaceCallingPkgMethodProxy;
+import com.lody.virtual.client.hook.base.StaticMethodProxy;
+import com.lody.virtual.client.ipc.VirtualLocationManager;
+import com.lody.virtual.client.stub.VASettings;
+import com.lody.virtual.helper.utils.ArrayUtils;
+import com.lody.virtual.helper.utils.Reflect;
+import com.lody.virtual.helper.utils.marks.FakeDeviceMark;
+import com.lody.virtual.helper.utils.marks.FakeLocMark;
+import com.lody.virtual.remote.vloc.VWifi;
 
 import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
 
 import mirror.android.net.wifi.IWifiManager;
+import mirror.android.net.wifi.WifiSsid;
 
 /**
  * @author Lody
  * @see android.net.wifi.WifiManager
  */
-@SuppressLint("HardwareIds")
-@Inject(MethodProxies.class)
 public class WifiManagerStub extends BinderInvocationProxy {
+
+    private class RemoveWorkSourceMethodProxy extends StaticMethodProxy {
+
+        RemoveWorkSourceMethodProxy(String name) {
+            super(name);
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+            int index = ArrayUtils.indexOfFirst(args, WorkSource.class);
+            if (index >= 0) {
+                args[index] = null;
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+
     public WifiManagerStub() {
         super(IWifiManager.Stub.asInterface, Context.WIFI_SERVICE);
     }
@@ -29,11 +65,68 @@ public WifiManagerStub() {
     @Override
     protected void onBindMethods() {
         super.onBindMethods();
+        addMethodProxy(new MethodProxy() {
+            @Override
+            public String getMethodName() {
+                return "isWifiEnabled";
+            }
+
+            @Override
+            public Object call(Object who, Method method, Object... args) throws Throwable {
+                if (VASettings.Wifi.FAKE_WIFI_STATE) {
+                    return true;
+                }
+                return super.call(who, method, args);
+            }
+        });
+        addMethodProxy(new MethodProxy() {
+            @Override
+            public String getMethodName() {
+                return "getWifiEnabledState";
+            }
+
+            @Override
+            public Object call(Object who, Method method, Object... args) throws Throwable {
+                if (VASettings.Wifi.FAKE_WIFI_STATE) {
+                    return WifiManager.WIFI_STATE_ENABLED;
+                }
+                return super.call(who, method, args);
+            }
+        });
+        addMethodProxy(new MethodProxy() {
+            @Override
+            public String getMethodName() {
+                return "createDhcpInfo";
+            }
+
+            @Override
+            public Object call(Object who, Method method, Object... args) throws Throwable {
+                if (VASettings.Wifi.FAKE_WIFI_STATE) {
+                    IPInfo ipInfo = getIPInfo();
+                    if (ipInfo != null) {
+                        return createDhcpInfo(ipInfo);
+                    }
+                }
+                return super.call(who, method, args);
+            }
+        });
         addMethodProxy(new GetConnectionInfo());
+        addMethodProxy(new GetScanResults());
+        addMethodProxy(new ReplaceCallingPkgMethodProxy("getBatchedScanResults"));
+        addMethodProxy(new RemoveWorkSourceMethodProxy("acquireWifiLock"));
+        addMethodProxy(new RemoveWorkSourceMethodProxy("updateWifiLockWorkSource"));
+        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
+            addMethodProxy(new RemoveWorkSourceMethodProxy("startLocationRestrictedScan"));
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            addMethodProxy(new RemoveWorkSourceMethodProxy("startScan"));
+            addMethodProxy(new RemoveWorkSourceMethodProxy("requestBatchedScan"));
+        }
     }
 
-    private static class GetConnectionInfo extends MethodProxy {
-
+    @FakeLocMark("Fake wifi bssid")
+    @FakeDeviceMark("fake wifi MAC")
+    private final class GetConnectionInfo extends MethodProxy {
         @Override
         public String getMethodName() {
             return "getConnectionInfo";
@@ -41,18 +134,133 @@ public String getMethodName() {
 
         @Override
         public Object call(Object who, Method method, Object... args) throws Throwable {
-            WifiInfo info = (WifiInfo) method.invoke(who, args);
-            PhoneInfoDelegate delegate = VirtualCore.get().getPhoneInfoDelegate();
-            if (info != null && delegate != null) {
-                String oldMac = info.getMacAddress();
-                if (oldMac != null) {
-                    String newMac = delegate.getMacAddress(oldMac, getAppUserId());
-                    if (!TextUtils.equals(oldMac, newMac)) {
-                        mirror.android.net.wifi.WifiInfo.mMacAddress.set(info, newMac);
+            WifiInfo wifiInfo = (WifiInfo) method.invoke(who, args);
+            if (isFakeLocationEnable()) {
+                mirror.android.net.wifi.WifiInfo.mBSSID.set(wifiInfo, "00:00:00:00:00:00");
+                mirror.android.net.wifi.WifiInfo.mMacAddress.set(wifiInfo, "00:00:00:00:00:00");
+            }
+            if (VASettings.Wifi.FAKE_WIFI_STATE) {
+                return createWifiInfo();
+            }
+            if (wifiInfo != null) {
+                mirror.android.net.wifi.WifiInfo.mMacAddress.set(wifiInfo, getDeviceInfo().wifiMac);
+            }
+            return wifiInfo;
+        }
+    }
+
+    @FakeLocMark("fake scan result")
+    private final class GetScanResults extends ReplaceCallingPkgMethodProxy {
+
+        public GetScanResults() {
+            super("getScanResults");
+        }
+
+        @Override
+        public Object call(Object who, Method method, Object... args) throws Throwable {
+//            noinspection unchecked
+            if (isFakeLocationEnable()) {
+                new ArrayList<ScanResult>(0);
+            }
+            return super.call(who, method, args);
+        }
+    }
+
+    private static ScanResult cloneScanResult(Parcelable scanResult) {
+        Parcel p = Parcel.obtain();
+        scanResult.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        ScanResult newScanResult = Reflect.on(scanResult).field("CREATOR").call("createFromParcel", p).get();
+        p.recycle();
+        return newScanResult;
+    }
+
+    public static class IPInfo {
+        NetworkInterface intf;
+        InetAddress addr;
+        String ip;
+        int ip_hex;
+        int netmask_hex;
+    }
+
+
+    private static IPInfo getIPInfo() {
+        try {
+            List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+            for (NetworkInterface intf : interfaces) {
+                List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
+                for (InetAddress addr : addrs) {
+                    if (!addr.isLoopbackAddress()) {
+                        String sAddr = addr.getHostAddress().toUpperCase();
+                        boolean isIPv4 = isIPv4Address(sAddr);
+                        if (isIPv4) {
+                            IPInfo info = new IPInfo();
+                            info.addr = addr;
+                            info.intf = intf;
+                            info.ip = sAddr;
+                            info.ip_hex = InetAddress_to_hex(addr);
+                            info.netmask_hex = netmask_to_hex(intf.getInterfaceAddresses().get(0).getNetworkPrefixLength());
+                            return info;
+                        }
                     }
                 }
             }
-            return info;
+        } catch (SocketException e) {
+            e.printStackTrace();
         }
+        return null;
+    }
+
+    private static boolean isIPv4Address(String input) {
+        Pattern IPV4_PATTERN = Pattern.compile("^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
+        return IPV4_PATTERN.matcher(input).matches();
+    }
+
+    private static int netmask_to_hex(int netmask_slash) {
+        int r = 0;
+        int b = 1;
+        for (int i = 0; i < netmask_slash; i++, b = b << 1)
+            r |= b;
+        return r;
+    }
+
+    private static int InetAddress_to_hex(InetAddress a) {
+        int result = 0;
+        byte b[] = a.getAddress();
+        for (int i = 0; i < 4; i++)
+            result |= (b[i] & 0xff) << (8 * i);
+        return result;
     }
+
+    private DhcpInfo createDhcpInfo(IPInfo ip) {
+        DhcpInfo i = new DhcpInfo();
+        i.ipAddress = ip.ip_hex;
+        i.netmask = ip.netmask_hex;
+        i.dns1 = 0x04040404;
+        i.dns2 = 0x08080808;
+        return i;
+    }
+
+    private static WifiInfo createWifiInfo() throws Exception {
+        WifiInfo info = mirror.android.net.wifi.WifiInfo.ctor.newInstance();
+        IPInfo ip = getIPInfo();
+        InetAddress address = (ip != null ? ip.addr : null);
+        mirror.android.net.wifi.WifiInfo.mNetworkId.set(info, 1);
+        mirror.android.net.wifi.WifiInfo.mSupplicantState.set(info, SupplicantState.COMPLETED);
+        mirror.android.net.wifi.WifiInfo.mBSSID.set(info, VASettings.Wifi.BSSID);
+        mirror.android.net.wifi.WifiInfo.mMacAddress.set(info, VASettings.Wifi.MAC);
+        mirror.android.net.wifi.WifiInfo.mIpAddress.set(info, address);
+        mirror.android.net.wifi.WifiInfo.mLinkSpeed.set(info, 65);
+        if (Build.VERSION.SDK_INT >= 21) {
+            mirror.android.net.wifi.WifiInfo.mFrequency.set(info, 5000); // MHz
+        }
+        mirror.android.net.wifi.WifiInfo.mRssi.set(info, 200); // MAX_RSSI
+        if (mirror.android.net.wifi.WifiInfo.mWifiSsid != null) {
+            mirror.android.net.wifi.WifiInfo.mWifiSsid.set(info, WifiSsid.createFromAsciiEncoded.call(VASettings.Wifi.SSID));
+        } else {
+            mirror.android.net.wifi.WifiInfo.mSSID.set(info, VASettings.Wifi.SSID);
+        }
+        return info;
+    }
+
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/ServiceConnectionDelegate.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/ServiceConnectionDelegate.java
index 320c93d6c..f901fe983 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/ServiceConnectionDelegate.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/secondary/ServiceConnectionDelegate.java
@@ -2,19 +2,29 @@
 
 import android.app.IServiceConnection;
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Build;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.lody.virtual.client.VClientImpl;
+import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.helper.collection.ArrayMap;
 import com.lody.virtual.server.IBinderDelegateService;
 
+import mirror.android.app.ActivityThread;
+import mirror.android.app.ContextImpl;
+import mirror.android.app.IServiceConnectionO;
+import mirror.android.app.LoadedApk;
+
 /**
  * @author Lody
  */
 
 public class ServiceConnectionDelegate extends IServiceConnection.Stub {
-
     private final static ArrayMap<IBinder, ServiceConnectionDelegate> DELEGATE_MAP = new ArrayMap<>();
     private IServiceConnection mConn;
 
@@ -22,6 +32,39 @@ private ServiceConnectionDelegate(IServiceConnection mConn) {
         this.mConn = mConn;
     }
 
+    public static IServiceConnection getDelegate(Context context, ServiceConnection connection,int flags) {
+        IServiceConnection sd = null;
+        if (connection == null) {
+            throw new IllegalArgumentException("connection is null");
+        }
+        try {
+            Object activityThread = ActivityThread.currentActivityThread.call();
+            Object loadApk = ContextImpl.mPackageInfo.get(VirtualCore.get().getContext());
+            Handler handler = ActivityThread.getHandler.call(activityThread);
+            sd = LoadedApk.getServiceDispatcher.call(loadApk, connection, context, handler, flags);
+        } catch (Exception e) {
+            Log.e("ConnectionDelegate", "getServiceDispatcher", e);
+        }
+        if (sd == null) {
+            throw new RuntimeException("Not supported in system context");
+        }
+        return getDelegate(sd);
+    }
+
+    public static IServiceConnection removeDelegate(Context context, ServiceConnection conn) {
+        IServiceConnection connection = null;
+        try{
+            Object loadApk = ContextImpl.mPackageInfo.get(VirtualCore.get().getContext());
+            connection = LoadedApk.forgetServiceDispatcher.call(loadApk, context, conn);
+        }catch (Exception e){
+            Log.e("ConnectionDelegate", "forgetServiceDispatcher", e);
+        }
+        if(connection == null){
+            return null;
+        }
+        return ServiceConnectionDelegate.removeDelegate(connection);
+    }
+
     public static ServiceConnectionDelegate getDelegate(IServiceConnection conn) {
         if(conn instanceof ServiceConnectionDelegate){
             return (ServiceConnectionDelegate)conn;
@@ -41,6 +84,10 @@ public static ServiceConnectionDelegate removeDelegate(IServiceConnection conn)
 
     @Override
     public void connected(ComponentName name, IBinder service) throws RemoteException {
+        connected(name, service, false);
+    }
+
+    public void connected(ComponentName name, IBinder service, boolean dead) throws RemoteException {
         IBinderDelegateService delegateService = IBinderDelegateService.Stub.asInterface(service);
         if (delegateService != null) {
             name = delegateService.getComponent();
@@ -50,6 +97,11 @@ public void connected(ComponentName name, IBinder service) throws RemoteExceptio
                 service = proxy;
             }
         }
-        mConn.connected(name, service);
+
+        if(Build.VERSION.SDK_INT>=26) {
+            IServiceConnectionO.connected.call(mConn, name, service, dead);
+        }else {
+            mConn.connected(name, service);
+        }
     }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/utils/MethodParameterUtils.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/utils/MethodParameterUtils.java
index 0bc648e12..088e1f18e 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/utils/MethodParameterUtils.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/utils/MethodParameterUtils.java
@@ -12,6 +12,17 @@
  */
 public class MethodParameterUtils {
 
+	public static <T> T getFirstParam(Object[] args, Class<T> tClass) {
+		if (args == null) {
+			return null;
+		}
+		int index = ArrayUtils.indexOfFirst(args, tClass);
+		if (index != -1) {
+			return (T) args[index];
+		}
+		return null;
+	}
+
 	public static String replaceFirstAppPkg(Object[] args) {
 		if (args == null) {
 			return null;
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/ServiceManagerNative.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/ServiceManagerNative.java
index 0f0ac2bce..35d7f4d02 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/ServiceManagerNative.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/ServiceManagerNative.java
@@ -16,97 +16,97 @@
  */
 public class ServiceManagerNative {
 
-	public static final String PACKAGE = "package";
-	public static final String ACTIVITY = "activity";
-	public static final String USER = "user";
-	public static final String APP = "app";
-	public static final String ACCOUNT = "account";
-	public static final String JOB = "job";
-	public static final String NOTIFICATION ="notification";
-	public static final String VS ="vs";
+    public static final String PACKAGE = "package";
+    public static final String ACTIVITY = "activity";
+    public static final String USER = "user";
+    public static final String APP = "app";
+    public static final String ACCOUNT = "account";
+    public static final String JOB = "job";
+    public static final String NOTIFICATION = "notification";
+    public static final String VS = "vs";
+    public static final String DEVICE = "device";
+    public static final String VIRTUAL_LOC = "virtual-loc";
 
-	public static final String SERVICE_DEF_AUTH = "virtual.service.BinderProvider";
-	private static final String TAG = ServiceManagerNative.class.getSimpleName();
-	public static String SERVICE_CP_AUTH = "virtual.service.BinderProvider";
+    public static final String SERVICE_DEF_AUTH = "virtual.service.BinderProvider";
+    private static final String TAG = ServiceManagerNative.class.getSimpleName();
+    public static String SERVICE_CP_AUTH = "virtual.service.BinderProvider";
 
-	private static IServiceFetcher sFetcher;
+    private static IServiceFetcher sFetcher;
 
-	private static IServiceFetcher getServiceFetcher() {
-		if (sFetcher == null) {
-			synchronized (ServiceManagerNative.class) {
-				if (sFetcher == null) {
-					Context context = VirtualCore.get().getContext();
-					Bundle response = new ProviderCall.Builder(context, SERVICE_CP_AUTH).methodName("@").call();
-					if (response != null) {
-						IBinder binder = BundleCompat.getBinder(response, "_VA_|_binder_");
-						linkBinderDied(binder);
-						sFetcher = IServiceFetcher.Stub.asInterface(binder);
-					}
-				}
-			}
-		}
-		return sFetcher;
-	}
+    private static IServiceFetcher getServiceFetcher() {
+        if (sFetcher == null || !sFetcher.asBinder().isBinderAlive()) {
+            synchronized (ServiceManagerNative.class) {
+                Context context = VirtualCore.get().getContext();
+                Bundle response = new ProviderCall.Builder(context, SERVICE_CP_AUTH).methodName("@").call();
+                if (response != null) {
+                    IBinder binder = BundleCompat.getBinder(response, "_VA_|_binder_");
+                    linkBinderDied(binder);
+                    sFetcher = IServiceFetcher.Stub.asInterface(binder);
+                }
+            }
+        }
+        return sFetcher;
+    }
 
-	public static void ensureServerStarted() {
+    public static void ensureServerStarted() {
         new ProviderCall.Builder(VirtualCore.get().getContext(), SERVICE_CP_AUTH).methodName("ensure_created").call();
-	}
+    }
 
-	public static void clearServerFetcher() {
+    public static void clearServerFetcher() {
         sFetcher = null;
     }
 
-	private static void linkBinderDied(final IBinder binder) {
-		IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
-			@Override
-			public void binderDied() {
-				binder.unlinkToDeath(this, 0);
-			}
-		};
-		try {
-			binder.linkToDeath(deathRecipient, 0);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
+    private static void linkBinderDied(final IBinder binder) {
+        IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
+            @Override
+            public void binderDied() {
+                binder.unlinkToDeath(this, 0);
+            }
+        };
+        try {
+            binder.linkToDeath(deathRecipient, 0);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
 
-	public static IBinder getService(String name) {
-		if (VirtualCore.get().isServerProcess()) {
-			return ServiceCache.getService(name);
-		}
-		IServiceFetcher fetcher = getServiceFetcher();
-		if (fetcher != null) {
-			try {
-				return fetcher.getService(name);
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-		}
-		VLog.e(TAG, "GetService(%s) return null.", name);
-		return null;
-	}
+    public static IBinder getService(String name) {
+        if (VirtualCore.get().isServerProcess()) {
+            return ServiceCache.getService(name);
+        }
+        IServiceFetcher fetcher = getServiceFetcher();
+        if (fetcher != null) {
+            try {
+                return fetcher.getService(name);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+        VLog.e(TAG, "GetService(%s) return null.", name);
+        return null;
+    }
 
-	public static void addService(String name, IBinder service) {
-		IServiceFetcher fetcher = getServiceFetcher();
-		if (fetcher != null) {
-			try {
-				fetcher.addService(name, service);
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-		}
+    public static void addService(String name, IBinder service) {
+        IServiceFetcher fetcher = getServiceFetcher();
+        if (fetcher != null) {
+            try {
+                fetcher.addService(name, service);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
 
-	}
+    }
 
-	public static void removeService(String name) {
-		IServiceFetcher fetcher = getServiceFetcher();
-		if (fetcher != null) {
-			try {
-				fetcher.removeService(name);
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-		}
-	}
+    public static void removeService(String name) {
+        IServiceFetcher fetcher = getServiceFetcher();
+        if (fetcher != null) {
+            try {
+                fetcher.removeService(name);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+    }
 
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VAccountManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VAccountManager.java
index ea4724b9d..646b17a81 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VAccountManager.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VAccountManager.java
@@ -1,231 +1,289 @@
 package com.lody.virtual.client.ipc;
 
 import android.accounts.Account;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
 import android.accounts.AuthenticatorDescription;
 import android.accounts.IAccountManagerResponse;
+import android.app.Activity;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.RemoteException;
 
+import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.env.VirtualRuntime;
+import com.lody.virtual.client.stub.AmsTask;
 import com.lody.virtual.os.VUserHandle;
 import com.lody.virtual.server.IAccountManager;
 
+import static com.lody.virtual.helper.compat.AccountManagerCompat.KEY_ANDROID_PACKAGE_NAME;
+
 /**
  * @author Lody
  */
 
 public class VAccountManager {
 
-	private static VAccountManager sMgr = new VAccountManager();
-
-	private IAccountManager mRemote;
-
-	public static VAccountManager get() {
-		return sMgr;
-	}
-
-	public IAccountManager getRemote() {
-		if (mRemote == null) {
-			Object remote = getStubInterface();
-			mRemote = LocalProxyUtils.genProxy(IAccountManager.class, remote);
-		}
-		return mRemote;
-	}
-
-	private Object getStubInterface() {
-		return IAccountManager.Stub
-				.asInterface(ServiceManagerNative.getService(ServiceManagerNative.ACCOUNT));
-	}
-
-	public AuthenticatorDescription[] getAuthenticatorTypes() {
-		try {
-			return getRemote().getAuthenticatorTypes(VUserHandle.myUserId());
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public void removeAccount(IAccountManagerResponse response, Account account, boolean expectActivityLaunch) {
-		try {
-			getRemote().removeAccount(VUserHandle.myUserId(), response, account, expectActivityLaunch);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void getAuthToken(IAccountManagerResponse response, Account account, String authTokenType, boolean notifyOnAuthFailure, boolean expectActivityLaunch, Bundle loginOptions) {
-		try {
-			getRemote().getAuthToken(VUserHandle.myUserId(), response, account, authTokenType, notifyOnAuthFailure, expectActivityLaunch, loginOptions);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public boolean addAccountExplicitly(Account account, String password, Bundle extras) {
-		try {
-			return getRemote().addAccountExplicitly(VUserHandle.myUserId(), account, password, extras);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public Account[] getAccounts(String type) {
-		try {
-			return getRemote().getAccounts(VUserHandle.myUserId(), type);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public String peekAuthToken(Account account, String authTokenType) {
-		try {
-			return getRemote().peekAuthToken(VUserHandle.myUserId(), account, authTokenType);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public String getPreviousName(Account account) {
-		try {
-			return getRemote().getPreviousName(VUserHandle.myUserId(), account);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public void hasFeatures(IAccountManagerResponse response, Account account, String[] features) {
-		try {
-			getRemote().hasFeatures(VUserHandle.myUserId(), response, account, features);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public boolean accountAuthenticated(Account account) {
-		try {
-			return getRemote().accountAuthenticated(VUserHandle.myUserId(), account);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public void clearPassword(Account account) {
-		try {
-			getRemote().clearPassword(VUserHandle.myUserId(), account);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void renameAccount(IAccountManagerResponse response, Account accountToRename, String newName) {
-		try {
-			getRemote().renameAccount(VUserHandle.myUserId(), response, accountToRename, newName);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void setPassword(Account account, String password) {
-		try {
-			getRemote().setPassword(VUserHandle.myUserId(), account, password);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void addAccount(IAccountManagerResponse response, String accountType, String authTokenType, String[] requiredFeatures, boolean expectActivityLaunch, Bundle optionsIn) {
-		try {
-			getRemote().addAccount(VUserHandle.myUserId(), response, accountType, authTokenType, requiredFeatures, expectActivityLaunch, optionsIn);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void updateCredentials(IAccountManagerResponse response, Account account, String authTokenType, boolean expectActivityLaunch, Bundle loginOptions) {
-		try {
-			getRemote().updateCredentials(VUserHandle.myUserId(), response, account, authTokenType, expectActivityLaunch, loginOptions);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public boolean removeAccountExplicitly(Account account) {
-		try {
-			return getRemote().removeAccountExplicitly(VUserHandle.myUserId(), account);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public void setUserData(Account account, String key, String value) {
-		try {
-			getRemote().setUserData(VUserHandle.myUserId(), account, key, value);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void editProperties(IAccountManagerResponse response, String accountType, boolean expectActivityLaunch) {
-		try {
-			getRemote().editProperties(VUserHandle.myUserId(), response, accountType, expectActivityLaunch);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void getAuthTokenLabel(IAccountManagerResponse response, String accountType, String authTokenType) {
-		try {
-			getRemote().getAuthTokenLabel(VUserHandle.myUserId(), response, accountType, authTokenType);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void confirmCredentials(IAccountManagerResponse response, Account account, Bundle options, boolean expectActivityLaunch) {
-		try {
-			getRemote().confirmCredentials(VUserHandle.myUserId(), response, account, options, expectActivityLaunch);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void invalidateAuthToken(String accountType, String authToken) {
-		try {
-			getRemote().invalidateAuthToken(VUserHandle.myUserId(), accountType, authToken);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void getAccountsByFeatures(IAccountManagerResponse response, String type, String[] features) {
-		try {
-			getRemote().getAccountsByFeatures(VUserHandle.myUserId(), response, type, features);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void setAuthToken(Account account, String authTokenType, String authToken) {
-		try {
-			getRemote().setAuthToken(VUserHandle.myUserId(), account, authTokenType, authToken);
-		} catch (RemoteException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public Object getPassword(Account account) {
-		try {
-			return getRemote().getPassword(VUserHandle.myUserId(), account);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public String getUserData(Account account, String key) {
-		try {
-			return getRemote().getUserData(VUserHandle.myUserId(), account, key);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
+    private static VAccountManager sMgr = new VAccountManager();
+
+    private IAccountManager mRemote;
+
+    public static VAccountManager get() {
+        return sMgr;
+    }
+
+    public IAccountManager getRemote() {
+        if (mRemote == null ||
+                (!mRemote.asBinder().pingBinder() && !VirtualCore.get().isVAppProcess())) {
+            synchronized (VAccountManager.class) {
+                Object remote = getStubInterface();
+                mRemote = LocalProxyUtils.genProxy(IAccountManager.class, remote);
+            }
+        }
+        return mRemote;
+    }
+
+    private Object getStubInterface() {
+        return IAccountManager.Stub
+                .asInterface(ServiceManagerNative.getService(ServiceManagerNative.ACCOUNT));
+    }
+
+    public AuthenticatorDescription[] getAuthenticatorTypes() {
+        try {
+            return getRemote().getAuthenticatorTypes(VUserHandle.myUserId());
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public void removeAccount(IAccountManagerResponse response, Account account, boolean expectActivityLaunch) {
+        try {
+            getRemote().removeAccount(VUserHandle.myUserId(), response, account, expectActivityLaunch);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void getAuthToken(IAccountManagerResponse response, Account account, String authTokenType, boolean notifyOnAuthFailure, boolean expectActivityLaunch, Bundle loginOptions) {
+        try {
+            getRemote().getAuthToken(VUserHandle.myUserId(), response, account, authTokenType, notifyOnAuthFailure, expectActivityLaunch, loginOptions);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public boolean addAccountExplicitly(Account account, String password, Bundle extras) {
+        try {
+            return getRemote().addAccountExplicitly(VUserHandle.myUserId(), account, password, extras);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public Account[] getAccounts(int userId, String type) {
+        try {
+            return getRemote().getAccounts(userId, type);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public Account[] getAccounts(String type) {
+        try {
+            return getRemote().getAccounts(VUserHandle.myUserId(), type);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public String peekAuthToken(Account account, String authTokenType) {
+        try {
+            return getRemote().peekAuthToken(VUserHandle.myUserId(), account, authTokenType);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public String getPreviousName(Account account) {
+        try {
+            return getRemote().getPreviousName(VUserHandle.myUserId(), account);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public void hasFeatures(IAccountManagerResponse response, Account account, String[] features) {
+        try {
+            getRemote().hasFeatures(VUserHandle.myUserId(), response, account, features);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public boolean accountAuthenticated(Account account) {
+        try {
+            return getRemote().accountAuthenticated(VUserHandle.myUserId(), account);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public void clearPassword(Account account) {
+        try {
+            getRemote().clearPassword(VUserHandle.myUserId(), account);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void renameAccount(IAccountManagerResponse response, Account accountToRename, String newName) {
+        try {
+            getRemote().renameAccount(VUserHandle.myUserId(), response, accountToRename, newName);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void setPassword(Account account, String password) {
+        try {
+            getRemote().setPassword(VUserHandle.myUserId(), account, password);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void addAccount(int userId, IAccountManagerResponse response, String accountType, String authTokenType, String[] requiredFeatures, boolean expectActivityLaunch, Bundle optionsIn) {
+        try {
+            getRemote().addAccount(userId, response, accountType, authTokenType, requiredFeatures, expectActivityLaunch, optionsIn);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void addAccount(IAccountManagerResponse response, String accountType, String authTokenType, String[] requiredFeatures, boolean expectActivityLaunch, Bundle optionsIn) {
+        try {
+            getRemote().addAccount(VUserHandle.myUserId(), response, accountType, authTokenType, requiredFeatures, expectActivityLaunch, optionsIn);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void updateCredentials(IAccountManagerResponse response, Account account, String authTokenType, boolean expectActivityLaunch, Bundle loginOptions) {
+        try {
+            getRemote().updateCredentials(VUserHandle.myUserId(), response, account, authTokenType, expectActivityLaunch, loginOptions);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public boolean removeAccountExplicitly(Account account) {
+        try {
+            return getRemote().removeAccountExplicitly(VUserHandle.myUserId(), account);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public void setUserData(Account account, String key, String value) {
+        try {
+            getRemote().setUserData(VUserHandle.myUserId(), account, key, value);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void editProperties(IAccountManagerResponse response, String accountType, boolean expectActivityLaunch) {
+        try {
+            getRemote().editProperties(VUserHandle.myUserId(), response, accountType, expectActivityLaunch);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void getAuthTokenLabel(IAccountManagerResponse response, String accountType, String authTokenType) {
+        try {
+            getRemote().getAuthTokenLabel(VUserHandle.myUserId(), response, accountType, authTokenType);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void confirmCredentials(IAccountManagerResponse response, Account account, Bundle options, boolean expectActivityLaunch) {
+        try {
+            getRemote().confirmCredentials(VUserHandle.myUserId(), response, account, options, expectActivityLaunch);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void invalidateAuthToken(String accountType, String authToken) {
+        try {
+            getRemote().invalidateAuthToken(VUserHandle.myUserId(), accountType, authToken);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void getAccountsByFeatures(IAccountManagerResponse response, String type, String[] features) {
+        try {
+            getRemote().getAccountsByFeatures(VUserHandle.myUserId(), response, type, features);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void setAuthToken(Account account, String authTokenType, String authToken) {
+        try {
+            getRemote().setAuthToken(VUserHandle.myUserId(), account, authTokenType, authToken);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public Object getPassword(Account account) {
+        try {
+            return getRemote().getPassword(VUserHandle.myUserId(), account);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public String getUserData(Account account, String key) {
+        try {
+            return getRemote().getUserData(VUserHandle.myUserId(), account, key);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    /**
+     * Asks the user to add an account of a specified type.  The authenticator
+     * for this account type processes this request with the appropriate user
+     * interface.  If the user does elect to create a new account, the account
+     * name is returned.
+     * <p>
+     * <p>This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     * <p>
+     *
+     */
+    public AccountManagerFuture<Bundle> addAccount(final int userId, final String accountType,
+                                                   final String authTokenType, final String[] requiredFeatures,
+                                                   final Bundle addAccountOptions,
+                                                   final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        final Bundle optionsIn = new Bundle();
+        if (addAccountOptions != null) {
+            optionsIn.putAll(addAccountOptions);
+        }
+        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, "android");
+
+        return new AmsTask(activity, handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                addAccount(userId, mResponse, accountType, authTokenType,
+                        requiredFeatures, activity != null, optionsIn);
+            }
+        }.start();
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VActivityManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VActivityManager.java
index 676cbe859..0b2b34b76 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VActivityManager.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VActivityManager.java
@@ -4,7 +4,9 @@
 import android.app.IServiceConnection;
 import android.app.Notification;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ProviderInfo;
 import android.os.Bundle;
@@ -14,10 +16,12 @@
 
 import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.env.VirtualRuntime;
+import com.lody.virtual.client.hook.secondary.ServiceConnectionDelegate;
 import com.lody.virtual.helper.compat.ActivityManagerCompat;
 import com.lody.virtual.helper.utils.ComponentUtils;
 import com.lody.virtual.os.VUserHandle;
 import com.lody.virtual.remote.AppTaskInfo;
+import com.lody.virtual.remote.BadgerInfo;
 import com.lody.virtual.remote.PendingIntentData;
 import com.lody.virtual.remote.PendingResultData;
 import com.lody.virtual.remote.VParceledListSlice;
@@ -45,17 +49,17 @@ public static VActivityManager get() {
     }
 
     public IActivityManager getService() {
-        if (mRemote == null) {
+        if (mRemote == null ||
+                (!mRemote.asBinder().pingBinder() && !VirtualCore.get().isVAppProcess())) {
             synchronized (VActivityManager.class) {
-                if (mRemote == null) {
-                    final Object remote = getRemoteInterface();
-                    mRemote = LocalProxyUtils.genProxy(IActivityManager.class, remote);
-                }
+                final Object remote = getRemoteInterface();
+                mRemote = LocalProxyUtils.genProxy(IActivityManager.class, remote);
             }
         }
         return mRemote;
     }
 
+
     private Object getRemoteInterface() {
         return IActivityManager.Stub
                 .asInterface(ServiceManagerNative.getService(ServiceManagerNative.ACTIVITY));
@@ -189,14 +193,32 @@ public boolean stopServiceToken(ComponentName className, IBinder token, int star
         }
     }
 
-    public void setServiceForeground(ComponentName className, IBinder token, int id, Notification notification, boolean keepNotification) {
+    public void setServiceForeground(ComponentName className, IBinder token, int id, Notification notification, boolean removeNotification) {
         try {
-            getService().setServiceForeground(className, token, id, notification, keepNotification, VUserHandle.myUserId());
+            getService().setServiceForeground(className, token, id, notification,removeNotification,  VUserHandle.myUserId());
         } catch (RemoteException e) {
             e.printStackTrace();
         }
     }
 
+    public int bindService(Context context, Intent service, ServiceConnection connection, int flags) {
+        try {
+            IServiceConnection conn = ServiceConnectionDelegate.getDelegate(context, connection, flags);
+            return getService().bindService(null, null, service, null, conn, flags, 0);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public boolean unbindService(Context context, ServiceConnection connection) {
+        try {
+            IServiceConnection conn = ServiceConnectionDelegate.removeDelegate(context, connection);
+            return getService().unbindService(conn, VUserHandle.myUserId());
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
     public int bindService(IBinder caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, int userId) {
         try {
             return getService().bindService(caller, token, service, resolvedType, connection, flags, userId);
@@ -468,4 +490,11 @@ public String getPackageForIntentSender(IBinder binder) {
         }
     }
 
+    public void notifyBadgerChange(BadgerInfo info) {
+        try {
+            getService().notifyBadgerChange(info);
+        } catch (RemoteException e) {
+            VirtualRuntime.crash(e);
+        }
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VDeviceManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VDeviceManager.java
new file mode 100644
index 000000000..2365a2c8a
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VDeviceManager.java
@@ -0,0 +1,49 @@
+package com.lody.virtual.client.ipc;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.env.VirtualRuntime;
+import com.lody.virtual.remote.VDeviceInfo;
+import com.lody.virtual.server.IDeviceInfoManager;
+
+/**
+ * @author Lody
+ */
+
+public class VDeviceManager {
+
+    private static final VDeviceManager sInstance = new VDeviceManager();
+    private IDeviceInfoManager mRemote;
+
+
+    public static VDeviceManager get() {
+        return sInstance;
+    }
+
+
+    public IDeviceInfoManager getRemote() {
+        if (mRemote == null ||
+                (!mRemote.asBinder().pingBinder() && !VirtualCore.get().isVAppProcess())) {
+            synchronized (this) {
+                Object remote = getRemoteInterface();
+                mRemote = LocalProxyUtils.genProxy(IDeviceInfoManager.class, remote);
+            }
+        }
+        return mRemote;
+    }
+
+    private Object getRemoteInterface() {
+        final IBinder binder = ServiceManagerNative.getService(ServiceManagerNative.DEVICE);
+        return IDeviceInfoManager.Stub.asInterface(binder);
+    }
+
+    public VDeviceInfo getDeviceInfo(int userId) {
+        try {
+            return getRemote().getDeviceInfo(userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VJobScheduler.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VJobScheduler.java
index b8aa230fa..eb1c947cf 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VJobScheduler.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VJobScheduler.java
@@ -1,9 +1,13 @@
 package com.lody.virtual.client.ipc;
 
+import android.annotation.TargetApi;
 import android.app.job.JobInfo;
+import android.app.job.JobWorkItem;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 
+import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.env.VirtualRuntime;
 import com.lody.virtual.server.IJobScheduler;
 
@@ -24,12 +28,11 @@ public static VJobScheduler get() {
     }
 
     public IJobScheduler getRemote() {
-        if (mRemote == null) {
+        if (mRemote == null ||
+                (!mRemote.asBinder().pingBinder() && !VirtualCore.get().isVAppProcess())) {
             synchronized (this) {
-                if (mRemote == null) {
-                    Object remote = getRemoteInterface();
-                    mRemote = LocalProxyUtils.genProxy(IJobScheduler.class, remote);
-                }
+                Object remote = getRemoteInterface();
+                mRemote = LocalProxyUtils.genProxy(IJobScheduler.class, remote);
             }
         }
         return mRemote;
@@ -71,4 +74,23 @@ public void cancel(int jobId) {
             e.printStackTrace();
         }
     }
+    public JobInfo getPendingJob(int jobId) {
+        try {
+            return getRemote().getPendingJob(jobId);
+        } catch (RemoteException e) {
+            return (JobInfo) VirtualRuntime.crash(e);
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.O)
+    public int enqueue(JobInfo job, Object workItem) {
+        if (workItem == null) {
+            return -1;
+        }
+        try {
+            return getRemote().enqueue(job, (JobWorkItem) workItem);
+        } catch (RemoteException e) {
+            return (Integer) VirtualRuntime.crash(e);
+        }
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VNotificationManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VNotificationManager.java
index 130f1181e..29a3ecf51 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VNotificationManager.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VNotificationManager.java
@@ -25,18 +25,18 @@ public static VNotificationManager get() {
     }
 
     public INotificationManager getService() {
-        if (mRemote == null) {
+        if (mRemote == null ||
+                (!mRemote.asBinder().pingBinder() && !VirtualCore.get().isVAppProcess())) {
             synchronized (VNotificationManager.class) {
-                if (mRemote == null) {
-                    final IBinder pmBinder = ServiceManagerNative.getService(ServiceManagerNative.NOTIFICATION);
-                    mRemote = INotificationManager.Stub.asInterface(pmBinder);
-                }
+                final IBinder pmBinder = ServiceManagerNative.getService(ServiceManagerNative.NOTIFICATION);
+                mRemote = INotificationManager.Stub.asInterface(pmBinder);
             }
         }
         return mRemote;
     }
 
     public boolean dealNotification(int id, Notification notification, String packageName) {
+        if(notification == null)return false;
         return VirtualCore.get().getHostPkg().equals(packageName)
                 || mNotificationCompat.dealNotification(id, notification, packageName);
     }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VPackageManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VPackageManager.java
index 04f3f6e44..ccd2644cb 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VPackageManager.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VPackageManager.java
@@ -13,241 +13,275 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 
+import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.env.VirtualRuntime;
+import com.lody.virtual.server.IPackageInstaller;
 import com.lody.virtual.server.IPackageManager;
 
 import java.util.List;
 
 /**
  * @author Lody
- *
  */
 public class VPackageManager {
 
-	private static final VPackageManager sMgr = new VPackageManager();
-	private IPackageManager mRemote;
-
-	public static VPackageManager get() {
-		return sMgr;
-	}
-
-	public IPackageManager getInterface() {
-		if (mRemote == null) {
-			synchronized (VPackageManager.class) {
-				if (mRemote == null) {
-					Object remote = getRemoteInterface();
-					mRemote = LocalProxyUtils.genProxy(IPackageManager.class, remote);
-				}
-			}
-		}
-		return mRemote;
-	}
-
-	private Object getRemoteInterface() {
+    private static final VPackageManager sMgr = new VPackageManager();
+    private IPackageManager mRemote;
+
+    public static VPackageManager get() {
+        return sMgr;
+    }
+
+    public IPackageManager getInterface() {
+        if (mRemote == null ||
+                (!mRemote.asBinder().pingBinder() && !VirtualCore.get().isVAppProcess())) {
+            synchronized (VPackageManager.class) {
+                Object remote = getRemoteInterface();
+                mRemote = LocalProxyUtils.genProxy(IPackageManager.class, remote);
+            }
+        }
+        return mRemote;
+    }
+
+    private Object getRemoteInterface() {
         final IBinder pmBinder = ServiceManagerNative.getService(ServiceManagerNative.PACKAGE);
         return IPackageManager.Stub.asInterface(pmBinder);
     }
 
-	public int checkPermission(String permName, String pkgName, int userId) {
-		try {
-			return getInterface().checkPermission(permName, pkgName, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) {
-		try {
-			return getInterface().resolveService(intent, resolvedType, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) {
-		try {
-			return getInterface().getPermissionGroupInfo(name, flags);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public List<ApplicationInfo> getInstalledApplications(int flags, int userId) {
-		try {
-			// noinspection unchecked
-			return getInterface().getInstalledApplications(flags, userId).getList();
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
-		try {
-			return getInterface().getPackageInfo(packageName, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId) {
-		try {
-			return getInterface().resolveIntent(intent, resolvedType, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public List<ResolveInfo> queryIntentContentProviders(Intent intent, String resolvedType, int flags, int userId) {
-		try {
-			return getInterface().queryIntentContentProviders(intent, resolvedType, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public ActivityInfo getReceiverInfo(ComponentName componentName, int flags, int userId) {
-		try {
-			return getInterface().getReceiverInfo(componentName, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public List<PackageInfo> getInstalledPackages(int flags, int userId) {
-		try {
-			return getInterface().getInstalledPackages(flags, userId).getList();
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public List<PermissionInfo> queryPermissionsByGroup(String group, int flags) {
-		try {
-			return getInterface().queryPermissionsByGroup(group, flags);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public PermissionInfo getPermissionInfo(String name, int flags) {
-		try {
-			return getInterface().getPermissionInfo(name, flags);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public ActivityInfo getActivityInfo(ComponentName componentName, int flags, int userId) {
-		try {
-			return getInterface().getActivityInfo(componentName, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags, int userId) {
-		try {
-			return getInterface().queryIntentReceivers(intent, resolvedType, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
-		try {
-			return getInterface().getAllPermissionGroups(flags);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) {
-		try {
-			return getInterface().queryIntentActivities(intent, resolvedType, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public List<ResolveInfo> queryIntentServices(Intent intent, String resolvedType, int flags, int userId) {
-		try {
-			return getInterface().queryIntentServices(intent, resolvedType, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
-		try {
-			return getInterface().getApplicationInfo(packageName, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
-		try {
-			return getInterface().resolveContentProvider(name, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public ServiceInfo getServiceInfo(ComponentName componentName, int flags, int userId) {
-		try {
-			return getInterface().getServiceInfo(componentName, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public ProviderInfo getProviderInfo(ComponentName componentName, int flags, int userId) {
-		try {
-			return getInterface().getProviderInfo(componentName, flags, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public boolean activitySupportsIntent(ComponentName component, Intent intent, String resolvedType) {
-		try {
-			return getInterface().activitySupportsIntent(component, intent, resolvedType);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
-		try {
-			// noinspection unchecked
-			return getInterface().queryContentProviders(processName, uid, flags).getList();
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public List<String> querySharedPackages(String packageName) {
-		try {
-			return getInterface().querySharedPackages(packageName);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public String[] getPackagesForUid(int uid) {
-		try {
-			return getInterface().getPackagesForUid(uid);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
-
-	public int getPackageUid(String packageName, int userId) {
-		try {
-			return getInterface().getPackageUid(packageName, userId);
-		} catch (RemoteException e) {
-			return VirtualRuntime.crash(e);
-		}
-	}
+    public int checkPermission(String permName, String pkgName, int userId) {
+        try {
+            return getInterface().checkPermission(permName, pkgName, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) {
+        try {
+            return getInterface().resolveService(intent, resolvedType, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) {
+        try {
+            return getInterface().getPermissionGroupInfo(name, flags);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<ApplicationInfo> getInstalledApplications(int flags, int userId) {
+        try {
+            // noinspection unchecked
+            return getInterface().getInstalledApplications(flags, userId).getList();
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
+        try {
+            return getInterface().getPackageInfo(packageName, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId) {
+        try {
+            return getInterface().resolveIntent(intent, resolvedType, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<ResolveInfo> queryIntentContentProviders(Intent intent, String resolvedType, int flags, int userId) {
+        try {
+            return getInterface().queryIntentContentProviders(intent, resolvedType, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public ActivityInfo getReceiverInfo(ComponentName componentName, int flags, int userId) {
+        try {
+            return getInterface().getReceiverInfo(componentName, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<PackageInfo> getInstalledPackages(int flags, int userId) {
+        try {
+            return getInterface().getInstalledPackages(flags, userId).getList();
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<PermissionInfo> queryPermissionsByGroup(String group, int flags) {
+        try {
+            return getInterface().queryPermissionsByGroup(group, flags);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public PermissionInfo getPermissionInfo(String name, int flags) {
+        try {
+            return getInterface().getPermissionInfo(name, flags);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public ActivityInfo getActivityInfo(ComponentName componentName, int flags, int userId) {
+        try {
+            return getInterface().getActivityInfo(componentName, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags, int userId) {
+        try {
+            return getInterface().queryIntentReceivers(intent, resolvedType, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+        try {
+            return getInterface().getAllPermissionGroups(flags);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) {
+        try {
+            return getInterface().queryIntentActivities(intent, resolvedType, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<ResolveInfo> queryIntentServices(Intent intent, String resolvedType, int flags, int userId) {
+        try {
+            return getInterface().queryIntentServices(intent, resolvedType, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
+        try {
+            ApplicationInfo info = getInterface().getApplicationInfo(packageName, flags, userId);
+            if (info == null) {
+                return null;
+            }
+            final int P = 28;
+            final String APACHE_LEGACY = "/system/framework/org.apache.http.legacy.boot.jar";
+            if (android.os.Build.VERSION.SDK_INT >= P && info.targetSdkVersion < P) {
+                String[] newSharedLibraryFiles;
+                if (info.sharedLibraryFiles == null) {
+                    newSharedLibraryFiles = new String[]{APACHE_LEGACY};
+                } else {
+                    int newLength = info.sharedLibraryFiles.length + 1;
+                    newSharedLibraryFiles = new String[newLength];
+                    System.arraycopy(info.sharedLibraryFiles, 0, newSharedLibraryFiles, 0, newLength - 1);
+                    newSharedLibraryFiles[newLength - 1] = APACHE_LEGACY;
+                }
+                info.sharedLibraryFiles = newSharedLibraryFiles;
+            }
+            return info;
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+        try {
+            return getInterface().resolveContentProvider(name, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public ServiceInfo getServiceInfo(ComponentName componentName, int flags, int userId) {
+        try {
+            return getInterface().getServiceInfo(componentName, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public ProviderInfo getProviderInfo(ComponentName componentName, int flags, int userId) {
+        try {
+            return getInterface().getProviderInfo(componentName, flags, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
 
+    public boolean activitySupportsIntent(ComponentName component, Intent intent, String resolvedType) {
+        try {
+            return getInterface().activitySupportsIntent(component, intent, resolvedType);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
+        try {
+            // noinspection unchecked
+            return getInterface().queryContentProviders(processName, uid, flags).getList();
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<String> querySharedPackages(String packageName) {
+        try {
+            return getInterface().querySharedPackages(packageName);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public String[] getPackagesForUid(int uid) {
+        try {
+            return getInterface().getPackagesForUid(uid);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public int getPackageUid(String packageName, int userId) {
+        try {
+            return getInterface().getPackageUid(packageName, userId);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public String getNameForUid(int uid) {
+        try {
+            return getInterface().getNameForUid(uid);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+
+    public IPackageInstaller getPackageInstaller() {
+        try {
+            return getInterface().getPackageInstaller();
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VirtualLocationManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VirtualLocationManager.java
new file mode 100644
index 000000000..4f05ee33a
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VirtualLocationManager.java
@@ -0,0 +1,178 @@
+package com.lody.virtual.client.ipc;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.env.VirtualRuntime;
+import com.lody.virtual.client.hook.base.MethodProxy;
+import com.lody.virtual.remote.vloc.VCell;
+import com.lody.virtual.remote.vloc.VLocation;
+import com.lody.virtual.server.IVirtualLocationManager;
+
+import java.util.List;
+
+/**
+ * @author Lody
+ */
+
+public class VirtualLocationManager {
+
+    private static final VirtualLocationManager sInstance = new VirtualLocationManager();
+    private IVirtualLocationManager mRemote;
+
+    public static final int MODE_CLOSE = 0;
+    public static final int MODE_USE_GLOBAL = 1;
+    public static final int MODE_USE_SELF = 2;
+
+
+    public static VirtualLocationManager get() {
+        return sInstance;
+    }
+
+
+    public IVirtualLocationManager getRemote() {
+        if (mRemote == null ||
+                (!mRemote.asBinder().pingBinder() && !VirtualCore.get().isVAppProcess())) {
+            synchronized (this) {
+                Object remote = getRemoteInterface();
+                mRemote = LocalProxyUtils.genProxy(IVirtualLocationManager.class, remote);
+            }
+        }
+        return mRemote;
+    }
+
+    private Object getRemoteInterface() {
+        final IBinder binder = ServiceManagerNative.getService(ServiceManagerNative.VIRTUAL_LOC);
+        return IVirtualLocationManager.Stub.asInterface(binder);
+    }
+
+    public int getMode(int userId, String pkg) {
+        try {
+            return getRemote().getMode(userId, pkg);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public int getMode() {
+        return getMode(MethodProxy.getAppUserId(), MethodProxy.getAppPkg());
+    }
+
+    public void setMode(int userId, String pkg, int mode) {
+        try {
+            getRemote().setMode(userId, pkg, mode);
+        } catch (RemoteException e) {
+            VirtualRuntime.crash(e);
+        }
+    }
+
+    public void setCell(int userId, String pkg, VCell cell) {
+        try {
+            getRemote().setCell(userId, pkg, cell);
+        } catch (RemoteException e) {
+            VirtualRuntime.crash(e);
+        }
+    }
+
+    public void setAllCell(int userId, String pkg, List<VCell> cell) {
+        try {
+            getRemote().setAllCell(userId, pkg, cell);
+        } catch (RemoteException e) {
+            VirtualRuntime.crash(e);
+        }
+    }
+
+    public void setNeighboringCell(int userId, String pkg, List<VCell> cell) {
+        try {
+            getRemote().setNeighboringCell(userId, pkg, cell);
+        } catch (RemoteException e) {
+            VirtualRuntime.crash(e);
+        }
+    }
+
+    public VCell getCell(int userId, String pkg) {
+        try {
+            return getRemote().getCell(userId, pkg);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<VCell> getAllCell(int userId, String pkg) {
+        try {
+            return getRemote().getAllCell(userId, pkg);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public List<VCell> getNeighboringCell(int userId, String pkg) {
+        try {
+            return getRemote().getNeighboringCell(userId, pkg);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+
+    public void setGlobalCell(VCell cell) {
+        try {
+            getRemote().setGlobalCell(cell);
+        } catch (RemoteException e) {
+            VirtualRuntime.crash(e);
+        }
+    }
+
+    public void setGlobalAllCell(List<VCell> cell) {
+        try {
+            getRemote().setGlobalAllCell(cell);
+        } catch (RemoteException e) {
+            VirtualRuntime.crash(e);
+        }
+    }
+
+    public void setGlobalNeighboringCell(List<VCell> cell) {
+        try {
+            getRemote().setGlobalNeighboringCell(cell);
+        } catch (RemoteException e) {
+            VirtualRuntime.crash(e);
+        }
+    }
+
+    public void setLocation(int userId, String pkg, VLocation loc) {
+        try {
+            getRemote().setLocation(userId, pkg, loc);
+        } catch (RemoteException e) {
+            VirtualRuntime.crash(e);
+        }
+    }
+
+    public VLocation getLocation(int userId, String pkg) {
+        try {
+            return getRemote().getLocation(userId, pkg);
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+
+    public VLocation getLocation() {
+        return getLocation(MethodProxy.getAppUserId(), MethodProxy.getAppPkg());
+    }
+
+    public void setGlobalLocation(VLocation loc) {
+        try {
+            getRemote().setGlobalLocation(loc);
+        } catch (RemoteException e) {
+            VirtualRuntime.crash(e);
+        }
+    }
+
+    public VLocation getGlobalLocation() {
+        try {
+            return getRemote().getGlobalLocation();
+        } catch (RemoteException e) {
+            return VirtualRuntime.crash(e);
+        }
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VirtualStorageManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VirtualStorageManager.java
index fbfa25d35..048e977e2 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VirtualStorageManager.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/ipc/VirtualStorageManager.java
@@ -4,6 +4,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 
+import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.env.VirtualRuntime;
 import com.lody.virtual.server.IVirtualStorageService;
 
@@ -23,12 +24,11 @@ public static VirtualStorageManager get() {
 
 
     public IVirtualStorageService getRemote() {
-        if (mRemote == null) {
+        if (mRemote == null ||
+                (!mRemote.asBinder().pingBinder() && !VirtualCore.get().isVAppProcess())) {
             synchronized (this) {
-                if (mRemote == null) {
-                    Object remote = getRemoteInterface();
-                    mRemote = LocalProxyUtils.genProxy(IVirtualStorageService.class, remote);
-                }
+                Object remote = getRemoteInterface();
+                mRemote = LocalProxyUtils.genProxy(IVirtualStorageService.class, remote);
             }
         }
         return mRemote;
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/natives/NativeMethods.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/natives/NativeMethods.java
index efc7d432a..878b6c7ab 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/natives/NativeMethods.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/natives/NativeMethods.java
@@ -4,6 +4,8 @@
 import android.media.AudioRecord;
 import android.os.Build;
 
+import com.lody.virtual.helper.utils.EncodeUtils;
+
 import java.lang.reflect.Method;
 
 import dalvik.system.DexFile;
@@ -21,8 +23,12 @@ public class NativeMethods {
     public static Method gAudioRecordNativeCheckPermission;
 
     public static void init() {
+        // anti-virus, fuck ESET-NOD32: a variant of Android/AdDisplay.AdLock.AL potentially unwanted
+        final String openDexFileNative = EncodeUtils.decode("b3BlbkRleEZpbGVOYXRpdmU=");
+        final String openDexFile = EncodeUtils.decode("b3BlbkRleEZpbGU=");
+
         String methodName =
-                Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? "openDexFileNative" : "openDexFile";
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? openDexFileNative : openDexFile;
         for (Method method : DexFile.class.getDeclaredMethods()) {
             if (method.getName().equals(methodName)) {
                 gOpenDexFileNative = method;
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/AmsTask.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/AmsTask.java
new file mode 100644
index 000000000..52179bd80
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/AmsTask.java
@@ -0,0 +1,197 @@
+package com.lody.virtual.client.stub;
+
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.IAccountManagerResponse;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import com.lody.virtual.client.env.VirtualRuntime;
+import com.lody.virtual.helper.utils.VLog;
+
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static android.accounts.AccountManager.ERROR_CODE_BAD_ARGUMENTS;
+import static android.accounts.AccountManager.ERROR_CODE_CANCELED;
+import static android.accounts.AccountManager.ERROR_CODE_INVALID_RESPONSE;
+import static android.accounts.AccountManager.ERROR_CODE_NETWORK_ERROR;
+import static android.accounts.AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION;
+import static android.accounts.AccountManager.KEY_INTENT;
+import static com.lody.virtual.helper.compat.AccountManagerCompat.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE;
+import static com.lody.virtual.helper.compat.AccountManagerCompat.ERROR_CODE_USER_RESTRICTED;
+
+public abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> {
+    protected final IAccountManagerResponse mResponse;
+    final Handler mHandler;
+    final AccountManagerCallback<Bundle> mCallback;
+    final Activity mActivity;
+
+    public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) {
+        super(new Callable<Bundle>() {
+            @Override
+            public Bundle call() throws Exception {
+                throw new IllegalStateException("this should never be called");
+            }
+        });
+
+        mHandler = handler;
+        mCallback = callback;
+        mActivity = activity;
+        mResponse = new Response();
+    }
+
+    public final AccountManagerFuture<Bundle> start() {
+        try {
+            doWork();
+        } catch (RemoteException e) {
+            setException(e);
+        }
+        return this;
+    }
+
+    @Override
+    protected void set(Bundle bundle) {
+        // TODO: somehow a null is being set as the result of the Future. Log this
+        // case to help debug where this is occurring. When this bug is fixed this
+        // condition statement should be removed.
+        if (bundle == null) {
+            VLog.e("AccountManager", "the bundle must not be null", new Exception());
+
+        }
+        super.set(bundle);
+    }
+
+    public abstract void doWork() throws RemoteException;
+
+    private Bundle internalGetResult(Long timeout, TimeUnit unit)
+            throws OperationCanceledException, IOException, AuthenticatorException {
+        try {
+            if (timeout == null) {
+                return get();
+            } else {
+                return get(timeout, unit);
+            }
+        } catch (CancellationException e) {
+            throw new OperationCanceledException();
+        } catch (TimeoutException e) {
+            // fall through and cancel
+        } catch (InterruptedException e) {
+            // fall through and cancel
+        } catch (ExecutionException e) {
+            final Throwable cause = e.getCause();
+            if (cause instanceof IOException) {
+                throw (IOException) cause;
+            } else if (cause instanceof UnsupportedOperationException) {
+                throw new AuthenticatorException(cause);
+            } else if (cause instanceof AuthenticatorException) {
+                throw (AuthenticatorException) cause;
+            } else if (cause instanceof RuntimeException) {
+                throw (RuntimeException) cause;
+            } else if (cause instanceof Error) {
+                throw (Error) cause;
+            } else {
+                throw new IllegalStateException(cause);
+            }
+        } finally {
+            cancel(true /* interrupt if running */);
+        }
+        throw new OperationCanceledException();
+    }
+
+    @Override
+    public Bundle getResult()
+            throws OperationCanceledException, IOException, AuthenticatorException {
+        return internalGetResult(null, null);
+    }
+
+    @Override
+    public Bundle getResult(long timeout, TimeUnit unit)
+            throws OperationCanceledException, IOException, AuthenticatorException {
+        return internalGetResult(timeout, unit);
+    }
+
+    @Override
+    protected void done() {
+        if (mCallback != null) {
+            postToHandler(mHandler, mCallback, this);
+        }
+    }
+
+    /**
+     * Handles the responses from the AccountManager
+     */
+    private class Response extends IAccountManagerResponse.Stub {
+        @Override
+        public void onResult(Bundle bundle) {
+            Intent intent = bundle.getParcelable(KEY_INTENT);
+            if (intent != null && mActivity != null) {
+                // since the user provided an Activity we will silently start intents
+                // that we see
+                mActivity.startActivity(intent);
+                // leave the Future running to wait for the real response to this request
+            } else if (bundle.getBoolean("retry")) {
+                try {
+                    doWork();
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            } else {
+                set(bundle);
+            }
+        }
+
+        @Override
+        public void onError(int code, String message) {
+            if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED
+                    || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) {
+                // the authenticator indicated that this request was canceled or we were
+                // forbidden to fulfill; cancel now
+                cancel(true /* mayInterruptIfRunning */);
+                return;
+            }
+            setException(convertErrorToException(code, message));
+        }
+    }
+
+    private Exception convertErrorToException(int code, String message) {
+        if (code == ERROR_CODE_NETWORK_ERROR) {
+            return new IOException(message);
+        }
+
+        if (code == ERROR_CODE_UNSUPPORTED_OPERATION) {
+            return new UnsupportedOperationException(message);
+        }
+
+        if (code == ERROR_CODE_INVALID_RESPONSE) {
+            return new AuthenticatorException(message);
+        }
+
+        if (code == ERROR_CODE_BAD_ARGUMENTS) {
+            return new IllegalArgumentException(message);
+        }
+
+        return new AuthenticatorException(message);
+    }
+
+    private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,
+                               final AccountManagerFuture<Bundle> future) {
+        handler = handler == null ? VirtualRuntime.getUIHandler() : handler;
+        handler.post(new Runnable() {
+            @Override
+            public void run() {
+                callback.run(future);
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseAccountTypeActivity.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseAccountTypeActivity.java
new file mode 100644
index 000000000..3bd4dcdd3
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseAccountTypeActivity.java
@@ -0,0 +1,175 @@
+package com.lody.virtual.client.stub;
+
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.lody.virtual.R;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.ipc.VAccountManager;
+import com.lody.virtual.helper.utils.VLog;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class ChooseAccountTypeActivity extends Activity {
+    private static final String TAG = "AccountChooser";
+
+    private HashMap<String, AuthInfo> mTypeToAuthenticatorInfo = new HashMap<String, AuthInfo>();
+    private ArrayList<AuthInfo> mAuthenticatorInfosToDisplay;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes
+        Set<String> setOfAllowableAccountTypes = null;
+        String[] validAccountTypes = getIntent().getStringArrayExtra(
+                ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
+        if (validAccountTypes != null) {
+            setOfAllowableAccountTypes = new HashSet<>(validAccountTypes.length);
+            Collections.addAll(setOfAllowableAccountTypes, validAccountTypes);
+        }
+
+        // create a map of account authenticators
+        buildTypeToAuthDescriptionMap();
+
+        // Create a list of authenticators that are allowable. Filter out those that
+        // don't match the allowable account types, if provided.
+        mAuthenticatorInfosToDisplay = new ArrayList<>(mTypeToAuthenticatorInfo.size());
+        for (Map.Entry<String, AuthInfo> entry: mTypeToAuthenticatorInfo.entrySet()) {
+            final String type = entry.getKey();
+            final AuthInfo info = entry.getValue();
+            if (setOfAllowableAccountTypes != null
+                    && !setOfAllowableAccountTypes.contains(type)) {
+                continue;
+            }
+            mAuthenticatorInfosToDisplay.add(info);
+        }
+
+        if (mAuthenticatorInfosToDisplay.isEmpty()) {
+            Bundle bundle = new Bundle();
+            bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "no allowable account types");
+            setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+            finish();
+            return;
+        }
+
+        if (mAuthenticatorInfosToDisplay.size() == 1) {
+            setResultAndFinish(mAuthenticatorInfosToDisplay.get(0).desc.type);
+            return;
+        }
+
+        setContentView(R.layout.choose_account_type);
+        // Setup the list
+        ListView list = (ListView) findViewById(android.R.id.list);
+        // Use an existing ListAdapter that will map an array of strings to TextViews
+        list.setAdapter(new AccountArrayAdapter(this,
+                android.R.layout.simple_list_item_1, mAuthenticatorInfosToDisplay));
+        list.setChoiceMode(ListView.CHOICE_MODE_NONE);
+        list.setTextFilterEnabled(false);
+        list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+                setResultAndFinish(mAuthenticatorInfosToDisplay.get(position).desc.type);
+            }
+        });
+    }
+
+    private void setResultAndFinish(final String type) {
+        Bundle bundle = new Bundle();
+        bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, type);
+        setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+        VLog.v(TAG, "ChooseAccountTypeActivity.setResultAndFinish: "
+                + "selected account type " + type);
+        finish();
+    }
+
+    private void buildTypeToAuthDescriptionMap() {
+        for(AuthenticatorDescription desc : VAccountManager.get().getAuthenticatorTypes()) {
+            String name = null;
+            Drawable icon = null;
+            try {
+                Resources res = VirtualCore.get().getResources(desc.packageName);
+                icon = res.getDrawable(desc.iconId);
+                final CharSequence sequence = res.getText(desc.labelId);
+                name = sequence.toString();
+                name = sequence.toString();
+            } catch (Resources.NotFoundException e) {
+                // Nothing we can do much here, just log
+                VLog.w(TAG, "No icon resource for account type " + desc.type);
+            }
+            AuthInfo authInfo = new AuthInfo(desc, name, icon);
+            mTypeToAuthenticatorInfo.put(desc.type, authInfo);
+        }
+    }
+
+    private static class AuthInfo {
+        final AuthenticatorDescription desc;
+        final String name;
+        final Drawable drawable;
+
+        AuthInfo(AuthenticatorDescription desc, String name, Drawable drawable) {
+            this.desc = desc;
+            this.name = name;
+            this.drawable = drawable;
+        }
+    }
+
+    private static class ViewHolder {
+        ImageView icon;
+        TextView text;
+    }
+
+    private static class AccountArrayAdapter extends ArrayAdapter<AuthInfo> {
+        private LayoutInflater mLayoutInflater;
+        private ArrayList<AuthInfo> mInfos;
+
+        AccountArrayAdapter(Context context, int textViewResourceId,
+                            ArrayList<AuthInfo> infos) {
+            super(context, textViewResourceId, infos);
+            mInfos = infos;
+            mLayoutInflater = (LayoutInflater) context.getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            ViewHolder holder;
+
+            if (convertView == null) {
+                convertView = mLayoutInflater.inflate(R.layout.choose_account_row, null);
+                holder = new ViewHolder();
+                holder.text = (TextView) convertView.findViewById(R.id.account_row_text);
+                holder.icon = (ImageView) convertView.findViewById(R.id.account_row_icon);
+                convertView.setTag(holder);
+            } else {
+                holder = (ViewHolder) convertView.getTag();
+            }
+
+            holder.text.setText(mInfos.get(position).name);
+            holder.icon.setImageDrawable(mInfos.get(position).drawable);
+
+            return convertView;
+        }
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseTypeAndAccountActivity.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseTypeAndAccountActivity.java
new file mode 100644
index 000000000..53d09cb1b
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ChooseTypeAndAccountActivity.java
@@ -0,0 +1,525 @@
+package com.lody.virtual.client.stub;
+
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorDescription;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.lody.virtual.R;
+import com.lody.virtual.client.ipc.VAccountManager;
+import com.lody.virtual.helper.utils.VLog;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ChooseTypeAndAccountActivity extends Activity
+        implements AccountManagerCallback<Bundle> {
+    private static final String TAG = "AccountChooser";
+
+    /**
+     * A Parcelable ArrayList of Account objects that limits the choosable accounts to those
+     * in this list, if this parameter is supplied.
+     */
+    public static final String EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST = "allowableAccounts";
+
+    /**
+     * A Parcelable ArrayList of String objects that limits the accounts to choose to those
+     * that match the types in this list, if this parameter is supplied. This list is also
+     * used to filter the allowable account types if add account is selected.
+     */
+    public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY = "allowableAccountTypes";
+
+    /**
+     * This is passed as the addAccountOptions parameter in AccountManager.addAccount()
+     * if it is called.
+     */
+    public static final String EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE = "addAccountOptions";
+
+    /**
+     * This is passed as the requiredFeatures parameter in AccountManager.addAccount()
+     * if it is called.
+     */
+    public static final String EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY =
+            "addAccountRequiredFeatures";
+
+    /**
+     * This is passed as the authTokenType string in AccountManager.addAccount()
+     * if it is called.
+     */
+    public static final String EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING = "authTokenType";
+
+    /**
+     * If set then the specified account is already "selected".
+     */
+    public static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount";
+
+    /**
+     * Deprecated. Providing this extra to {@link ChooseTypeAndAccountActivity}
+     * will have no effect.
+     */
+    @Deprecated
+    public static final String EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT =
+            "alwaysPromptForAccount";
+
+    /**
+     * If set then this string willb e used as the description rather than
+     * the default.
+     */
+    public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE =
+            "descriptionTextOverride";
+
+    public static final int REQUEST_NULL = 0;
+    public static final int REQUEST_CHOOSE_TYPE = 1;
+    public static final int REQUEST_ADD_ACCOUNT = 2;
+
+    private static final String KEY_INSTANCE_STATE_PENDING_REQUEST = "pendingRequest";
+    private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
+    private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
+    private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
+    private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountList";
+    public static final String KEY_USER_ID = "userId";
+
+    private static final int SELECTED_ITEM_NONE = -1;
+
+    private Set<Account> mSetOfAllowableAccounts;
+    private Set<String> mSetOfRelevantAccountTypes;
+    private String mSelectedAccountName = null;
+    private boolean mSelectedAddNewAccount = false;
+    private String mDescriptionOverride;
+
+    private ArrayList<Account> mAccounts;
+    private int mPendingRequest = REQUEST_NULL;
+    private Parcelable[] mExistingAccounts = null;
+    private int mSelectedItemIndex;
+    private Button mOkButton;
+    private int mCallingUserId;
+    private boolean mDontShowPicker;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        // save some items we use frequently
+        final Intent intent = getIntent();
+
+        if (savedInstanceState != null) {
+            mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST);
+            mExistingAccounts =
+                    savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS);
+
+            // Makes sure that any user selection is preserved across orientation changes.
+            mSelectedAccountName = savedInstanceState.getString(
+                    KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
+
+            mSelectedAddNewAccount = savedInstanceState.getBoolean(
+                    KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
+            mAccounts = savedInstanceState.getParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST);
+            mCallingUserId = savedInstanceState.getInt(KEY_USER_ID);
+        } else {
+            mPendingRequest = REQUEST_NULL;
+            mExistingAccounts = null;
+            mCallingUserId = intent.getIntExtra(KEY_USER_ID, -1);
+            // If the selected account as specified in the intent matches one in the list we will
+            // show is as pre-selected.
+            Account selectedAccount = intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT);
+            if (selectedAccount != null) {
+                mSelectedAccountName = selectedAccount.name;
+            }
+        }
+        VLog.v(TAG, "selected account name is " + mSelectedAccountName);
+
+        mSetOfAllowableAccounts = getAllowableAccountSet(intent);
+        mSetOfRelevantAccountTypes = getReleventAccountTypes(intent);
+        mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
+
+        mAccounts = getAcceptableAccountChoices(VAccountManager.get());
+
+        if (mDontShowPicker) {
+            super.onCreate(savedInstanceState);
+            return;
+        }
+
+        // In cases where the activity does not need to show an account picker, cut the chase
+        // and return the result directly. Eg:
+        // Single account -> select it directly
+        // No account -> launch add account activity directly
+        if (mPendingRequest == REQUEST_NULL) {
+            // If there are no relevant accounts and only one relevant account type go directly to
+            // add account. Otherwise let the user choose.
+            if (mAccounts.isEmpty()) {
+                setNonLabelThemeAndCallSuperCreate(savedInstanceState);
+                if (mSetOfRelevantAccountTypes.size() == 1) {
+                    runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next());
+                } else {
+                    startChooseAccountTypeActivity();
+                }
+            }
+        }
+
+        String[] listItems = getListOfDisplayableOptions(mAccounts);
+        mSelectedItemIndex = getItemIndexToSelect(
+                mAccounts, mSelectedAccountName, mSelectedAddNewAccount);
+
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.choose_type_and_account);
+        overrideDescriptionIfSupplied(mDescriptionOverride);
+        populateUIAccountList(listItems);
+
+        // Only enable "OK" button if something has been selected.
+        mOkButton = (Button) findViewById(android.R.id.button2);
+        mOkButton.setEnabled(mSelectedItemIndex != SELECTED_ITEM_NONE);
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "ChooseTypeAndAccountActivity.onDestroy()");
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onSaveInstanceState(final Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(KEY_INSTANCE_STATE_PENDING_REQUEST, mPendingRequest);
+        if (mPendingRequest == REQUEST_ADD_ACCOUNT) {
+            outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts);
+        }
+        if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
+            if (mSelectedItemIndex == mAccounts.size()) {
+                outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true);
+            } else {
+                outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
+                outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME,
+                        mAccounts.get(mSelectedItemIndex).name);
+            }
+        }
+        outState.putParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST, mAccounts);
+    }
+
+    public void onCancelButtonClicked(View view) {
+        onBackPressed();
+    }
+
+    public void onOkButtonClicked(View view) {
+        if (mSelectedItemIndex == mAccounts.size()) {
+            // Selected "Add New Account" option
+            startChooseAccountTypeActivity();
+        } else if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
+            onAccountSelected(mAccounts.get(mSelectedItemIndex));
+        }
+    }
+
+    // Called when the choose account type activity (for adding an account) returns.
+    // If it was a success read the account and set it in the result. In all cases
+    // return the result and finish this activity.
+    @Override
+    protected void onActivityResult(final int requestCode, final int resultCode,
+                                    final Intent data) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (data != null && data.getExtras() != null) data.getExtras().keySet();
+            Bundle extras = data != null ? data.getExtras() : null;
+            Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult(reqCode=" + requestCode
+                    + ", resCode=" + resultCode + ", extras=" + extras + ")");
+        }
+
+        // we got our result, so clear the fact that we had a pending request
+        mPendingRequest = REQUEST_NULL;
+
+        if (resultCode == RESULT_CANCELED) {
+            // if canceling out of addAccount and the original state caused us to skip this,
+            // finish this activity
+            if (mAccounts.isEmpty()) {
+                setResult(Activity.RESULT_CANCELED);
+                finish();
+            }
+            return;
+        }
+
+        if (resultCode == RESULT_OK) {
+            if (requestCode == REQUEST_CHOOSE_TYPE) {
+                if (data != null) {
+                    String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
+                    if (accountType != null) {
+                        runAddAccountForAuthenticator(accountType);
+                        return;
+                    }
+                }
+                Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find account "
+                        + "type, pretending the request was canceled");
+            } else if (requestCode == REQUEST_ADD_ACCOUNT) {
+                String accountName = null;
+                String accountType = null;
+
+                if (data != null) {
+                    accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
+                    accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
+                }
+
+                if (accountName == null || accountType == null) {
+                    Account[] currentAccounts = VAccountManager.get().getAccounts(mCallingUserId, null);
+                    Set<Account> preExistingAccounts = new HashSet<>();
+                    for (Parcelable accountParcel : mExistingAccounts) {
+                        preExistingAccounts.add((Account) accountParcel);
+                    }
+                    for (Account account : currentAccounts) {
+                        if (!preExistingAccounts.contains(account)) {
+                            accountName = account.name;
+                            accountType = account.type;
+                            break;
+                        }
+                    }
+                }
+
+                if (accountName != null || accountType != null) {
+                    setResultAndFinish(accountName, accountType);
+                    return;
+                }
+            }
+            Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find added "
+                    + "account, pretending the request was canceled");
+        }
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled");
+        }
+        setResult(Activity.RESULT_CANCELED);
+        finish();
+    }
+
+    protected void runAddAccountForAuthenticator(String type) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "runAddAccountForAuthenticator: " + type);
+        }
+        final Bundle options = getIntent().getBundleExtra(
+                ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE);
+        final String[] requiredFeatures = getIntent().getStringArrayExtra(
+                ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY);
+        final String authTokenType = getIntent().getStringExtra(
+                ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING);
+        VAccountManager.get().addAccount(mCallingUserId, type, authTokenType, requiredFeatures,
+                options, null /* activity */, this /* callback */, null /* Handler */);
+    }
+
+    @Override
+    public void run(final AccountManagerFuture<Bundle> accountManagerFuture) {
+        try {
+            final Bundle accountManagerResult = accountManagerFuture.getResult();
+            final Intent intent = accountManagerResult.getParcelable(
+                    AccountManager.KEY_INTENT);
+            if (intent != null) {
+                mPendingRequest = REQUEST_ADD_ACCOUNT;
+                mExistingAccounts = VAccountManager.get().getAccounts(mCallingUserId, null);
+                intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
+                startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
+                return;
+            }
+        } catch (OperationCanceledException e) {
+            setResult(Activity.RESULT_CANCELED);
+            finish();
+            return;
+        } catch (IOException e) {
+        } catch (AuthenticatorException e) {
+        }
+        Bundle bundle = new Bundle();
+        bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error communicating with server");
+        setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+        finish();
+    }
+
+    /**
+     * The default activity theme shows label at the top. Set a theme which does
+     * not show label, which effectively makes the activity invisible. Note that
+     * no content is being set. If something gets set, using this theme may be
+     * useless.
+     */
+    private void setNonLabelThemeAndCallSuperCreate(Bundle savedInstanceState) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            setTheme(android.R.style.Theme_Material_Light_Dialog_NoActionBar);
+        } else {
+            setTheme(android.R.style.Theme_Holo_Light_Dialog_NoActionBar);
+        }
+        super.onCreate(savedInstanceState);
+    }
+
+    private void onAccountSelected(Account account) {
+        Log.d(TAG, "selected account " + account);
+        setResultAndFinish(account.name, account.type);
+    }
+
+    private void setResultAndFinish(final String accountName, final String accountType) {
+        Bundle bundle = new Bundle();
+        bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
+        bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
+        setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
+        VLog.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: "
+                + "selected account " + accountName + ", " + accountType);
+        finish();
+    }
+
+    private void startChooseAccountTypeActivity() {
+        VLog.v(TAG, "ChooseAccountTypeActivity.startChooseAccountTypeActivity()");
+        final Intent intent = new Intent(this, ChooseAccountTypeActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+        intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
+                getIntent().getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY));
+        intent.putExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE,
+                getIntent().getBundleExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE));
+        intent.putExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY,
+                getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY));
+        intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING,
+                getIntent().getStringExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING));
+        startActivityForResult(intent, REQUEST_CHOOSE_TYPE);
+        mPendingRequest = REQUEST_CHOOSE_TYPE;
+    }
+
+    /**
+     * @return a value between 0 (inclusive) and accounts.size() (inclusive) or SELECTED_ITEM_NONE.
+     * An index value of accounts.size() indicates 'Add account' option.
+     */
+    private int getItemIndexToSelect(ArrayList<Account> accounts, String selectedAccountName,
+                                     boolean selectedAddNewAccount) {
+        // If "Add account" option was previously selected by user, preserve it across
+        // orientation changes.
+        if (selectedAddNewAccount) {
+            return accounts.size();
+        }
+        // search for the selected account name if present
+        for (int i = 0; i < accounts.size(); i++) {
+            if (accounts.get(i).name.equals(selectedAccountName)) {
+                return i;
+            }
+        }
+        // no account selected.
+        return SELECTED_ITEM_NONE;
+    }
+
+    private String[] getListOfDisplayableOptions(ArrayList<Account> accounts) {
+        // List of options includes all accounts found together with "Add new account" as the
+        // last item in the list.
+        String[] listItems = new String[accounts.size() + 1];
+        for (int i = 0; i < accounts.size(); i++) {
+            listItems[i] = accounts.get(i).name;
+        }
+        listItems[accounts.size()] = getResources().getString(
+                R.string.add_account_button_label);
+        return listItems;
+    }
+
+    /**
+     * Create a list of Account objects for each account that is acceptable. Filter out
+     * accounts that don't match the allowable types, if provided, or that don't match the
+     * allowable accounts, if provided.
+     */
+    private ArrayList<Account> getAcceptableAccountChoices(VAccountManager accountManager) {
+        final Account[] accounts = accountManager.getAccounts(mCallingUserId, null);
+        ArrayList<Account> accountsToPopulate = new ArrayList<>(accounts.length);
+        for (Account account : accounts) {
+            if (mSetOfAllowableAccounts != null && !mSetOfAllowableAccounts.contains(account)) {
+                continue;
+            }
+            if (mSetOfRelevantAccountTypes != null
+                    && !mSetOfRelevantAccountTypes.contains(account.type)) {
+                continue;
+            }
+            accountsToPopulate.add(account);
+        }
+        return accountsToPopulate;
+    }
+
+    /**
+     * Return a set of account types specified by the intent as well as supported by the
+     * AccountManager.
+     */
+    private Set<String> getReleventAccountTypes(final Intent intent) {
+        // An account type is relevant iff it is allowed by the caller and supported by the account
+        // manager.
+        Set<String> setOfRelevantAccountTypes;
+        final String[] allowedAccountTypes =
+                intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
+        AuthenticatorDescription[] descs = VAccountManager.get().getAuthenticatorTypes();
+        Set<String> supportedAccountTypes = new HashSet<String>(descs.length);
+        for (AuthenticatorDescription desc : descs) {
+            supportedAccountTypes.add(desc.type);
+        }
+        if (allowedAccountTypes != null) {
+            setOfRelevantAccountTypes = new HashSet<>();
+            Collections.addAll(setOfRelevantAccountTypes, allowedAccountTypes);
+            setOfRelevantAccountTypes.retainAll(supportedAccountTypes);
+        } else {
+            setOfRelevantAccountTypes = supportedAccountTypes;
+        }
+        return setOfRelevantAccountTypes;
+    }
+
+    /**
+     * Returns a set of whitelisted accounts given by the intent or null if none specified by the
+     * intent.
+     */
+    private Set<Account> getAllowableAccountSet(final Intent intent) {
+        Set<Account> setOfAllowableAccounts = null;
+        final ArrayList<Parcelable> validAccounts =
+                intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST);
+        if (validAccounts != null) {
+            setOfAllowableAccounts = new HashSet<>(validAccounts.size());
+            for (Parcelable parcelable : validAccounts) {
+                setOfAllowableAccounts.add((Account) parcelable);
+            }
+        }
+        return setOfAllowableAccounts;
+    }
+
+    /**
+     * Overrides the description text view for the picker activity if specified by the intent.
+     * If not specified then makes the description invisible.
+     */
+    private void overrideDescriptionIfSupplied(String descriptionOverride) {
+        TextView descriptionView = (TextView) findViewById(R.id.description);
+        if (!TextUtils.isEmpty(descriptionOverride)) {
+            descriptionView.setText(descriptionOverride);
+        } else {
+            descriptionView.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Populates the UI ListView with the given list of items and selects an item
+     * based on {@code mSelectedItemIndex} member variable.
+     */
+    private void populateUIAccountList(String[] listItems) {
+        ListView list = (ListView) findViewById(android.R.id.list);
+        list.setAdapter(new ArrayAdapter<>(this,
+                android.R.layout.simple_list_item_single_choice, listItems));
+        list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+        list.setItemsCanFocus(false);
+        list.setOnItemClickListener(
+                new AdapterView.OnItemClickListener() {
+                    @Override
+                    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+                        mSelectedItemIndex = position;
+                        mOkButton.setEnabled(true);
+                    }
+                });
+        if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
+            list.setItemChecked(mSelectedItemIndex, true);
+            VLog.v(TAG, "List item " + mSelectedItemIndex + " should be selected");
+        }
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/DaemonJobService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/DaemonJobService.java
new file mode 100644
index 000000000..28ade5d4b
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/DaemonJobService.java
@@ -0,0 +1,56 @@
+package com.lody.virtual.client.stub;
+
+import android.annotation.TargetApi;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+
+import com.lody.virtual.server.pm.PrivilegeAppOptimizer;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * author: weishu on 2018/4/10.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class DaemonJobService extends JobService {
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        PrivilegeAppOptimizer.notifyBootFinish();
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        return false;
+    }
+
+    public static void scheduleJob(Context context) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+            return;
+        }
+
+        try {
+            JobScheduler jobScheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE);
+
+            if (jobScheduler == null) {
+                return;
+            }
+
+            JobInfo jobInfo = new JobInfo.Builder(1, new ComponentName(context, DaemonJobService.class))
+                    .setRequiresCharging(false)
+                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                    .setPeriodic(TimeUnit.MINUTES.toMillis(15))
+                    .build();
+
+            jobScheduler.schedule(jobInfo);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/DaemonService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/DaemonService.java
index 643f73c2f..3c5e43acd 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/DaemonService.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/DaemonService.java
@@ -4,8 +4,14 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Build;
 import android.os.IBinder;
 
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.env.Constants;
+
+import java.io.File;
+
 
 /**
  * @author Lody
@@ -15,8 +21,19 @@ public class DaemonService extends Service {
 
     private static final int NOTIFY_ID = 1001;
 
+	static boolean showNotification = true;
+
 	public static void startup(Context context) {
+		File flagFile = context.getFileStreamPath(Constants.NO_NOTIFICATION_FLAG);
+		if (Build.VERSION.SDK_INT >= 25 && flagFile.exists()) {
+			showNotification = false;
+		}
+
 		context.startService(new Intent(context, DaemonService.class));
+		if (VirtualCore.get().isServerProcess()) {
+			// PrivilegeAppOptimizer.notifyBootFinish();
+			DaemonJobService.scheduleJob(context);
+		}
 	}
 
 	@Override
@@ -33,9 +50,11 @@ public IBinder onBind(Intent intent) {
 	@Override
 	public void onCreate() {
 		super.onCreate();
+		if (!showNotification) {
+			return;
+		}
         startService(new Intent(this, InnerService.class));
         startForeground(NOTIFY_ID, new Notification());
-
 	}
 
 	@Override
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ShortcutHandleActivity.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ShortcutHandleActivity.java
index de80e501e..32fb45e57 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ShortcutHandleActivity.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/ShortcutHandleActivity.java
@@ -1,11 +1,12 @@
 package com.lody.virtual.client.stub;
 
 import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
-import android.util.Log;
 
+import com.lody.virtual.client.env.Constants;
 import com.lody.virtual.client.ipc.VActivityManager;
 
 import java.net.URISyntaxException;
@@ -47,6 +48,16 @@ protected void onCreate(Bundle savedInstanceState) {
             return;
         }
 
+        Bundle extras = intent.getExtras();
+        if (extras != null) {
+            Bundle targetBundle = new Bundle(extras);
+            for (String key : extras.keySet()) {
+                if (key.startsWith("_VA_")) {
+                    targetBundle.remove(key);
+                }
+            }
+            targetIntent.putExtras(targetBundle);
+        }
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
             targetIntent.setSelector(null);
         }
@@ -58,8 +69,16 @@ protected void onCreate(Bundle savedInstanceState) {
                 e.printStackTrace();
             }
         } else {
-            splashIntent.putExtra(Intent.EXTRA_INTENT, targetIntent);
-            splashIntent.putExtra(Intent.EXTRA_CC, userId);
+            splashIntent.putExtra(Constants.PASS_KEY_INTENT, targetIntent);
+            splashIntent.putExtra(Constants.PASS_KEY_USER, userId);
+            String pkg = targetIntent.getPackage();
+            if (pkg == null) {
+                ComponentName component = targetIntent.getComponent();
+                if (component != null) {
+                    pkg = component.getPackageName();
+                }
+            }
+            splashIntent.putExtra(Constants.PASS_PKG_NAME_ARGUMENT, pkg);
             startActivity(splashIntent);
         }
 
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubActivity.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubActivity.java
index d7110fa21..4e2bb740b 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubActivity.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubActivity.java
@@ -5,6 +5,7 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 
+import com.lody.virtual.client.VClientImpl;
 import com.lody.virtual.client.core.InvocationStubManager;
 import com.lody.virtual.client.env.VirtualRuntime;
 import com.lody.virtual.client.hook.proxies.am.HCallbackStub;
@@ -32,6 +33,7 @@ protected void onCreate(Bundle savedInstanceState) {
                 // Retry to inject the HCallback to instead of the exist one.
 				InvocationStubManager.getInstance().checkEnv(HCallbackStub.class);
 				Intent intent = r.intent;
+				intent.setExtrasClassLoader(VClientImpl.get().getCurrentApplication().getClassLoader());
 				startActivity(intent);
 			} else {
                 // Start the target Activity in other process.
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubCP.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubCP.java
new file mode 100644
index 000000000..aed30b52b
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubCP.java
@@ -0,0 +1,377 @@
+package com.lody.virtual.client.stub;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.IBinder;
+import android.os.Process;
+
+import com.lody.virtual.client.VClientImpl;
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.helper.compat.BundleCompat;
+
+/**
+ * @author Lody
+ *
+ */
+public class StubCP extends ContentProvider {
+
+	@Override
+	public boolean onCreate() {
+		return true;
+	}
+
+	@Override
+	public Bundle call(String method, String arg, Bundle extras) {
+		if ("_VA_|_init_process_".equals(method)) {
+			return initProcess(extras);
+		}
+		return null;
+	}
+
+	private Bundle initProcess(Bundle extras) {
+		ConditionVariable lock = VirtualCore.get().getInitLock();
+		if (lock != null) {
+			lock.block();
+		}
+		IBinder token = BundleCompat.getBinder(extras,"_VA_|_binder_");
+		int vuid = extras.getInt("_VA_|_vuid_");
+		VClientImpl client = VClientImpl.get();
+		client.initProcess(token, vuid);
+		Bundle res = new Bundle();
+		BundleCompat.putBinder(res, "_VA_|_client_", client.asBinder());
+		res.putInt("_VA_|_pid_", Process.myPid());
+		return res;
+	}
+
+	@Override
+	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+		return null;
+	}
+
+	@Override
+	public String getType(Uri uri) {
+		return null;
+	}
+
+	@Override
+	public Uri insert(Uri uri, ContentValues values) {
+		return null;
+	}
+
+	@Override
+	public int delete(Uri uri, String selection, String[] selectionArgs) {
+		return 0;
+	}
+
+	@Override
+	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+		return 0;
+	}
+
+
+	public static class C0 extends StubCP {
+	}
+
+	public static class C1 extends StubCP {
+	}
+
+	public static class C2 extends StubCP {
+	}
+
+	public static class C3 extends StubCP {
+	}
+
+	public static class C4 extends StubCP {
+	}
+
+	public static class C5 extends StubCP {
+	}
+
+	public static class C6 extends StubCP {
+	}
+
+	public static class C7 extends StubCP {
+	}
+
+	public static class C8 extends StubCP {
+	}
+
+	public static class C9 extends StubCP {
+	}
+
+	public static class C10 extends StubCP {
+	}
+
+	public static class C11 extends StubCP {
+	}
+
+	public static class C12 extends StubCP {
+	}
+
+	public static class C13 extends StubCP {
+	}
+
+	public static class C14 extends StubCP {
+	}
+
+	public static class C15 extends StubCP {
+	}
+
+	public static class C16 extends StubCP {
+	}
+
+	public static class C17 extends StubCP {
+	}
+
+	public static class C18 extends StubCP {
+	}
+
+	public static class C19 extends StubCP {
+	}
+
+	public static class C20 extends StubCP {
+	}
+
+	public static class C21 extends StubCP {
+	}
+
+	public static class C22 extends StubCP {
+	}
+
+	public static class C23 extends StubCP {
+	}
+
+	public static class C24 extends StubCP {
+	}
+
+	public static class C25 extends StubCP {
+	}
+
+	public static class C26 extends StubCP {
+	}
+
+	public static class C27 extends StubCP {
+	}
+
+	public static class C28 extends StubCP {
+	}
+
+	public static class C29 extends StubCP {
+	}
+
+	public static class C30 extends StubCP {
+	}
+
+	public static class C31 extends StubCP {
+	}
+
+	public static class C32 extends StubCP {
+	}
+
+	public static class C33 extends StubCP {
+	}
+
+	public static class C34 extends StubCP {
+	}
+
+	public static class C35 extends StubCP {
+	}
+
+	public static class C36 extends StubCP {
+	}
+
+	public static class C37 extends StubCP {
+	}
+
+	public static class C38 extends StubCP {
+	}
+
+	public static class C39 extends StubCP {
+	}
+
+	public static class C40 extends StubCP {
+	}
+
+	public static class C41 extends StubCP {
+	}
+
+	public static class C42 extends StubCP {
+	}
+
+	public static class C43 extends StubCP {
+	}
+
+	public static class C44 extends StubCP {
+	}
+
+	public static class C45 extends StubCP {
+	}
+
+	public static class C46 extends StubCP {
+	}
+
+	public static class C47 extends StubCP {
+	}
+
+	public static class C48 extends StubCP {
+	}
+
+	public static class C49 extends StubCP {
+	}
+
+	public static class C50 extends StubCP {
+	}
+
+	public static class C51 extends StubCP {
+	}
+
+	public static class C52 extends StubCP {
+	}
+
+	public static class C53 extends StubCP {
+	}
+
+	public static class C54 extends StubCP {
+	}
+
+	public static class C55 extends StubCP {
+	}
+
+	public static class C56 extends StubCP {
+	}
+
+	public static class C57 extends StubCP {
+	}
+
+	public static class C58 extends StubCP {
+	}
+
+	public static class C59 extends StubCP {
+	}
+
+	public static class C60 extends StubCP {
+	}
+
+	public static class C61 extends StubCP {
+	}
+
+	public static class C62 extends StubCP {
+	}
+
+	public static class C63 extends StubCP {
+	}
+
+	public static class C64 extends StubCP {
+	}
+
+	public static class C65 extends StubCP {
+	}
+
+	public static class C66 extends StubCP {
+	}
+
+	public static class C67 extends StubCP {
+	}
+
+	public static class C68 extends StubCP {
+	}
+
+	public static class C69 extends StubCP {
+	}
+
+	public static class C70 extends StubCP {
+	}
+
+	public static class C71 extends StubCP {
+	}
+
+	public static class C72 extends StubCP {
+	}
+
+	public static class C73 extends StubCP {
+	}
+
+	public static class C74 extends StubCP {
+	}
+
+	public static class C75 extends StubCP {
+	}
+
+	public static class C76 extends StubCP {
+	}
+
+	public static class C77 extends StubCP {
+	}
+
+	public static class C78 extends StubCP {
+	}
+
+	public static class C79 extends StubCP {
+	}
+
+	public static class C80 extends StubCP {
+	}
+
+	public static class C81 extends StubCP {
+	}
+
+	public static class C82 extends StubCP {
+	}
+
+	public static class C83 extends StubCP {
+	}
+
+	public static class C84 extends StubCP {
+	}
+
+	public static class C85 extends StubCP {
+	}
+
+	public static class C86 extends StubCP {
+	}
+
+	public static class C87 extends StubCP {
+	}
+
+	public static class C88 extends StubCP {
+	}
+
+	public static class C89 extends StubCP {
+	}
+
+	public static class C90 extends StubCP {
+	}
+
+	public static class C91 extends StubCP {
+	}
+
+	public static class C92 extends StubCP {
+	}
+
+	public static class C93 extends StubCP {
+	}
+
+	public static class C94 extends StubCP {
+	}
+
+	public static class C95 extends StubCP {
+	}
+
+	public static class C96 extends StubCP {
+	}
+
+	public static class C97 extends StubCP {
+	}
+
+	public static class C98 extends StubCP {
+	}
+
+	public static class C99 extends StubCP {
+	}
+
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubContentProvider.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubContentProvider.java
deleted file mode 100644
index d87bed7c1..000000000
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubContentProvider.java
+++ /dev/null
@@ -1,377 +0,0 @@
-package com.lody.virtual.client.stub;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.ConditionVariable;
-import android.os.IBinder;
-import android.os.Process;
-
-import com.lody.virtual.client.VClientImpl;
-import com.lody.virtual.client.core.VirtualCore;
-import com.lody.virtual.helper.compat.BundleCompat;
-
-/**
- * @author Lody
- *
- */
-public class StubContentProvider extends ContentProvider {
-
-	@Override
-	public boolean onCreate() {
-		return true;
-	}
-
-	@Override
-	public Bundle call(String method, String arg, Bundle extras) {
-		if ("_VA_|_init_process_".equals(method)) {
-			return initProcess(extras);
-		}
-		return null;
-	}
-
-	private Bundle initProcess(Bundle extras) {
-		ConditionVariable lock = VirtualCore.get().getInitLock();
-		if (lock != null) {
-			lock.block();
-		}
-		IBinder token = BundleCompat.getBinder(extras,"_VA_|_binder_");
-		int vuid = extras.getInt("_VA_|_vuid_");
-		VClientImpl client = VClientImpl.get();
-		client.initProcess(token, vuid);
-		Bundle res = new Bundle();
-		BundleCompat.putBinder(res, "_VA_|_client_", client.asBinder());
-		res.putInt("_VA_|_pid_", Process.myPid());
-		return res;
-	}
-
-	@Override
-	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
-		return null;
-	}
-
-	@Override
-	public String getType(Uri uri) {
-		return null;
-	}
-
-	@Override
-	public Uri insert(Uri uri, ContentValues values) {
-		return null;
-	}
-
-	@Override
-	public int delete(Uri uri, String selection, String[] selectionArgs) {
-		return 0;
-	}
-
-	@Override
-	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-		return 0;
-	}
-
-
-	public static class C0 extends StubContentProvider {
-	}
-
-	public static class C1 extends StubContentProvider {
-	}
-
-	public static class C2 extends StubContentProvider {
-	}
-
-	public static class C3 extends StubContentProvider {
-	}
-
-	public static class C4 extends StubContentProvider {
-	}
-
-	public static class C5 extends StubContentProvider {
-	}
-
-	public static class C6 extends StubContentProvider {
-	}
-
-	public static class C7 extends StubContentProvider {
-	}
-
-	public static class C8 extends StubContentProvider {
-	}
-
-	public static class C9 extends StubContentProvider {
-	}
-
-	public static class C10 extends StubContentProvider {
-	}
-
-	public static class C11 extends StubContentProvider {
-	}
-
-	public static class C12 extends StubContentProvider {
-	}
-
-	public static class C13 extends StubContentProvider {
-	}
-
-	public static class C14 extends StubContentProvider {
-	}
-
-	public static class C15 extends StubContentProvider {
-	}
-
-	public static class C16 extends StubContentProvider {
-	}
-
-	public static class C17 extends StubContentProvider {
-	}
-
-	public static class C18 extends StubContentProvider {
-	}
-
-	public static class C19 extends StubContentProvider {
-	}
-
-	public static class C20 extends StubContentProvider {
-	}
-
-	public static class C21 extends StubContentProvider {
-	}
-
-	public static class C22 extends StubContentProvider {
-	}
-
-	public static class C23 extends StubContentProvider {
-	}
-
-	public static class C24 extends StubContentProvider {
-	}
-
-	public static class C25 extends StubContentProvider {
-	}
-
-	public static class C26 extends StubContentProvider {
-	}
-
-	public static class C27 extends StubContentProvider {
-	}
-
-	public static class C28 extends StubContentProvider {
-	}
-
-	public static class C29 extends StubContentProvider {
-	}
-
-	public static class C30 extends StubContentProvider {
-	}
-
-	public static class C31 extends StubContentProvider {
-	}
-
-	public static class C32 extends StubContentProvider {
-	}
-
-	public static class C33 extends StubContentProvider {
-	}
-
-	public static class C34 extends StubContentProvider {
-	}
-
-	public static class C35 extends StubContentProvider {
-	}
-
-	public static class C36 extends StubContentProvider {
-	}
-
-	public static class C37 extends StubContentProvider {
-	}
-
-	public static class C38 extends StubContentProvider {
-	}
-
-	public static class C39 extends StubContentProvider {
-	}
-
-	public static class C40 extends StubContentProvider {
-	}
-
-	public static class C41 extends StubContentProvider {
-	}
-
-	public static class C42 extends StubContentProvider {
-	}
-
-	public static class C43 extends StubContentProvider {
-	}
-
-	public static class C44 extends StubContentProvider {
-	}
-
-	public static class C45 extends StubContentProvider {
-	}
-
-	public static class C46 extends StubContentProvider {
-	}
-
-	public static class C47 extends StubContentProvider {
-	}
-
-	public static class C48 extends StubContentProvider {
-	}
-
-	public static class C49 extends StubContentProvider {
-	}
-
-	public static class C50 extends StubContentProvider {
-	}
-
-	public static class C51 extends StubContentProvider {
-	}
-
-	public static class C52 extends StubContentProvider {
-	}
-
-	public static class C53 extends StubContentProvider {
-	}
-
-	public static class C54 extends StubContentProvider {
-	}
-
-	public static class C55 extends StubContentProvider {
-	}
-
-	public static class C56 extends StubContentProvider {
-	}
-
-	public static class C57 extends StubContentProvider {
-	}
-
-	public static class C58 extends StubContentProvider {
-	}
-
-	public static class C59 extends StubContentProvider {
-	}
-
-	public static class C60 extends StubContentProvider {
-	}
-
-	public static class C61 extends StubContentProvider {
-	}
-
-	public static class C62 extends StubContentProvider {
-	}
-
-	public static class C63 extends StubContentProvider {
-	}
-
-	public static class C64 extends StubContentProvider {
-	}
-
-	public static class C65 extends StubContentProvider {
-	}
-
-	public static class C66 extends StubContentProvider {
-	}
-
-	public static class C67 extends StubContentProvider {
-	}
-
-	public static class C68 extends StubContentProvider {
-	}
-
-	public static class C69 extends StubContentProvider {
-	}
-
-	public static class C70 extends StubContentProvider {
-	}
-
-	public static class C71 extends StubContentProvider {
-	}
-
-	public static class C72 extends StubContentProvider {
-	}
-
-	public static class C73 extends StubContentProvider {
-	}
-
-	public static class C74 extends StubContentProvider {
-	}
-
-	public static class C75 extends StubContentProvider {
-	}
-
-	public static class C76 extends StubContentProvider {
-	}
-
-	public static class C77 extends StubContentProvider {
-	}
-
-	public static class C78 extends StubContentProvider {
-	}
-
-	public static class C79 extends StubContentProvider {
-	}
-
-	public static class C80 extends StubContentProvider {
-	}
-
-	public static class C81 extends StubContentProvider {
-	}
-
-	public static class C82 extends StubContentProvider {
-	}
-
-	public static class C83 extends StubContentProvider {
-	}
-
-	public static class C84 extends StubContentProvider {
-	}
-
-	public static class C85 extends StubContentProvider {
-	}
-
-	public static class C86 extends StubContentProvider {
-	}
-
-	public static class C87 extends StubContentProvider {
-	}
-
-	public static class C88 extends StubContentProvider {
-	}
-
-	public static class C89 extends StubContentProvider {
-	}
-
-	public static class C90 extends StubContentProvider {
-	}
-
-	public static class C91 extends StubContentProvider {
-	}
-
-	public static class C92 extends StubContentProvider {
-	}
-
-	public static class C93 extends StubContentProvider {
-	}
-
-	public static class C94 extends StubContentProvider {
-	}
-
-	public static class C95 extends StubContentProvider {
-	}
-
-	public static class C96 extends StubContentProvider {
-	}
-
-	public static class C97 extends StubContentProvider {
-	}
-
-	public static class C98 extends StubContentProvider {
-	}
-
-	public static class C99 extends StubContentProvider {
-	}
-
-
-}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubExcludeFromRecentActivity.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubExcludeFromRecentActivity.java
new file mode 100644
index 000000000..b5befa40c
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubExcludeFromRecentActivity.java
@@ -0,0 +1,161 @@
+package com.lody.virtual.client.stub;
+
+/**
+ * deal with Activity manifest property excludeFromRecents
+ * deal with the bug : Mobile Legends has two recent task
+ * added by xiawanli,  2018.9.6
+ */
+public abstract class StubExcludeFromRecentActivity extends StubActivity {
+
+    public static class C0 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C1 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C2 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C3 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C4 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C5 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C6 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C7 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C8 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C9 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C10 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C11 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C12 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C13 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C14 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C15 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C16 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C17 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C18 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C19 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C20 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C21 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C22 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C23 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C24 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C25 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C26 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C27 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C28 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C29 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C30 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C31 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C32 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C33 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C34 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C35 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C36 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C37 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C38 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C39 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C40 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C41 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C42 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C43 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C44 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C45 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C46 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C47 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C48 extends StubExcludeFromRecentActivity {
+    }
+
+    public static class C49 extends StubExcludeFromRecentActivity {
+    }
+
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/VASettings.java
similarity index 60%
rename from VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java
rename to VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/VASettings.java
index 55d208a45..c6b26374a 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/StubManifest.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/client/stub/VASettings.java
@@ -6,21 +6,24 @@
  * @author Lody
  */
 
-public class StubManifest {
+public class VASettings {
     public static final String STUB_DEF_AUTHORITY = "virtual_stub_";
-    public static final boolean ENABLE_GMS = false;
+    public static final String ACTION_BADGER_CHANGE = "com.lody.virtual.BADGER_CHANGE";
     public static String STUB_ACTIVITY = StubActivity.class.getName();
     public static String STUB_DIALOG = StubDialog.class.getName();
-    public static String STUB_CP = StubContentProvider.class.getName();
+    public static String STUB_CP = StubCP.class.getName();
     public static String STUB_JOB = StubJob.class.getName();
     public static String RESOLVER_ACTIVITY = ResolverActivity.class.getName();
+    public static String STUB_EXCLUDE_FROM_RECENT_ACTIVITY = StubExcludeFromRecentActivity.class.getName();
     public static String STUB_CP_AUTHORITY = "virtual_stub_";
     public static int STUB_COUNT = 50;
+    public static String[] PRIVILEGE_APPS = new String[]{
+            "com.google.android.gms"
+    };
 
     /**
      * If enable,
      * App run in VA will allowed to create shortcut on your Desktop.
-     *
      */
     public static boolean ENABLE_INNER_SHORTCUT = true;
 
@@ -32,6 +35,10 @@ public class StubManifest {
      */
     public static boolean ENABLE_IO_REDIRECT = true;
 
+    public static String getStubExcludeFromRecentActivityName(int index) {
+        return String.format(Locale.ENGLISH, "%s$C%d", STUB_EXCLUDE_FROM_RECENT_ACTIVITY, index);
+    }
+
     public static String getStubActivityName(int index) {
         return String.format(Locale.ENGLISH, "%s$C%d", STUB_ACTIVITY, index);
     }
@@ -48,4 +55,15 @@ public static String getStubAuthority(int index) {
         return String.format(Locale.ENGLISH, "%s%d", STUB_CP_AUTHORITY, index);
     }
 
+    public static class Wifi {
+        public static boolean FAKE_WIFI_STATE = false;
+        public static String DEFAULT_BSSID = "66:55:44:33:22:11";
+        public static String DEFAULT_MAC = "11:22:33:44:55:66";
+        public static String DEFAULT_SSID = "VirtualApp";
+
+        public static String BSSID = DEFAULT_BSSID;
+        public static String MAC = DEFAULT_MAC;
+        public static String SSID = DEFAULT_SSID;
+    }
+
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/ArtDexOptimizer.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/ArtDexOptimizer.java
new file mode 100644
index 000000000..231190cc5
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/ArtDexOptimizer.java
@@ -0,0 +1,102 @@
+package com.lody.virtual.helper;
+
+import android.os.Build;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import mirror.dalvik.system.VMRuntime;
+
+/**
+ * @author Lody
+ */
+public class ArtDexOptimizer {
+
+    /**
+     * Optimize the dex in compile mode.
+     *
+     * @param dexFilePath dex file path
+     * @param oatFilePath oat file path
+     * @throws IOException
+     */
+    public static void compileDex2Oat(String dexFilePath, String oatFilePath) throws IOException {
+        final File oatFile = new File(oatFilePath);
+        if (!oatFile.exists()) {
+            oatFile.getParentFile().mkdirs();
+        }
+
+        final List<String> commandAndParams = new ArrayList<>();
+        commandAndParams.add("dex2oat");
+        // for 7.1.1, duplicate class fix
+        if (Build.VERSION.SDK_INT >= 24) {
+            commandAndParams.add("--runtime-arg");
+            commandAndParams.add("-classpath");
+            commandAndParams.add("--runtime-arg");
+            commandAndParams.add("&");
+        }
+        commandAndParams.add("--dex-file=" + dexFilePath);
+        commandAndParams.add("--oat-file=" + oatFilePath);
+        commandAndParams.add("--instruction-set=" + VMRuntime.getCurrentInstructionSet.call());
+        commandAndParams.add("--compiler-filter=everything");
+        if (Build.VERSION.SDK_INT >= 22) {
+            commandAndParams.add("--compile-pic");
+        }
+        if (Build.VERSION.SDK_INT > 25) {
+            // commandAndParams.add("--compiler-filter=quicken");
+            commandAndParams.add("--inline-max-code-units=0");
+        } else {
+            // commandAndParams.add("--compiler-filter=interpret-only");
+            if (Build.VERSION.SDK_INT >= 23) {
+                commandAndParams.add("--inline-depth-limit=0");
+            }
+        }
+
+        final ProcessBuilder pb = new ProcessBuilder(commandAndParams);
+        pb.redirectErrorStream(true);
+        final Process dex2oatProcess = pb.start();
+        StreamConsumer.consumeInputStream(dex2oatProcess.getInputStream());
+        StreamConsumer.consumeInputStream(dex2oatProcess.getErrorStream());
+        try {
+            final int ret = dex2oatProcess.waitFor();
+            if (ret != 0) {
+                throw new IOException("dex2oat works unsuccessfully, exit code: " + ret);
+            }
+        } catch (InterruptedException e) {
+            throw new IOException("dex2oat is interrupted, msg: " + e.getMessage(), e);
+        }
+    }
+
+    private static class StreamConsumer {
+        static final Executor STREAM_CONSUMER = Executors.newSingleThreadExecutor();
+
+        static void consumeInputStream(final InputStream is) {
+            STREAM_CONSUMER.execute(new Runnable() {
+                @Override
+                public void run() {
+                    if (is == null) {
+                        return;
+                    }
+                    final byte[] buffer = new byte[256];
+                    try {
+                        while ((is.read(buffer)) > 0) {
+                            // To satisfy checkstyle rules.
+                        }
+                    } catch (IOException ignored) {
+                        // Ignored.
+                    } finally {
+                        try {
+                            is.close();
+                        } catch (Exception ignored) {
+                            // Ignored.
+                        }
+                    }
+                }
+            });
+        }
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/PersistenceLayer.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/PersistenceLayer.java
index 5156e3463..a0eeb468c 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/PersistenceLayer.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/PersistenceLayer.java
@@ -22,19 +22,26 @@ public PersistenceLayer(File persistenceFile) {
     public final File getPersistenceFile() {
         return mPersistenceFile;
     }
+
     public abstract int getCurrentVersion();
 
-    public abstract void writeMagic(Parcel p);
+    public void writeMagic(Parcel p) {
+    }
 
-    public abstract boolean verifyMagic(Parcel p);
+    public boolean verifyMagic(Parcel p) {
+        return true;
+    }
 
     public abstract void writePersistenceData(Parcel p);
 
     public abstract void readPersistenceData(Parcel p);
 
-    public abstract boolean onVersionConflict(int fileVersion, int currentVersion);
+    public boolean onVersionConflict(int fileVersion, int currentVersion) {
+        return false;
+    }
 
-    public abstract void onPersistenceFileDamage();
+    public void onPersistenceFileDamage() {
+    }
 
     public void save() {
         Parcel p = Parcel.obtain();
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/BuildCompat.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/BuildCompat.java
index 4105d4174..55852937b 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/BuildCompat.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/BuildCompat.java
@@ -8,9 +8,21 @@
 
 public class BuildCompat {
 
+    public static int getPreviewSDKInt() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            try {
+                return Build.VERSION.PREVIEW_SDK_INT;
+            } catch (Throwable e) {
+                // ignore
+            }
+        }
+        return 0;
+    }
+
     public static boolean isOreo() {
 
-        return (Build.VERSION.SDK_INT == 25 && Build.VERSION.PREVIEW_SDK_INT > 0)
+        return (Build.VERSION.SDK_INT == 25 && getPreviewSDKInt() > 0)
                 || Build.VERSION.SDK_INT > 25;
     }
-}
+
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/BundleCompat.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/BundleCompat.java
index 0b3a622a7..5f2c2ea5e 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/BundleCompat.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/BundleCompat.java
@@ -3,26 +3,48 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
+
+import mirror.android.os.BaseBundle;
+import mirror.android.os.BundleICS;
 
 /**
  * @author Lody
- *
  */
 public class BundleCompat {
-	public static IBinder getBinder(Bundle bundle, String key) {
-		if (Build.VERSION.SDK_INT >= 18) {
-			return bundle.getBinder(key);
-		} else {
-			return mirror.android.os.Bundle.getIBinder.call(bundle, key);
-		}
-	}
+    public static IBinder getBinder(Bundle bundle, String key) {
+        if (Build.VERSION.SDK_INT >= 18) {
+            return bundle.getBinder(key);
+        } else {
+            return mirror.android.os.Bundle.getIBinder.call(bundle, key);
+        }
+    }
 
-	public static void putBinder(Bundle bundle, String key, IBinder value) {
-		if (Build.VERSION.SDK_INT >= 18) {
-			bundle.putBinder(key, value);
-		} else {
-			mirror.android.os.Bundle.putIBinder.call(bundle, key, value);
-		}
-	}
+    public static void putBinder(Bundle bundle, String key, IBinder value) {
+        if (Build.VERSION.SDK_INT >= 18) {
+            bundle.putBinder(key, value);
+        } else {
+            mirror.android.os.Bundle.putIBinder.call(bundle, key, value);
+        }
+    }
 
+    public static void clearParcelledData(Bundle bundle) {
+        Parcel obtain = Parcel.obtain();
+        obtain.writeInt(0);
+        obtain.setDataPosition(0);
+        Parcel parcel;
+        if (BaseBundle.TYPE != null) {
+            parcel = BaseBundle.mParcelledData.get(bundle);
+            if (parcel != null) {
+                parcel.recycle();
+            }
+            BaseBundle.mParcelledData.set(bundle, obtain);
+        } else if (BundleICS.TYPE != null) {
+            parcel = BundleICS.mParcelledData.get(bundle);
+            if (parcel != null) {
+                parcel.recycle();
+            }
+            BundleICS.mParcelledData.set(bundle, obtain);
+        }
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/ContentResolverCompat.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/ContentResolverCompat.java
new file mode 100644
index 000000000..377e78d08
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/ContentResolverCompat.java
@@ -0,0 +1,27 @@
+package com.lody.virtual.helper.compat;
+
+/**
+ * @author Lody
+ */
+
+public class ContentResolverCompat {
+
+    public static final int SYNC_OBSERVER_TYPE_STATUS = 1 << 3;
+
+    
+    public static final int SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS = 1;
+    
+    public static final int SYNC_ERROR_AUTHENTICATION = 2;
+    
+    public static final int SYNC_ERROR_IO = 3;
+    
+    public static final int SYNC_ERROR_PARSE = 4;
+    
+    public static final int SYNC_ERROR_CONFLICT = 5;
+    
+    public static final int SYNC_ERROR_TOO_MANY_DELETIONS = 6;
+    
+    public static final int SYNC_ERROR_TOO_MANY_RETRIES = 7;
+    
+    public static final int SYNC_ERROR_INTERNAL = 8;
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/IApplicationThreadCompat.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/IApplicationThreadCompat.java
index 762647992..1f70ca626 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/IApplicationThreadCompat.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/IApplicationThreadCompat.java
@@ -7,9 +7,14 @@
 import android.os.IInterface;
 import android.os.RemoteException;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import mirror.android.app.IApplicationThread;
 import mirror.android.app.IApplicationThreadICSMR1;
 import mirror.android.app.IApplicationThreadKitkat;
+import mirror.android.app.IApplicationThreadOreo;
+import mirror.android.app.ServiceStartArgs;
 import mirror.android.content.res.CompatibilityInfo;
 
 /**
@@ -18,45 +23,51 @@
 
 public class IApplicationThreadCompat {
 
-	public static void scheduleCreateService(IInterface appThread, IBinder token, ServiceInfo info,
-			int processState) throws RemoteException {
-
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
-			IApplicationThreadKitkat.scheduleCreateService.call(appThread, token, info, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO.get(),
-						processState);
-		} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
-			IApplicationThreadICSMR1.scheduleCreateService.call(appThread, token, info, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO.get());
-		} else {
-			IApplicationThread.scheduleCreateService.call(appThread, token, info);
-		}
-
-	}
-
-	public static void scheduleBindService(IInterface appThread, IBinder token, Intent intent, boolean rebind,
-			int processState) throws RemoteException {
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
-			IApplicationThreadKitkat.scheduleBindService.call(appThread, token, intent, rebind, processState);
-		} else {
-			IApplicationThread.scheduleBindService.call(appThread, token, intent, rebind);
-		}
-	}
-
-	public static void scheduleUnbindService(IInterface appThread, IBinder token, Intent intent) throws RemoteException {
-		IApplicationThread.scheduleUnbindService.call(appThread, token, intent);
-	}
-
-	public static void scheduleServiceArgs(IInterface appThread, IBinder token, boolean taskRemoved,
-			int startId, int flags, Intent args) throws RemoteException {
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
-			IApplicationThreadICSMR1.scheduleServiceArgs.call(appThread, token, taskRemoved, startId, flags, args);
-		} else {
-			IApplicationThread.scheduleServiceArgs.call(appThread, token, startId, flags, args);
-		}
-	}
-
-
-	public static void scheduleStopService(IInterface appThread, IBinder token) throws RemoteException {
-		IApplicationThread.scheduleStopService.call(appThread, token);
-	}
+    public static void scheduleCreateService(IInterface appThread, IBinder token, ServiceInfo info,
+                                             int processState) throws RemoteException {
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            IApplicationThreadKitkat.scheduleCreateService.call(appThread, token, info, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO.get(),
+                    processState);
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+            IApplicationThreadICSMR1.scheduleCreateService.call(appThread, token, info, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO.get());
+        } else {
+            IApplicationThread.scheduleCreateService.call(appThread, token, info);
+        }
+
+    }
+
+    public static void scheduleBindService(IInterface appThread, IBinder token, Intent intent, boolean rebind,
+                                           int processState) throws RemoteException {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            IApplicationThreadKitkat.scheduleBindService.call(appThread, token, intent, rebind, processState);
+        } else {
+            IApplicationThread.scheduleBindService.call(appThread, token, intent, rebind);
+        }
+    }
+
+    public static void scheduleUnbindService(IInterface appThread, IBinder token, Intent intent) throws RemoteException {
+        IApplicationThread.scheduleUnbindService.call(appThread, token, intent);
+    }
+
+    public static void scheduleServiceArgs(IInterface appThread, IBinder token, boolean taskRemoved,
+                                           int startId, int flags, Intent args) throws RemoteException {
+
+        if (Build.VERSION.SDK_INT >= 26) {
+            List<Object> list = new ArrayList<>(1);
+            Object serviceStartArg = ServiceStartArgs.ctor.newInstance(taskRemoved, startId, flags, args);
+            list.add(serviceStartArg);
+            IApplicationThreadOreo.scheduleServiceArgs.call(appThread, token, ParceledListSliceCompat.create(list));
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+            IApplicationThreadICSMR1.scheduleServiceArgs.call(appThread, token, taskRemoved, startId, flags, args);
+        } else {
+            IApplicationThread.scheduleServiceArgs.call(appThread, token, startId, flags, args);
+        }
+    }
+
+
+    public static void scheduleStopService(IInterface appThread, IBinder token) throws RemoteException {
+        IApplicationThread.scheduleStopService.call(appThread, token);
+    }
 
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/PackageParserCompat.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/PackageParserCompat.java
index 4f5ec4271..f65afd878 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/PackageParserCompat.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/PackageParserCompat.java
@@ -25,6 +25,7 @@
 import mirror.android.content.pm.PackageParserLollipop22;
 import mirror.android.content.pm.PackageParserMarshmallow;
 import mirror.android.content.pm.PackageParserNougat;
+import mirror.android.content.pm.PackageParserP28;
 import mirror.android.content.pm.PackageUserState;
 
 import static android.os.Build.VERSION_CODES.JELLY_BEAN;
@@ -170,7 +171,9 @@ public static PackageInfo generatePackageInfo(Package p, int flags, long firstIn
     }
 
     public static void collectCertificates(PackageParser parser, Package p, int flags) throws Throwable {
-        if (API_LEVEL >= N) {
+        if (API_LEVEL >= 28) {
+            PackageParserP28.collectCertificates.callWithException(p, true);
+        } else if (API_LEVEL >= N) {
             PackageParserNougat.collectCertificates.callWithException(p, flags);
         } else if (API_LEVEL >= M) {
             PackageParserMarshmallow.collectCertificates.callWithException(parser, p, flags);
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/ParceledListSliceCompat.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/ParceledListSliceCompat.java
index 1cc551d42..eef62eddf 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/ParceledListSliceCompat.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/compat/ParceledListSliceCompat.java
@@ -1,6 +1,7 @@
 package com.lody.virtual.helper.compat;
 
 import java.lang.reflect.Method;
+import java.util.Collections;
 import java.util.List;
 
 import mirror.android.content.pm.ParceledListSlice;
@@ -29,4 +30,15 @@ public static  Object create(List list) {
 		}
 	}
 
+	public static List getList(Object parceledList) {
+		if (parceledList == null || parceledList.getClass() != ParceledListSlice.TYPE) {
+			return Collections.EMPTY_LIST;
+		}
+
+		if (ParceledListSliceJBMR2.getList != null) {
+			return ParceledListSliceJBMR2.getList.call(parceledList);
+		} else {
+			return ParceledListSlice.getList.call(parceledList);
+		}
+	}
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ArrayUtils.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ArrayUtils.java
index a297d3cf2..b2b2e9cfa 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ArrayUtils.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ArrayUtils.java
@@ -8,6 +8,14 @@
  */
 public class ArrayUtils {
 
+	public static Object[] push(Object[] array, Object item)
+	{
+		Object[] longer = new Object[array.length + 1];
+		System.arraycopy(array, 0, longer, 0, array.length);
+		longer[array.length] = item;
+		return longer;
+	}
+
 	public static <T> boolean contains(T[] array, T value) {
 		return indexOf(array, value) != -1;
 	}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/AtomicFile.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/AtomicFile.java
index f375cee18..14b456e94 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/AtomicFile.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/AtomicFile.java
@@ -59,7 +59,7 @@ public void delete() {
      * with the new data.  You <em>must not</em> directly close the given
      * FileOutputStream; instead call either {@link #finishWrite(FileOutputStream)}
      * or {@link #failWrite(FileOutputStream)}.
-     *
+     * <p>
      * <p>Note that if another thread is currently performing
      * a write, this will simply replace whatever that thread is writing
      * with the new file being written by this thread, and when the other
@@ -137,7 +137,7 @@ public void failWrite(FileOutputStream str) {
      * incomplete write, this will roll back to the last good data before
      * opening for read.  You should call close() on the FileInputStream when
      * you are done reading from it.
-     *
+     * <p>
      * <p>Note that if another thread is currently performing
      * a write, this will incorrectly consider it to be in the state of a bad
      * write and roll back, causing the new data currently being written to
@@ -163,7 +163,7 @@ public byte[] readFully() throws IOException {
             int avail = stream.available();
             byte[] data = new byte[avail];
             while (true) {
-                int amt = stream.read(data, pos, data.length-pos);
+                int amt = stream.read(data, pos, data.length - pos);
                 //Log.i("foo", "Read " + amt + " bytes at " + pos
                 //        + " of avail " + data.length);
                 if (amt <= 0) {
@@ -173,8 +173,8 @@ public byte[] readFully() throws IOException {
                 }
                 pos += amt;
                 avail = stream.available();
-                if (avail > data.length-pos) {
-                    byte[] newData = new byte[pos+avail];
+                if (avail > data.length - pos) {
+                    byte[] newData = new byte[pos + avail];
                     System.arraycopy(data, 0, newData, 0, pos);
                     data = newData;
                 }
@@ -184,6 +184,31 @@ public byte[] readFully() throws IOException {
         }
     }
 
+    /**
+     * @deprecated This is not safe.
+     */
+    public void truncate() throws IOException {
+        try {
+            FileOutputStream fos = new FileOutputStream(mBaseName);
+            fos.getFD().sync();
+            fos.close();
+        } catch (FileNotFoundException e) {
+            throw new IOException("Couldn't append " + mBaseName);
+        } catch (IOException e) {
+        }
+    }
+
+    /**
+     * @deprecated This is not safe.
+     */
+    @Deprecated public FileOutputStream openAppend() throws IOException {
+        try {
+            return new FileOutputStream(mBaseName, true);
+        } catch (FileNotFoundException e) {
+            throw new IOException("Couldn't append " + mBaseName);
+        }
+    }
+
     static boolean sync(FileOutputStream stream) {
         try {
             if (stream != null) {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ComponentUtils.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ComponentUtils.java
index 093872d4f..520b64030 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ComponentUtils.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/ComponentUtils.java
@@ -5,11 +5,18 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ComponentInfo;
+import android.os.IBinder;
+import android.os.Parcelable;
 
+import com.lody.virtual.GmsSupport;
 import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.env.SpecialComponentList;
-import com.lody.virtual.client.hook.secondary.GmsSupport;
+import com.lody.virtual.client.ipc.VActivityManager;
+import com.lody.virtual.client.stub.StubPendingActivity;
+import com.lody.virtual.client.stub.StubPendingReceiver;
+import com.lody.virtual.client.stub.StubPendingService;
 import com.lody.virtual.helper.compat.ObjectsCompat;
+import com.lody.virtual.os.VUserHandle;
 
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 
@@ -127,4 +134,43 @@ public static Intent redirectBroadcastIntent(Intent intent, int userId) {
         }
         return newIntent;
     }
+
+    public static Intent redirectIntentSender(int type, String creator, Intent intent, IBinder iBinder) {
+        Intent cloneFilter = intent.cloneFilter();
+        switch (type) {
+            case 1:
+                cloneFilter.setClass(VirtualCore.get().getContext(), StubPendingReceiver.class);
+                break;
+            case 2:
+                if (VirtualCore.get().resolveActivityInfo(intent, VUserHandle.myUserId()) != null) {
+                    cloneFilter.setClass(VirtualCore.get().getContext(), StubPendingActivity.class);
+                    cloneFilter.setFlags(intent.getFlags());
+                    if (iBinder != null) {
+                        try {
+                            Parcelable activityForToken = VActivityManager.get().getActivityForToken(iBinder);
+                            if (activityForToken != null) {
+                                cloneFilter.putExtra("_VA_|_caller_", activityForToken);
+                                break;
+                            }
+                        } catch (Throwable th) {
+                            break;
+                        }
+                    }
+                }
+                break;
+            case 4:
+                if (VirtualCore.get().resolveServiceInfo(intent, VUserHandle.myUserId()) != null) {
+                    cloneFilter.setClass(VirtualCore.get().getContext(), StubPendingService.class);
+                    break;
+                }
+                break;
+            default:
+                return null;
+        }
+        cloneFilter.putExtra("_VA_|_user_id_", VUserHandle.myUserId());
+        cloneFilter.putExtra("_VA_|_intent_", intent);
+        cloneFilter.putExtra("_VA_|_creator_", creator);
+        cloneFilter.putExtra("_VA_|_from_inner_", true);
+        return cloneFilter;
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/DeviceUtil.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/DeviceUtil.java
new file mode 100644
index 000000000..9b12985fe
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/DeviceUtil.java
@@ -0,0 +1,25 @@
+package com.lody.virtual.helper.utils;
+
+import android.os.Build;
+
+/**
+ * author: weishu on 18/1/17.
+ */
+
+public class DeviceUtil {
+    /**
+     * is Meizu Android L/M device
+     * @return
+     */
+    public static boolean isMeizuBelowN() {
+        if (Build.VERSION.SDK_INT > 23) {
+            return false;
+        }
+        String display = Build.DISPLAY;
+        return display.toLowerCase().contains("flyme");
+    }
+
+    public static boolean isSamsung() {
+        return "samsung".equalsIgnoreCase(Build.BRAND) || "samsung".equalsIgnoreCase(Build.MANUFACTURER);
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/EncodeUtils.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/EncodeUtils.java
new file mode 100644
index 000000000..9f179e29f
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/EncodeUtils.java
@@ -0,0 +1,14 @@
+package com.lody.virtual.helper.utils;
+
+import android.util.Base64;
+
+/**
+ * @author weishu
+ * @date 18/3/20.
+ */
+public class EncodeUtils {
+
+    public static String decode(String base64) {
+        return new String(Base64.decode(base64, 0));
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/FileUtils.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/FileUtils.java
index 53acd11d8..78dd630c2 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/FileUtils.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/FileUtils.java
@@ -1,5 +1,7 @@
 package com.lody.virtual.helper.utils;
 
+import android.content.Context;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Parcel;
 import android.system.Os;
@@ -14,6 +16,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -21,6 +24,7 @@
 import java.nio.channels.FileChannel;
 import java.nio.channels.ReadableByteChannel;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -38,7 +42,7 @@ public static void chmod(String path, int mode) throws Exception {
                 Os.chmod(path, mode);
                 return;
             } catch (Exception e) {
-                e.printStackTrace();
+                // ignore
             }
         }
 
@@ -89,16 +93,37 @@ public static byte[] toByteArray(InputStream inStream) throws IOException {
     }
 
     public static boolean deleteDir(File dir) {
+        if (dir == null) {
+            return false;
+        }
+        boolean success = true;
         if (dir.isDirectory()) {
             String[] children = dir.list();
             for (String file : children) {
-                boolean success = deleteDir(new File(dir, file));
+                boolean ret = deleteDir(new File(dir, file));
+                if (!ret) {
+                    success = false;
+                }
+            }
+            if (success) {
+                // if all subdirectory are deleted, delete the dir itself.
+                return dir.delete();
+            }
+        }
+        return dir.delete();
+    }
+
+    public static boolean deleteDir(File dir, Set<File> ignores) {
+        if (dir.isDirectory()) {
+            String[] children = dir.list();
+            for (String file : children) {
+                boolean success = deleteDir(new File(dir, file), ignores);
                 if (!success) {
                     return false;
                 }
             }
         }
-        return dir.delete();
+        return ignores != null && ignores.contains(dir) || dir.delete();
     }
 
     public static boolean deleteDir(String dir) {
@@ -116,6 +141,43 @@ public static void writeToFile(InputStream dataIns, File target) throws IOExcept
         bos.close();
     }
 
+    public static String getFileFromUri(Context context, Uri packageUri) {
+
+        if (packageUri == null) {
+            return null;
+        }
+
+        final String SCHEME_FILE = "file";
+        final String SCHEME_CONTENT = "content";
+        String sourcePath = null;
+
+        if (SCHEME_FILE.equals(packageUri.getScheme())) {
+            sourcePath = packageUri.getPath();
+        } else if (SCHEME_CONTENT.equals(packageUri.getScheme())){
+            InputStream inputStream = null;
+            OutputStream outputStream = null;
+            File sharedFileCopy = new File(context.getCacheDir(), packageUri.getLastPathSegment());
+            try {
+                inputStream = context.getContentResolver().openInputStream(packageUri);
+                outputStream = new FileOutputStream(sharedFileCopy);
+                byte[] buffer = new byte[1024];
+                int count;
+                while ((count = inputStream.read(buffer)) > 0) {
+                    outputStream.write(buffer, 0, count);
+                }
+                outputStream.flush();
+
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                FileUtils.closeQuietly(inputStream);
+                FileUtils.closeQuietly(outputStream);
+            }
+            sourcePath = sharedFileCopy.getPath();
+        }
+        return sourcePath;
+    }
+
     public static void writeToFile(byte[] data, File target) throws IOException {
         FileOutputStream fo = null;
         ReadableByteChannel src = null;
@@ -164,6 +226,43 @@ public static void copyFile(File source, File target) throws IOException {
         }
     }
 
+    public static void copyFile(String source, String target) throws IOException {
+        File from = new File(source);
+        if (!from.exists()) {
+            return;
+        }
+        if (from.isFile()) {
+            copyFile(from, new File(target));
+        } else {
+            copyDir(source, target);
+        }
+    }
+
+    public static void copyDir(String sourcePath, String targetPath) throws IOException {
+        File from = new File(sourcePath);
+        if (!from.exists()) {
+            return;
+        }
+
+        File to = new File(targetPath);
+        if (!to.exists()) {
+            boolean mkdirs = to.mkdirs();
+            if (!mkdirs) {
+                return;
+            }
+        }
+
+        String[] child = from.list();
+        for (String file : child) {
+            File childSource = new File(sourcePath, file);
+            if (childSource.isDirectory()) {
+                copyDir(sourcePath + File.separator + file, targetPath + File.separator + file);
+            } else {
+                copyFile(childSource, new File(targetPath, file));
+            }
+        }
+    }
+
     public static void closeQuietly(Closeable closeable) {
         if (closeable != null) {
             try {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/Reflect.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/Reflect.java
index a52669fce..e4714c8f8 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/Reflect.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/Reflect.java
@@ -476,7 +476,7 @@ public Reflect create(Object... args) throws ReflectException {
     public <P> P as(Class<P> proxyType) {
         final boolean isMap = (object instanceof Map);
         final InvocationHandler handler = new InvocationHandler() {
-            @Mark
+            
             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                 String name = method.getName();
                 try {
@@ -528,7 +528,6 @@ private boolean match(Class<?>[] declaredTypes, Class<?>[] actualTypes) {
     /**
      * {@inheritDoc}
      */
-    @Mark
     public int hashCode() {
         return object.hashCode();
     }
@@ -536,7 +535,6 @@ public int hashCode() {
     /**
      * {@inheritDoc}
      */
-    @Mark
     public boolean equals(Object obj) {
         return obj instanceof Reflect && object.equals(((Reflect) obj).get());
 
@@ -545,7 +543,6 @@ public boolean equals(Object obj) {
     /**
      * {@inheritDoc}
      */
-    @Mark
     public String toString() {
         return object.toString();
     }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/SchedulerTask.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/SchedulerTask.java
new file mode 100644
index 000000000..eec3f0507
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/SchedulerTask.java
@@ -0,0 +1,35 @@
+package com.lody.virtual.helper.utils;
+
+import android.os.Handler;
+
+/**
+ * @author Lody
+ */
+
+public abstract class SchedulerTask implements Runnable {
+    private Handler mHandler;
+    private long mDelay;
+
+    public SchedulerTask(Handler handler, long delay) {
+        this.mHandler = handler;
+        this.mDelay = delay;
+    }
+
+    public void schedule() {
+        mHandler.post(mInnerRunnable);
+    }
+
+    public void cancel() {
+        mHandler.removeCallbacks(mInnerRunnable);
+    }
+
+    private final Runnable mInnerRunnable = new Runnable() {
+        @Override
+        public void run() {
+            SchedulerTask.this.run();
+            if(mDelay > 0) {
+                mHandler.postDelayed(this, mDelay);
+            }
+        }
+    };
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/XmlSerializerAndParser.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/XmlSerializerAndParser.java
new file mode 100644
index 000000000..aca3df479
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/XmlSerializerAndParser.java
@@ -0,0 +1,12 @@
+package com.lody.virtual.helper.utils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+public interface XmlSerializerAndParser<T> {
+    void writeAsXml(T item, XmlSerializer out) throws IOException;
+    T createFromXml(XmlPullParser parser) throws IOException, XmlPullParserException;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/marks/FakeDeviceMark.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/marks/FakeDeviceMark.java
new file mode 100644
index 000000000..720e07593
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/marks/FakeDeviceMark.java
@@ -0,0 +1,13 @@
+package com.lody.virtual.helper.utils.marks;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface FakeDeviceMark {
+    String value() default "";
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/Mark.java b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/marks/FakeLocMark.java
similarity index 59%
rename from VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/Mark.java
rename to VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/marks/FakeLocMark.java
index 041589ffe..b12189dd5 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/Mark.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/helper/utils/marks/FakeLocMark.java
@@ -1,4 +1,4 @@
-package com.lody.virtual.helper.utils;
+package com.lody.virtual.helper.utils.marks;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -6,7 +6,8 @@
 import java.lang.annotation.Target;
 
 
-@Target(ElementType.METHOD)
+@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.SOURCE)
-public @interface Mark {
+public @interface FakeLocMark {
+    String value() default "";
 }
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/os/VEnvironment.java b/VirtualApp/lib/src/main/java/com/lody/virtual/os/VEnvironment.java
index 53e6d0ecb..819b85115 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/os/VEnvironment.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/os/VEnvironment.java
@@ -2,12 +2,17 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.os.Environment;
 
 import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.helper.utils.EncodeUtils;
 import com.lody.virtual.helper.utils.FileUtils;
 import com.lody.virtual.helper.utils.VLog;
 
 import java.io.File;
+import java.util.Locale;
+
+import mirror.dalvik.system.VMRuntime;
 
 /**
  * @author Lody
@@ -34,7 +39,7 @@ public class VEnvironment {
         DALVIK_CACHE_DIRECTORY = ensureCreated(new File(ROOT, "opt"));
     }
 
-    public static void systemReady(){
+    public static void systemReady() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
             try {
                 FileUtils.chmod(ROOT.getAbsolutePath(), FileUtils.FileMode.MODE_755);
@@ -64,7 +69,8 @@ public static File getDataUserPackageDirectory(int userId,
     }
 
     public static File getPackageResourcePath(String packgeName) {
-        return new File(getDataAppPackageDirectory(packgeName), "base.apk");
+        return new File(getDataAppPackageDirectory(packgeName),
+                EncodeUtils.decode("YmFzZS5hcGs=")); // base.apk
     }
 
     public static File getDataAppDirectory() {
@@ -83,12 +89,19 @@ public static File getAccountConfigFile() {
         return new File(getSystemSecureDirectory(), "account-list.ini");
     }
 
+    public static File getVirtualLocationFile() {
+        return new File(getSystemSecureDirectory(), "virtual-loc.ini");
+    }
+
+    public static File getDeviceInfoFile() {
+        return new File(getSystemSecureDirectory(), "device-info.ini");
+    }
+
     public static File getPackageListFile() {
         return new File(getSystemSecureDirectory(), "packages.ini");
     }
 
     /**
-     *
      * @return Virtual storage config file
      */
     public static File getVSConfigFile() {
@@ -109,13 +122,27 @@ public static File getDalvikCacheDirectory() {
     }
 
     public static File getOdexFile(String packageName) {
-        return new File(DALVIK_CACHE_DIRECTORY, "data@app@" + packageName + "-1@base.apk@classes.dex");
+        if (isAndroidO()) {
+            // in Android O, the oatfile is relate with classloader, we must ensure the correct location to avoid repeated load dex.
+            String instructionSet = VMRuntime.getCurrentInstructionSet.call();
+            File oatDir = ensureCreated(new File(getDataAppPackageDirectory(packageName), "oat" + File.separator + instructionSet));
+            return new File(oatDir, EncodeUtils.decode("YmFzZS5vZGV4")); // base.odex
+        } else {
+            // return new File(DALVIK_CACHE_DIRECTORY, "data@app@" + packageName + "-1@base.apk@classes.dex");
+            return new File(DALVIK_CACHE_DIRECTORY, EncodeUtils.decode("ZGF0YUBhcHBA") +
+                    packageName +
+                    EncodeUtils.decode("LTFAYmFzZS5hcGtAY2xhc3Nlcy5kZXg="));
+        }
     }
 
     public static File getDataAppPackageDirectory(String packageName) {
         return ensureCreated(new File(getDataAppDirectory(), packageName));
     }
 
+    public static File getAppLibDirectory(String packageName) {
+        return ensureCreated(new File(getDataAppPackageDirectory(packageName), "lib"));
+    }
+
     public static File getPackageCacheFile(String packageName) {
         return new File(getDataAppPackageDirectory(packageName), "package.ini");
     }
@@ -132,6 +159,44 @@ public static File getUserSystemDirectory(int userId) {
         return new File(USER_DIRECTORY, String.valueOf(userId));
     }
 
+    public static File getVirtualStorageBaseDir() {
+        File externalFilesRoot = Environment.getExternalStorageDirectory();
+        if (externalFilesRoot != null) {
+            File vBaseDir = new File(externalFilesRoot, "VirtualXposed");
+            File vSdcard = new File(vBaseDir, "vsdcard");
+            return ensureCreated(vSdcard);
+        }
+        return null;
+    }
+
+    public static File getVirtualStorageDir(String packageName, int userId) {
+        File virtualStorageBaseDir = getVirtualStorageBaseDir();
+        // Apps may share sdcard files, we can not separate them by package.
+        if (virtualStorageBaseDir == null) {
+            return null;
+        }
+        File userBase = new File(virtualStorageBaseDir, String.valueOf(userId));
+        return ensureCreated(userBase);
+    }
+
+    // /sdcard/Android/data/<host_package>/virtual/<user>
+    public static File getVirtualPrivateStorageDir(int userId) {
+        String base = String.format(Locale.ENGLISH, "%s/Android/data/%s/%s/%d", Environment.getExternalStorageDirectory(),
+                VirtualCore.get().getHostPkg(), "virtual", userId);
+        File file = new File(base);
+        return ensureCreated(file);
+    }
+
+    public static File getVirtualPrivateStorageDir(int userId, String packageName) {
+        File file = new File(getVirtualPrivateStorageDir(userId), packageName);
+        return ensureCreated(file);
+    }
+
+    public static File getWifiMacFile(int userId) {
+        // return new File(getUserSystemDirectory(userId), "wifiMacAddress");
+        return new File(getUserSystemDirectory(userId), EncodeUtils.decode("d2lmaU1hY0FkZHJlc3M="));
+    }
+
     public static File getDataDirectory() {
         return DATA_DIRECTORY;
     }
@@ -143,4 +208,8 @@ public static File getSystemSecureDirectory() {
     public static File getPackageInstallerStageDir() {
         return ensureCreated(new File(DATA_DIRECTORY, ".session_dir"));
     }
+
+    public static boolean isAndroidO() {
+        return Build.VERSION.SDK_INT > 25;
+    }
 }
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/os/VUserInfo.java b/VirtualApp/lib/src/main/java/com/lody/virtual/os/VUserInfo.java
index 8abcfbbe9..7755eb77e 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/os/VUserInfo.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/os/VUserInfo.java
@@ -70,7 +70,6 @@ public class VUserInfo implements Parcelable {
 
     /** User is only partially created. */
     public boolean partial;
-    public boolean guestToRemove;
 
     public VUserInfo(int id, String name, int flags) {
         this(id, name, null, flags);
@@ -121,7 +120,6 @@ public VUserInfo(VUserInfo orig) {
         lastLoggedInTime = orig.lastLoggedInTime;
         partial = orig.partial;
         profileGroupId = orig.profileGroupId;
-        guestToRemove = orig.guestToRemove;
     }
 
     @Override
@@ -143,7 +141,6 @@ public void writeToParcel(Parcel dest, int parcelableFlags) {
         dest.writeLong(lastLoggedInTime);
         dest.writeInt(partial ? 1 : 0);
         dest.writeInt(profileGroupId);
-        dest.writeInt(guestToRemove ? 1 : 0);
     }
 
     public static final Parcelable.Creator<VUserInfo> CREATOR
@@ -166,6 +163,5 @@ private VUserInfo(Parcel source) {
         lastLoggedInTime = source.readLong();
         partial = source.readInt() != 0;
         profileGroupId = source.readInt();
-        guestToRemove = source.readInt() != 0;
     }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/os/VUserManager.java b/VirtualApp/lib/src/main/java/com/lody/virtual/os/VUserManager.java
index aeef66ccb..fdeb317ab 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/os/VUserManager.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/os/VUserManager.java
@@ -4,6 +4,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.ipc.LocalProxyUtils;
 import com.lody.virtual.client.ipc.ServiceManagerNative;
 import com.lody.virtual.server.IUserManager;
 
@@ -17,7 +19,7 @@
 public class VUserManager {
 
     private static String TAG = "VUserManager";
-    private final IUserManager mService;
+    private IUserManager mService;
 
     /**
      * Key for user restrictions. Specifies if a user is disallowed from adding and removing
@@ -119,6 +121,21 @@ public VUserManager(IUserManager service) {
         mService = service;
     }
 
+    private IUserManager getService() {
+        if (mService == null
+                || (!VirtualCore.get().isVAppProcess() && !mService.asBinder().pingBinder())) {
+            synchronized (this) {
+                Object remote = getStubInterface();
+                mService = LocalProxyUtils.genProxy(IUserManager.class, remote);
+            }
+        }
+        return mService;
+    }
+
+    private Object getStubInterface() {
+        return IUserManager.Stub.asInterface(ServiceManagerNative.getService(USER));
+    }
+
     /**
      * Returns whether the system supports multiple users.
      * @return true if multiple users can be created, false if it is a single user device.
@@ -145,7 +162,7 @@ public int getUserHandle() {
      */
     public String getUserName() {
         try {
-            return mService.getUserInfo(getUserHandle()).name;
+            return getService().getUserInfo(getUserHandle()).name;
         } catch (RemoteException re) {
             Log.w(TAG, "Could not get user name", re);
             return "";
@@ -169,7 +186,7 @@ public boolean isUserAGoat() {
      */
     public VUserInfo getUserInfo(int handle) {
         try {
-            return mService.getUserInfo(handle);
+            return getService().getUserInfo(handle);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not get user info", re);
             return null;
@@ -215,7 +232,7 @@ public VUserHandle getUserForSerialNumber(long serialNumber) {
      */
     public VUserInfo createUser(String name, int flags) {
         try {
-            return mService.createUser(name, flags);
+            return getService().createUser(name, flags);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not create a user", re);
             return null;
@@ -237,7 +254,7 @@ public int getUserCount() {
      */
     public List<VUserInfo> getUsers() {
         try {
-            return mService.getUsers(false);
+            return getService().getUsers(false);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not get user list", re);
             return null;
@@ -252,7 +269,7 @@ public List<VUserInfo> getUsers() {
      */
     public List<VUserInfo> getUsers(boolean excludeDying) {
         try {
-            return mService.getUsers(excludeDying);
+            return getService().getUsers(excludeDying);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not get user list", re);
             return null;
@@ -266,7 +283,7 @@ public List<VUserInfo> getUsers(boolean excludeDying) {
      */
     public boolean removeUser(int handle) {
         try {
-            return mService.removeUser(handle);
+            return getService().removeUser(handle);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not remove user ", re);
             return false;
@@ -282,7 +299,7 @@ public boolean removeUser(int handle) {
      */
     public void setUserName(int handle, String name) {
         try {
-            mService.setUserName(handle, name);
+            getService().setUserName(handle, name);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not set the user name ", re);
         }
@@ -296,7 +313,7 @@ public void setUserName(int handle, String name) {
      */
     public void setUserIcon(int handle, Bitmap icon) {
         try {
-            mService.setUserIcon(handle, icon);
+            getService().setUserIcon(handle, icon);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not set the user icon ", re);
         }
@@ -310,7 +327,7 @@ public void setUserIcon(int handle, Bitmap icon) {
      */
     public Bitmap getUserIcon(int handle) {
         try {
-            return mService.getUserIcon(handle);
+            return getService().getUserIcon(handle);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not get the user icon ", re);
             return null;
@@ -325,7 +342,7 @@ public Bitmap getUserIcon(int handle) {
      */
     public void setGuestEnabled(boolean enable) {
         try {
-            mService.setGuestEnabled(enable);
+            getService().setGuestEnabled(enable);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not change guest account availability to " + enable);
         }
@@ -338,7 +355,7 @@ public void setGuestEnabled(boolean enable) {
      */
     public boolean isGuestEnabled() {
         try {
-            return mService.isGuestEnabled();
+            return getService().isGuestEnabled();
         } catch (RemoteException re) {
             Log.w(TAG, "Could not retrieve guest enabled state");
             return false;
@@ -352,7 +369,7 @@ public boolean isGuestEnabled() {
      */
     public void wipeUser(int handle) {
         try {
-            mService.wipeUser(handle);
+            getService().wipeUser(handle);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not wipe user " + handle);
         }
@@ -377,7 +394,7 @@ public static int getMaxSupportedUsers() {
      */
     public int getUserSerialNumber(int handle) {
         try {
-            return mService.getUserSerialNumber(handle);
+            return getService().getUserSerialNumber(handle);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not get serial number for user " + handle);
         }
@@ -395,7 +412,7 @@ public int getUserSerialNumber(int handle) {
      */
     public int getUserHandle(int userSerialNumber) {
         try {
-            return mService.getUserHandle(userSerialNumber);
+            return getService().getUserHandle(userSerialNumber);
         } catch (RemoteException re) {
             Log.w(TAG, "Could not get VUserHandle for user " + userSerialNumber);
         }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/remote/BadgerInfo.java b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/BadgerInfo.java
new file mode 100644
index 000000000..db8d8efa8
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/BadgerInfo.java
@@ -0,0 +1,53 @@
+package com.lody.virtual.remote;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.lody.virtual.os.VUserHandle;
+
+/**
+ * @author Lody
+ */
+public class BadgerInfo implements Parcelable {
+
+    public int userId;
+    public String packageName;
+    public int badgerCount;
+    public String className;
+
+    public BadgerInfo() {
+        userId = VUserHandle.myUserId();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(userId);
+        dest.writeString(packageName);
+        dest.writeInt(badgerCount);
+        dest.writeString(className);
+    }
+
+    protected BadgerInfo(Parcel in) {
+        userId = in.readInt();
+        packageName = in.readString();
+        badgerCount = in.readInt();
+        className = in.readString();
+    }
+
+    public static final Parcelable.Creator<BadgerInfo> CREATOR = new Parcelable.Creator<BadgerInfo>() {
+        @Override
+        public BadgerInfo createFromParcel(Parcel source) {
+            return new BadgerInfo(source);
+        }
+
+        @Override
+        public BadgerInfo[] newArray(int size) {
+            return new BadgerInfo[size];
+        }
+    };
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/remote/InstallResult.java b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/InstallResult.java
index f0049b148..dfa0ac208 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/remote/InstallResult.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/InstallResult.java
@@ -53,4 +53,14 @@ public void writeToParcel(Parcel dest, int flags) {
 	public int describeContents() {
 		return 0;
 	}
+
+	@Override
+	public String toString() {
+		return "InstallResult{" +
+				"isSuccess=" + isSuccess +
+				", isUpdate=" + isUpdate +
+				", packageName='" + packageName + '\'' +
+				", error='" + error + '\'' +
+				'}';
+	}
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/remote/InstalledAppInfo.java b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/InstalledAppInfo.java
index 308f79604..d93da4932 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/remote/InstalledAppInfo.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/InstalledAppInfo.java
@@ -1,6 +1,7 @@
 package com.lody.virtual.remote;
 
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -19,15 +20,13 @@ public final class InstalledAppInfo implements Parcelable {
     public String apkPath;
     public String libPath;
     public boolean dependSystem;
-    public boolean artFlyMode;
     public int appId;
 
-    public InstalledAppInfo(String packageName, String apkPath, String libPath, boolean dependSystem, boolean artFlyMode, int appId) {
+    public InstalledAppInfo(String packageName, String apkPath, String libPath, boolean dependSystem, boolean skipDexOpt, int appId) {
         this.packageName = packageName;
         this.apkPath = apkPath;
         this.libPath = libPath;
         this.dependSystem = dependSystem;
-        this.artFlyMode = artFlyMode;
         this.appId = appId;
     }
 
@@ -39,6 +38,10 @@ public ApplicationInfo getApplicationInfo(int userId) {
         return VPackageManager.get().getApplicationInfo(packageName, 0, userId);
     }
 
+    public PackageInfo getPackageInfo(int userId) {
+        return VPackageManager.get().getPackageInfo(packageName, 0, userId);
+    }
+
     public int[] getInstalledUsers() {
         return VirtualCore.get().getPackageInstalledUsers(packageName);
     }
@@ -58,7 +61,6 @@ public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(this.apkPath);
         dest.writeString(this.libPath);
         dest.writeByte(this.dependSystem ? (byte) 1 : (byte) 0);
-        dest.writeByte(this.artFlyMode ? (byte) 1 : (byte) 0);
         dest.writeInt(this.appId);
     }
 
@@ -67,7 +69,6 @@ protected InstalledAppInfo(Parcel in) {
         this.apkPath = in.readString();
         this.libPath = in.readString();
         this.dependSystem = in.readByte() != 0;
-        this.artFlyMode = in.readByte() != 0;
         this.appId = in.readInt();
     }
 
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/remote/SyncInfo.java b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/SyncInfo.java
new file mode 100644
index 000000000..f01f7f97c
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/SyncInfo.java
@@ -0,0 +1,71 @@
+package com.lody.virtual.remote;
+
+import android.accounts.Account;
+import android.os.Parcel;
+import android.os.Parcelable.Creator;
+
+/**
+ * Information about the sync operation that is currently underway.
+ */
+public class SyncInfo {
+
+    public final int authorityId;
+
+    /**
+     * The {@link Account} that is currently being synced.
+     */
+    public final Account account;
+
+    /**
+     * The authority of the provider that is currently being synced.
+     */
+    public final String authority;
+
+    /**
+     * The start time of the current sync operation in milliseconds since boot.
+     * This is represented in elapsed real time.
+     * See {@link android.os.SystemClock#elapsedRealtime()}.
+     */
+    public final long startTime;
+
+    public SyncInfo(int authorityId, Account account, String authority,
+                    long startTime) {
+        this.authorityId = authorityId;
+        this.account = account;
+        this.authority = authority;
+        this.startTime = startTime;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(authorityId);
+        account.writeToParcel(parcel, 0);
+        parcel.writeString(authority);
+        parcel.writeLong(startTime);
+    }
+
+    SyncInfo(Parcel parcel) {
+        authorityId = parcel.readInt();
+        account = new Account(parcel);
+        authority = parcel.readString();
+        startTime = parcel.readLong();
+    }
+
+    public android.content.SyncInfo create() {
+        return mirror.android.content.SyncInfo.ctor.newInstance(authorityId, account, authority, startTime);
+    }
+
+    public static final Creator<SyncInfo> CREATOR = new Creator<SyncInfo>() {
+        public SyncInfo createFromParcel(Parcel in) {
+            return new SyncInfo(in);
+        }
+
+        public SyncInfo[] newArray(int size) {
+            return new SyncInfo[size];
+        }
+    };
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/remote/VDeviceInfo.java b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/VDeviceInfo.java
new file mode 100644
index 000000000..3b05bc7c4
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/VDeviceInfo.java
@@ -0,0 +1,78 @@
+package com.lody.virtual.remote;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.lody.virtual.os.VEnvironment;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * @author Lody
+ */
+public class VDeviceInfo implements Parcelable {
+
+    public String deviceId;
+    public String androidId;
+    public String wifiMac;
+    public String bluetoothMac;
+    public String iccId;
+    public String serial;
+    public String gmsAdId;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(this.deviceId);
+        dest.writeString(this.androidId);
+        dest.writeString(this.wifiMac);
+        dest.writeString(this.bluetoothMac);
+        dest.writeString(this.iccId);
+        dest.writeString(this.serial);
+        dest.writeString(this.gmsAdId);
+    }
+
+    public VDeviceInfo() {}
+
+    public VDeviceInfo(Parcel in) {
+        this.deviceId = in.readString();
+        this.androidId = in.readString();
+        this.wifiMac = in.readString();
+        this.bluetoothMac = in.readString();
+        this.iccId = in.readString();
+        this.serial = in.readString();
+        this.gmsAdId = in.readString();
+    }
+
+    public static final Parcelable.Creator<VDeviceInfo> CREATOR = new Parcelable.Creator<VDeviceInfo>() {
+        @Override
+        public VDeviceInfo createFromParcel(Parcel source) {
+            return new VDeviceInfo(source);
+        }
+
+        @Override
+        public VDeviceInfo[] newArray(int size) {
+            return new VDeviceInfo[size];
+        }
+    };
+
+    public File getWifiFile(int userId) {
+        File wifiMacFie = VEnvironment.getWifiMacFile(userId);
+        if (!wifiMacFie.exists()) {
+            try {
+                RandomAccessFile file = new RandomAccessFile(wifiMacFie, "rws");
+                file.write((wifiMac + "\n").getBytes());
+                file.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return wifiMacFie;
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/remote/vloc/VCell.java b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/vloc/VCell.java
new file mode 100644
index 000000000..3e0fe173a
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/vloc/VCell.java
@@ -0,0 +1,67 @@
+package com.lody.virtual.remote.vloc;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @author Lody
+ */
+
+public class VCell implements Parcelable {
+
+    public int type;
+    public int mcc;
+    public int mnc;
+    public int psc;
+    public int lac;
+    public int cid;
+    public int baseStationId;
+    public int systemId;
+    public int networkId;
+
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(this.type);
+        dest.writeInt(this.mcc);
+        dest.writeInt(this.mnc);
+        dest.writeInt(this.psc);
+        dest.writeInt(this.lac);
+        dest.writeInt(this.cid);
+        dest.writeInt(this.baseStationId);
+        dest.writeInt(this.systemId);
+        dest.writeInt(this.networkId);
+    }
+
+    public VCell() {
+    }
+
+    public VCell(Parcel in) {
+        this.type = in.readInt();
+        this.mcc = in.readInt();
+        this.mnc = in.readInt();
+        this.psc = in.readInt();
+        this.lac = in.readInt();
+        this.cid = in.readInt();
+        this.baseStationId = in.readInt();
+        this.systemId = in.readInt();
+        this.networkId = in.readInt();
+    }
+
+    public static final Parcelable.Creator<VCell> CREATOR = new Parcelable.Creator<VCell>() {
+        @Override
+        public VCell createFromParcel(Parcel source) {
+            return new VCell(source);
+        }
+
+        @Override
+        public VCell[] newArray(int size) {
+            return new VCell[size];
+        }
+    };
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/remote/vloc/VLocation.java b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/vloc/VLocation.java
new file mode 100644
index 000000000..d6d596a49
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/vloc/VLocation.java
@@ -0,0 +1,100 @@
+package com.lody.virtual.remote.vloc;
+
+import android.location.Location;
+import android.location.LocationManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.lody.virtual.client.env.VirtualGPSSatalines;
+import com.lody.virtual.helper.utils.Reflect;
+
+/**
+ * @author Lody
+ */
+
+public class VLocation implements Parcelable {
+
+    public double latitude = 0.0;
+    public double longitude = 0.0;
+    public double altitude = 0.0f;
+    public float accuracy = 0.0f;
+    public float speed;
+    public float bearing;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeDouble(latitude);
+        dest.writeDouble(longitude);
+        dest.writeDouble(altitude);
+        dest.writeFloat(accuracy);
+        dest.writeFloat(speed);
+        dest.writeFloat(bearing);
+    }
+
+    public VLocation() {
+    }
+
+    public VLocation(Parcel in) {
+        latitude = in.readDouble();
+        longitude = in.readDouble();
+        altitude = in.readDouble();
+        accuracy = in.readFloat();
+        speed = in.readFloat();
+        bearing = in.readFloat();
+    }
+
+    public boolean isEmpty() {
+        return latitude == 0 && longitude == 0;
+    }
+
+    public static final Parcelable.Creator<VLocation> CREATOR = new Parcelable.Creator<VLocation>() {
+        @Override
+        public VLocation createFromParcel(Parcel source) {
+            return new VLocation(source);
+        }
+
+        @Override
+        public VLocation[] newArray(int size) {
+            return new VLocation[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "VLocation{" +
+                "latitude=" + latitude +
+                ", longitude=" + longitude +
+                ", altitude=" + altitude +
+                ", accuracy=" + accuracy +
+                ", speed=" + speed +
+                ", bearing=" + bearing +
+                '}';
+    }
+
+    public Location toSysLocation() {
+        Location location = new Location(LocationManager.GPS_PROVIDER);
+        location.setAccuracy(8f);
+        Bundle extraBundle = new Bundle();
+        location.setBearing(bearing);
+        Reflect.on(location).call("setIsFromMockProvider", false);
+        location.setLatitude(latitude);
+        location.setLongitude(longitude);
+        location.setSpeed(speed);
+        location.setTime(System.currentTimeMillis());
+        location.setExtras(extraBundle);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            location.setElapsedRealtimeNanos(277000000);
+        }
+        int svCount = VirtualGPSSatalines.get().getSvCount();
+        extraBundle.putInt("satellites", svCount);
+        extraBundle.putInt("satellitesvalue", svCount);
+        return location;
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/remote/vloc/VWifi.java b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/vloc/VWifi.java
new file mode 100644
index 000000000..339943b98
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/remote/vloc/VWifi.java
@@ -0,0 +1,57 @@
+package com.lody.virtual.remote.vloc;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @author Lody
+ */
+
+public class VWifi implements Parcelable {
+
+    public String ssid;
+    public String bssid;
+    public String capabilities;
+    public int level;
+    public int frequency;
+    public long timestamp;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(this.ssid);
+        dest.writeString(this.bssid);
+        dest.writeString(this.capabilities);
+        dest.writeInt(this.level);
+        dest.writeInt(this.frequency);
+        dest.writeLong(this.timestamp);
+    }
+
+    public VWifi() {
+    }
+
+    public VWifi(Parcel in) {
+        this.ssid = in.readString();
+        this.bssid = in.readString();
+        this.capabilities = in.readString();
+        this.level = in.readInt();
+        this.frequency = in.readInt();
+        this.timestamp = in.readLong();
+    }
+
+    public static final Parcelable.Creator<VWifi> CREATOR = new Parcelable.Creator<VWifi>() {
+        @Override
+        public VWifi createFromParcel(Parcel source) {
+            return new VWifi(source);
+        }
+
+        @Override
+        public VWifi[] newArray(int size) {
+            return new VWifi[size];
+        }
+    };
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/BinderProvider.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/BinderProvider.java
index 33bfa3a9a..327ce44a4 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/BinderProvider.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/BinderProvider.java
@@ -17,8 +17,10 @@
 import com.lody.virtual.server.accounts.VAccountManagerService;
 import com.lody.virtual.server.am.BroadcastSystem;
 import com.lody.virtual.server.am.VActivityManagerService;
+import com.lody.virtual.server.device.VDeviceManagerService;
 import com.lody.virtual.server.interfaces.IServiceFetcher;
 import com.lody.virtual.server.job.VJobSchedulerService;
+import com.lody.virtual.server.location.VirtualLocationService;
 import com.lody.virtual.server.notification.VNotificationManagerService;
 import com.lody.virtual.server.pm.VAppManagerService;
 import com.lody.virtual.server.pm.VPackageManagerService;
@@ -56,6 +58,8 @@ public boolean onCreate() {
         VAccountManagerService.systemReady();
         addService(ServiceManagerNative.ACCOUNT, VAccountManagerService.get());
         addService(ServiceManagerNative.VS, VirtualStorageService.get());
+        addService(ServiceManagerNative.DEVICE, VDeviceManagerService.get());
+        addService(ServiceManagerNative.VIRTUAL_LOC, VirtualLocationService.get());
         return true;
     }
 
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/IJobScheduler.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/IJobScheduler.java
new file mode 100644
index 000000000..2b0381363
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/IJobScheduler.java
@@ -0,0 +1,291 @@
+/*
+ * This file is auto-generated.  DO NOT MODIFY.
+ * Original file: /Users/weishu/dev/github/VirtualXposed/VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IJobScheduler.aidl
+ */
+package com.lody.virtual.server;
+
+import android.app.job.JobInfo;
+import android.app.job.JobWorkItem;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * IPC interface that supports the app-facing {@link android.app.job.JobScheduler} api.
+ */
+public interface IJobScheduler extends android.os.IInterface {
+    /**
+     * Local-side IPC implementation stub class.
+     */
+    public static abstract class Stub extends android.os.Binder implements com.lody.virtual.server.IJobScheduler {
+        private static final java.lang.String DESCRIPTOR = "com.lody.virtual.server.IJobScheduler";
+
+        /**
+         * Construct the stub at attach it to the interface.
+         */
+        public Stub() {
+            this.attachInterface(this, DESCRIPTOR);
+        }
+
+        /**
+         * Cast an IBinder object into an com.lody.virtual.server.IJobScheduler interface,
+         * generating a proxy if needed.
+         */
+        public static com.lody.virtual.server.IJobScheduler asInterface(android.os.IBinder obj) {
+            if ((obj == null)) {
+                return null;
+            }
+            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+            if (((iin != null) && (iin instanceof com.lody.virtual.server.IJobScheduler))) {
+                return ((com.lody.virtual.server.IJobScheduler) iin);
+            }
+            return new com.lody.virtual.server.IJobScheduler.Stub.Proxy(obj);
+        }
+
+        @Override
+        public android.os.IBinder asBinder() {
+            return this;
+        }
+
+        @Override
+        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
+            switch (code) {
+                case INTERFACE_TRANSACTION: {
+                    reply.writeString(DESCRIPTOR);
+                    return true;
+                }
+                case TRANSACTION_schedule: {
+                    data.enforceInterface(DESCRIPTOR);
+                    JobInfo _arg0;
+                    if ((0 != data.readInt())) {
+                        _arg0 = JobInfo.CREATOR.createFromParcel(data);
+                    } else {
+                        _arg0 = null;
+                    }
+                    int _result = this.schedule(_arg0);
+                    reply.writeNoException();
+                    reply.writeInt(_result);
+                    return true;
+                }
+                case TRANSACTION_cancel: {
+                    data.enforceInterface(DESCRIPTOR);
+                    int _arg0;
+                    _arg0 = data.readInt();
+                    this.cancel(_arg0);
+                    reply.writeNoException();
+                    return true;
+                }
+                case TRANSACTION_cancelAll: {
+                    data.enforceInterface(DESCRIPTOR);
+                    this.cancelAll();
+                    reply.writeNoException();
+                    return true;
+                }
+                case TRANSACTION_getAllPendingJobs: {
+                    data.enforceInterface(DESCRIPTOR);
+                    List<JobInfo> _result = this.getAllPendingJobs();
+                    reply.writeNoException();
+                    reply.writeTypedList(_result);
+                    return true;
+                }
+                case TRANSACTION_enqueue: {
+                    data.enforceInterface(DESCRIPTOR);
+                    JobInfo _arg0;
+                    if ((0 != data.readInt())) {
+                        _arg0 = JobInfo.CREATOR.createFromParcel(data);
+                    } else {
+                        _arg0 = null;
+                    }
+                    JobWorkItem _arg1;
+                    if ((0 != data.readInt())) {
+                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                            _arg1 = JobWorkItem.CREATOR.createFromParcel(data);
+                        } else {
+                            _arg1 = null;
+                        }
+                    } else {
+                        _arg1 = null;
+                    }
+                    int _result = this.enqueue(_arg0, _arg1);
+                    reply.writeNoException();
+                    reply.writeInt(_result);
+                    return true;
+                }
+                case TRANSACTION_getPendingJob: {
+                    data.enforceInterface(DESCRIPTOR);
+                    int _arg0;
+                    _arg0 = data.readInt();
+                    JobInfo _result = this.getPendingJob(_arg0);
+                    reply.writeNoException();
+                    if ((_result != null)) {
+                        reply.writeInt(1);
+                        _result.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                    } else {
+                        reply.writeInt(0);
+                    }
+                    return true;
+                }
+            }
+            return super.onTransact(code, data, reply, flags);
+        }
+
+        private static class Proxy implements com.lody.virtual.server.IJobScheduler {
+            private android.os.IBinder mRemote;
+
+            Proxy(android.os.IBinder remote) {
+                mRemote = remote;
+            }
+
+            @Override
+            public android.os.IBinder asBinder() {
+                return mRemote;
+            }
+
+            public java.lang.String getInterfaceDescriptor() {
+                return DESCRIPTOR;
+            }
+
+            @Override
+            public int schedule(android.app.job.JobInfo job) throws android.os.RemoteException {
+                android.os.Parcel _data = android.os.Parcel.obtain();
+                android.os.Parcel _reply = android.os.Parcel.obtain();
+                int _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    if ((job != null)) {
+                        _data.writeInt(1);
+                        job.writeToParcel(_data, 0);
+                    } else {
+                        _data.writeInt(0);
+                    }
+                    mRemote.transact(Stub.TRANSACTION_schedule, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.readInt();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
+
+            @Override
+            public void cancel(int jobId) throws android.os.RemoteException {
+                android.os.Parcel _data = android.os.Parcel.obtain();
+                android.os.Parcel _reply = android.os.Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(jobId);
+                    mRemote.transact(Stub.TRANSACTION_cancel, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
+            public void cancelAll() throws android.os.RemoteException {
+                android.os.Parcel _data = android.os.Parcel.obtain();
+                android.os.Parcel _reply = android.os.Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    mRemote.transact(Stub.TRANSACTION_cancelAll, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
+            public java.util.List<android.app.job.JobInfo> getAllPendingJobs() throws android.os.RemoteException {
+                android.os.Parcel _data = android.os.Parcel.obtain();
+                android.os.Parcel _reply = android.os.Parcel.obtain();
+                java.util.List<android.app.job.JobInfo> _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    mRemote.transact(Stub.TRANSACTION_getAllPendingJobs, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.createTypedArrayList(android.app.job.JobInfo.CREATOR);
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
+
+            @Override
+            public int enqueue(android.app.job.JobInfo job, android.app.job.JobWorkItem work) throws android.os.RemoteException {
+                android.os.Parcel _data = android.os.Parcel.obtain();
+                android.os.Parcel _reply = android.os.Parcel.obtain();
+                int _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    if ((job != null)) {
+                        _data.writeInt(1);
+                        job.writeToParcel(_data, 0);
+                    } else {
+                        _data.writeInt(0);
+                    }
+                    if ((work != null)) {
+                        _data.writeInt(1);
+                        work.writeToParcel(_data, 0);
+                    } else {
+                        _data.writeInt(0);
+                    }
+                    mRemote.transact(Stub.TRANSACTION_enqueue, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.readInt();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
+
+            @Override
+            public android.app.job.JobInfo getPendingJob(int i) throws android.os.RemoteException {
+                android.os.Parcel _data = android.os.Parcel.obtain();
+                android.os.Parcel _reply = android.os.Parcel.obtain();
+                android.app.job.JobInfo _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(i);
+                    mRemote.transact(Stub.TRANSACTION_getPendingJob, _data, _reply, 0);
+                    _reply.readException();
+                    if ((0 != _reply.readInt())) {
+                        _result = android.app.job.JobInfo.CREATOR.createFromParcel(_reply);
+                    } else {
+                        _result = null;
+                    }
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
+        }
+
+        static final int TRANSACTION_schedule = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+        static final int TRANSACTION_cancel = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+        static final int TRANSACTION_cancelAll = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
+        static final int TRANSACTION_getAllPendingJobs = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
+        static final int TRANSACTION_enqueue = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
+        static final int TRANSACTION_getPendingJob = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5);
+    }
+
+    public int schedule(android.app.job.JobInfo job) throws android.os.RemoteException;
+
+    public void cancel(int jobId) throws android.os.RemoteException;
+
+    public void cancelAll() throws android.os.RemoteException;
+
+    public java.util.List<android.app.job.JobInfo> getAllPendingJobs() throws android.os.RemoteException;
+
+    public int enqueue(android.app.job.JobInfo job, android.app.job.JobWorkItem work) throws android.os.RemoteException;
+
+    public android.app.job.JobInfo getPendingJob(int i) throws android.os.RemoteException;
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/IAccountParser.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/IAccountParser.java
deleted file mode 100644
index 0dfb6280c..000000000
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/IAccountParser.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.lody.virtual.server.accounts;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-
-public interface IAccountParser {
-    XmlResourceParser getParser(Context context, ServiceInfo serviceInfo, String name);
-    Resources getResources(Context context, ApplicationInfo appInfo) throws Exception;
-}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/AppAccountParser.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/RegisteredServicesParser.java
similarity index 95%
rename from VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/AppAccountParser.java
rename to VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/RegisteredServicesParser.java
index f0b59879f..2ec9d235e 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/AppAccountParser.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/RegisteredServicesParser.java
@@ -11,9 +11,8 @@
 import com.lody.virtual.server.pm.PackageCacheManager;
 import com.lody.virtual.server.pm.PackageSetting;
 
-class AppAccountParser implements IAccountParser {
+public class RegisteredServicesParser {
 
-    @Override
     public XmlResourceParser getParser(Context context, ServiceInfo serviceInfo, String name) {
         Bundle meta = serviceInfo.metaData;
         if (meta != null) {
@@ -29,7 +28,6 @@ public XmlResourceParser getParser(Context context, ServiceInfo serviceInfo, Str
         return null;
     }
 
-    @Override
     public Resources getResources(Context context, ApplicationInfo appInfo) throws Exception {
         PackageSetting ps = PackageCacheManager.getSetting(appInfo.packageName);
         if (ps != null) {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/VAccountManagerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/VAccountManagerService.java
index 98300d1ec..fb37458fb 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/VAccountManagerService.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/VAccountManagerService.java
@@ -64,1353 +64,1353 @@
  */
 public class VAccountManagerService extends IAccountManager.Stub {
 
-	private static final AtomicReference<VAccountManagerService> sInstance = new AtomicReference<>();
-	private static final long CHECK_IN_TIME = 30 * 24 * 60 * 1000L;
-	private static final String TAG = VAccountManagerService.class.getSimpleName();
-	private final SparseArray<List<VAccount>> accountsByUserId = new SparseArray<>();
-	private final LinkedList<AuthTokenRecord> authTokenRecords = new LinkedList<>();
-	private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<>();
-	private final AuthenticatorCache cache = new AuthenticatorCache();
-	private Context mContext = VirtualCore.get().getContext();
-	private long lastAccountChangeTime = 0;
-
-
-	public static VAccountManagerService get() {
-		return sInstance.get();
-	}
-
-	public static void systemReady() {
-		VAccountManagerService service = new VAccountManagerService();
-		service.readAllAccounts();
-		sInstance.set(service);
-	}
-
-
-	private static AuthenticatorDescription parseAuthenticatorDescription(Resources resources, String packageName,
-																		  AttributeSet attributeSet) {
-		TypedArray array = resources.obtainAttributes(attributeSet, R_Hide.styleable.AccountAuthenticator.get());
-		try {
-			String accountType = array.getString(R_Hide.styleable.AccountAuthenticator_accountType.get());
-			int label = array.getResourceId(R_Hide.styleable.AccountAuthenticator_label.get(), 0);
-			int icon = array.getResourceId(R_Hide.styleable.AccountAuthenticator_icon.get(), 0);
-			int smallIcon = array.getResourceId(R_Hide.styleable.AccountAuthenticator_smallIcon.get(), 0);
-			int accountPreferences = array.getResourceId(R_Hide.styleable.AccountAuthenticator_accountPreferences.get(), 0);
-			boolean customTokens = array.getBoolean(R_Hide.styleable.AccountAuthenticator_customTokens.get(), false);
-			if (TextUtils.isEmpty(accountType)) {
-				return null;
-			}
-			return new AuthenticatorDescription(accountType, packageName, label, icon, smallIcon, accountPreferences,
-					customTokens);
-		} finally {
-			array.recycle();
-		}
-	}
-
-
-	@Override
-	public AuthenticatorDescription[] getAuthenticatorTypes(int userId) {
-		synchronized (cache) {
-			AuthenticatorDescription[] descArray = new AuthenticatorDescription[cache.authenticators.size()];
-			int i = 0;
-			for (AuthenticatorInfo info : cache.authenticators.values()) {
-				descArray[i] = info.desc;
-				i++;
-			}
-			return descArray;
-		}
-	}
-
-	@Override
-	public void getAccountsByFeatures(int userId, IAccountManagerResponse response, String type, String[] features) {
-		if (response == null) throw new IllegalArgumentException("response is null");
-		if (type == null) throw new IllegalArgumentException("accountType is null");
-		AuthenticatorInfo info = getAuthenticatorInfo(type);
-		if (info == null) {
-			Bundle bundle = new Bundle();
-			bundle.putParcelableArray(AccountManager.KEY_ACCOUNTS, new Account[0]);
-			try {
-				response.onResult(bundle);
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-			return;
-		}
-
-		if (features == null || features.length == 0) {
-			Bundle bundle = new Bundle();
-			bundle.putParcelableArray(AccountManager.KEY_ACCOUNTS, getAccounts(userId, type));
-			try {
-				response.onResult(bundle);
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-		} else {
-			new GetAccountsByTypeAndFeatureSession(response, userId, info, features).bind();
-		}
-	}
-
-	@Override
-	public final String getPreviousName(int userId, Account account) {
-		if (account == null) throw new IllegalArgumentException("account is null");
-		synchronized (accountsByUserId) {
-			String previousName = null;
-			VAccount vAccount = getAccount(userId, account);
-			if (vAccount != null) {
-				previousName = vAccount.previousName;
-			}
-			return previousName;
-		}
-	}
-
-
-	@Override
-	public Account[] getAccounts(int userId, String type) {
-		List<Account> accountList = getAccountList(userId, type);
-		return accountList.toArray(new Account[accountList.size()]);
-	}
-
-
-	private List<Account> getAccountList(int userId, String type) {
-		synchronized (accountsByUserId) {
-			List<Account> accounts = new ArrayList<>();
-			List<VAccount> vAccounts = accountsByUserId.get(userId);
-			if (vAccounts != null) {
-				for (VAccount vAccount : vAccounts) {
-					if (type == null || vAccount.type.equals(type)) {
-						accounts.add(new Account(vAccount.name, vAccount.type));
-					}
-				}
-			}
-			return accounts;
-		}
-	}
-
-	@Override
-	public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
-		try {
-			return super.onTransact(code, data, reply, flags);
-		} catch (Throwable e) {
-			e.printStackTrace();
-			throw e;
-		}
-	}
-
-	@Override
-	public final void getAuthToken(final int userId, final IAccountManagerResponse response, final Account account, final String authTokenType, final boolean notifyOnAuthFailure, boolean expectActivityLaunch, final Bundle loginOptions) {
-		if (response == null) {
-			throw new IllegalArgumentException("response is null");
-		}
-		try {
-			if (account == null) {
-				VLog.w(TAG, "getAuthToken called with null account");
-				response.onError(ERROR_CODE_BAD_ARGUMENTS, "account is null");
-				return;
-			}
-			if (authTokenType == null) {
-				VLog.w(TAG, "getAuthToken called with null authTokenType");
-				response.onError(ERROR_CODE_BAD_ARGUMENTS, "authTokenType is null");
-				return;
-			}
-		} catch (RemoteException e) {
-			e.printStackTrace();
-			return;
-		}
-		AuthenticatorInfo info = getAuthenticatorInfo(account.type);
-		if (info == null) {
-			try {
-				response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-			return;
-		}
-		// Get the calling package. We will use it for the purpose of caching.
-		final String callerPkg = loginOptions.getString(AccountManagerCompat.KEY_ANDROID_PACKAGE_NAME);
-		final boolean customTokens = info.desc.customTokens;
-
-		loginOptions.putInt(AccountManager.KEY_CALLER_UID, VBinder.getCallingUid());
-		loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid());
-		if (notifyOnAuthFailure) {
-			loginOptions.putBoolean(AccountManagerCompat.KEY_NOTIFY_ON_FAILURE, true);
-		}
-		if (!customTokens) {
-			VAccount vAccount;
-			synchronized (accountsByUserId) {
-				vAccount = getAccount(userId, account);
-			}
-			String authToken = vAccount != null ? vAccount.authTokens.get(authTokenType) : null;
-			if (authToken != null) {
-				Bundle result = new Bundle();
-				result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
-				result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
-				result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
-				onResult(response, result);
-				return;
-			}
-		}
-		if (customTokens) {
-			String authToken = getCustomAuthToken(userId, account, authTokenType, callerPkg);
-			if (authToken != null) {
-				Bundle result = new Bundle();
-				result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
-				result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
-				result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
-				onResult(response, result);
-				return;
-			}
-		}
-		new Session(response, userId, info, expectActivityLaunch, false, account.name) {
-
-			@Override
-			protected String toDebugString(long now) {
-				return super.toDebugString(now) + ", getAuthToken"
-						+ ", " + account
-						+ ", authTokenType " + authTokenType
-						+ ", loginOptions " + loginOptions
-						+ ", notifyOnAuthFailure " + notifyOnAuthFailure;
-			}
-
-			@Override
-			public void run() throws RemoteException {
-				mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
-			}
-
-			@Override
-			public void onResult(Bundle result) throws RemoteException {
-				if (result != null) {
-					String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
-					if (authToken != null) {
-						String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
-						String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
-						if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
-							onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
-									"the type and name should not be empty");
-							return;
-						}
-						if (!customTokens) {
-							synchronized (accountsByUserId) {
-								VAccount account = getAccount(userId, name, type);
-								if (account == null) {
-									List<VAccount> accounts = accountsByUserId.get(userId);
-									if (accounts == null) {
-										accounts = new ArrayList<>();
-										accountsByUserId.put(userId, accounts);
-									}
-									account = new VAccount(userId, new Account(name, type));
-									accounts.add(account);
-									saveAllAccounts();
-								}
-							}
-						}
-						long expiryMillis = result.getLong(
-								AccountManagerCompat.KEY_CUSTOM_TOKEN_EXPIRY, 0L);
-						if (customTokens
-								&& expiryMillis > System.currentTimeMillis()) {
-							AuthTokenRecord record = new AuthTokenRecord(userId, account, authTokenType, callerPkg, authToken, expiryMillis);
-							synchronized (authTokenRecords) {
-								authTokenRecords.remove(record);
-								authTokenRecords.add(record);
-							}
-						}
-					}
-					Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
-					if (intent != null && notifyOnAuthFailure && !customTokens) {
-						// TODO: send Signin error Notification
-					}
-				}
-				super.onResult(result);
-			}
-		}.bind();
-	}
-
-
-	@Override
-	public void setPassword(int userId, Account account, String password) {
-		if (account == null) throw new IllegalArgumentException("account is null");
-		setPasswordInternal(userId, account, password);
-	}
-
-	private void setPasswordInternal(int userId, Account account, String password) {
-		synchronized (accountsByUserId) {
-			VAccount vAccount = getAccount(userId, account);
-			if (vAccount != null) {
-				vAccount.password = password;
-				vAccount.authTokens.clear();
-				saveAllAccounts();
-				synchronized (authTokenRecords) {
-					Iterator<AuthTokenRecord> iterator = authTokenRecords.iterator();
-					while (iterator.hasNext()) {
-						AuthTokenRecord record = iterator.next();
-						if (record.userId == userId && record.account.equals(account)) {
-							iterator.remove();
-						}
-					}
-				}
-				sendAccountsChangedBroadcast(userId);
-			}
-		}
-	}
-
-	@Override
-	public void setAuthToken(int userId, Account account, String authTokenType, String authToken) {
-		if (account == null) throw new IllegalArgumentException("account is null");
-		if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
-		synchronized (accountsByUserId) {
-			VAccount vAccount = getAccount(userId, account);
-			if (vAccount != null) {
-				// FIXME: cancelNotification
-				vAccount.authTokens.put(authTokenType, authToken);
-				this.saveAllAccounts();
-			}
-		}
-	}
-
-
-	@Override
-	public void setUserData(int userId, Account account, String key, String value) {
-		if (key == null) throw new IllegalArgumentException("key is null");
-		if (account == null) throw new IllegalArgumentException("account is null");
-		VAccount vAccount = getAccount(userId, account);
-		if (vAccount != null) {
-			synchronized (accountsByUserId) {
-				vAccount.userDatas.put(key, value);
-				saveAllAccounts();
-			}
-		}
-	}
-
-
-	@Override
-	public void hasFeatures(int userId, IAccountManagerResponse response,
-							final Account account, final String[] features) {
-		if (response == null) throw new IllegalArgumentException("response is null");
-		if (account == null) throw new IllegalArgumentException("account is null");
-		if (features == null) throw new IllegalArgumentException("features is null");
-		AuthenticatorInfo info = this.getAuthenticatorInfo(account.type);
-		if (info == null) {
-			try {
-				response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-			return;
-		}
-		new Session(response, userId, info, false, true, account.name) {
-
-			@Override
-			public void run() throws RemoteException {
-				try {
-					mAuthenticator.hasFeatures(this, account, features);
-				} catch (RemoteException e) {
-					onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
-				}
-			}
-
-			@Override
-			public void onResult(Bundle result) throws RemoteException {
-				IAccountManagerResponse response = getResponseAndClose();
-				if (response != null) {
-					try {
-						if (result == null) {
-							response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
-							return;
-						}
-						Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
-								+ response);
-						final Bundle newResult = new Bundle();
-						newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
-								result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false));
-						response.onResult(newResult);
-					} catch (RemoteException e) {
-						// if the caller is dead then there is no one to care about remote exceptions
-						Log.v(TAG, "failure while notifying response", e);
-					}
-				}
-			}
-		}.bind();
-	}
-
-
-	@Override
-	public void updateCredentials(int userId, final IAccountManagerResponse response, final Account account,
-								  final String authTokenType, final boolean expectActivityLaunch,
-								  final Bundle loginOptions) {
-		if (response == null) throw new IllegalArgumentException("response is null");
-		if (account == null) throw new IllegalArgumentException("account is null");
-		if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
-		AuthenticatorInfo info = this.getAuthenticatorInfo(account.type);
-		if (info == null) {
-			try {
-				response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-			return;
-		}
-		new Session(response, userId, info, expectActivityLaunch, false, account.name) {
-
-			@Override
-			public void run() throws RemoteException {
-				mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
-			}
-
-			@Override
-			protected String toDebugString(long now) {
-				if (loginOptions != null) loginOptions.keySet();
-				return super.toDebugString(now) + ", updateCredentials"
-						+ ", " + account
-						+ ", authTokenType " + authTokenType
-						+ ", loginOptions " + loginOptions;
-			}
-
-		}.bind();
-	}
-
-	@Override
-	public String getPassword(int userId, Account account) {
-		if (account == null) throw new IllegalArgumentException("account is null");
-		synchronized (accountsByUserId) {
-			VAccount vAccount = getAccount(userId, account);
-			if (vAccount != null) {
-				return vAccount.password;
-			}
-			return null;
-		}
-	}
-
-	@Override
-	public String getUserData(int userId, Account account, String key) {
-		if (account == null) throw new IllegalArgumentException("account is null");
-		if (key == null) throw new IllegalArgumentException("key is null");
-		synchronized (accountsByUserId) {
-			VAccount vAccount = getAccount(userId, account);
-			if (vAccount != null) {
-				return vAccount.userDatas.get(key);
-			}
-			return null;
-		}
-	}
-
-	@Override
-	public void editProperties(int userId, IAccountManagerResponse response, final String accountType,
-							   final boolean expectActivityLaunch) {
-		if (response == null) throw new IllegalArgumentException("response is null");
-		if (accountType == null) throw new IllegalArgumentException("accountType is null");
-		AuthenticatorInfo info = this.getAuthenticatorInfo(accountType);
-		if (info == null) {
-			try {
-				response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-			return;
-		}
-		new Session(response, userId, info, expectActivityLaunch, true, null) {
-
-			@Override
-			public void run() throws RemoteException {
-				mAuthenticator.editProperties(this, mAuthenticatorInfo.desc.type);
-			}
-
-			@Override
-			protected String toDebugString(long now) {
-				return super.toDebugString(now) + ", editProperties"
-						+ ", accountType " + accountType;
-			}
-
-		}.bind();
-
-	}
-
-
-	@Override
-	public void getAuthTokenLabel(int userId, IAccountManagerResponse response, final String accountType,
-								  final String authTokenType) {
-		if (accountType == null) throw new IllegalArgumentException("accountType is null");
-		if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
-		AuthenticatorInfo info = getAuthenticatorInfo(accountType);
-		if (info == null) {
-			try {
-				response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-			return;
-		}
-		new Session(response, userId, info, false, false, null) {
-
-			@Override
-			public void run() throws RemoteException {
-				mAuthenticator.getAuthTokenLabel(this, authTokenType);
-			}
-
-			@Override
-			public void onResult(Bundle result) throws RemoteException {
-				if (result != null) {
-					String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL);
-					Bundle bundle = new Bundle();
-					bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, label);
-					super.onResult(bundle);
-				} else {
-					super.onResult(null);
-				}
-			}
-		}.bind();
-	}
-
-	public void confirmCredentials(int userId, IAccountManagerResponse response, final Account account, final Bundle options, final boolean expectActivityLaunch) {
-		if (response == null) throw new IllegalArgumentException("response is null");
-		if (account == null) throw new IllegalArgumentException("account is null");
-		AuthenticatorInfo info = getAuthenticatorInfo(account.type);
-		if (info == null) {
-			try {
-				response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-			return;
-		}
-		new Session(response, userId, info, expectActivityLaunch, true, account.name, true, true) {
-
-			@Override
-			public void run() throws RemoteException {
-				mAuthenticator.confirmCredentials(this, account, options);
-			}
-
-		}.bind();
-
-	}
-
-	@Override
-	public void addAccount(int userId, final IAccountManagerResponse response, final String accountType,
-						   final String authTokenType, final String[] requiredFeatures,
-						   final boolean expectActivityLaunch, final Bundle optionsIn) {
-		if (response == null) throw new IllegalArgumentException("response is null");
-		if (accountType == null) throw new IllegalArgumentException("accountType is null");
-		AuthenticatorInfo info = getAuthenticatorInfo(accountType);
-		if (info == null) {
-			try {
-				response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-			return;
-		}
-		new Session(response, userId, info, expectActivityLaunch, true, null, false, true) {
-
-			@Override
-			public void run() throws RemoteException {
-				mAuthenticator.addAccount(this, mAuthenticatorInfo.desc.type, authTokenType, requiredFeatures,
-						optionsIn);
-			}
-
-			@Override
-			protected String toDebugString(long now) {
-				return super.toDebugString(now) + ", addAccount"
-						+ ", accountType " + accountType
-						+ ", requiredFeatures "
-						+ (requiredFeatures != null
-						? TextUtils.join(",", requiredFeatures)
-						: null);
-			}
-
-		}.bind();
-
-	}
-
-	@Override
-	public boolean addAccountExplicitly(int userId, Account account, String password, Bundle extras) {
-		if (account == null) throw new IllegalArgumentException("account is null");
-		return insertAccountIntoDatabase(userId, account, password, extras);
-	}
-
-	@Override
-	public boolean removeAccountExplicitly(int userId, Account account) {
-		return account != null && removeAccountInternal(userId, account);
-	}
-
-	@Override
-	public void renameAccount(int userId, IAccountManagerResponse response, Account accountToRename, String newName) {
-		if (accountToRename == null) throw new IllegalArgumentException("account is null");
-		Account resultingAccount = renameAccountInternal(userId, accountToRename, newName);
-		Bundle result = new Bundle();
-		result.putString(AccountManager.KEY_ACCOUNT_NAME, resultingAccount.name);
-		result.putString(AccountManager.KEY_ACCOUNT_TYPE, resultingAccount.type);
-		try {
-			response.onResult(result);
-		} catch (RemoteException e) {
-			Log.w(TAG, e.getMessage());
-		}
-	}
-
-	@Override
-	public void removeAccount(final int userId, IAccountManagerResponse response, final Account account,
-							  boolean expectActivityLaunch) {
-		if (response == null) throw new IllegalArgumentException("response is null");
-		if (account == null) throw new IllegalArgumentException("account is null");
-		AuthenticatorInfo info = this.getAuthenticatorInfo(account.type);
-		if (info == null) {
-			try {
-				response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
-			} catch (RemoteException e) {
-				e.printStackTrace();
-			}
-			return;
-		}
-		// FIXME: Cancel Notification
-
-		new Session(response, userId, info, expectActivityLaunch, true, account.name) {
-			@Override
-			protected String toDebugString(long now) {
-				return super.toDebugString(now) + ", removeAccount"
-						+ ", account " + account;
-			}
-
-			@Override
-			public void run() throws RemoteException {
-				mAuthenticator.getAccountRemovalAllowed(this, account);
-			}
-
-			@Override
-			public void onResult(Bundle result) throws RemoteException {
-				if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
-						&& !result.containsKey(AccountManager.KEY_INTENT)) {
-					final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
-					if (removalAllowed) {
-						removeAccountInternal(userId, account);
-					}
-					IAccountManagerResponse response = getResponseAndClose();
-					if (response != null) {
-						Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
-								+ response);
-						Bundle result2 = new Bundle();
-						result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
-						try {
-							response.onResult(result2);
-						} catch (RemoteException e) {
-							e.printStackTrace();
-						}
-					}
-				}
-				super.onResult(result);
-			}
-
-		}.bind();
-
-	}
-
-	@Override
-	public void clearPassword(int userId, Account account) {
-		if (account == null) throw new IllegalArgumentException("account is null");
-		setPasswordInternal(userId, account, null);
-	}
-
-	private boolean removeAccountInternal(int userId, Account account) {
-		List<VAccount> accounts = accountsByUserId.get(userId);
-		if (accounts != null) {
-			Iterator<VAccount> iterator = accounts.iterator();
-			while (iterator.hasNext()) {
-				VAccount vAccount = iterator.next();
-				if (userId == vAccount.userId
-						&& TextUtils.equals(vAccount.name, account.name)
-						&& TextUtils.equals(account.type, vAccount.type)) {
-					iterator.remove();
-					saveAllAccounts();
-					sendAccountsChangedBroadcast(userId);
-					return true;
-				}
-			}
-		}
-		return false;
-	}
-
-
-	@Override
-	public boolean accountAuthenticated(int userId, final Account account) {
-		if (account == null) {
-			throw new IllegalArgumentException("account is null");
-		}
-		synchronized (accountsByUserId) {
-			VAccount vAccount = getAccount(userId, account);
-			if (vAccount != null) {
-				vAccount.lastAuthenticatedTime = System.currentTimeMillis();
-				saveAllAccounts();
-				return true;
-			}
-			return false;
-		}
-	}
-
-	@Override
-	public void invalidateAuthToken(int userId, String accountType, String authToken) {
-		if (accountType == null) throw new IllegalArgumentException("accountType is null");
-		if (authToken == null) throw new IllegalArgumentException("authToken is null");
-		synchronized (accountsByUserId) {
-			List<VAccount> accounts = accountsByUserId.get(userId);
-			if (accounts != null) {
-				boolean changed = false;
-				for (VAccount account : accounts) {
-					if (account.type.equals(accountType)) {
-						account.authTokens.values().remove(authToken);
-						changed = true;
-					}
-				}
-				if (changed) {
-					saveAllAccounts();
-				}
-			}
-			synchronized (authTokenRecords) {
-				Iterator<AuthTokenRecord> iterator = authTokenRecords.iterator();
-				while (iterator.hasNext()) {
-					AuthTokenRecord record = iterator.next();
-					if (record.userId == userId && record.authTokenType.equals(accountType)
-							&& record.authToken.equals(authToken)) {
-						iterator.remove();
-					}
-				}
-			}
-		}
-	}
-
-
-	private Account renameAccountInternal(int userId, Account accountToRename, String newName) {
-		// TODO: Cancel Notification
-		synchronized (accountsByUserId) {
-			VAccount vAccount = getAccount(userId, accountToRename);
-			if (vAccount != null) {
-				vAccount.previousName = vAccount.name;
-				vAccount.name = newName;
-				saveAllAccounts();
-				Account newAccount = new Account(vAccount.name, vAccount.type);
-				synchronized (authTokenRecords) {
-					for (AuthTokenRecord record : authTokenRecords) {
-						if (record.userId == userId && record.account.equals(accountToRename)) {
-							record.account = newAccount;
-						}
-					}
-				}
-				sendAccountsChangedBroadcast(userId);
-				return newAccount;
-			}
-		}
-		return accountToRename;
-	}
-
-	@Override
-	public String peekAuthToken(int userId, Account account, String authTokenType) {
-		if (account == null) throw new IllegalArgumentException("account is null");
-		if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
-		synchronized (accountsByUserId) {
-			VAccount vAccount = getAccount(userId, account);
-			if (vAccount != null) {
-				return vAccount.authTokens.get(authTokenType);
-			}
-			return null;
-		}
-	}
-
-
-	private String getCustomAuthToken(int userId, Account account, String authTokenType, String packageName) {
-		AuthTokenRecord record = new AuthTokenRecord(userId, account, authTokenType, packageName);
-		String authToken = null;
-		long now = System.currentTimeMillis();
-		synchronized (authTokenRecords) {
-			Iterator<AuthTokenRecord> iterator = authTokenRecords.iterator();
-			while (iterator.hasNext()) {
-				AuthTokenRecord one = iterator.next();
-				if (one.expiryEpochMillis > 0 && one.expiryEpochMillis < now) {
-					iterator.remove();
-				} else if (record.equals(one)) {
-					authToken = record.authToken;
-				}
-			}
-		}
-		return authToken;
-	}
-
-	private void onResult(IAccountManagerResponse response, Bundle result) {
-		try {
-			response.onResult(result);
-		} catch (RemoteException e) {
-			// if the caller is dead then there is no one to care about remote
-			// exceptions
-			e.printStackTrace();
-		}
-	}
-
-	private AuthenticatorInfo getAuthenticatorInfo(String type) {
-		synchronized (cache) {
-			return type == null ? null : cache.authenticators.get(type);
-		}
-	}
-
-
-	private VAccount getAccount(int userId, Account account) {
-		return this.getAccount(userId, account.name, account.type);
-	}
-
-	private boolean insertAccountIntoDatabase(int userId, Account account, String password, Bundle extras) {
-		if (account == null) {
-			return false;
-		}
-		synchronized (accountsByUserId) {
-			VAccount vAccount = new VAccount(userId, account);
-			vAccount.password = password;
-			// convert the [Bundle] to [Map<String, String>]
-			if (extras != null) {
-				for (String key : extras.keySet()) {
-					Object value = extras.get(key);
-					if (value instanceof String) {
-						vAccount.userDatas.put(key, (String) value);
-					}
-				}
-			}
-			List<VAccount> accounts = accountsByUserId.get(userId);
-			if (accounts == null) {
-				accounts = new ArrayList<>();
-				accountsByUserId.put(userId, accounts);
-			}
-			accounts.add(vAccount);
-			saveAllAccounts();
-			sendAccountsChangedBroadcast(vAccount.userId);
-			return true;
-		}
-	}
-
-	private void sendAccountsChangedBroadcast(int userId) {
-		Intent intent = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
-		VActivityManagerService.get().sendBroadcastAsUser(intent, new VUserHandle(userId));
-		broadcastCheckInNowIfNeed(userId);
-	}
-
-	private void broadcastCheckInNowIfNeed(int userId) {
-		long time = System.currentTimeMillis();
-		if (Math.abs(time - lastAccountChangeTime) > CHECK_IN_TIME) {
-			lastAccountChangeTime = time;
-			saveAllAccounts();
-			Intent intent = new Intent("android.server.checkin.CHECKIN_NOW");
-			VActivityManagerService.get().sendBroadcastAsUser(intent, new VUserHandle(userId));
-		}
-	}
-
-	/**
-	 * Serializing all accounts
-	 */
-	private void saveAllAccounts() {
-		File accountFile = VEnvironment.getAccountConfigFile();
-		Parcel dest = Parcel.obtain();
-		try {
-			dest.writeInt(1);
-			List<VAccount> accounts = new ArrayList<>();
-			for (int i = 0; i < this.accountsByUserId.size(); i++) {
-				List<VAccount> list = this.accountsByUserId.valueAt(i);
-				if (list != null) {
-					accounts.addAll(list);
-				}
-			}
-			dest.writeInt(accounts.size());
-			for (VAccount account : accounts) {
-				account.writeToParcel(dest, 0);
-			}
-			dest.writeLong(lastAccountChangeTime);
-			FileOutputStream fileOutputStream = new FileOutputStream(accountFile);
-			fileOutputStream.write(dest.marshall());
-			fileOutputStream.close();
-		} catch (Exception e) {
-			e.printStackTrace();
-		}
-		dest.recycle();
-	}
-
-	/**
-	 * Read all accounts from file.
-	 */
-	private void readAllAccounts() {
-		File accountFile = VEnvironment.getAccountConfigFile();
-		refreshAuthenticatorCache(null);
-		if (accountFile.exists()) {
-			accountsByUserId.clear();
-			Parcel dest = Parcel.obtain();
-			try {
-				FileInputStream is = new FileInputStream(accountFile);
-				byte[] bytes = new byte[(int) accountFile.length()];
-				int readLength = is.read(bytes);
-				is.close();
-				if (readLength != bytes.length) {
-					throw new IOException(String.format(Locale.ENGLISH, "Expect length %d, but got %d.", bytes.length, readLength));
-				}
-				dest.unmarshall(bytes, 0, bytes.length);
-				dest.setDataPosition(0);
-				dest.readInt(); // skip the magic
-				int size = dest.readInt(); // the VAccount's size we need to read
-				boolean invalid = false;
-				while (size-- > 0) {
-					VAccount account = new VAccount(dest);
-					VLog.d(TAG, "Reading account : " + account.type);
-					AuthenticatorInfo info = cache.authenticators.get(account.type);
-					if (info != null) {
-						List<VAccount> accounts = accountsByUserId.get(account.userId);
-						if (accounts == null) {
-							accounts = new ArrayList<>();
-							accountsByUserId.put(account.userId, accounts);
-						}
-						accounts.add(account);
-					} else {
-						invalid = true;
-					}
-				}
-				lastAccountChangeTime = dest.readLong();
-				if (invalid) {
-					saveAllAccounts();
-				}
-			} catch (Exception e) {
-				e.printStackTrace();
-			} finally {
-				dest.recycle();
-			}
-		}
-	}
-
-
-	private VAccount getAccount(int userId, String accountName, String accountType) {
-		List<VAccount> accounts = accountsByUserId.get(userId);
-		if (accounts != null) {
-			for (VAccount account : accounts) {
-				if (TextUtils.equals(account.name, accountName) && TextUtils.equals(account.type, accountType)) {
-					return account;
-				}
-			}
-		}
-		return null;
-	}
-
-
-	public void refreshAuthenticatorCache(String packageName) {
-		cache.authenticators.clear();
-		Intent intent = new Intent(AccountManager.ACTION_AUTHENTICATOR_INTENT);
-		if (packageName != null) {
-			intent.setPackage(packageName);
-		}
-		generateServicesMap(
-				VPackageManagerService.get().queryIntentServices(intent, null, PackageManager.GET_META_DATA, 0),
-				cache.authenticators, new AppAccountParser());
-	}
-
-	private void generateServicesMap(List<ResolveInfo> services, Map<String, AuthenticatorInfo> map,
-									 IAccountParser accountParser) {
-		for (ResolveInfo info : services) {
-			XmlResourceParser parser = accountParser.getParser(mContext, info.serviceInfo,
-					AccountManager.AUTHENTICATOR_META_DATA_NAME);
-			if (parser != null) {
-				try {
-					AttributeSet attributeSet = Xml.asAttributeSet(parser);
-					int type;
-					while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
-						// Nothing to do
-					}
-					if (AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME.equals(parser.getName())) {
-						AuthenticatorDescription desc = parseAuthenticatorDescription(
-								accountParser.getResources(mContext, info.serviceInfo.applicationInfo),
-								info.serviceInfo.packageName, attributeSet);
-						if (desc != null) {
-							map.put(desc.type, new AuthenticatorInfo(desc, info.serviceInfo));
-						}
-					}
-				} catch (Exception e) {
-					e.printStackTrace();
-				}
-			}
-		}
-	}
-
-	final static class AuthTokenRecord {
-		public int userId;
-		public Account account;
-		public long expiryEpochMillis;
-		public String authToken;
-		private String authTokenType;
-		private String packageName;
-
-		AuthTokenRecord(int userId, Account account, String authTokenType, String packageName, String authToken,
-						long expiryEpochMillis) {
-			this.userId = userId;
-			this.account = account;
-			this.authTokenType = authTokenType;
-			this.packageName = packageName;
-			this.authToken = authToken;
-			this.expiryEpochMillis = expiryEpochMillis;
-		}
-
-		AuthTokenRecord(int userId, Account account, String authTokenType, String packageName) {
-			this.userId = userId;
-			this.account = account;
-			this.authTokenType = authTokenType;
-			this.packageName = packageName;
-		}
-
-		@Override
-		public boolean equals(Object o) {
-			if (this == o)
-				return true;
-			if (o == null || getClass() != o.getClass())
-				return false;
-			AuthTokenRecord that = (AuthTokenRecord) o;
-			return userId == that.userId
-					&& account.equals(that.account)
-					&& authTokenType.equals(that.authTokenType)
-					&& packageName.equals(that.packageName);
-		}
-
-		@Override
-		public int hashCode() {
-			return ((this.userId * 31 + this.account.hashCode()) * 31
-					+ this.authTokenType.hashCode()) * 31
-					+ this.packageName.hashCode();
-		}
-	}
-
-	private final class AuthenticatorInfo {
-		final AuthenticatorDescription desc;
-		final ServiceInfo serviceInfo;
-
-		AuthenticatorInfo(AuthenticatorDescription desc, ServiceInfo info) {
-			this.desc = desc;
-			this.serviceInfo = info;
-		}
-	}
-
-	private final class AuthenticatorCache {
-		final Map<String, AuthenticatorInfo> authenticators = new HashMap<>();
-	}
-
-	private abstract class Session extends IAccountAuthenticatorResponse.Stub
-			implements IBinder.DeathRecipient, ServiceConnection {
-		final int mUserId;
-		final AuthenticatorInfo mAuthenticatorInfo;
-		private final boolean mStripAuthTokenFromResult;
-		public int mNumResults;
-		IAccountAuthenticator mAuthenticator;
-		private IAccountManagerResponse mResponse;
-		private boolean mExpectActivityLaunch;
-		private long mCreationTime;
-		private String mAccountName;
-		private boolean mAuthDetailsRequired;
-		private boolean mUpdateLastAuthenticatedTime;
-		private int mNumRequestContinued;
-		private int mNumErrors;
-
-
-		Session(IAccountManagerResponse response, int userId, AuthenticatorInfo info, boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName, boolean authDetailsRequired, boolean updateLastAuthenticatedTime) {
-			if (info == null) throw new IllegalArgumentException("accountType is null");
-			this.mStripAuthTokenFromResult = stripAuthTokenFromResult;
-			this.mResponse = response;
-			this.mUserId = userId;
-			this.mAuthenticatorInfo = info;
-			this.mExpectActivityLaunch = expectActivityLaunch;
-			this.mCreationTime = SystemClock.elapsedRealtime();
-			this.mAccountName = accountName;
-			this.mAuthDetailsRequired = authDetailsRequired;
-			this.mUpdateLastAuthenticatedTime = updateLastAuthenticatedTime;
-			synchronized (mSessions) {
-				mSessions.put(toString(), this);
-			}
-			if (response != null) {
-				try {
-					response.asBinder().linkToDeath(this, 0 /* flags */);
-				} catch (RemoteException e) {
-					mResponse = null;
-					binderDied();
-				}
-			}
-		}
-
-		Session(IAccountManagerResponse response, int userId, AuthenticatorInfo info, boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName) {
-			this(response, userId, info, expectActivityLaunch, stripAuthTokenFromResult, accountName, false, false);
-		}
-
-		IAccountManagerResponse getResponseAndClose() {
-			if (mResponse == null) {
-				// this session has already been closed
-				return null;
-			}
-			IAccountManagerResponse response = mResponse;
-			close(); // this clears mResponse so we need to save the response before this call
-			return response;
-		}
-
-		private void close() {
-			synchronized (mSessions) {
-				if (mSessions.remove(toString()) == null) {
-					// the session was already closed, so bail out now
-					return;
-				}
-			}
-			if (mResponse != null) {
-				// stop listening for response deaths
-				mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);
-
-				// clear this so that we don't accidentally send any further results
-				mResponse = null;
-			}
-			unbind();
-		}
-
-		@Override
-		public void onServiceConnected(ComponentName name, IBinder service) {
-			mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
-			try {
-				run();
-			} catch (RemoteException e) {
-				onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
-						"remote exception");
-			}
-		}
-
-		@Override
-		public void onRequestContinued() {
-			mNumRequestContinued++;
-		}
-
-		@Override
-		public void onError(int errorCode, String errorMessage) {
-			mNumErrors++;
-			IAccountManagerResponse response = getResponseAndClose();
-			if (response != null) {
-				Log.v(TAG, getClass().getSimpleName()
-						+ " calling onError() on response " + response);
-				try {
-					response.onError(errorCode, errorMessage);
-				} catch (RemoteException e) {
-					Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
-				}
-			} else {
-				Log.v(TAG, "Session.onError: already closed");
-			}
-		}
-
-		@Override
-		public void onServiceDisconnected(ComponentName name) {
-			mAuthenticator = null;
-			IAccountManagerResponse response = getResponseAndClose();
-			if (response != null) {
-				try {
-					response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
-							"disconnected");
-				} catch (RemoteException e) {
-					Log.v(TAG, "Session.onServiceDisconnected: "
-							+ "caught RemoteException while responding", e);
-				}
-			}
-		}
-
-		@Override
-		public void onResult(Bundle result) throws RemoteException {
-			mNumResults++;
-			if (result != null) {
-				boolean isSuccessfulConfirmCreds = result.getBoolean(
-						AccountManager.KEY_BOOLEAN_RESULT, false);
-				boolean isSuccessfulUpdateCredsOrAddAccount =
-						result.containsKey(AccountManager.KEY_ACCOUNT_NAME)
-								&& result.containsKey(AccountManager.KEY_ACCOUNT_TYPE);
-				// We should only update lastAuthenticated time, if
-				// mUpdateLastAuthenticatedTime is true and the confirmRequest
-				// or updateRequest was successful
-				boolean needUpdate = mUpdateLastAuthenticatedTime
-						&& (isSuccessfulConfirmCreds || isSuccessfulUpdateCredsOrAddAccount);
-				if (needUpdate || mAuthDetailsRequired) {
-					synchronized (accountsByUserId) {
-						VAccount account = getAccount(mUserId, mAccountName, mAuthenticatorInfo.desc.type);
-						if (needUpdate && account != null) {
-							account.lastAuthenticatedTime = System.currentTimeMillis();
-							saveAllAccounts();
-						}
-						if (mAuthDetailsRequired) {
-							long lastAuthenticatedTime = -1;
-							if (account != null) {
-								lastAuthenticatedTime = account.lastAuthenticatedTime;
-							}
-							result.putLong(AccountManagerCompat.KEY_LAST_AUTHENTICATED_TIME, lastAuthenticatedTime);
-						}
-					}
-				}
-			}
-			if (result != null
-					&& !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
+    private static final AtomicReference<VAccountManagerService> sInstance = new AtomicReference<>();
+    private static final long CHECK_IN_TIME = 30 * 24 * 60 * 1000L;
+    private static final String TAG = VAccountManagerService.class.getSimpleName();
+    private final SparseArray<List<VAccount>> accountsByUserId = new SparseArray<>();
+    private final LinkedList<AuthTokenRecord> authTokenRecords = new LinkedList<>();
+    private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<>();
+    private final AuthenticatorCache cache = new AuthenticatorCache();
+    private Context mContext = VirtualCore.get().getContext();
+    private long lastAccountChangeTime = 0;
+
+
+    public static VAccountManagerService get() {
+        return sInstance.get();
+    }
+
+    public static void systemReady() {
+        VAccountManagerService service = new VAccountManagerService();
+        service.readAllAccounts();
+        sInstance.set(service);
+    }
+
+
+    private static AuthenticatorDescription parseAuthenticatorDescription(Resources resources, String packageName,
+                                                                          AttributeSet attributeSet) {
+        TypedArray array = resources.obtainAttributes(attributeSet, R_Hide.styleable.AccountAuthenticator.get());
+        try {
+            String accountType = array.getString(R_Hide.styleable.AccountAuthenticator_accountType.get());
+            int label = array.getResourceId(R_Hide.styleable.AccountAuthenticator_label.get(), 0);
+            int icon = array.getResourceId(R_Hide.styleable.AccountAuthenticator_icon.get(), 0);
+            int smallIcon = array.getResourceId(R_Hide.styleable.AccountAuthenticator_smallIcon.get(), 0);
+            int accountPreferences = array.getResourceId(R_Hide.styleable.AccountAuthenticator_accountPreferences.get(), 0);
+            boolean customTokens = array.getBoolean(R_Hide.styleable.AccountAuthenticator_customTokens.get(), false);
+            if (TextUtils.isEmpty(accountType)) {
+                return null;
+            }
+            return new AuthenticatorDescription(accountType, packageName, label, icon, smallIcon, accountPreferences,
+                    customTokens);
+        } finally {
+            array.recycle();
+        }
+    }
+
+
+    @Override
+    public AuthenticatorDescription[] getAuthenticatorTypes(int userId) {
+        synchronized (cache) {
+            AuthenticatorDescription[] descArray = new AuthenticatorDescription[cache.authenticators.size()];
+            int i = 0;
+            for (AuthenticatorInfo info : cache.authenticators.values()) {
+                descArray[i] = info.desc;
+                i++;
+            }
+            return descArray;
+        }
+    }
+
+    @Override
+    public void getAccountsByFeatures(int userId, IAccountManagerResponse response, String type, String[] features) {
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (type == null) throw new IllegalArgumentException("accountType is null");
+        AuthenticatorInfo info = getAuthenticatorInfo(type);
+        if (info == null) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelableArray(AccountManager.KEY_ACCOUNTS, new Account[0]);
+            try {
+                response.onResult(bundle);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+
+        if (features == null || features.length == 0) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelableArray(AccountManager.KEY_ACCOUNTS, getAccounts(userId, type));
+            try {
+                response.onResult(bundle);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        } else {
+            new GetAccountsByTypeAndFeatureSession(response, userId, info, features).bind();
+        }
+    }
+
+    @Override
+    public final String getPreviousName(int userId, Account account) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        synchronized (accountsByUserId) {
+            String previousName = null;
+            VAccount vAccount = getAccount(userId, account);
+            if (vAccount != null) {
+                previousName = vAccount.previousName;
+            }
+            return previousName;
+        }
+    }
+
+
+    @Override
+    public Account[] getAccounts(int userId, String type) {
+        List<Account> accountList = getAccountList(userId, type);
+        return accountList.toArray(new Account[accountList.size()]);
+    }
+
+
+    private List<Account> getAccountList(int userId, String type) {
+        synchronized (accountsByUserId) {
+            List<Account> accounts = new ArrayList<>();
+            List<VAccount> vAccounts = accountsByUserId.get(userId);
+            if (vAccounts != null) {
+                for (VAccount vAccount : vAccounts) {
+                    if (type == null || vAccount.type.equals(type)) {
+                        accounts.add(new Account(vAccount.name, vAccount.type));
+                    }
+                }
+            }
+            return accounts;
+        }
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (Throwable e) {
+            e.printStackTrace();
+            throw e;
+        }
+    }
+
+    @Override
+    public final void getAuthToken(final int userId, final IAccountManagerResponse response, final Account account, final String authTokenType, final boolean notifyOnAuthFailure, boolean expectActivityLaunch, final Bundle loginOptions) {
+        if (response == null) {
+            throw new IllegalArgumentException("response is null");
+        }
+        try {
+            if (account == null) {
+                VLog.w(TAG, "getAuthToken called with null account");
+                response.onError(ERROR_CODE_BAD_ARGUMENTS, "account is null");
+                return;
+            }
+            if (authTokenType == null) {
+                VLog.w(TAG, "getAuthToken called with null authTokenType");
+                response.onError(ERROR_CODE_BAD_ARGUMENTS, "authTokenType is null");
+                return;
+            }
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            return;
+        }
+        AuthenticatorInfo info = getAuthenticatorInfo(account.type);
+        if (info == null) {
+            try {
+                response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+        // Get the calling package. We will use it for the purpose of caching.
+        final String callerPkg = loginOptions.getString(AccountManagerCompat.KEY_ANDROID_PACKAGE_NAME);
+        final boolean customTokens = info.desc.customTokens;
+
+        loginOptions.putInt(AccountManager.KEY_CALLER_UID, VBinder.getCallingUid());
+        loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid());
+        if (notifyOnAuthFailure) {
+            loginOptions.putBoolean(AccountManagerCompat.KEY_NOTIFY_ON_FAILURE, true);
+        }
+        if (!customTokens) {
+            VAccount vAccount;
+            synchronized (accountsByUserId) {
+                vAccount = getAccount(userId, account);
+            }
+            String authToken = vAccount != null ? vAccount.authTokens.get(authTokenType) : null;
+            if (authToken != null) {
+                Bundle result = new Bundle();
+                result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
+                result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+                result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+                onResult(response, result);
+                return;
+            }
+        }
+        if (customTokens) {
+            String authToken = getCustomAuthToken(userId, account, authTokenType, callerPkg);
+            if (authToken != null) {
+                Bundle result = new Bundle();
+                result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
+                result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+                result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+                onResult(response, result);
+                return;
+            }
+        }
+        new Session(response, userId, info, expectActivityLaunch, false, account.name) {
+
+            @Override
+            protected String toDebugString(long now) {
+                return super.toDebugString(now) + ", getAuthToken"
+                        + ", " + account
+                        + ", authTokenType " + authTokenType
+                        + ", loginOptions " + loginOptions
+                        + ", notifyOnAuthFailure " + notifyOnAuthFailure;
+            }
+
+            @Override
+            public void run() throws RemoteException {
+                mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
+            }
+
+            @Override
+            public void onResult(Bundle result) throws RemoteException {
+                if (result != null) {
+                    String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
+                    if (authToken != null) {
+                        String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
+                        String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
+                        if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
+                            onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                                    "the type and name should not be empty");
+                            return;
+                        }
+                        if (!customTokens) {
+                            synchronized (accountsByUserId) {
+                                VAccount account = getAccount(userId, name, type);
+                                if (account == null) {
+                                    List<VAccount> accounts = accountsByUserId.get(userId);
+                                    if (accounts == null) {
+                                        accounts = new ArrayList<>();
+                                        accountsByUserId.put(userId, accounts);
+                                    }
+                                    account = new VAccount(userId, new Account(name, type));
+                                    accounts.add(account);
+                                    saveAllAccounts();
+                                }
+                            }
+                        }
+                        long expiryMillis = result.getLong(
+                                AccountManagerCompat.KEY_CUSTOM_TOKEN_EXPIRY, 0L);
+                        if (customTokens
+                                && expiryMillis > System.currentTimeMillis()) {
+                            AuthTokenRecord record = new AuthTokenRecord(userId, account, authTokenType, callerPkg, authToken, expiryMillis);
+                            synchronized (authTokenRecords) {
+                                authTokenRecords.remove(record);
+                                authTokenRecords.add(record);
+                            }
+                        }
+                    }
+                    Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+                    if (intent != null && notifyOnAuthFailure && !customTokens) {
+                        // TODO: send Signin error Notification
+                    }
+                }
+                super.onResult(result);
+            }
+        }.bind();
+    }
+
+
+    @Override
+    public void setPassword(int userId, Account account, String password) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        setPasswordInternal(userId, account, password);
+    }
+
+    private void setPasswordInternal(int userId, Account account, String password) {
+        synchronized (accountsByUserId) {
+            VAccount vAccount = getAccount(userId, account);
+            if (vAccount != null) {
+                vAccount.password = password;
+                vAccount.authTokens.clear();
+                saveAllAccounts();
+                synchronized (authTokenRecords) {
+                    Iterator<AuthTokenRecord> iterator = authTokenRecords.iterator();
+                    while (iterator.hasNext()) {
+                        AuthTokenRecord record = iterator.next();
+                        if (record.userId == userId && record.account.equals(account)) {
+                            iterator.remove();
+                        }
+                    }
+                }
+                sendAccountsChangedBroadcast(userId);
+            }
+        }
+    }
+
+    @Override
+    public void setAuthToken(int userId, Account account, String authTokenType, String authToken) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        synchronized (accountsByUserId) {
+            VAccount vAccount = getAccount(userId, account);
+            if (vAccount != null) {
+                // FIXME: cancelNotification
+                vAccount.authTokens.put(authTokenType, authToken);
+                this.saveAllAccounts();
+            }
+        }
+    }
+
+
+    @Override
+    public void setUserData(int userId, Account account, String key, String value) {
+        if (key == null) throw new IllegalArgumentException("key is null");
+        if (account == null) throw new IllegalArgumentException("account is null");
+        VAccount vAccount = getAccount(userId, account);
+        if (vAccount != null) {
+            synchronized (accountsByUserId) {
+                vAccount.userDatas.put(key, value);
+                saveAllAccounts();
+            }
+        }
+    }
+
+
+    @Override
+    public void hasFeatures(int userId, IAccountManagerResponse response,
+                            final Account account, final String[] features) {
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (features == null) throw new IllegalArgumentException("features is null");
+        AuthenticatorInfo info = this.getAuthenticatorInfo(account.type);
+        if (info == null) {
+            try {
+                response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+        new Session(response, userId, info, false, true, account.name) {
+
+            @Override
+            public void run() throws RemoteException {
+                try {
+                    mAuthenticator.hasFeatures(this, account, features);
+                } catch (RemoteException e) {
+                    onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
+                }
+            }
+
+            @Override
+            public void onResult(Bundle result) throws RemoteException {
+                IAccountManagerResponse response = getResponseAndClose();
+                if (response != null) {
+                    try {
+                        if (result == null) {
+                            response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
+                            return;
+                        }
+                        Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+                                + response);
+                        final Bundle newResult = new Bundle();
+                        newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
+                                result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false));
+                        response.onResult(newResult);
+                    } catch (RemoteException e) {
+                        // if the caller is dead then there is no one to care about remote exceptions
+                        Log.v(TAG, "failure while notifying response", e);
+                    }
+                }
+            }
+        }.bind();
+    }
+
+
+    @Override
+    public void updateCredentials(int userId, final IAccountManagerResponse response, final Account account,
+                                  final String authTokenType, final boolean expectActivityLaunch,
+                                  final Bundle loginOptions) {
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        AuthenticatorInfo info = this.getAuthenticatorInfo(account.type);
+        if (info == null) {
+            try {
+                response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+        new Session(response, userId, info, expectActivityLaunch, false, account.name) {
+
+            @Override
+            public void run() throws RemoteException {
+                mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
+            }
+
+            @Override
+            protected String toDebugString(long now) {
+                if (loginOptions != null) loginOptions.keySet();
+                return super.toDebugString(now) + ", updateCredentials"
+                        + ", " + account
+                        + ", authTokenType " + authTokenType
+                        + ", loginOptions " + loginOptions;
+            }
+
+        }.bind();
+    }
+
+    @Override
+    public String getPassword(int userId, Account account) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        synchronized (accountsByUserId) {
+            VAccount vAccount = getAccount(userId, account);
+            if (vAccount != null) {
+                return vAccount.password;
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public String getUserData(int userId, Account account, String key) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (key == null) throw new IllegalArgumentException("key is null");
+        synchronized (accountsByUserId) {
+            VAccount vAccount = getAccount(userId, account);
+            if (vAccount != null) {
+                return vAccount.userDatas.get(key);
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public void editProperties(int userId, IAccountManagerResponse response, final String accountType,
+                               final boolean expectActivityLaunch) {
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        AuthenticatorInfo info = this.getAuthenticatorInfo(accountType);
+        if (info == null) {
+            try {
+                response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+        new Session(response, userId, info, expectActivityLaunch, true, null) {
+
+            @Override
+            public void run() throws RemoteException {
+                mAuthenticator.editProperties(this, mAuthenticatorInfo.desc.type);
+            }
+
+            @Override
+            protected String toDebugString(long now) {
+                return super.toDebugString(now) + ", editProperties"
+                        + ", accountType " + accountType;
+            }
+
+        }.bind();
+
+    }
+
+
+    @Override
+    public void getAuthTokenLabel(int userId, IAccountManagerResponse response, final String accountType,
+                                  final String authTokenType) {
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        AuthenticatorInfo info = getAuthenticatorInfo(accountType);
+        if (info == null) {
+            try {
+                response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+        new Session(response, userId, info, false, false, null) {
+
+            @Override
+            public void run() throws RemoteException {
+                mAuthenticator.getAuthTokenLabel(this, authTokenType);
+            }
+
+            @Override
+            public void onResult(Bundle result) throws RemoteException {
+                if (result != null) {
+                    String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL);
+                    Bundle bundle = new Bundle();
+                    bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, label);
+                    super.onResult(bundle);
+                } else {
+                    super.onResult(null);
+                }
+            }
+        }.bind();
+    }
+
+    public void confirmCredentials(int userId, IAccountManagerResponse response, final Account account, final Bundle options, final boolean expectActivityLaunch) {
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (account == null) throw new IllegalArgumentException("account is null");
+        AuthenticatorInfo info = getAuthenticatorInfo(account.type);
+        if (info == null) {
+            try {
+                response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+        new Session(response, userId, info, expectActivityLaunch, true, account.name, true, true) {
+
+            @Override
+            public void run() throws RemoteException {
+                mAuthenticator.confirmCredentials(this, account, options);
+            }
+
+        }.bind();
+
+    }
+
+    @Override
+    public void addAccount(int userId, final IAccountManagerResponse response, final String accountType,
+                           final String authTokenType, final String[] requiredFeatures,
+                           final boolean expectActivityLaunch, final Bundle optionsIn) {
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        AuthenticatorInfo info = getAuthenticatorInfo(accountType);
+        if (info == null) {
+            try {
+                response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+        new Session(response, userId, info, expectActivityLaunch, true, null, false, true) {
+
+            @Override
+            public void run() throws RemoteException {
+                mAuthenticator.addAccount(this, mAuthenticatorInfo.desc.type, authTokenType, requiredFeatures,
+                        optionsIn);
+            }
+
+            @Override
+            protected String toDebugString(long now) {
+                return super.toDebugString(now) + ", addAccount"
+                        + ", accountType " + accountType
+                        + ", requiredFeatures "
+                        + (requiredFeatures != null
+                        ? TextUtils.join(",", requiredFeatures)
+                        : null);
+            }
+
+        }.bind();
+
+    }
+
+    @Override
+    public boolean addAccountExplicitly(int userId, Account account, String password, Bundle extras) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        return insertAccountIntoDatabase(userId, account, password, extras);
+    }
+
+    @Override
+    public boolean removeAccountExplicitly(int userId, Account account) {
+        return account != null && removeAccountInternal(userId, account);
+    }
+
+    @Override
+    public void renameAccount(int userId, IAccountManagerResponse response, Account accountToRename, String newName) {
+        if (accountToRename == null) throw new IllegalArgumentException("account is null");
+        Account resultingAccount = renameAccountInternal(userId, accountToRename, newName);
+        Bundle result = new Bundle();
+        result.putString(AccountManager.KEY_ACCOUNT_NAME, resultingAccount.name);
+        result.putString(AccountManager.KEY_ACCOUNT_TYPE, resultingAccount.type);
+        try {
+            response.onResult(result);
+        } catch (RemoteException e) {
+            Log.w(TAG, e.getMessage());
+        }
+    }
+
+    @Override
+    public void removeAccount(final int userId, IAccountManagerResponse response, final Account account,
+                              boolean expectActivityLaunch) {
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (account == null) throw new IllegalArgumentException("account is null");
+        AuthenticatorInfo info = this.getAuthenticatorInfo(account.type);
+        if (info == null) {
+            try {
+                response.onError(ERROR_CODE_BAD_ARGUMENTS, "account.type does not exist");
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            return;
+        }
+        // FIXME: Cancel Notification
+
+        new Session(response, userId, info, expectActivityLaunch, true, account.name) {
+            @Override
+            protected String toDebugString(long now) {
+                return super.toDebugString(now) + ", removeAccount"
+                        + ", account " + account;
+            }
+
+            @Override
+            public void run() throws RemoteException {
+                mAuthenticator.getAccountRemovalAllowed(this, account);
+            }
+
+            @Override
+            public void onResult(Bundle result) throws RemoteException {
+                if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
+                        && !result.containsKey(AccountManager.KEY_INTENT)) {
+                    final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+                    if (removalAllowed) {
+                        removeAccountInternal(userId, account);
+                    }
+                    IAccountManagerResponse response = getResponseAndClose();
+                    if (response != null) {
+                        Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+                                + response);
+                        Bundle result2 = new Bundle();
+                        result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
+                        try {
+                            response.onResult(result2);
+                        } catch (RemoteException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }
+                super.onResult(result);
+            }
+
+        }.bind();
+
+    }
+
+    @Override
+    public void clearPassword(int userId, Account account) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        setPasswordInternal(userId, account, null);
+    }
+
+    private boolean removeAccountInternal(int userId, Account account) {
+        List<VAccount> accounts = accountsByUserId.get(userId);
+        if (accounts != null) {
+            Iterator<VAccount> iterator = accounts.iterator();
+            while (iterator.hasNext()) {
+                VAccount vAccount = iterator.next();
+                if (userId == vAccount.userId
+                        && TextUtils.equals(vAccount.name, account.name)
+                        && TextUtils.equals(account.type, vAccount.type)) {
+                    iterator.remove();
+                    saveAllAccounts();
+                    sendAccountsChangedBroadcast(userId);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    @Override
+    public boolean accountAuthenticated(int userId, final Account account) {
+        if (account == null) {
+            throw new IllegalArgumentException("account is null");
+        }
+        synchronized (accountsByUserId) {
+            VAccount vAccount = getAccount(userId, account);
+            if (vAccount != null) {
+                vAccount.lastAuthenticatedTime = System.currentTimeMillis();
+                saveAllAccounts();
+                return true;
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public void invalidateAuthToken(int userId, String accountType, String authToken) {
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        if (authToken == null) throw new IllegalArgumentException("authToken is null");
+        synchronized (accountsByUserId) {
+            List<VAccount> accounts = accountsByUserId.get(userId);
+            if (accounts != null) {
+                boolean changed = false;
+                for (VAccount account : accounts) {
+                    if (account.type.equals(accountType)) {
+                        account.authTokens.values().remove(authToken);
+                        changed = true;
+                    }
+                }
+                if (changed) {
+                    saveAllAccounts();
+                }
+            }
+            synchronized (authTokenRecords) {
+                Iterator<AuthTokenRecord> iterator = authTokenRecords.iterator();
+                while (iterator.hasNext()) {
+                    AuthTokenRecord record = iterator.next();
+                    if (record.userId == userId && record.authTokenType.equals(accountType)
+                            && record.authToken.equals(authToken)) {
+                        iterator.remove();
+                    }
+                }
+            }
+        }
+    }
+
+
+    private Account renameAccountInternal(int userId, Account accountToRename, String newName) {
+        // TODO: Cancel Notification
+        synchronized (accountsByUserId) {
+            VAccount vAccount = getAccount(userId, accountToRename);
+            if (vAccount != null) {
+                vAccount.previousName = vAccount.name;
+                vAccount.name = newName;
+                saveAllAccounts();
+                Account newAccount = new Account(vAccount.name, vAccount.type);
+                synchronized (authTokenRecords) {
+                    for (AuthTokenRecord record : authTokenRecords) {
+                        if (record.userId == userId && record.account.equals(accountToRename)) {
+                            record.account = newAccount;
+                        }
+                    }
+                }
+                sendAccountsChangedBroadcast(userId);
+                return newAccount;
+            }
+        }
+        return accountToRename;
+    }
+
+    @Override
+    public String peekAuthToken(int userId, Account account, String authTokenType) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        synchronized (accountsByUserId) {
+            VAccount vAccount = getAccount(userId, account);
+            if (vAccount != null) {
+                return vAccount.authTokens.get(authTokenType);
+            }
+            return null;
+        }
+    }
+
+
+    private String getCustomAuthToken(int userId, Account account, String authTokenType, String packageName) {
+        AuthTokenRecord record = new AuthTokenRecord(userId, account, authTokenType, packageName);
+        String authToken = null;
+        long now = System.currentTimeMillis();
+        synchronized (authTokenRecords) {
+            Iterator<AuthTokenRecord> iterator = authTokenRecords.iterator();
+            while (iterator.hasNext()) {
+                AuthTokenRecord one = iterator.next();
+                if (one.expiryEpochMillis > 0 && one.expiryEpochMillis < now) {
+                    iterator.remove();
+                } else if (record.equals(one)) {
+                    authToken = record.authToken;
+                }
+            }
+        }
+        return authToken;
+    }
+
+    private void onResult(IAccountManagerResponse response, Bundle result) {
+        try {
+            response.onResult(result);
+        } catch (RemoteException e) {
+            // if the caller is dead then there is no one to care about remote
+            // exceptions
+            e.printStackTrace();
+        }
+    }
+
+    private AuthenticatorInfo getAuthenticatorInfo(String type) {
+        synchronized (cache) {
+            return type == null ? null : cache.authenticators.get(type);
+        }
+    }
+
+
+    private VAccount getAccount(int userId, Account account) {
+        return this.getAccount(userId, account.name, account.type);
+    }
+
+    private boolean insertAccountIntoDatabase(int userId, Account account, String password, Bundle extras) {
+        if (account == null) {
+            return false;
+        }
+        synchronized (accountsByUserId) {
+            VAccount vAccount = new VAccount(userId, account);
+            vAccount.password = password;
+            // convert the [Bundle] to [Map<String, String>]
+            if (extras != null) {
+                for (String key : extras.keySet()) {
+                    Object value = extras.get(key);
+                    if (value instanceof String) {
+                        vAccount.userDatas.put(key, (String) value);
+                    }
+                }
+            }
+            List<VAccount> accounts = accountsByUserId.get(userId);
+            if (accounts == null) {
+                accounts = new ArrayList<>();
+                accountsByUserId.put(userId, accounts);
+            }
+            accounts.add(vAccount);
+            saveAllAccounts();
+            sendAccountsChangedBroadcast(vAccount.userId);
+            return true;
+        }
+    }
+
+    private void sendAccountsChangedBroadcast(int userId) {
+        Intent intent = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
+        VActivityManagerService.get().sendBroadcastAsUser(intent, new VUserHandle(userId));
+        broadcastCheckInNowIfNeed(userId);
+    }
+
+    private void broadcastCheckInNowIfNeed(int userId) {
+        long time = System.currentTimeMillis();
+        if (Math.abs(time - lastAccountChangeTime) > CHECK_IN_TIME) {
+            lastAccountChangeTime = time;
+            saveAllAccounts();
+            Intent intent = new Intent("android.server.checkin.CHECKIN_NOW");
+            VActivityManagerService.get().sendBroadcastAsUser(intent, new VUserHandle(userId));
+        }
+    }
+
+    /**
+     * Serializing all accounts
+     */
+    private void saveAllAccounts() {
+        File accountFile = VEnvironment.getAccountConfigFile();
+        Parcel dest = Parcel.obtain();
+        try {
+            dest.writeInt(1);
+            List<VAccount> accounts = new ArrayList<>();
+            for (int i = 0; i < this.accountsByUserId.size(); i++) {
+                List<VAccount> list = this.accountsByUserId.valueAt(i);
+                if (list != null) {
+                    accounts.addAll(list);
+                }
+            }
+            dest.writeInt(accounts.size());
+            for (VAccount account : accounts) {
+                account.writeToParcel(dest, 0);
+            }
+            dest.writeLong(lastAccountChangeTime);
+            FileOutputStream fileOutputStream = new FileOutputStream(accountFile);
+            fileOutputStream.write(dest.marshall());
+            fileOutputStream.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        dest.recycle();
+    }
+
+    /**
+     * Read all accounts from file.
+     */
+    private void readAllAccounts() {
+        File accountFile = VEnvironment.getAccountConfigFile();
+        refreshAuthenticatorCache(null);
+        if (accountFile.exists()) {
+            accountsByUserId.clear();
+            Parcel dest = Parcel.obtain();
+            try {
+                FileInputStream is = new FileInputStream(accountFile);
+                byte[] bytes = new byte[(int) accountFile.length()];
+                int readLength = is.read(bytes);
+                is.close();
+                if (readLength != bytes.length) {
+                    throw new IOException(String.format(Locale.ENGLISH, "Expect length %d, but got %d.", bytes.length, readLength));
+                }
+                dest.unmarshall(bytes, 0, bytes.length);
+                dest.setDataPosition(0);
+                dest.readInt(); // skip the magic
+                int size = dest.readInt(); // the VAccount's size we need to read
+                boolean invalid = false;
+                while (size-- > 0) {
+                    VAccount account = new VAccount(dest);
+                    VLog.d(TAG, "Reading account : " + account.type);
+                    AuthenticatorInfo info = cache.authenticators.get(account.type);
+                    if (info != null) {
+                        List<VAccount> accounts = accountsByUserId.get(account.userId);
+                        if (accounts == null) {
+                            accounts = new ArrayList<>();
+                            accountsByUserId.put(account.userId, accounts);
+                        }
+                        accounts.add(account);
+                    } else {
+                        invalid = true;
+                    }
+                }
+                lastAccountChangeTime = dest.readLong();
+                if (invalid) {
+                    saveAllAccounts();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                dest.recycle();
+            }
+        }
+    }
+
+
+    private VAccount getAccount(int userId, String accountName, String accountType) {
+        List<VAccount> accounts = accountsByUserId.get(userId);
+        if (accounts != null) {
+            for (VAccount account : accounts) {
+                if (TextUtils.equals(account.name, accountName) && TextUtils.equals(account.type, accountType)) {
+                    return account;
+                }
+            }
+        }
+        return null;
+    }
+
+
+    public void refreshAuthenticatorCache(String packageName) {
+        cache.authenticators.clear();
+        Intent intent = new Intent(AccountManager.ACTION_AUTHENTICATOR_INTENT);
+        if (packageName != null) {
+            intent.setPackage(packageName);
+        }
+        generateServicesMap(
+                VPackageManagerService.get().queryIntentServices(intent, null, PackageManager.GET_META_DATA, 0),
+                cache.authenticators, new RegisteredServicesParser());
+    }
+
+    private void generateServicesMap(List<ResolveInfo> services, Map<String, AuthenticatorInfo> map,
+                                     RegisteredServicesParser accountParser) {
+        for (ResolveInfo info : services) {
+            XmlResourceParser parser = accountParser.getParser(mContext, info.serviceInfo,
+                    AccountManager.AUTHENTICATOR_META_DATA_NAME);
+            if (parser != null) {
+                try {
+                    AttributeSet attributeSet = Xml.asAttributeSet(parser);
+                    int type;
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+                        // Nothing to do
+                    }
+                    if (AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME.equals(parser.getName())) {
+                        AuthenticatorDescription desc = parseAuthenticatorDescription(
+                                accountParser.getResources(mContext, info.serviceInfo.applicationInfo),
+                                info.serviceInfo.packageName, attributeSet);
+                        if (desc != null) {
+                            map.put(desc.type, new AuthenticatorInfo(desc, info.serviceInfo));
+                        }
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    final static class AuthTokenRecord {
+        public int userId;
+        public Account account;
+        public long expiryEpochMillis;
+        public String authToken;
+        private String authTokenType;
+        private String packageName;
+
+        AuthTokenRecord(int userId, Account account, String authTokenType, String packageName, String authToken,
+                        long expiryEpochMillis) {
+            this.userId = userId;
+            this.account = account;
+            this.authTokenType = authTokenType;
+            this.packageName = packageName;
+            this.authToken = authToken;
+            this.expiryEpochMillis = expiryEpochMillis;
+        }
+
+        AuthTokenRecord(int userId, Account account, String authTokenType, String packageName) {
+            this.userId = userId;
+            this.account = account;
+            this.authTokenType = authTokenType;
+            this.packageName = packageName;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+            AuthTokenRecord that = (AuthTokenRecord) o;
+            return userId == that.userId
+                    && account.equals(that.account)
+                    && authTokenType.equals(that.authTokenType)
+                    && packageName.equals(that.packageName);
+        }
+
+        @Override
+        public int hashCode() {
+            return ((this.userId * 31 + this.account.hashCode()) * 31
+                    + this.authTokenType.hashCode()) * 31
+                    + this.packageName.hashCode();
+        }
+    }
+
+    private final class AuthenticatorInfo {
+        final AuthenticatorDescription desc;
+        final ServiceInfo serviceInfo;
+
+        AuthenticatorInfo(AuthenticatorDescription desc, ServiceInfo info) {
+            this.desc = desc;
+            this.serviceInfo = info;
+        }
+    }
+
+    private final class AuthenticatorCache {
+        final Map<String, AuthenticatorInfo> authenticators = new HashMap<>();
+    }
+
+    private abstract class Session extends IAccountAuthenticatorResponse.Stub
+            implements IBinder.DeathRecipient, ServiceConnection {
+        final int mUserId;
+        final AuthenticatorInfo mAuthenticatorInfo;
+        private final boolean mStripAuthTokenFromResult;
+        public int mNumResults;
+        IAccountAuthenticator mAuthenticator;
+        private IAccountManagerResponse mResponse;
+        private boolean mExpectActivityLaunch;
+        private long mCreationTime;
+        private String mAccountName;
+        private boolean mAuthDetailsRequired;
+        private boolean mUpdateLastAuthenticatedTime;
+        private int mNumRequestContinued;
+        private int mNumErrors;
+
+
+        Session(IAccountManagerResponse response, int userId, AuthenticatorInfo info, boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName, boolean authDetailsRequired, boolean updateLastAuthenticatedTime) {
+            if (info == null) throw new IllegalArgumentException("accountType is null");
+            this.mStripAuthTokenFromResult = stripAuthTokenFromResult;
+            this.mResponse = response;
+            this.mUserId = userId;
+            this.mAuthenticatorInfo = info;
+            this.mExpectActivityLaunch = expectActivityLaunch;
+            this.mCreationTime = SystemClock.elapsedRealtime();
+            this.mAccountName = accountName;
+            this.mAuthDetailsRequired = authDetailsRequired;
+            this.mUpdateLastAuthenticatedTime = updateLastAuthenticatedTime;
+            synchronized (mSessions) {
+                mSessions.put(toString(), this);
+            }
+            if (response != null) {
+                try {
+                    response.asBinder().linkToDeath(this, 0 /* flags */);
+                } catch (RemoteException e) {
+                    mResponse = null;
+                    binderDied();
+                }
+            }
+        }
+
+        Session(IAccountManagerResponse response, int userId, AuthenticatorInfo info, boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName) {
+            this(response, userId, info, expectActivityLaunch, stripAuthTokenFromResult, accountName, false, false);
+        }
+
+        IAccountManagerResponse getResponseAndClose() {
+            if (mResponse == null) {
+                // this session has already been closed
+                return null;
+            }
+            IAccountManagerResponse response = mResponse;
+            close(); // this clears mResponse so we need to save the response before this call
+            return response;
+        }
+
+        private void close() {
+            synchronized (mSessions) {
+                if (mSessions.remove(toString()) == null) {
+                    // the session was already closed, so bail out now
+                    return;
+                }
+            }
+            if (mResponse != null) {
+                // stop listening for response deaths
+                mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);
+
+                // clear this so that we don't accidentally send any further results
+                mResponse = null;
+            }
+            unbind();
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
+            try {
+                run();
+            } catch (RemoteException e) {
+                onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+                        "remote exception");
+            }
+        }
+
+        @Override
+        public void onRequestContinued() {
+            mNumRequestContinued++;
+        }
+
+        @Override
+        public void onError(int errorCode, String errorMessage) {
+            mNumErrors++;
+            IAccountManagerResponse response = getResponseAndClose();
+            if (response != null) {
+                Log.v(TAG, getClass().getSimpleName()
+                        + " calling onError() on response " + response);
+                try {
+                    response.onError(errorCode, errorMessage);
+                } catch (RemoteException e) {
+                    Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
+                }
+            } else {
+                Log.v(TAG, "Session.onError: already closed");
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mAuthenticator = null;
+            IAccountManagerResponse response = getResponseAndClose();
+            if (response != null) {
+                try {
+                    response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+                            "disconnected");
+                } catch (RemoteException e) {
+                    Log.v(TAG, "Session.onServiceDisconnected: "
+                            + "caught RemoteException while responding", e);
+                }
+            }
+        }
+
+        @Override
+        public void onResult(Bundle result) throws RemoteException {
+            mNumResults++;
+            if (result != null) {
+                boolean isSuccessfulConfirmCreds = result.getBoolean(
+                        AccountManager.KEY_BOOLEAN_RESULT, false);
+                boolean isSuccessfulUpdateCredsOrAddAccount =
+                        result.containsKey(AccountManager.KEY_ACCOUNT_NAME)
+                                && result.containsKey(AccountManager.KEY_ACCOUNT_TYPE);
+                // We should only update lastAuthenticated time, if
+                // mUpdateLastAuthenticatedTime is true and the confirmRequest
+                // or updateRequest was successful
+                boolean needUpdate = mUpdateLastAuthenticatedTime
+                        && (isSuccessfulConfirmCreds || isSuccessfulUpdateCredsOrAddAccount);
+                if (needUpdate || mAuthDetailsRequired) {
+                    synchronized (accountsByUserId) {
+                        VAccount account = getAccount(mUserId, mAccountName, mAuthenticatorInfo.desc.type);
+                        if (needUpdate && account != null) {
+                            account.lastAuthenticatedTime = System.currentTimeMillis();
+                            saveAllAccounts();
+                        }
+                        if (mAuthDetailsRequired) {
+                            long lastAuthenticatedTime = -1;
+                            if (account != null) {
+                                lastAuthenticatedTime = account.lastAuthenticatedTime;
+                            }
+                            result.putLong(AccountManagerCompat.KEY_LAST_AUTHENTICATED_TIME, lastAuthenticatedTime);
+                        }
+                    }
+                }
+            }
+            if (result != null
+                    && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
 //				String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
 //				String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
 //				if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
 //					Account account = new Account(accountName, accountType);
 //					FIXME: Cancel Notification
 //				}
-			}
-			Intent intent = null;
-			if (result != null) {
-				intent = result.getParcelable(AccountManager.KEY_INTENT);
-			}
-			IAccountManagerResponse response;
-			if (mExpectActivityLaunch && result != null
-					&& result.containsKey(AccountManager.KEY_INTENT)) {
-				response = mResponse;
-			} else {
-				response = getResponseAndClose();
-			}
-			if (response != null) {
-				try {
-					if (result == null) {
-						Log.v(TAG, getClass().getSimpleName()
-								+ " calling onError() on response " + response);
-						response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
-								"null bundle returned");
-					} else {
-						if (mStripAuthTokenFromResult) {
-							result.remove(AccountManager.KEY_AUTHTOKEN);
-						}
-						Log.v(TAG, getClass().getSimpleName()
-								+ " calling onResult() on response " + response);
-						if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) &&
-								(intent == null)) {
-							// All AccountManager error codes are greater than 0
-							response.onError(result.getInt(AccountManager.KEY_ERROR_CODE),
-									result.getString(AccountManager.KEY_ERROR_MESSAGE));
-						} else {
-							response.onResult(result);
-						}
-					}
-				} catch (RemoteException e) {
-					// if the caller is dead then there is no one to care about remote exceptions
-					Log.v(TAG, "failure while notifying response", e);
-				}
-			}
-		}
-
-		public abstract void run() throws RemoteException;
-
-		void bind() {
-			Log.v(TAG, "initiating bind to authenticator type " + mAuthenticatorInfo.desc.type);
-			Intent intent = new Intent();
-			intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
-			intent.setClassName(mAuthenticatorInfo.serviceInfo.packageName, mAuthenticatorInfo.serviceInfo.name);
-			intent.putExtra("_VA_|_user_id_", mUserId);
-
-			if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
-				Log.d(TAG, "bind attempt failed for " + toDebugString());
-				onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
-			}
-		}
-
-		protected String toDebugString() {
-			return toDebugString(SystemClock.elapsedRealtime());
-		}
-
-		protected String toDebugString(long now) {
-			return "Session: expectLaunch " + mExpectActivityLaunch
-					+ ", connected " + (mAuthenticator != null)
-					+ ", stats (" + mNumResults + "/" + mNumRequestContinued
-					+ "/" + mNumErrors + ")"
-					+ ", lifetime " + ((now - mCreationTime) / 1000.0);
-		}
-
-		private void unbind() {
-			if (mAuthenticator != null) {
-				mAuthenticator = null;
-				mContext.unbindService(this);
-			}
-		}
-
-		@Override
-		public void binderDied() {
-			mResponse = null;
-			close();
-		}
-	}
-
-	private class GetAccountsByTypeAndFeatureSession extends Session {
-		private final String[] mFeatures;
-		private volatile Account[] mAccountsOfType = null;
-		private volatile ArrayList<Account> mAccountsWithFeatures = null;
-		private volatile int mCurrentAccount = 0;
-
-		public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response, int userId, AuthenticatorInfo info, String[] features) {
-			super(response, userId, info, false /* expectActivityLaunch */,
-					true /* stripAuthTokenFromResult */, null /* accountName */);
-			mFeatures = features;
-		}
-
-		@Override
-		public void run() throws RemoteException {
-			mAccountsOfType = getAccounts(mUserId, mAuthenticatorInfo.desc.type);
-			// check whether each account matches the requested features
-			mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
-			mCurrentAccount = 0;
-
-			checkAccount();
-		}
-
-		public void checkAccount() {
-			if (mCurrentAccount >= mAccountsOfType.length) {
-				sendResult();
-				return;
-			}
-
-			final IAccountAuthenticator accountAuthenticator = mAuthenticator;
-			if (accountAuthenticator == null) {
-				// It is possible that the authenticator has died, which is indicated by
-				// mAuthenticator being set to null. If this happens then just abort.
-				// There is no need to send back a result or error in this case since
-				// that already happened when mAuthenticator was cleared.
-				Log.v(TAG, "checkAccount: aborting session since we are no longer"
-						+ " connected to the authenticator, " + toDebugString());
-				return;
-			}
-			try {
-				accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
-			} catch (RemoteException e) {
-				onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
-			}
-		}
-
-		@Override
-		public void onResult(Bundle result) {
-			mNumResults++;
-			if (result == null) {
-				onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
-				return;
-			}
-			if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
-				mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
-			}
-			mCurrentAccount++;
-			checkAccount();
-		}
-
-		public void sendResult() {
-			IAccountManagerResponse response = getResponseAndClose();
-			if (response != null) {
-				try {
-					Account[] accounts = new Account[mAccountsWithFeatures.size()];
-					for (int i = 0; i < accounts.length; i++) {
-						accounts[i] = mAccountsWithFeatures.get(i);
-					}
-					if (Log.isLoggable(TAG, Log.VERBOSE)) {
-						Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
-								+ response);
-					}
-					Bundle result = new Bundle();
-					result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
-					response.onResult(result);
-				} catch (RemoteException e) {
-					// if the caller is dead then there is no one to care about remote exceptions
-					Log.v(TAG, "failure while notifying response", e);
-				}
-			}
-		}
-
-
-		@Override
-		protected String toDebugString(long now) {
-			return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
-					+ ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
-		}
-	}
+            }
+            Intent intent = null;
+            if (result != null) {
+                intent = result.getParcelable(AccountManager.KEY_INTENT);
+            }
+            IAccountManagerResponse response;
+            if (mExpectActivityLaunch && result != null
+                    && result.containsKey(AccountManager.KEY_INTENT)) {
+                response = mResponse;
+            } else {
+                response = getResponseAndClose();
+            }
+            if (response != null) {
+                try {
+                    if (result == null) {
+                        Log.v(TAG, getClass().getSimpleName()
+                                + " calling onError() on response " + response);
+                        response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                                "null bundle returned");
+                    } else {
+                        if (mStripAuthTokenFromResult) {
+                            result.remove(AccountManager.KEY_AUTHTOKEN);
+                        }
+                        Log.v(TAG, getClass().getSimpleName()
+                                + " calling onResult() on response " + response);
+                        if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) &&
+                                (intent == null)) {
+                            // All AccountManager error codes are greater than 0
+                            response.onError(result.getInt(AccountManager.KEY_ERROR_CODE),
+                                    result.getString(AccountManager.KEY_ERROR_MESSAGE));
+                        } else {
+                            response.onResult(result);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    // if the caller is dead then there is no one to care about remote exceptions
+                    Log.v(TAG, "failure while notifying response", e);
+                }
+            }
+        }
+
+        public abstract void run() throws RemoteException;
+
+        void bind() {
+            Log.v(TAG, "initiating bind to authenticator type " + mAuthenticatorInfo.desc.type);
+            Intent intent = new Intent();
+            intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
+            intent.setClassName(mAuthenticatorInfo.serviceInfo.packageName, mAuthenticatorInfo.serviceInfo.name);
+            intent.putExtra("_VA_|_user_id_", mUserId);
+
+            if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+                Log.d(TAG, "bind attempt failed for " + toDebugString());
+                onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
+            }
+        }
+
+        protected String toDebugString() {
+            return toDebugString(SystemClock.elapsedRealtime());
+        }
+
+        protected String toDebugString(long now) {
+            return "Session: expectLaunch " + mExpectActivityLaunch
+                    + ", connected " + (mAuthenticator != null)
+                    + ", stats (" + mNumResults + "/" + mNumRequestContinued
+                    + "/" + mNumErrors + ")"
+                    + ", lifetime " + ((now - mCreationTime) / 1000.0);
+        }
+
+        private void unbind() {
+            if (mAuthenticator != null) {
+                mAuthenticator = null;
+                mContext.unbindService(this);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            mResponse = null;
+            close();
+        }
+    }
+
+    private class GetAccountsByTypeAndFeatureSession extends Session {
+        private final String[] mFeatures;
+        private volatile Account[] mAccountsOfType = null;
+        private volatile ArrayList<Account> mAccountsWithFeatures = null;
+        private volatile int mCurrentAccount = 0;
+
+        public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response, int userId, AuthenticatorInfo info, String[] features) {
+            super(response, userId, info, false /* expectActivityLaunch */,
+                    true /* stripAuthTokenFromResult */, null /* accountName */);
+            mFeatures = features;
+        }
+
+        @Override
+        public void run() throws RemoteException {
+            mAccountsOfType = getAccounts(mUserId, mAuthenticatorInfo.desc.type);
+            // check whether each account matches the requested features
+            mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
+            mCurrentAccount = 0;
+
+            checkAccount();
+        }
+
+        public void checkAccount() {
+            if (mCurrentAccount >= mAccountsOfType.length) {
+                sendResult();
+                return;
+            }
+
+            final IAccountAuthenticator accountAuthenticator = mAuthenticator;
+            if (accountAuthenticator == null) {
+                // It is possible that the authenticator has died, which is indicated by
+                // mAuthenticator being set to null. If this happens then just abort.
+                // There is no need to send back a result or error in this case since
+                // that already happened when mAuthenticator was cleared.
+                Log.v(TAG, "checkAccount: aborting session since we are no longer"
+                        + " connected to the authenticator, " + toDebugString());
+                return;
+            }
+            try {
+                accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
+            } catch (RemoteException e) {
+                onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
+            }
+        }
+
+        @Override
+        public void onResult(Bundle result) {
+            mNumResults++;
+            if (result == null) {
+                onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
+                return;
+            }
+            if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
+                mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
+            }
+            mCurrentAccount++;
+            checkAccount();
+        }
+
+        public void sendResult() {
+            IAccountManagerResponse response = getResponseAndClose();
+            if (response != null) {
+                try {
+                    Account[] accounts = new Account[mAccountsWithFeatures.size()];
+                    for (int i = 0; i < accounts.length; i++) {
+                        accounts[i] = mAccountsWithFeatures.get(i);
+                    }
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+                                + response);
+                    }
+                    Bundle result = new Bundle();
+                    result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
+                    response.onResult(result);
+                } catch (RemoteException e) {
+                    // if the caller is dead then there is no one to care about remote exceptions
+                    Log.v(TAG, "failure while notifying response", e);
+                }
+            }
+        }
+
+
+        @Override
+        protected String toDebugString(long now) {
+            return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
+                    + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
+        }
+    }
 
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/VContentService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/VContentService.java
new file mode 100644
index 000000000..13cd656d2
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/VContentService.java
@@ -0,0 +1,185 @@
+package com.lody.virtual.server.accounts;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncAdapterType;
+import android.content.SyncRequest;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.lody.virtual.server.pm.VAppManagerService;
+import com.lody.virtual.server.pm.VPackageManagerService;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import mirror.android.content.SyncAdapterTypeN;
+import mirror.com.android.internal.R_Hide;
+
+/**
+ * @author Lody
+ */
+
+public class VContentService {
+
+    private Context mContext;
+    private final SparseArray<Map<VSyncRecord.SyncRecordKey, VSyncRecord>> mRecords = new SparseArray<>();
+    private final Map<String, SyncAdapterInfo> mAppSyncAdapterInfos = new HashMap<>();
+
+    private class SyncAdapterInfo {
+        SyncAdapterType adapterType;
+        ServiceInfo serviceInfo;
+
+        SyncAdapterInfo(SyncAdapterType adapterType, ServiceInfo serviceInfo) {
+            this.adapterType = adapterType;
+            this.serviceInfo = serviceInfo;
+        }
+    }
+
+    public void refreshServiceCache(String packageName) {
+        Intent intent = new Intent("android.content.SyncAdapter");
+        if (packageName != null) {
+            intent.setPackage(packageName);
+        }
+        generateServicesMap(
+                VPackageManagerService.get().queryIntentServices(
+                        intent, null, PackageManager.GET_META_DATA, 0
+                ),
+                mAppSyncAdapterInfos,
+                new RegisteredServicesParser()
+        );
+    }
+
+    public void syncAsUser(SyncRequest request, int userId) {
+        Account account = mirror.android.content.SyncRequest.mAccountToSync.get(request);
+        String authority = mirror.android.content.SyncRequest.mAuthority.get(request);
+        Bundle extras = mirror.android.content.SyncRequest.mExtras.get(request);
+        boolean isPeriodic = mirror.android.content.SyncRequest.mIsPeriodic.get(request);
+        long syncRunTimeSecs = mirror.android.content.SyncRequest.mSyncRunTimeSecs.get(request);
+        if (!isAccountExist(userId, account, authority)) {
+            return;
+        }
+        VSyncRecord.SyncRecordKey key = new VSyncRecord.SyncRecordKey(account, authority);
+        VSyncRecord.SyncExtras syncExtras = new VSyncRecord.SyncExtras(extras);
+        int isSyncable = getIsSyncableAsUser(account, authority, userId);
+        synchronized (mRecords) {
+            Map<VSyncRecord.SyncRecordKey, VSyncRecord> map = mRecords.get(userId);
+            if (map == null) {
+                map = new HashMap<>();
+                mRecords.put(userId, map);
+            }
+            VSyncRecord record = map.get(key);
+            if (record == null) {
+                record = new VSyncRecord(userId, account, authority);
+                map.put(key, record);
+            }
+            if (isSyncable < 0) {
+                // Initialisation sync.
+                Bundle newExtras = new Bundle();
+                newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+                record.extras.add(new VSyncRecord.SyncExtras(newExtras));
+            }
+            if (isPeriodic) {
+                VSyncRecord.PeriodicSyncConfig periodicSyncConfig = new VSyncRecord.PeriodicSyncConfig(syncRunTimeSecs);
+                record.configs.put(syncExtras, periodicSyncConfig);
+            } else {
+                record.extras.add(syncExtras);
+            }
+
+
+        }
+    }
+
+
+    private boolean isAccountExist(int userId, Account account, String providerName) {
+        synchronized (mAppSyncAdapterInfos) {
+            SyncAdapterInfo info = mAppSyncAdapterInfos.get(account.type + "/" + providerName);
+            return info != null
+                    && VAppManagerService.get().isAppInstalled(info.serviceInfo.packageName);
+        }
+    }
+
+    public int getIsSyncableAsUser(Account account, String providerName, int userId) {
+        VSyncRecord.SyncRecordKey key = new VSyncRecord.SyncRecordKey(account, providerName);
+        synchronized (mRecords) {
+            Map<VSyncRecord.SyncRecordKey, VSyncRecord> map = mRecords.get(userId);
+            if (map == null) {
+                return -1;
+            }
+            VSyncRecord record = map.get(key);
+            if (record == null) {
+                return -1;
+            }
+            return record.syncable;
+        }
+
+    }
+
+    private void generateServicesMap(List<ResolveInfo> services, Map<String, SyncAdapterInfo> map,
+                                     RegisteredServicesParser accountParser) {
+        for (ResolveInfo info : services) {
+            XmlResourceParser parser = accountParser.getParser(mContext, info.serviceInfo, "android.content.SyncAdapter");
+            if (parser != null) {
+                try {
+                    AttributeSet attributeSet = Xml.asAttributeSet(parser);
+                    int type;
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+                        // Nothing to do
+                    }
+                    if ("sync-adapter".equals(parser.getName())) {
+                        SyncAdapterType adapterType = parseSyncAdapterType(
+                                accountParser.getResources(mContext, info.serviceInfo.applicationInfo), attributeSet);
+                        if (adapterType != null) {
+                            String key = adapterType.accountType + "/" + adapterType.authority;
+                            map.put(key, new SyncAdapterInfo(adapterType, info.serviceInfo));
+                        }
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    private SyncAdapterType parseSyncAdapterType(Resources res, AttributeSet set) {
+        TypedArray obtainAttributes = res.obtainAttributes(set, R_Hide.styleable.SyncAdapter.get());
+        try {
+            String contentAuthority = obtainAttributes.getString(R_Hide.styleable.SyncAdapter_contentAuthority.get());
+            String accountType = obtainAttributes.getString(R_Hide.styleable.SyncAdapter_accountType.get());
+            if (contentAuthority == null || accountType == null) {
+                obtainAttributes.recycle();
+                return null;
+            }
+            boolean userVisible = obtainAttributes.getBoolean(R_Hide.styleable.SyncAdapter_userVisible.get(), true);
+            boolean supportsUploading = obtainAttributes.getBoolean(R_Hide.styleable.SyncAdapter_supportsUploading.get(), true);
+            boolean isAlwaysSyncable = obtainAttributes.getBoolean(R_Hide.styleable.SyncAdapter_isAlwaysSyncable.get(), true);
+            boolean allowParallelSyncs = obtainAttributes.getBoolean(R_Hide.styleable.SyncAdapter_allowParallelSyncs.get(), true);
+            String settingsActivity = obtainAttributes.getString(R_Hide.styleable.SyncAdapter_settingsActivity.get());
+            SyncAdapterType type;
+            if (SyncAdapterTypeN.ctor != null) {
+                type = SyncAdapterTypeN.ctor.newInstance(contentAuthority, accountType, userVisible, supportsUploading, isAlwaysSyncable, allowParallelSyncs, settingsActivity, null);
+                obtainAttributes.recycle();
+                return type;
+            }
+            type = mirror.android.content.SyncAdapterType.ctor.newInstance(contentAuthority, accountType, userVisible, supportsUploading, isAlwaysSyncable, allowParallelSyncs, settingsActivity);
+            obtainAttributes.recycle();
+            return type;
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/VSyncRecord.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/VSyncRecord.java
new file mode 100644
index 000000000..053bdab2a
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/accounts/VSyncRecord.java
@@ -0,0 +1,197 @@
+package com.lody.virtual.server.accounts;
+
+import android.accounts.Account;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Lody
+ */
+
+public class VSyncRecord {
+
+    public int userId;
+    public SyncRecordKey key;
+    public int syncable = -1;
+    public boolean isPeriodic = false;
+    public Map<SyncExtras, PeriodicSyncConfig> configs = new HashMap<>();
+    public List<SyncExtras> extras = new ArrayList<>();
+
+    public VSyncRecord(int userId, Account account, String authority) {
+        this.userId = userId;
+        key = new SyncRecordKey(account, authority);
+    }
+
+    public static class SyncExtras implements Parcelable {
+        Bundle extras;
+
+        public SyncExtras(Bundle extras) {
+            this.extras = extras;
+        }
+
+        SyncExtras(Parcel in) {
+            this.extras = in.readBundle(getClass().getClassLoader());
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeBundle(this.extras);
+        }
+
+        public static final Parcelable.Creator<SyncExtras> CREATOR = new Parcelable.Creator<SyncExtras>() {
+            @Override
+            public SyncExtras createFromParcel(Parcel source) {
+                return new SyncExtras(source);
+            }
+
+            @Override
+            public SyncExtras[] newArray(int size) {
+                return new SyncExtras[size];
+            }
+        };
+
+        @Override
+        public boolean equals(Object obj) {
+            return VSyncRecord.equals(this.extras, ((SyncExtras) obj).extras, false);
+        }
+    }
+
+    public static class SyncRecordKey implements Parcelable {
+
+        Account account;
+        String authority;
+
+        SyncRecordKey(Account account, String authority) {
+            this.account = account;
+            this.authority = authority;
+        }
+
+        SyncRecordKey(Parcel in) {
+            this.account = in.readParcelable(Account.class.getClassLoader());
+            this.authority = in.readString();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeParcelable(this.account, flags);
+            dest.writeString(this.authority);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            SyncRecordKey that = (SyncRecordKey) o;
+
+            if (account != null ? !account.equals(that.account) : that.account != null)
+                return false;
+            return authority != null ? authority.equals(that.authority) : that.authority == null;
+        }
+
+        public static final Parcelable.Creator<SyncRecordKey> CREATOR = new Parcelable.Creator<SyncRecordKey>() {
+            @Override
+            public SyncRecordKey createFromParcel(Parcel source) {
+                return new SyncRecordKey(source);
+            }
+
+            @Override
+            public SyncRecordKey[] newArray(int size) {
+                return new SyncRecordKey[size];
+            }
+        };
+    }
+
+    public static boolean equals(Bundle a, Bundle b, boolean sameSize) {
+        if (a == b) {
+            return true;
+        }
+        if (sameSize && a.size() != b.size()) {
+            return false;
+        }
+        if (a.size() <= b.size()) {
+            Bundle smaller = a;
+            a = b;
+            b = smaller;
+        }
+        for (String key : a.keySet()) {
+            if (sameSize || !isIgnoredKey(key)) {
+                if (!b.containsKey(key)) {
+                    return false;
+                }
+                //noinspection ConstantConditions
+                if (!a.get(key).equals(b.get(key))) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    static class PeriodicSyncConfig implements Parcelable {
+
+        long syncRunTimeSecs;
+
+        public PeriodicSyncConfig(long syncRunTimeSecs) {
+            this.syncRunTimeSecs = syncRunTimeSecs;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(this.syncRunTimeSecs);
+        }
+
+        PeriodicSyncConfig(Parcel in) {
+            this.syncRunTimeSecs = in.readLong();
+        }
+
+        public static final Parcelable.Creator<PeriodicSyncConfig> CREATOR = new Parcelable.Creator<PeriodicSyncConfig>() {
+            @Override
+            public PeriodicSyncConfig createFromParcel(Parcel source) {
+                return new PeriodicSyncConfig(source);
+            }
+
+            @Override
+            public PeriodicSyncConfig[] newArray(int size) {
+                return new PeriodicSyncConfig[size];
+            }
+        };
+    }
+
+    private static boolean isIgnoredKey(String str) {
+        return str.equals("expedited")
+                || str.equals("ignore_settings")
+                || str.equals("ignore_backoff")
+                || str.equals("do_not_retry")
+                || str.equals("force")
+                || str.equals("upload")
+                || str.equals("deletions_override")
+                || str.equals("discard_deletions")
+                || str.equals("expected_upload")
+                || str.equals("expected_download")
+                || str.equals("sync_priority")
+                || str.equals("allow_metered")
+                || str.equals("initialize");
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/ActivityStack.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/ActivityStack.java
index be0c63dd7..6c8709de5 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/ActivityStack.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/ActivityStack.java
@@ -5,6 +5,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -13,7 +15,7 @@
 
 import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.env.VirtualRuntime;
-import com.lody.virtual.client.stub.StubManifest;
+import com.lody.virtual.client.stub.VASettings;
 import com.lody.virtual.helper.utils.ArrayUtils;
 import com.lody.virtual.helper.utils.ClassUtils;
 import com.lody.virtual.helper.utils.ComponentUtils;
@@ -74,6 +76,7 @@ private static ActivityRecord topActivityInTask(TaskRecord task) {
         }
     }
 
+
     private void deliverNewIntentLocked(ActivityRecord sourceRecord, ActivityRecord targetRecord, Intent intent) {
         if (targetRecord == null) {
             return;
@@ -130,18 +133,16 @@ private ActivityRecord findActivityByToken(int userId, IBinder token) {
 
     private boolean markTaskByClearTarget(TaskRecord task, ClearTarget clearTarget, ComponentName component) {
         boolean marked = false;
-        switch (clearTarget) {
-            case TASK: {
-                synchronized (task.activities) {
+        synchronized (task.activities) {
+            switch (clearTarget) {
+                case TASK: {
                     for (ActivityRecord r : task.activities) {
                         r.marked = true;
                         marked = true;
                     }
                 }
-            }
-            break;
-            case SPEC_ACTIVITY: {
-                synchronized (task.activities) {
+                break;
+                case SPEC_ACTIVITY: {
                     for (ActivityRecord r : task.activities) {
                         if (r.component.equals(component)) {
                             r.marked = true;
@@ -149,10 +150,8 @@ private boolean markTaskByClearTarget(TaskRecord task, ClearTarget clearTarget,
                         }
                     }
                 }
-            }
-            break;
-            case TOP: {
-                synchronized (task.activities) {
+                break;
+                case TOP: {
                     int N = task.activities.size();
                     while (N-- > 0) {
                         ActivityRecord r = task.activities.get(N);
@@ -167,10 +166,9 @@ private boolean markTaskByClearTarget(TaskRecord task, ClearTarget clearTarget,
                         }
                     }
                 }
+                break;
             }
-            break;
         }
-
         return marked;
     }
 
@@ -278,6 +276,7 @@ int startActivityLocked(int userId, Intent intent, ActivityInfo info, IBinder re
         ReuseTarget reuseTarget = ReuseTarget.CURRENT;
         ClearTarget clearTarget = ClearTarget.NOTHING;
         boolean clearTop = containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        boolean clearTask = containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);
 
         if (intent.getComponent() == null) {
             intent.setComponent(new ComponentName(info.packageName, info.name));
@@ -289,7 +288,7 @@ int startActivityLocked(int userId, Intent intent, ActivityInfo info, IBinder re
             removeFlags(intent, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
             clearTarget = ClearTarget.TOP;
         }
-        if (containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK)) {
+        if (clearTask) {
             if (containFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK)) {
                 clearTarget = ClearTarget.TASK;
             } else {
@@ -371,7 +370,7 @@ int startActivityLocked(int userId, Intent intent, ActivityInfo info, IBinder re
         } else {
             boolean delivered = false;
             mAM.moveTaskToFront(reuseTask.taskId, 0);
-            boolean startTaskToFront = !clearTop && ComponentUtils.isSameIntent(intent, reuseTask.taskRoot);
+            boolean startTaskToFront = !clearTask && !clearTop && ComponentUtils.isSameIntent(intent, reuseTask.taskRoot);
 
             if (clearTarget.deliverIntent || singleTop) {
                 taskMarked = markTaskByClearTarget(reuseTask, clearTarget, intent.getComponent());
@@ -402,7 +401,7 @@ int startActivityLocked(int userId, Intent intent, ActivityInfo info, IBinder re
         return 0;
     }
 
-    private Intent startActivityInNewTaskLocked(int userId, Intent intent, ActivityInfo info, Bundle options) {
+    private void startActivityInNewTaskLocked(int userId, Intent intent, ActivityInfo info, Bundle options) {
         Intent destIntent = startActivityProcess(userId, null, intent, info);
         if (destIntent != null) {
             destIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -422,7 +421,6 @@ private Intent startActivityInNewTaskLocked(int userId, Intent intent, ActivityI
                 VirtualCore.get().getContext().startActivity(destIntent);
             }
         }
-        return destIntent;
     }
 
     private void scheduleFinishMarkedActivityLocked() {
@@ -447,15 +445,14 @@ public void run() {
         }
     }
 
-    private boolean startActivityFromSourceTask(TaskRecord task, Intent intent, ActivityInfo info, String resultWho,
-                                                int requestCode, Bundle options) {
-        ActivityRecord top = topActivityInTask(task);
+    private void startActivityFromSourceTask(TaskRecord task, Intent intent, ActivityInfo info, String resultWho,
+                                             int requestCode, Bundle options) {
+        ActivityRecord top = task.activities.isEmpty() ? null : task.activities.get(task.activities.size() - 1);
         if (top != null) {
             if (startActivityProcess(task.userId, top, intent, info) != null) {
                 realStartActivityLocked(top.token, intent, resultWho, requestCode, options);
             }
         }
-        return false;
     }
 
 
@@ -530,16 +527,32 @@ private String fetchStubActivity(int vpid, ActivityInfo targetInfo) {
                 showWallpaper = ent.array.getBoolean(R_Styleable_Window_windowShowWallpaper, false);
                 isTranslucent = ent.array.getBoolean(R_Styleable_Window_windowIsTranslucent, false);
                 isFloating = ent.array.getBoolean(R_Styleable_Window_windowIsFloating, false);
+            }else{
+                Resources resources=VirtualCore.get().getResources(targetInfo.packageName);
+                if(resources!=null) {
+                    TypedArray typedArray = resources.newTheme().obtainStyledAttributes(targetInfo.theme, R_Styleable_Window);
+                    if(typedArray!=null){
+                        showWallpaper = typedArray.getBoolean(R_Styleable_Window_windowShowWallpaper, false);
+                        isTranslucent = typedArray.getBoolean(R_Styleable_Window_windowIsTranslucent, false);
+                        isFloating = typedArray.getBoolean(R_Styleable_Window_windowIsFloating, false);
+                    }
+                }
             }
         } catch (Throwable e) {
             e.printStackTrace();
         }
 
+        // deal with manifest Activity style android:excludeFromRecents="true", becasue Mobile Legends has two recent task
+        boolean isExcludeFromRecents = ((targetInfo.flags & ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS) != 0);
+        if (isExcludeFromRecents) {
+            return VASettings.getStubExcludeFromRecentActivityName(vpid);
+        }
+
         boolean isDialogStyle = isFloating || isTranslucent || showWallpaper;
         if (isDialogStyle) {
-            return StubManifest.getStubDialogName(vpid);
+            return VASettings.getStubDialogName(vpid);
         } else {
-            return StubManifest.getStubActivityName(vpid);
+            return VASettings.getStubActivityName(vpid);
         }
     }
 
@@ -644,7 +657,7 @@ ComponentName getCallingActivity(int userId, IBinder token) {
         synchronized (mHistory) {
             ActivityRecord r = findActivityByToken(userId, token);
             if (r != null) {
-                return r.caller;
+                return r.caller != null ? r.caller : r.component;
             }
             return null;
         }
@@ -654,9 +667,9 @@ public String getCallingPackage(int userId, IBinder token) {
         synchronized (mHistory) {
             ActivityRecord r = findActivityByToken(userId, token);
             if (r != null) {
-                return r.caller != null ? r.caller.getPackageName() : null;
+                return r.caller != null ? r.caller.getPackageName() : "android";
             }
-            return null;
+            return "android";
         }
     }
 
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/BroadcastSystem.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/BroadcastSystem.java
index f22a26c6e..99dc9f61c 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/BroadcastSystem.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/BroadcastSystem.java
@@ -7,12 +7,15 @@
 import android.content.pm.ActivityInfo;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 
 import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.env.SpecialComponentList;
 import com.lody.virtual.helper.collection.ArrayMap;
+import com.lody.virtual.helper.utils.Reflect;
 import com.lody.virtual.helper.utils.VLog;
 import com.lody.virtual.remote.PendingResultData;
 import com.lody.virtual.server.pm.PackageSetting;
@@ -60,8 +63,12 @@ private BroadcastSystem(Context context, VActivityManagerService ams, VAppManage
         this.mContext = context;
         this.mApp = app;
         this.mAMS = ams;
-        mScheduler = new StaticScheduler();
-        mTimeoutHandler = new TimeoutHandler();
+        HandlerThread broadcastThread = new HandlerThread("BroadcastThread");
+        HandlerThread anrThread = new HandlerThread("BroadcastAnrThread");
+        broadcastThread.start();
+        anrThread.start();
+        mScheduler = new StaticScheduler(broadcastThread.getLooper());
+        mTimeoutHandler = new TimeoutHandler(anrThread.getLooper());
         fuckHuaWeiVerifier();
     }
 
@@ -96,7 +103,15 @@ private void fuckHuaWeiVerifier() {
             if (packageInfo != null) {
                 Object receiverResource = LoadedApkHuaWei.mReceiverResource.get(packageInfo);
                 if (receiverResource != null) {
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                    if (Build.VERSION.SDK_INT >= 26) {
+                        Map<Integer, List<String>> whiteListMap = Reflect.on(receiverResource).get("mWhiteListMap");
+                        List<String> whiteList = whiteListMap.get(0);
+                        if (whiteList == null) {
+                            whiteList = new ArrayList<>();
+                            whiteListMap.put(0, whiteList);
+                        }
+                        whiteList.add(mContext.getPackageName());
+                    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                         if (ReceiverResourceN.mWhiteList != null) {
                             List<String> whiteList = ReceiverResourceN.mWhiteList.get(receiverResource);
                             List<String> newWhiteList = new ArrayList<>();
@@ -107,7 +122,6 @@ private void fuckHuaWeiVerifier() {
                             }
                             ReceiverResourceN.mWhiteList.set(receiverResource, newWhiteList);
                         }
-
                     } else {
                         if (ReceiverResourceM.mWhiteList != null) {
                             String[] whiteList = ReceiverResourceM.mWhiteList.get(receiverResource);
@@ -197,6 +211,9 @@ void broadcastSent(int vuid, ActivityInfo receiverInfo, PendingResultData res) {
 
     private static final class StaticScheduler extends Handler {
 
+        StaticScheduler(Looper looper) {
+            super(looper);
+        }
     }
 
     private static final class BroadcastRecord {
@@ -212,6 +229,11 @@ private static final class BroadcastRecord {
     }
 
     private final class TimeoutHandler extends Handler {
+
+        TimeoutHandler(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             IBinder token = (IBinder) msg.obj;
@@ -244,6 +266,10 @@ public void onReceive(Context context, Intent intent) {
             if ((intent.getFlags() & FLAG_RECEIVER_REGISTERED_ONLY) != 0 || isInitialStickyBroadcast()) {
                 return;
             }
+            String privilegePkg = intent.getStringExtra("_VA_|_privilege_pkg_");
+            if (privilegePkg != null && !info.packageName.equals(privilegePkg)) {
+                return;
+            }
             PendingResult result = goAsync();
             if (!mAMS.handleStaticBroadcast(appId, info, intent, new PendingResultData(result))) {
                 result.finish();
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/ServiceRecord.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/ServiceRecord.java
index 20603b01a..d1b85fd74 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/ServiceRecord.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/ServiceRecord.java
@@ -1,8 +1,7 @@
 package com.lody.virtual.server.am;
 
 import android.app.IServiceConnection;
-import android.content.ComponentName;
-import android.content.Context;
+import android.app.Notification;
 import android.content.Intent;
 import android.content.pm.ServiceInfo;
 import android.os.Binder;
@@ -10,28 +9,22 @@
 import android.os.RemoteException;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 public class ServiceRecord extends Binder {
-	final HashMap<Intent.FilterComparison, IntentBindRecord> bindings
-			= new HashMap<Intent.FilterComparison, IntentBindRecord>();
+	public final List<IntentBindRecord> bindings = new ArrayList<>();
 	public long activeSince;
 	public long lastActivityTime;
-	public ComponentName name; // service component.
 	public ServiceInfo serviceInfo;
 	public int startId;
 	public ProcessRecord process;
-	final HashMap<IBinder, ArrayList<ConnectionRecord>> connections
-			= new HashMap<IBinder, ArrayList<ConnectionRecord>>();
+	public int foregroundId;
+	public Notification foregroundNoti;
 
 	public boolean containConnection(IServiceConnection connection) {
-		for (IntentBindRecord record : bindings.values()) {
+		for (IntentBindRecord record : bindings) {
 			if (record.containConnection(connection)) {
 				return true;
 			}
@@ -39,49 +32,6 @@ public boolean containConnection(IServiceConnection connection) {
 		return false;
 	}
 
-	public IntentBindRecord retrieveIntentBindRecord(Intent intent) {
-		Intent.FilterComparison filter = new Intent.FilterComparison(intent);
-		IntentBindRecord i = bindings.get(filter);
-		if (i == null) {
-			i = new IntentBindRecord();
-			i.intent = intent;
-			bindings.put(filter, i);
-		}
-
-		return i;
-	}
-
-	public AppBindRecord retrieveAppBindingLocked(Intent intent, ProcessRecord app) {
-		IntentBindRecord i = retrieveIntentBindRecord(intent);
-		AppBindRecord a = i.apps.get(app);
-		if (a != null) {
-			return a;
-		}
-		a = new AppBindRecord(this, i, app);
-		i.apps.put(app, a);
-		return a;
-	}
-
-	public boolean hasAutoCreateConnections() {
-		Collection<ArrayList<ConnectionRecord>> connectionRecords = connections.values();
-		if (connectionRecords == null) {
-			return false;
-		}
-
-		Iterator<ArrayList<ConnectionRecord>> connectionRecordIterator
-				= connectionRecords.iterator();
-		while (connectionRecordIterator.hasNext()) {
-			ArrayList<ConnectionRecord> cr = connectionRecordIterator.next();
-			for (int i=0; i<cr.size(); i++) {
-				if ((cr.get(i).flags & Context.BIND_AUTO_CREATE) != 0) {
-					return true;
-				}
-			}
-		}
-
-		return false;
-	}
-
 	public int getClientCount() {
 		return bindings.size();
 	}
@@ -90,7 +40,7 @@ public int getClientCount() {
 	int getConnectionCount() {
 		int count = 0;
 		synchronized (bindings) {
-			for (IntentBindRecord record : bindings.values()) {
+			for (IntentBindRecord record : bindings) {
 				count += record.connections.size();
 			}
 		}
@@ -100,8 +50,8 @@ int getConnectionCount() {
 
 	IntentBindRecord peekBinding(Intent service) {
 		synchronized (bindings) {
-			for (IntentBindRecord bindRecord : bindings.values()) {
-				if (bindRecord.intent != null && bindRecord.intent.filterEquals(service)) {
+			for (IntentBindRecord bindRecord : bindings) {
+				if (bindRecord.intent.filterEquals(service)) {
 					return bindRecord;
 				}
 			}
@@ -115,38 +65,18 @@ void addToBoundIntent(Intent intent, IServiceConnection connection) {
 			record = new IntentBindRecord();
 			record.intent = intent;
 			synchronized (bindings) {
-				Intent.FilterComparison filter = new Intent.FilterComparison(intent);
-				bindings.put(filter, record);
+				bindings.add(record);
 			}
 		}
 		record.addConnection(connection);
 	}
 
 	public static class IntentBindRecord {
-		final HashMap<ProcessRecord, AppBindRecord> apps
-				= new HashMap<ProcessRecord, AppBindRecord>();
 		public  final List<IServiceConnection> connections = Collections.synchronizedList(new ArrayList<IServiceConnection>());
 		public IBinder binder;
-		/** Set when we have initiated a request for this binder. */
-		boolean requested;
-		/** Set when we still need to tell the service all clients are unbound. */
-		boolean hasBound;
 		Intent intent;
 		public boolean doRebind = false;
 
-		int collectFlags() {
-			int flags = 0;
-			Set<Map.Entry<ProcessRecord,AppBindRecord>> entrySet = apps.entrySet();
-			for (Map.Entry<ProcessRecord,AppBindRecord> app : entrySet) {
-				if (app.getValue().connections.size() > 0) {
-					for (ConnectionRecord conn : app.getValue().connections) {
-						flags |= conn.flags;
-					}
-				}
-			}
-			return flags;
-		}
-
 		public boolean containConnection(IServiceConnection connection) {
 			for (IServiceConnection con : connections) {
 				if (con.asBinder() == connection.asBinder()) {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/VActivityManagerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/VActivityManagerService.java
index 6e5ddbaa7..90563495b 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/VActivityManagerService.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/am/VActivityManagerService.java
@@ -4,6 +4,7 @@
 import android.app.IServiceConnection;
 import android.app.IStopUserCallback;
 import android.app.Notification;
+import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,9 +30,11 @@
 
 import com.lody.virtual.client.IVClient;
 import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.env.Constants;
 import com.lody.virtual.client.env.SpecialComponentList;
 import com.lody.virtual.client.ipc.ProviderCall;
-import com.lody.virtual.client.stub.StubManifest;
+import com.lody.virtual.client.ipc.VNotificationManager;
+import com.lody.virtual.client.stub.VASettings;
 import com.lody.virtual.helper.collection.ArrayMap;
 import com.lody.virtual.helper.collection.SparseArray;
 import com.lody.virtual.helper.compat.ActivityManagerCompat;
@@ -43,6 +46,7 @@
 import com.lody.virtual.os.VBinder;
 import com.lody.virtual.os.VUserHandle;
 import com.lody.virtual.remote.AppTaskInfo;
+import com.lody.virtual.remote.BadgerInfo;
 import com.lody.virtual.remote.PendingIntentData;
 import com.lody.virtual.remote.PendingResultData;
 import com.lody.virtual.remote.VParceledListSlice;
@@ -55,14 +59,15 @@
 import com.lody.virtual.server.secondary.BinderDelegateService;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
+import mirror.android.app.IServiceConnectionO;
+
 import static android.os.Process.killProcess;
 import static com.lody.virtual.os.VUserHandle.getUserId;
 
@@ -71,19 +76,19 @@
  */
 public class VActivityManagerService extends IActivityManager.Stub {
 
-    private static final boolean BROADCAST_NOT_STARTED_PKG = true;
+    private static final boolean BROADCAST_NOT_STARTED_PKG = false;
 
     private static final AtomicReference<VActivityManagerService> sService = new AtomicReference<>();
     private static final String TAG = VActivityManagerService.class.getSimpleName();
     private final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>();
     private final ActivityStack mMainStack = new ActivityStack(this);
     private final Set<ServiceRecord> mHistory = new HashSet<ServiceRecord>();
-    final ArrayMap<IBinder, ArrayList<ConnectionRecord>> mServiceConnections
-            = new ArrayMap<IBinder, ArrayList<ConnectionRecord>>();
     private final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>();
     private final PendingIntents mPendingIntents = new PendingIntents();
     private ActivityManager am = (ActivityManager) VirtualCore.get().getContext()
             .getSystemService(Context.ACTIVITY_SERVICE);
+    private NotificationManager nm = (NotificationManager) VirtualCore.get().getContext()
+            .getSystemService(Context.NOTIFICATION_SERVICE);
 
     public static VActivityManagerService get() {
         return sService.get();
@@ -211,12 +216,12 @@ public ComponentName getActivityClassForToken(int userId, IBinder token) {
     }
 
 
-    public void processDead(ProcessRecord record) {
+    private void processDead(ProcessRecord record) {
         synchronized (mHistory) {
             Iterator<ServiceRecord> iterator = mHistory.iterator();
             while (iterator.hasNext()) {
                 ServiceRecord r = iterator.next();
-                if (r.process.pid == record.pid) {
+                if (r.process != null && r.process.pid == record.pid) {
                     iterator.remove();
                 }
             }
@@ -239,7 +244,7 @@ public IBinder acquireProviderClient(int userId, ProviderInfo info) {
         synchronized (this) {
             r = startProcessIfNeedLocked(processName, userId, info.packageName);
         }
-        if (r != null && r.client.asBinder().isBinderAlive()) {
+        if (r != null && r.client.asBinder().pingBinder()) {
             try {
                 return r.client.acquireProviderClient(info);
             } catch (RemoteException e) {
@@ -299,6 +304,7 @@ private ServiceRecord findRecordLocked(IServiceConnection connection) {
         }
     }
 
+
     @Override
     public ComponentName startService(IBinder caller, Intent service, String resolvedType, int userId) {
         synchronized (this) {
@@ -306,41 +312,6 @@ public ComponentName startService(IBinder caller, Intent service, String resolve
         }
     }
 
-    // For the pending bind(with the flag that is not BIND_AUTO_CREATE)
-    private final void requestServiceBindingsLocked(ServiceRecord r) {
-        if (r.bindings == null) {
-            return;
-        }
-        for (ServiceRecord.IntentBindRecord record : r.bindings.values()) {
-            if (!requestServiceBindingLocked(r, record, false)) {
-                break;
-            }
-        }
-    }
-
-    private final boolean requestServiceBindingLocked(ServiceRecord r,
-                                                      ServiceRecord.IntentBindRecord i,
-                                                      boolean rebind) {
-        if (r.process == null || r.process.appThread == null) {
-            // If service is not currently running, can't yet bind.
-            return false;
-        }
-        if ((!i.requested || rebind) && i.apps.size() > 0) {
-            try {
-                IApplicationThreadCompat.scheduleBindService(r.process.appThread, r,
-                        i.intent, rebind, 0);
-                if (!rebind) {
-                    i.requested = true;
-                }
-                i.hasBound = true;
-                i.doRebind = false;
-            } catch (RemoteException e) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     private ComponentName startServiceCommon(Intent service,
                                              boolean scheduleServiceArgs, int userId) {
         ServiceInfo serviceInfo = resolveServiceInfo(service, userId);
@@ -357,37 +328,19 @@ private ComponentName startServiceCommon(Intent service,
         }
         IInterface appThread = targetApp.appThread;
         ServiceRecord r = findRecordLocked(userId, serviceInfo);
-        boolean needCreateService = false;
         if (r == null) {
             r = new ServiceRecord();
-            r.name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
             r.startId = 0;
             r.activeSince = SystemClock.elapsedRealtime();
             r.process = targetApp;
             r.serviceInfo = serviceInfo;
-            needCreateService = true;
-        } else {
-            if (r.process == null) {
-                r.process = targetApp;
-                needCreateService = true;
-            }
-        }
-
-        if (needCreateService) {
             try {
                 IApplicationThreadCompat.scheduleCreateService(appThread, r, r.serviceInfo, 0);
             } catch (RemoteException e) {
                 e.printStackTrace();
             }
-
-            // Note: If the service has been called for not AUTO_CREATE binding, the corresponding
-            // ServiceRecord is already in mHistory, so we use Set to replace List to avoid add
-            // ServiceRecord twice
             addRecord(r);
-
-            requestServiceBindingsLocked(r);
         }
-
         r.lastActivityTime = SystemClock.uptimeMillis();
         if (scheduleServiceArgs) {
             r.startId++;
@@ -413,8 +366,7 @@ public int stopService(IBinder caller, Intent service, String resolvedType, int
             if (r == null) {
                 return 0;
             }
-
-            stopServiceCommon(r);
+            stopServiceCommon(r, ComponentUtils.toComponentName(serviceInfo));
             return 1;
         }
     }
@@ -424,95 +376,42 @@ public boolean stopServiceToken(ComponentName className, IBinder token, int star
         synchronized (this) {
             ServiceRecord r = (ServiceRecord) token;
             if (r != null && (r.startId == startId || startId == -1)) {
-                stopServiceCommon(r);
+                stopServiceCommon(r, className);
                 return true;
             }
+
             return false;
         }
     }
 
-    /**
-     * Extracting common method of stopService(see bringDownServiceIfNeededLocked in android source)
-     * @param r
-     */
-    private void stopServiceCommon(ServiceRecord r) {
-        if (r.hasAutoCreateConnections()) {
-            return;
-        }
-
-        // Report to all of the connections that the service is no longer
-        // available.
-        if (r.connections != null && r.connections.values() != null) {
-            Iterator<ArrayList<ConnectionRecord>> crs = r.connections.values().iterator();
-            while (crs.hasNext()) {
-                ArrayList<ConnectionRecord> c = crs.next();
-                for (int i = 0; i < c.size(); i++) {
-                    ConnectionRecord cr = c.get(i);
-                    // There is still a connection to the service that is
-                    // being brought down.  Mark it as dead.
-                    cr.serviceDead = true;
-                    try {
-                        cr.conn.connected(r.name, null);
-                    } catch (Exception e) {
-
+    private void stopServiceCommon(ServiceRecord r, ComponentName className) {
+        for (ServiceRecord.IntentBindRecord bindRecord : r.bindings) {
+            for (IServiceConnection connection : bindRecord.connections) {
+                // Report to all of the connections that the service is no longer
+                // available.
+                try {
+                    if(Build.VERSION.SDK_INT >= 26) {
+                        IServiceConnectionO.connected.call(connection, className, null, true);
+                    } else {
+                        connection.connected(className, null);
                     }
+                } catch (RemoteException e) {
+                    e.printStackTrace();
                 }
             }
-        }
-
-        // Tell the service that it has been unbound.
-        if (r.process != null && r.process.appThread != null) {
-            Set<Map.Entry<Intent.FilterComparison, ServiceRecord.IntentBindRecord>> entrySet
-                    = r.bindings.entrySet();
-            for (Map.Entry<Intent.FilterComparison, ServiceRecord.IntentBindRecord> entry
-                    : entrySet) {
-                ServiceRecord.IntentBindRecord ibr = entry.getValue();
-                if (ibr.hasBound) {
-                    try {
-                        ibr.hasBound = false;
-                        IApplicationThreadCompat.scheduleUnbindService(r.process.appThread,
-                                r, ibr.intent);
-                    } catch (Exception e) {
-
-                    }
-                }
+            try {
+                IApplicationThreadCompat.scheduleUnbindService(r.process.appThread, r, bindRecord.intent);
+            } catch (RemoteException e) {
+                e.printStackTrace();
             }
         }
-
         try {
             IApplicationThreadCompat.scheduleStopService(r.process.appThread, r);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
         mHistory.remove(r);
-    }
-
-    @Override
-    public void setServiceForeground(ComponentName className, IBinder token, int id, Notification notification,
-                                     boolean keepNotification, int userId) {
-
-    }
 
-    final ProcessRecord getRecordForAppLocked(IBinder caller, int userId) {
-        synchronized (mProcessNames) {
-            ArrayMap<String, SparseArray<ProcessRecord>> map = mProcessNames.getMap();
-            int N = map.size();
-            while (N-- > 0) {
-                SparseArray<ProcessRecord> uids = map.valueAt(N);
-                for (int i = 0; i < uids.size(); i++) {
-                    ProcessRecord r = uids.valueAt(i);
-                    if (userId != VUserHandle.USER_ALL) {
-                        if (r.userId != userId) {
-                            continue;
-                        }
-                    }
-                    if (caller == r.appThread.asBinder()) {
-                        return r;
-                    }
-                }
-            }
-        }
-        return null;
     }
 
     @Override
@@ -531,44 +430,12 @@ public int bindService(IBinder caller, IBinder token, Intent service, String res
                     r = findRecordLocked(userId, serviceInfo);
                 }
             }
-            boolean canBind = true;
             if (r == null) {
-                r = new ServiceRecord();
-                r.name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
-                r.startId = 0;
-                r.activeSince = SystemClock.elapsedRealtime();
-                r.serviceInfo = serviceInfo;
-                ServiceRecord.IntentBindRecord intentBindRecord
-                        = r.retrieveIntentBindRecord(service);
-                intentBindRecord.addConnection(connection);
-                addRecord(r);
-                canBind = false;
-            }
-            ProcessRecord processRecord = getRecordForAppLocked(caller, userId);
-            AppBindRecord b = r.retrieveAppBindingLocked(service, processRecord);
-            ConnectionRecord c = new ConnectionRecord(b, connection, flags);
-            IBinder binder = connection.asBinder();
-            ArrayList<ConnectionRecord> clist = r.connections.get(binder);
-            if (clist == null) {
-                clist = new ArrayList<ConnectionRecord>();
-                r.connections.put(binder, clist);
-            }
-            clist.add(c);
-            b.connections.add(c);
-
-            clist = mServiceConnections.get(binder);
-            if (clist == null) {
-                clist = new ArrayList<ConnectionRecord>();
-                mServiceConnections.put(binder, clist);
-            }
-            clist.add(c);
-
-            if (!canBind) {
                 return 0;
             }
             ServiceRecord.IntentBindRecord boundRecord = r.peekBinding(service);
 
-            if (boundRecord != null && boundRecord.binder != null && boundRecord.binder.isBinderAlive()) {
+            if (boundRecord != null && boundRecord.binder != null && boundRecord.binder.pingBinder()) {
                 if (boundRecord.doRebind) {
                     try {
                         IApplicationThreadCompat.scheduleBindService(r.process.appThread, r, service, true, 0);
@@ -577,7 +444,7 @@ public int bindService(IBinder caller, IBinder token, Intent service, String res
                     }
                 }
                 ComponentName componentName = new ComponentName(r.serviceInfo.packageName, r.serviceInfo.name);
-                connectService(connection, componentName, boundRecord);
+                connectService(connection, componentName, boundRecord, false);
             } else {
                 try {
                     IApplicationThreadCompat.scheduleBindService(r.process.appThread, r, service, false, 0);
@@ -591,61 +458,39 @@ public int bindService(IBinder caller, IBinder token, Intent service, String res
         }
     }
 
-    void removeConnectionLocked(
-            ConnectionRecord c) {
-        IBinder binder = c.conn.asBinder();
-        AppBindRecord b = c.binding;
-        ServiceRecord s = b.service;
-        ArrayList<ConnectionRecord> clist = s.connections.get(binder);
-        if (clist != null) {
-            clist.remove(c);
-            if (clist.size() == 0) {
-                s.connections.remove(binder);
-            }
-        }
-        b.connections.remove(c);
-        clist = mServiceConnections.get(binder);
-        if (clist != null) {
-            clist.remove(c);
-            if (clist.size() == 0) {
-                mServiceConnections.remove(binder);
-            }
-        }
-
-        if (b.connections.size() == 0) {
-            b.intent.apps.remove(b.client);
-        }
-
-        b.intent.removeConnection(c.conn);
 
-        ServiceRecord r = findRecordLocked(c.conn);
-        if (r == null) {
-            return;
-        }
+    @Override
+    public boolean unbindService(IServiceConnection connection, int userId) {
+        synchronized (this) {
+            ServiceRecord r = findRecordLocked(connection);
+            if (r == null) {
+                return false;
+            }
 
-        if (!c.serviceDead) {
-            try {
-                IApplicationThreadCompat.scheduleUnbindService(r.process.appThread, r, b.intent.intent);
-            } catch (RemoteException e) {
-                e.printStackTrace();
+            for (ServiceRecord.IntentBindRecord bindRecord : r.bindings) {
+                if (!bindRecord.containConnection(connection)) {
+                    continue;
+                }
+                bindRecord.removeConnection(connection);
+                try {
+                    IApplicationThreadCompat.scheduleUnbindService(r.process.appThread, r, bindRecord.intent);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
             }
-        }
-    }
 
-    @Override
-    public boolean unbindService(IServiceConnection connection, int userId) {
-        IBinder binder = connection.asBinder();
-        ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
-        if (clist == null) {
-            return false;
-        } else {
-            while (clist.size() > 0) {
-                ConnectionRecord r = clist.get(0);
-                removeConnectionLocked(r);
+            if (r.startId <= 0 && r.getConnectionCount() <= 0) {
+                try {
+                    IApplicationThreadCompat.scheduleStopService(r.process.appThread, r);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+                    mHistory.remove(r);
+                }
             }
+            return true;
         }
-
-        return true;
     }
 
     @Override
@@ -709,17 +554,21 @@ public void publishService(IBinder token, Intent intent, IBinder service, int us
                     boundRecord.binder = service;
                     for (IServiceConnection conn : boundRecord.connections) {
                         ComponentName component = ComponentUtils.toComponentName(r.serviceInfo);
-                        connectService(conn, component, boundRecord);
+                        connectService(conn, component, boundRecord, false);
                     }
                 }
             }
         }
     }
 
-    private void connectService(IServiceConnection conn, ComponentName component, ServiceRecord.IntentBindRecord r) {
+    private void connectService(IServiceConnection conn, ComponentName component, ServiceRecord.IntentBindRecord r,boolean dead) {
         try {
             BinderDelegateService delegateService = new BinderDelegateService(component, r.binder);
-            conn.connected(component, delegateService);
+            if (Build.VERSION.SDK_INT >= 26) {
+                IServiceConnectionO.connected.call(conn, component, delegateService, dead);
+            } else {
+                conn.connected(component, delegateService);
+            }
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -752,6 +601,51 @@ public VParceledListSlice<ActivityManager.RunningServiceInfo> getServices(int ma
         }
     }
 
+    @Override
+    public void setServiceForeground(ComponentName className, IBinder token, int id, Notification notification,
+                                     boolean removeNotification, int userId) {
+        ServiceRecord r = (ServiceRecord) token;
+        if (r != null) {
+            if (id != 0) {
+                if (notification == null) {
+                    throw new IllegalArgumentException("null notification");
+                }
+                if (r.foregroundId != id) {
+                    if (r.foregroundId != 0) {
+                        cancelNotification(userId, r.foregroundId, r.serviceInfo.packageName);
+                    }
+                    r.foregroundId = id;
+                }
+                r.foregroundNoti = notification;
+                postNotification(userId, id, r.serviceInfo.packageName, notification);
+            } else {
+                if (removeNotification) {
+                    cancelNotification(userId, r.foregroundId, r.serviceInfo.packageName);
+                    r.foregroundId = 0;
+                    r.foregroundNoti = null;
+                }
+            }
+        }
+    }
+
+    private void cancelNotification(int userId, int id, String pkg) {
+        id = VNotificationManager.get().dealNotificationId(id, pkg, null, userId);
+        String tag = VNotificationManager.get().dealNotificationTag(id, pkg, null, userId);
+        nm.cancel(tag, id);
+    }
+
+    private void postNotification(int userId, int id, String pkg, Notification notification) {
+        id = VNotificationManager.get().dealNotificationId(id, pkg, null, userId);
+        String tag = VNotificationManager.get().dealNotificationTag(id, pkg, null, userId);
+//        VNotificationManager.get().dealNotification(id, notification, pkg);
+        VNotificationManager.get().addNotification(id, tag, pkg, userId);
+        try {
+            nm.notify(tag, id, notification);
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+    }
+
     @Override
     public void processRestarted(String packageName, String processName, int userId) {
         int callingPid = getCallingPid();
@@ -777,7 +671,7 @@ private int parseVPid(String stubProcessName) {
             try {
                 return Integer.parseInt(stubProcessName.substring(prefix.length()));
             } catch (NumberFormatException e) {
-                e.printStackTrace();
+                // ignore
             }
         }
         return -1;
@@ -853,7 +747,7 @@ private void onProcessDead(ProcessRecord record) {
 
     @Override
     public int getFreeStubCount() {
-        return StubManifest.STUB_COUNT - mPidsSelfLocked.size();
+        return VASettings.STUB_COUNT - mPidsSelfLocked.size();
     }
 
     @Override
@@ -881,7 +775,7 @@ ProcessRecord startProcessIfNeedLocked(String processName, int userId, String pa
         }
         int uid = VUserHandle.getUid(userId, ps.appId);
         ProcessRecord app = mProcessNames.get(processName, uid);
-        if (app != null && app.client.asBinder().isBinderAlive()) {
+        if (app != null && app.client.asBinder().pingBinder()) {
             return app;
         }
         int vpid = queryFreeStubProcessLocked();
@@ -922,7 +816,7 @@ private ProcessRecord performStartProcessLocked(int vuid, int vpid, ApplicationI
         extras.putInt("_VA_|_vuid_", vuid);
         extras.putString("_VA_|_process_", processName);
         extras.putString("_VA_|_pkg_", info.packageName);
-        Bundle res = ProviderCall.call(StubManifest.getStubAuthority(vpid), "_VA_|_init_process_", null, extras);
+        Bundle res = ProviderCall.call(VASettings.getStubAuthority(vpid), "_VA_|_init_process_", null, extras);
         if (res == null) {
             return null;
         }
@@ -933,7 +827,7 @@ private ProcessRecord performStartProcessLocked(int vuid, int vpid, ApplicationI
     }
 
     private int queryFreeStubProcessLocked() {
-        for (int vpid = 0; vpid < StubManifest.STUB_COUNT; vpid++) {
+        for (int vpid = 0; vpid < VASettings.STUB_COUNT; vpid++) {
             int N = mPidsSelfLocked.size();
             boolean using = false;
             while (N-- > 0) {
@@ -979,10 +873,10 @@ public List<String> getProcessPkgList(int pid) {
         synchronized (mPidsSelfLocked) {
             ProcessRecord r = mPidsSelfLocked.get(pid);
             if (r != null) {
-                return new ArrayList<String>(r.pkgList);
+                return new ArrayList<>(r.pkgList);
             }
         }
-        return null;
+        return Collections.emptyList();
     }
 
     @Override
@@ -1195,7 +1089,8 @@ private void handleStaticBroadcastAsUser(int vuid, ActivityInfo info, Intent int
                                              PendingResultData result) {
         synchronized (this) {
             ProcessRecord r = findProcessLocked(info.processName, vuid);
-            if (BROADCAST_NOT_STARTED_PKG && r == null) {
+            if ((BROADCAST_NOT_STARTED_PKG || isStartProcessForBroadcast(info.processName, info.packageName))
+                    && r == null) {
                 r = startProcessIfNeedLocked(info.processName, getUserId(vuid), info.packageName);
             }
             if (r != null && r.appThread != null) {
@@ -1205,6 +1100,10 @@ private void handleStaticBroadcastAsUser(int vuid, ActivityInfo info, Intent int
         }
     }
 
+    private static boolean isStartProcessForBroadcast(String processName, String packageName) {
+        return Constants.PRIVILEGE_APP.contains(packageName);
+    }
+
     private void performScheduleReceiver(IVClient client, int vuid, ActivityInfo info, Intent intent,
                                          PendingResultData result) {
 
@@ -1223,4 +1122,13 @@ private void performScheduleReceiver(IVClient client, int vuid, ActivityInfo inf
     public void broadcastFinish(PendingResultData res) {
         BroadcastSystem.get().broadcastFinish(res);
     }
+
+    @Override
+    public void notifyBadgerChange(BadgerInfo info) throws RemoteException {
+        Intent intent = new Intent(VASettings.ACTION_BADGER_CHANGE);
+        intent.putExtra("userId", info.userId);
+        intent.putExtra("packageName", info.packageName);
+        intent.putExtra("badgerCount", info.badgerCount);
+        VirtualCore.get().getContext().sendBroadcast(intent);
+    }
 }
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/device/DeviceInfoPersistenceLayer.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/device/DeviceInfoPersistenceLayer.java
new file mode 100644
index 000000000..6319fa4c8
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/device/DeviceInfoPersistenceLayer.java
@@ -0,0 +1,72 @@
+package com.lody.virtual.server.device;
+
+import android.os.Parcel;
+
+import com.lody.virtual.helper.PersistenceLayer;
+import com.lody.virtual.helper.collection.SparseArray;
+import com.lody.virtual.os.VEnvironment;
+import com.lody.virtual.remote.VDeviceInfo;
+
+/**
+ * @author Lody
+ */
+
+public class DeviceInfoPersistenceLayer extends PersistenceLayer {
+
+    private VDeviceManagerService mService;
+
+    public DeviceInfoPersistenceLayer(VDeviceManagerService service) {
+        super(VEnvironment.getDeviceInfoFile());
+        this.mService = service;
+    }
+
+    @Override
+    public int getCurrentVersion() {
+        return 1;
+    }
+
+    @Override
+    public void writeMagic(Parcel p) {
+
+    }
+
+    @Override
+    public boolean verifyMagic(Parcel p) {
+        return true;
+    }
+
+    @Override
+    public void writePersistenceData(Parcel p) {
+        SparseArray<VDeviceInfo> infos = mService.getDeviceInfos();
+        int size = infos.size();
+        p.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            int userId = infos.keyAt(i);
+            VDeviceInfo info = infos.valueAt(i);
+            p.writeInt(userId);
+            info.writeToParcel(p, 0);
+        }
+    }
+
+    @Override
+    public void readPersistenceData(Parcel p) {
+        SparseArray<VDeviceInfo> infos = mService.getDeviceInfos();
+        infos.clear();
+        int size = p.readInt();
+        while (size-- > 0) {
+            int userId = p.readInt();
+            VDeviceInfo info = new VDeviceInfo(p);
+            infos.put(userId, info);
+        }
+    }
+
+    @Override
+    public boolean onVersionConflict(int fileVersion, int currentVersion) {
+        return false;
+    }
+
+    @Override
+    public void onPersistenceFileDamage() {
+        getPersistenceFile().delete();
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/device/VDeviceManagerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/device/VDeviceManagerService.java
new file mode 100644
index 000000000..cf5407e37
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/device/VDeviceManagerService.java
@@ -0,0 +1,211 @@
+package com.lody.virtual.server.device;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Build;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+
+import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.helper.collection.SparseArray;
+import com.lody.virtual.remote.VDeviceInfo;
+import com.lody.virtual.server.IDeviceInfoManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * @author Lody
+ */
+
+public class VDeviceManagerService extends IDeviceInfoManager.Stub {
+
+    private static VDeviceManagerService sInstance = new VDeviceManagerService();
+    private final SparseArray<VDeviceInfo> mDeviceInfos = new SparseArray<>();
+    private DeviceInfoPersistenceLayer mPersistenceLayer = new DeviceInfoPersistenceLayer(this);
+    private UsedDeviceInfoPool mPool = new UsedDeviceInfoPool();
+
+    public static VDeviceManagerService get() {
+        return sInstance;
+    }
+
+    private final class UsedDeviceInfoPool {
+        List<String> deviceIds = new ArrayList<>();
+        List<String> androidIds = new ArrayList<>();
+        List<String> wifiMacs = new ArrayList<>();
+        List<String> bluetoothMacs = new ArrayList<>();
+        List<String> iccIds = new ArrayList<>();
+    }
+
+    public VDeviceManagerService() {
+        mPersistenceLayer.read();
+        for (int i = 0; i < mDeviceInfos.size(); i++) {
+            VDeviceInfo info = mDeviceInfos.valueAt(i);
+            addDeviceInfoToPool(info);
+        }
+    }
+
+    private void addDeviceInfoToPool(VDeviceInfo info) {
+        mPool.deviceIds.add(info.deviceId);
+        mPool.androidIds.add(info.androidId);
+        mPool.wifiMacs.add(info.wifiMac);
+        mPool.bluetoothMacs.add(info.bluetoothMac);
+        mPool.iccIds.add(info.iccId);
+    }
+
+    @Override
+    public VDeviceInfo getDeviceInfo(int userId) throws RemoteException {
+        VDeviceInfo info;
+        synchronized (mDeviceInfos) {
+            info = mDeviceInfos.get(userId);
+            if (info == null) {
+                info = generateDeviceInfo();
+                mDeviceInfos.put(userId, info);
+                mPersistenceLayer.save();
+            }
+        }
+        return info;
+    }
+
+    @Override
+    public void updateDeviceInfo(int userId, VDeviceInfo info) throws RemoteException {
+        synchronized (mDeviceInfos) {
+            if (info != null) {
+                mDeviceInfos.put(userId, info);
+                mPersistenceLayer.save();
+            }
+        }
+    }
+
+    private VDeviceInfo generateRandomDeviceInfo() {
+        VDeviceInfo info = new VDeviceInfo();
+        String value;
+        do {
+            value = generate10(15);
+            info.deviceId = value;
+        } while (mPool.deviceIds.contains(value));
+        do {
+            value = generate16(16);
+            info.androidId = value;
+        } while (mPool.androidIds.contains(value));
+        do {
+            value = generateMac();
+            info.wifiMac = value;
+        } while (mPool.wifiMacs.contains(value));
+        do {
+            value = generateMac();
+            info.bluetoothMac = value;
+        } while (mPool.bluetoothMacs.contains(value));
+
+        do {
+            value = generate10(20);
+            info.iccId = value;
+        } while (mPool.iccIds.contains(value));
+
+        info.serial = generateSerial();
+
+        addDeviceInfoToPool(info);
+        return info;
+    }
+
+    @SuppressLint("HardwareIds")
+    private VDeviceInfo generateDeviceInfo() {
+        VDeviceInfo info = generateRandomDeviceInfo();
+        Context context = VirtualCore.get().getContext();
+        if (context == null) {
+            return info;
+        }
+
+        try {
+            String deviceId = null;
+            final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+            if (tm != null) {
+                deviceId = tm.getDeviceId();
+            }
+            if (deviceId != null) {
+                info.deviceId = deviceId;
+            }
+
+            String android_id = Settings.System.getString(context.getContentResolver(), Settings.System.ANDROID_ID);
+            if (android_id != null) {
+                info.androidId = android_id;
+            }
+
+            info.serial = Build.SERIAL;
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+        return info;
+    }
+
+    SparseArray<VDeviceInfo> getDeviceInfos() {
+        return mDeviceInfos;
+    }
+
+    private static String generate10(int length) {
+        Random random = new Random();
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            sb.append(random.nextInt(10));
+        }
+        return sb.toString();
+    }
+
+    private static String generate16(int length) {
+        Random random = new Random();
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            int nextInt = random.nextInt(16);
+            if (nextInt < 10) {
+                sb.append(nextInt);
+            } else {
+                sb.append((char) (nextInt + 87));
+            }
+        }
+        return sb.toString();
+    }
+
+    private static String generateMac() {
+        Random random = new Random();
+        StringBuilder sb = new StringBuilder();
+        int next = 1;
+        int cur = 0;
+        while (cur < 12) {
+            int val = random.nextInt(16);
+            if (val < 10) {
+                sb.append(val);
+            } else {
+                sb.append((char) (val + 87));
+            }
+            if (cur == next && cur != 11) {
+                sb.append(":");
+                next += 2;
+            }
+            cur++;
+        }
+        return sb.toString();
+    }
+
+    @SuppressLint("HardwareIds")
+    private static String generateSerial() {
+        String serial;
+        if (Build.SERIAL == null || Build.SERIAL.length() <= 0) {
+            serial = "0123456789ABCDEF";
+        } else {
+            serial = Build.SERIAL;
+        }
+        List<Character> list = new ArrayList<>();
+        for (char c : serial.toCharArray()) {
+            list.add(c);
+        }
+        Collections.shuffle(list);
+        StringBuilder sb = new StringBuilder();
+        for (Character c : list) {
+            sb.append(c.charValue());
+        }
+        return sb.toString();
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/job/VJobSchedulerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/job/VJobSchedulerService.java
index 6f6743acb..ebd871412 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/job/VJobSchedulerService.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/job/VJobSchedulerService.java
@@ -3,6 +3,7 @@
 import android.annotation.TargetApi;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
+import android.app.job.JobWorkItem;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Build;
@@ -14,7 +15,7 @@
 
 import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.ipc.VJobScheduler;
-import com.lody.virtual.client.stub.StubManifest;
+import com.lody.virtual.client.stub.VASettings;
 import com.lody.virtual.helper.utils.Singleton;
 import com.lody.virtual.os.VBinder;
 import com.lody.virtual.os.VEnvironment;
@@ -48,7 +49,7 @@ public class VJobSchedulerService extends IJobScheduler.Stub {
     private final ComponentName mJobProxyComponent;
 
     private VJobSchedulerService() {
-        mJobProxyComponent = new ComponentName(VirtualCore.get().getHostPkg(), StubManifest.STUB_JOB);
+        mJobProxyComponent = new ComponentName(VirtualCore.get().getHostPkg(), VASettings.STUB_JOB);
         readJobs();
     }
 
@@ -310,7 +311,7 @@ public List<JobInfo> getAllPendingJobs() throws RemoteException {
             Iterator<JobInfo> iterator = jobs.listIterator();
             while (iterator.hasNext()) {
                 JobInfo job = iterator.next();
-                if (!StubManifest.STUB_JOB.equals(job.getService().getClassName())) {
+                if (!VASettings.STUB_JOB.equals(job.getService().getClassName())) {
                     // Schedule by Host, invisible in VA.
                     iterator.remove();
                     continue;
@@ -333,6 +334,16 @@ public List<JobInfo> getAllPendingJobs() throws RemoteException {
         return jobs;
     }
 
+    @Override
+    public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public JobInfo getPendingJob(int i) throws RemoteException {
+        return null;
+    }
+
 
     public Map.Entry<JobId, JobConfig> findJobByVirtualJobId(int virtualJobId) {
         synchronized (mJobStore) {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/location/VirtualLocationService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/location/VirtualLocationService.java
new file mode 100644
index 000000000..dcd387906
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/location/VirtualLocationService.java
@@ -0,0 +1,269 @@
+package com.lody.virtual.server.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import com.lody.virtual.helper.PersistenceLayer;
+import com.lody.virtual.helper.collection.SparseArray;
+import com.lody.virtual.os.VEnvironment;
+import com.lody.virtual.remote.vloc.VCell;
+import com.lody.virtual.remote.vloc.VLocation;
+import com.lody.virtual.server.IVirtualLocationManager;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Lody
+ */
+
+public class VirtualLocationService extends IVirtualLocationManager.Stub {
+
+    private static final VirtualLocationService sInstance = new VirtualLocationService();
+    private final SparseArray<Map<String, VLocConfig>> mLocConfigs = new SparseArray<>();
+    private final VLocConfig mGlobalConfig = new VLocConfig();
+
+    private static final int MODE_CLOSE = 0;
+    private static final int MODE_USE_GLOBAL = 1;
+    private static final int MODE_USE_SELF = 2;
+
+    private static class VLocConfig implements Parcelable {
+        int mode;
+        VCell cell;
+        List<VCell> allCell;
+        List<VCell> neighboringCell;
+        VLocation location;
+
+        public void set(VLocConfig other) {
+            this.mode = other.mode;
+            this.cell = other.cell;
+            this.allCell = other.allCell;
+            this.neighboringCell = other.neighboringCell;
+            this.location = other.location;
+        }
+
+        VLocConfig() {
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(this.mode);
+            dest.writeParcelable(this.cell, flags);
+            dest.writeTypedList(this.allCell);
+            dest.writeTypedList(this.neighboringCell);
+            dest.writeParcelable(this.location, flags);
+        }
+
+        VLocConfig(Parcel in) {
+            this.mode = in.readInt();
+            this.cell = in.readParcelable(VCell.class.getClassLoader());
+            this.allCell = in.createTypedArrayList(VCell.CREATOR);
+            this.neighboringCell = in.createTypedArrayList(VCell.CREATOR);
+            this.location = in.readParcelable(VLocation.class.getClassLoader());
+        }
+
+        public static final Creator<VLocConfig> CREATOR = new Creator<VLocConfig>() {
+            @Override
+            public VLocConfig createFromParcel(Parcel source) {
+                return new VLocConfig(source);
+            }
+
+            @Override
+            public VLocConfig[] newArray(int size) {
+                return new VLocConfig[size];
+            }
+        };
+    }
+
+    private final PersistenceLayer mPersistenceLayer = new PersistenceLayer(VEnvironment.getVirtualLocationFile()) {
+        @Override
+        public int getCurrentVersion() {
+            return 1;
+        }
+
+        @Override
+        public void writePersistenceData(Parcel p) {
+            mGlobalConfig.writeToParcel(p, 0);
+            p.writeInt(mLocConfigs.size());
+            for (int i = 0; i < mLocConfigs.size(); i++) {
+                int userId = mLocConfigs.keyAt(i);
+                Map<String, VLocConfig> pkgs = mLocConfigs.valueAt(i);
+                p.writeInt(userId);
+                p.writeMap(pkgs);
+            }
+        }
+
+        @Override
+        public void readPersistenceData(Parcel p) {
+            mGlobalConfig.set(new VLocConfig(p));
+            mLocConfigs.clear();
+            int size = p.readInt();
+            while (size-- > 0) {
+                int userId = p.readInt();
+                //noinspection unchecked
+                Map<String, VLocConfig> pkgs = p.readHashMap(getClass().getClassLoader());
+                mLocConfigs.put(userId, pkgs);
+            }
+        }
+    };
+
+    public static VirtualLocationService get() {
+        return sInstance;
+    }
+
+    private VirtualLocationService() {
+        mPersistenceLayer.read();
+    }
+
+    @Override
+    public int getMode(int userId, String pkg) throws RemoteException {
+        synchronized (mLocConfigs) {
+            VLocConfig config = getOrCreateConfig(userId, pkg);
+            mPersistenceLayer.save();
+            return config.mode;
+        }
+    }
+
+    @Override
+    public void setMode(int userId, String pkg, int mode) throws RemoteException {
+        synchronized (mLocConfigs) {
+            getOrCreateConfig(userId, pkg).mode = mode;
+            mPersistenceLayer.save();
+        }
+    }
+
+    private VLocConfig getOrCreateConfig(int userId, String pkg) {
+        Map<String, VLocConfig> pkgs = mLocConfigs.get(userId);
+        if (pkgs == null) {
+            pkgs = new HashMap<>();
+            mLocConfigs.put(userId, pkgs);
+        }
+        VLocConfig config = pkgs.get(pkg);
+        if (config == null) {
+            config = new VLocConfig();
+            config.mode = MODE_CLOSE;
+            pkgs.put(pkg, config);
+        }
+        return config;
+    }
+
+    @Override
+    public void setCell(int userId, String pkg, VCell cell) throws RemoteException {
+        getOrCreateConfig(userId, pkg).cell = cell;
+        mPersistenceLayer.save();
+    }
+
+    @Override
+    public void setAllCell(int userId, String pkg, List<VCell> cell) throws RemoteException {
+        getOrCreateConfig(userId, pkg).allCell = cell;
+        mPersistenceLayer.save();
+    }
+
+    @Override
+    public void setNeighboringCell(int userId, String pkg, List<VCell> cell) throws RemoteException {
+        getOrCreateConfig(userId, pkg).neighboringCell = cell;
+        mPersistenceLayer.save();
+    }
+
+    @Override
+    public void setGlobalCell(VCell cell) throws RemoteException {
+        mGlobalConfig.cell = cell;
+        mPersistenceLayer.save();
+    }
+
+    @Override
+    public void setGlobalAllCell(List<VCell> cell) throws RemoteException {
+        mGlobalConfig.allCell = cell;
+        mPersistenceLayer.save();
+    }
+
+    @Override
+    public void setGlobalNeighboringCell(List<VCell> cell) throws RemoteException {
+        mGlobalConfig.neighboringCell = cell;
+        mPersistenceLayer.save();
+    }
+
+    @Override
+    public VCell getCell(int userId, String pkg) throws RemoteException {
+        VLocConfig config = getOrCreateConfig(userId, pkg);
+        mPersistenceLayer.save();
+        switch (config.mode) {
+            case MODE_USE_SELF:
+                return config.cell;
+            case MODE_USE_GLOBAL:
+                return mGlobalConfig.cell;
+            case MODE_CLOSE:
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public List<VCell> getAllCell(int userId, String pkg) throws RemoteException {
+        VLocConfig config = getOrCreateConfig(userId, pkg);
+        mPersistenceLayer.save();
+        switch (config.mode) {
+            case MODE_USE_SELF:
+                return config.allCell;
+            case MODE_USE_GLOBAL:
+                return mGlobalConfig.allCell;
+            case MODE_CLOSE:
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public List<VCell> getNeighboringCell(int userId, String pkg) throws RemoteException {
+        VLocConfig config = getOrCreateConfig(userId, pkg);
+        mPersistenceLayer.save();
+        switch (config.mode) {
+            case MODE_USE_SELF:
+                return config.neighboringCell;
+            case MODE_USE_GLOBAL:
+                return mGlobalConfig.neighboringCell;
+            case MODE_CLOSE:
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public void setLocation(int userId, String pkg, VLocation loc) throws RemoteException {
+        getOrCreateConfig(userId, pkg).location = loc;
+        mPersistenceLayer.save();
+    }
+
+    @Override
+    public VLocation getLocation(int userId, String pkg) throws RemoteException {
+        VLocConfig config = getOrCreateConfig(userId, pkg);
+        mPersistenceLayer.save();
+        switch (config.mode) {
+            case MODE_USE_SELF:
+                return config.location;
+            case MODE_USE_GLOBAL:
+                return mGlobalConfig.location;
+            case MODE_CLOSE:
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public void setGlobalLocation(VLocation loc) throws RemoteException {
+        mGlobalConfig.location = loc;
+    }
+
+    @Override
+    public VLocation getGlobalLocation() throws RemoteException {
+        return mGlobalConfig.location;
+    }
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MappedMemoryRegion.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MappedMemoryRegion.java
new file mode 100644
index 000000000..832707fdc
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MappedMemoryRegion.java
@@ -0,0 +1,47 @@
+package com.lody.virtual.server.memory;
+
+/**
+ * @author Lody
+ */
+public class MappedMemoryRegion {
+
+    public static class FileMapping {
+        public final long offset;
+        public final long majorDeviceNumber;
+        public final long minorDeviceNumber;
+        public final long inode;
+
+        public FileMapping(long off, long major, long minor, long inode) {
+            this.offset = off;
+            this.majorDeviceNumber = major;
+            this.minorDeviceNumber = minor;
+            this.inode = inode;
+        }
+    }
+
+    public final long startAddress;
+    public final long endAddress;
+    public final boolean isReadable;
+    public final boolean isWritable;
+    public final boolean isExecutable;
+    public final boolean isShared;
+
+    public final FileMapping fileMapInfo;
+
+    public final String description;
+
+    public MappedMemoryRegion(long start, long end, boolean read, boolean write, boolean exec, boolean shared, long off, long majorDevNum, long minorDevNum, long inode, String desc) {
+        this.startAddress = start;
+        this.endAddress = end;
+        this.isReadable = read;
+        this.isWritable = write;
+        this.isExecutable = exec;
+        this.isShared = shared;
+        this.fileMapInfo = (inode == 0) ? null : new FileMapping(off, majorDevNum, minorDevNum, inode);
+        this.description = desc;
+    }
+
+    public boolean isMappedFromFile() {
+        return this.fileMapInfo != null;
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MemoryRegionParser.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MemoryRegionParser.java
new file mode 100644
index 000000000..5491db5bd
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MemoryRegionParser.java
@@ -0,0 +1,70 @@
+package com.lody.virtual.server.memory;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Lody
+ */
+public class MemoryRegionParser {
+    /**
+     * Regular Expression for /proc/self/maps line is
+     * <p>
+     * ([0-9a-f]+)  -   ([0-9a-f]+)  \s  ([r-]) ([w-]) ([x-]) ([sp])   \s    ([0-9a-f]+)   \s  ([0-9a-f]+) :   ([0-9a-f]+)     \s  (\d+)   \s?  (.*)
+     * StartAddress        EndAddress    Read   Write  Execute Shared        Filemap Offset   Major devnum    Minor devnum         Inode       Description
+     */
+    public static final String PATTERN = "([0-9a-f]+)-([0-9a-f]+)\\s([r-])([w-])([x-])([sp])\\s([0-9a-f]+)\\s([0-9a-f]+):([0-9a-f]+)\\s(\\d+)\\s?(.*)";
+    public final static Pattern MAPS_LINE_PATTERN = Pattern.compile(PATTERN, Pattern.CASE_INSENSITIVE);
+
+    private static long parseHex(String s) {
+        return Long.parseLong(s, 16);
+    }
+
+    private static MappedMemoryRegion parseMapLine(String line) {
+        line = line.trim();
+        Matcher m = MAPS_LINE_PATTERN.matcher(line);
+        if (!m.matches()) {
+            throw new IllegalArgumentException(String.format("The provided line does not match the pattern for /proc/$pid/maps lines. Given: %s", line));
+        }
+
+        if (m.groupCount() != 11) // group(0) not included in this.
+        {
+            throw new InternalError(String.format(Locale.ENGLISH, "Invalid group count: Found %d, but expected %d", m.groupCount(), 12));
+        }
+
+        long start = parseHex(m.group(1));
+        long end = parseHex(m.group(2));
+        boolean read = m.group(3).equals("r");
+        boolean write = m.group(4).equals("w");
+        boolean exec = m.group(5).equals("x");
+        boolean shared = m.group(6).equals("s");
+        long fileOffset = parseHex(m.group(7));
+        long majorDevNum = parseHex(m.group(8));
+        long minorDevNum = parseHex(m.group(9));
+        long inode = parseHex(m.group(10));
+        String desc = m.group(11);
+
+        return new MappedMemoryRegion(start, end, read, write, exec, shared, fileOffset, majorDevNum, minorDevNum, inode, desc);
+    }
+
+
+    public static List<MappedMemoryRegion> getMemoryRegions(int pid) throws IOException {
+        List<MappedMemoryRegion> list = new LinkedList<>();
+        BufferedReader reader = new BufferedReader(new FileReader(String.format(Locale.ENGLISH, "/proc/%d/maps", pid)));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            MappedMemoryRegion region = parseMapLine(line);
+            if (region.isReadable && region.isWritable && !region.description.endsWith("(deleted)")) {
+                list.add(region);
+            }
+        }
+        return list;
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MemoryScanEngine.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MemoryScanEngine.java
new file mode 100644
index 000000000..c9b21c385
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MemoryScanEngine.java
@@ -0,0 +1,111 @@
+package com.lody.virtual.server.memory;
+
+import com.lody.virtual.helper.utils.VLog;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author Lody
+ */
+public class MemoryScanEngine {
+
+    private List<MappedMemoryRegion> regions;
+
+    private int pid;
+    private ProcessMemory memory;
+    private static final int PAGE = 4096;
+    private List<Match> matches;
+
+    public MemoryScanEngine(int pid) throws IOException {
+        this.pid = pid;
+        this.memory = new ProcessMemory(pid);
+        updateMemoryLayout();
+    }
+
+    public void updateMemoryLayout() {
+        try {
+            regions = MemoryRegionParser.getMemoryRegions(pid);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public List<Match> getMatches() {
+        return matches;
+    }
+
+    public void search(MemoryValue value) throws IOException {
+        matches = new LinkedList<>();
+        byte[] bytes = new byte[PAGE];
+        byte[] valueBytes = value.toBytes();
+        for (MappedMemoryRegion region : regions) {
+            long start = region.startAddress;
+            long end = region.endAddress;
+            try {
+                while (start < end) {
+                    int read = Math.min(bytes.length, (int) (end - start));
+                    read = memory.read(start, bytes, read);
+                    matches.addAll(matchBytes(region, start, bytes, read, valueBytes));
+                    start += PAGE;
+                }
+            } catch (IOException e) {
+                VLog.e(getClass().getSimpleName(), "Unable to read region : " + region.description);
+            }
+        }
+    }
+
+    public void modify(Match match, MemoryValue value) throws IOException {
+        memory.write(match.address, value.toBytes());
+    }
+
+    public void modifyAll(MemoryValue value) throws IOException {
+        for (Match match : matches) {
+            modify(match, value);
+        }
+    }
+
+    public class Match {
+        MappedMemoryRegion region;
+        long address;
+        int len;
+
+        public Match(MappedMemoryRegion region, long address, int len) {
+            this.region = region;
+            this.address = address;
+            this.len = len;
+        }
+    }
+
+
+    private List<Match> matchBytes(MappedMemoryRegion region, long startAddress, byte[] page, int read, byte[] value) {
+        List<Match> matches = new LinkedList<>();
+        int start = 0;
+        int len = value.length;
+        int step = 2;
+        while (start < read) {
+            boolean match = true;
+            for (int i = 0; i < len && i + start < read; i++) {
+                if (page[start + i] != value[i]) {
+                    match = false;
+                    break;
+                }
+            }
+            if (match) {
+                matches.add(new Match(region, startAddress + start, len));
+            }
+            start += step;
+        }
+        return matches;
+    }
+
+
+    public void close() {
+        try {
+            memory.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MemoryValue.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MemoryValue.java
new file mode 100644
index 000000000..59f7cd3c8
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/MemoryValue.java
@@ -0,0 +1,67 @@
+package com.lody.virtual.server.memory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * @author Lody
+ */
+public abstract class MemoryValue {
+    
+    private static final ByteOrder BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+
+    public enum ValueType {
+        INT2, // short
+        INT4, // int
+        INT8, // long
+    }
+
+    public abstract byte[] toBytes();
+
+
+    public static class INT2 extends MemoryValue {
+
+        private short val;
+
+        public INT2(short val) {
+            this.val = val;
+        }
+
+        @Override
+        public byte[] toBytes() {
+            ByteBuffer buffer = ByteBuffer.allocate(2);
+            return buffer.putShort(val).order(BYTE_ORDER).array();
+        }
+    }
+
+    public static class INT4 extends MemoryValue {
+
+        private int val;
+
+        public INT4(int val) {
+            this.val = val;
+        }
+
+        @Override
+        public byte[] toBytes() {
+            ByteBuffer buffer = ByteBuffer.allocate(4);
+            return buffer.order(BYTE_ORDER).putInt(val).array();
+        }
+    }
+
+    public static class INT8 extends MemoryValue {
+
+        private long val;
+
+        public INT8(long val) {
+            this.val = val;
+        }
+
+        @Override
+        public byte[] toBytes() {
+            ByteBuffer buffer = ByteBuffer.allocate(8);
+            return buffer.order(BYTE_ORDER).putLong(val).array();
+        }
+    }
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/ProcessMemory.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/ProcessMemory.java
new file mode 100644
index 000000000..0c86baae5
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/memory/ProcessMemory.java
@@ -0,0 +1,33 @@
+package com.lody.virtual.server.memory;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Locale;
+
+/**
+ * @author Lody
+ */
+public class ProcessMemory {
+
+    private int pid;
+    private RandomAccessFile memFile;
+
+    public ProcessMemory(int pid) throws IOException {
+        this.pid = pid;
+        this.memFile = new RandomAccessFile(String.format(Locale.ENGLISH, "/proc/%d/mem", pid), "rw");
+    }
+
+    public void write(long offset, byte[] bytes) throws IOException {
+        memFile.seek(offset);
+        memFile.write(bytes);
+    }
+
+    public int read(long offset, byte[] bytes, int len) throws IOException {
+        memFile.seek(offset);
+        return memFile.read(bytes, 0, len);
+    }
+
+    public void close() throws IOException {
+        memFile.close();
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationCompatCompatV14.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationCompatCompatV14.java
index 31cc4944d..be5711467 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationCompatCompatV14.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationCompatCompatV14.java
@@ -30,14 +30,17 @@ public boolean dealNotification(int id, Notification notification, final String
             return false;
         }
         if (VirtualCore.get().isOutsideInstalled(packageName)) {
-            getNotificationFixer().fixIconImage(appContext.getResources(), notification.contentView, false, notification);
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-                getNotificationFixer().fixIconImage(appContext.getResources(), notification.bigContentView, false, notification);
+            if(notification.icon != 0) {
+                getNotificationFixer().fixIconImage(appContext.getResources(), notification.contentView, false, notification);
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                    getNotificationFixer().fixIconImage(appContext.getResources(), notification.bigContentView, false, notification);
+                }
+                notification.icon = getHostContext().getApplicationInfo().icon;
             }
-            notification.icon = getHostContext().getApplicationInfo().icon;
             return true;
         }
         if (notification.tickerView != null) {
+
             if (isSystemLayout(notification.tickerView)) {
                 getNotificationFixer().fixRemoteViewActions(appContext, false, notification.tickerView);
             } else {
@@ -75,7 +78,9 @@ public boolean dealNotification(int id, Notification notification, final String
                 }
             }
         }
-        notification.icon = getHostContext().getApplicationInfo().icon;
+        if(notification.icon != 0) {
+            notification.icon = getHostContext().getApplicationInfo().icon;
+        }
         return true;
     }
 
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationCompatCompatV21.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationCompatCompatV21.java
index bfddd6674..3fd8c035e 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationCompatCompatV21.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationCompatCompatV21.java
@@ -1,5 +1,6 @@
 package com.lody.virtual.server.notification;
 
+import android.annotation.TargetApi;
 import android.app.Notification;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -9,6 +10,7 @@
 import android.text.TextUtils;
 import android.widget.RemoteViews;
 
+import com.lody.virtual.helper.compat.SystemPropertiesCompat;
 import com.lody.virtual.helper.utils.Reflect;
 
 import static com.lody.virtual.os.VEnvironment.getPackageResourcePath;
@@ -16,6 +18,7 @@
 /**
  * @author 247321543
  */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 /* package */ class NotificationCompatCompatV21 extends NotificationCompatCompatV14 {
 
     private static final String TAG = NotificationCompatCompatV21.class.getSimpleName();
@@ -26,12 +29,9 @@
 
     @Override
     public boolean dealNotification(int id, Notification notification, String packageName) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            Context appContext = getAppContext(packageName);
-            return resolveRemoteViews(appContext, packageName, notification)
-                    || resolveRemoteViews(appContext, packageName, notification.publicVersion);
-        }
-        return super.dealNotification(id, notification, packageName);
+        Context appContext = getAppContext(packageName);
+        return resolveRemoteViews(appContext, packageName, notification)
+                || resolveRemoteViews(appContext, packageName, notification.publicVersion);
     }
 
     private boolean resolveRemoteViews(Context appContext, String packageName, Notification notification) {
@@ -50,7 +50,6 @@ private boolean resolveRemoteViews(Context appContext, String packageName, Notif
 
         //Fix RemoteViews
         getNotificationFixer().fixNotificationRemoteViews(appContext, notification);
-        //Fix Icon
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             getNotificationFixer().fixIcon(notification.getSmallIcon(), appContext, packageInfo != null);
             getNotificationFixer().fixIcon(notification.getLargeIcon(), appContext, packageInfo != null);
@@ -69,6 +68,7 @@ private boolean resolveRemoteViews(Context appContext, String packageName, Notif
         fixApplicationInfo(notification.contentView, proxyApplicationInfo);
         fixApplicationInfo(notification.bigContentView, proxyApplicationInfo);
         fixApplicationInfo(notification.headsUpContentView, proxyApplicationInfo);
+        fixCustomNotificationOnColorOs(notification);
         Bundle bundle = Reflect.on(notification).get("extras");
         if (bundle != null) {
             bundle.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, proxyApplicationInfo);
@@ -112,4 +112,37 @@ private void fixApplicationInfo(RemoteViews remoteViews, ApplicationInfo ai) {
             mirror.android.widget.RemoteViews.mApplication.set(remoteViews, ai);
         }
     }
+
+    /**
+     * http://bbs.coloros.com/thread-379265-1-1.html
+     * https://www.coloros.com/thread-519347-1-1.html
+     * @param notification the notification
+     */
+    private void fixCustomNotificationOnColorOs(Notification notification) {
+        final String opporom = "ro.build.version.opporom";
+        final String colorOsVersion = SystemPropertiesCompat.get(opporom, "");
+        if (TextUtils.isEmpty(colorOsVersion)) {
+            return;
+        }
+
+        // http://bbs.coloros.com/thread-311896-1-1.html
+        // this can take effect also, but we do not use it.
+        // notification.flags |= Notification.FLAG_ONGOING_EVENT;
+
+        if (!colorOsVersion.toLowerCase().startsWith("v3")) {
+            return;
+        }
+        if (notification.contentView != null) {
+            notification.contentView = null;
+        }
+        if (notification.headsUpContentView != null) {
+            notification.headsUpContentView = null;
+        }
+        if (notification.bigContentView != null) {
+            notification.bigContentView = null;
+        }
+        if (notification.tickerView != null) {
+            notification.tickerView = null;
+        }
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationFixer.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationFixer.java
index e9d926db4..84e3c616d 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationFixer.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/NotificationFixer.java
@@ -16,6 +16,7 @@
 import android.widget.RemoteViews;
 
 import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.helper.utils.OSUtils;
 import com.lody.virtual.helper.utils.Reflect;
 import com.lody.virtual.helper.utils.VLog;
 
@@ -76,6 +77,7 @@ private static Bitmap drawableToBitMap(Drawable drawable) {
         }
     }
 
+    @TargetApi(Build.VERSION_CODES.M)
     void fixIcon(Icon icon, Context appContext, boolean installed) {
         if (icon == null) {
             return;
@@ -140,8 +142,7 @@ boolean fixRemoteViewActions(Context appContext, boolean installed, final Remote
                         mActions.remove(action);
                         continue;
                     }
-                    if (ReflectionActionCompat.isInstance(action)
-                            || (action.getClass().getSimpleName().endsWith("ReflectionAction"))) {
+                    if (ReflectionActionCompat.isInstance(action)) {
                         int viewId = Reflect.on(action).get("viewId");
 
                         String methodName = Reflect.on(action).get("methodName");
@@ -200,7 +201,7 @@ boolean fixRemoteViewActions(Context appContext, boolean installed, final Remote
     }
 
     void fixIconImage(Resources resources, RemoteViews remoteViews, boolean hasIconBitmap, Notification notification) {
-        if (remoteViews == null) return;
+        if (remoteViews == null || notification.icon == 0) return;
         if (!mNotificationCompat.isSystemLayout(remoteViews)) {
             return;
         }
@@ -213,12 +214,12 @@ void fixIconImage(Resources resources, RemoteViews remoteViews, boolean hasIconB
                 drawable.setLevel(notification.iconLevel);
                 Bitmap bitmap = drawableToBitMap(drawable);
                 remoteViews.setImageViewBitmap(id, bitmap);
-            }
-            if (Build.VERSION.SDK_INT >= 21) {
-                remoteViews.setInt(id, "setBackgroundColor", Color.TRANSPARENT);
-            }
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-                remoteViews.setViewPadding(id, 0, 0, 0, 0);
+                //emui
+                if(OSUtils.getInstance().isEmui()) {
+                    if (notification.largeIcon == null) {
+                        notification.largeIcon = bitmap;
+                    }
+                }
             }
         } catch (Exception e) {
             e.printStackTrace();
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/VNotificationManagerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/VNotificationManagerService.java
index b3c9df85e..8ba98af09 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/VNotificationManagerService.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/notification/VNotificationManagerService.java
@@ -98,7 +98,6 @@ public void addNotification(int id, String tag, String packageName, int userId)
                 mNotifications.put(packageName, list);
             }
             if (!list.contains(notificationInfo)) {
-                VLog.d(TAG, "add " + tag + " " + id);
                 list.add(notificationInfo);
             }
         }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/PackageSetting.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/PackageSetting.java
index d76297d0c..f3a049b2f 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/PackageSetting.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/PackageSetting.java
@@ -28,10 +28,8 @@ public PackageSetting[] newArray(int size) {
     public String apkPath;
     public String libPath;
     public boolean dependSystem;
-    /**
-     * In this mode we skip the dex2oat so we can load the class.dex very fast.
-     */
-    public boolean artFlyMode;
+    @Deprecated
+    public boolean skipDexOpt;
     public int appId;
     public long firstInstallTime;
     public long lastUpdateTime;
@@ -48,11 +46,11 @@ protected PackageSetting(Parcel in) {
         this.appId = in.readInt();
         //noinspection unchecked
         this.userState = in.readSparseArray(PackageUserState.class.getClassLoader());
-        this.artFlyMode = in.readByte() != 0;
+        this.skipDexOpt = in.readByte() != 0;
     }
 
     public InstalledAppInfo getAppInfo() {
-        return new InstalledAppInfo(packageName, apkPath, libPath, dependSystem, artFlyMode, appId);
+        return new InstalledAppInfo(packageName, apkPath, libPath, dependSystem, skipDexOpt, appId);
     }
 
     PackageUserState modifyUserState(int userId) {
@@ -97,7 +95,7 @@ public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(this.appId);
         //noinspection unchecked
         dest.writeSparseArray((SparseArray) this.userState);
-        dest.writeByte(this.artFlyMode ? (byte) 1 : (byte) 0);
+        dest.writeByte(this.skipDexOpt ? (byte) 1 : (byte) 0);
     }
 
     public boolean isLaunched(int userId) {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/PrivilegeAppOptimizer.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/PrivilegeAppOptimizer.java
new file mode 100644
index 000000000..275377895
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/PrivilegeAppOptimizer.java
@@ -0,0 +1,76 @@
+package com.lody.virtual.server.pm;
+
+import android.content.Intent;
+
+import com.lody.virtual.client.env.Constants;
+import com.lody.virtual.client.stub.VASettings;
+import com.lody.virtual.os.VUserHandle;
+import com.lody.virtual.server.am.VActivityManagerService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Lody
+ */
+
+public class PrivilegeAppOptimizer {
+
+    private static final PrivilegeAppOptimizer sInstance = new PrivilegeAppOptimizer();
+    private final List<String> privilegeApps = new ArrayList<>();
+
+    private PrivilegeAppOptimizer() {
+        Collections.addAll(privilegeApps, VASettings.PRIVILEGE_APPS);
+    }
+
+    public static PrivilegeAppOptimizer get() {
+        return sInstance;
+    }
+
+    public List<String> getPrivilegeApps() {
+        return Collections.unmodifiableList(privilegeApps);
+    }
+
+    public void addPrivilegeApp(String packageName) {
+        privilegeApps.add(packageName);
+    }
+
+    public void removePrivilegeApp(String packageName) {
+        privilegeApps.remove(packageName);
+    }
+
+    public boolean isPrivilegeApp(String packageName) {
+        return privilegeApps.contains(packageName);
+    }
+
+    public void performOptimizeAllApps() {
+        for (String pkg : privilegeApps) {
+            performOptimize(pkg, VUserHandle.USER_ALL);
+        }
+    }
+
+    public boolean performOptimize(String packageName, int userId) {
+        VActivityManagerService.get().sendBroadcastAsUser(
+                specifyApp(new Intent(Intent.ACTION_BOOT_COMPLETED), packageName, userId)
+                , new VUserHandle(userId));
+        return true;
+    }
+
+    public static void notifyBootFinish() {
+        for (String pkg : Constants.PRIVILEGE_APP) {
+            try {
+                PrivilegeAppOptimizer.get().performOptimize(pkg, 0);
+            } catch (Throwable ignored) {
+            }
+        }
+    }
+
+    private Intent specifyApp(Intent intent, String packageName, int userId) {
+        intent.putExtra("_VA_|_privilege_pkg_", packageName);
+        intent.putExtra("_VA_|_user_id_", userId);
+        intent.putExtra("_VA_|_intent_", new Intent(Intent.ACTION_BOOT_COMPLETED));
+        return intent;
+    }
+
+}
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java
index a4c5590e8..aa851f9cd 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VAppManagerService.java
@@ -9,8 +9,7 @@
 import com.lody.virtual.client.core.InstallStrategy;
 import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.env.VirtualRuntime;
-import com.lody.virtual.client.hook.secondary.GmsSupport;
-import com.lody.virtual.client.stub.StubManifest;
+import com.lody.virtual.helper.ArtDexOptimizer;
 import com.lody.virtual.helper.collection.IntArray;
 import com.lody.virtual.helper.compat.NativeLibraryHelperCompat;
 import com.lody.virtual.helper.utils.ArrayUtils;
@@ -38,6 +37,8 @@
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
+import dalvik.system.DexFile;
+
 /**
  * @author Lody
  */
@@ -75,19 +76,20 @@ public void scanApps() {
         synchronized (this) {
             mBooting = true;
             mPersistenceLayer.read();
-            if (StubManifest.ENABLE_GMS && !GmsSupport.isGoogleFrameworkInstalled()) {
-                GmsSupport.installGms(0);
-            }
+            PrivilegeAppOptimizer.get().performOptimizeAllApps();
             mBooting = false;
         }
     }
 
     private void cleanUpResidualFiles(PackageSetting ps) {
+        VLog.w(TAG, "cleanUpResidualFiles: " + ps.packageName);
         File dataAppDir = VEnvironment.getDataAppPackageDirectory(ps.packageName);
         FileUtils.deleteDir(dataAppDir);
-        for (int userId : VUserManagerService.get().getUserIds()) {
-            FileUtils.deleteDir(VEnvironment.getDataUserPackageDirectory(userId, ps.packageName));
-        }
+
+        // We shouldn't remove user data here!!! Just remove the package.
+        // for (int userId : VUserManagerService.get().getUserIds()) {
+        //     FileUtils.deleteDir(VEnvironment.getDataUserPackageDirectory(userId, ps.packageName));
+        // }
     }
 
 
@@ -148,7 +150,6 @@ public synchronized InstallResult installPackage(String path, int flags, boolean
         if (path == null) {
             return InstallResult.makeFailure("path = NULL");
         }
-        boolean artFlyMode = VirtualRuntime.isArt() && (flags & InstallStrategy.ART_FLY_MODE) != 0;
         File packageFile = new File(path);
         if (!packageFile.exists() || !packageFile.isFile()) {
             return InstallResult.makeFailure("Package File is not exist.");
@@ -173,7 +174,7 @@ public synchronized InstallResult installPackage(String path, int flags, boolean
                 return res;
             }
             if (!canUpdate(existOne, pkg, flags)) {
-                return InstallResult.makeFailure("Not allowed to update the package.");
+                return InstallResult.makeFailure("Can not update the package (such as version downrange).");
             }
             res.isUpdate = true;
         }
@@ -221,7 +222,6 @@ public synchronized InstallResult installPackage(String path, int flags, boolean
         } else {
             ps = new PackageSetting();
         }
-        ps.artFlyMode = artFlyMode;
         ps.dependSystem = dependSystem;
         ps.apkPath = packageFile.getPath();
         ps.libPath = libDir.getPath();
@@ -240,6 +240,26 @@ public synchronized InstallResult installPackage(String path, int flags, boolean
         PackageParserEx.savePackageCache(pkg);
         PackageCacheManager.put(pkg, ps);
         mPersistenceLayer.save();
+        if (!dependSystem) {
+            boolean runDexOpt = false;
+            if (VirtualRuntime.isArt()) {
+                try {
+                    ArtDexOptimizer.compileDex2Oat(ps.apkPath, VEnvironment.getOdexFile(ps.packageName).getPath());
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    runDexOpt = true;
+                }
+            } else {
+                runDexOpt = true;
+            }
+            if (runDexOpt) {
+                try {
+                    DexFile.loadDex(ps.apkPath, VEnvironment.getOdexFile(ps.packageName).getPath(), 0).close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
         BroadcastSystem.get().startApp(pkg);
         if (notify) {
             notifyAppInstalled(ps, -1);
@@ -305,6 +325,48 @@ public synchronized boolean uninstallPackage(String packageName) {
         return false;
     }
 
+    @Override
+    public boolean clearPackageAsUser(int userId, String packageName) throws RemoteException {
+        if (!VUserManagerService.get().exists(userId)) {
+            return false;
+        }
+        PackageSetting ps = PackageCacheManager.getSetting(packageName);
+        if (ps != null) {
+            int[] userIds = getPackageInstalledUsers(packageName);
+            if (!ArrayUtils.contains(userIds, userId)) {
+                return false;
+            }
+            if (userIds.length == 1) {
+                clearPackage(packageName);
+            } else {
+                // Just hidden it
+                VActivityManagerService.get().killAppByPkg(packageName, userId);
+                ps.setInstalled(userId, false);
+                mPersistenceLayer.save();
+                FileUtils.deleteDir(VEnvironment.getDataUserPackageDirectory(userId, packageName));
+                FileUtils.deleteDir(VEnvironment.getVirtualPrivateStorageDir(userId, packageName));
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean clearPackage(String packageName) throws RemoteException {
+        try {
+            BroadcastSystem.get().stopApp(packageName);
+            VActivityManagerService.get().killAppByPkg(packageName, VUserHandle.USER_ALL);
+
+            for (int id : VUserManagerService.get().getUserIds()) {
+                FileUtils.deleteDir(VEnvironment.getDataUserPackageDirectory(id, packageName));
+                FileUtils.deleteDir(VEnvironment.getVirtualPrivateStorageDir(id, packageName));
+            }
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
     @Override
     public synchronized boolean uninstallPackageAsUser(String packageName, int userId) {
         if (!VUserManagerService.get().exists(userId)) {
@@ -325,6 +387,7 @@ public synchronized boolean uninstallPackageAsUser(String packageName, int userI
                 notifyAppUninstalled(ps, userId);
                 mPersistenceLayer.save();
                 FileUtils.deleteDir(VEnvironment.getDataUserPackageDirectory(userId, packageName));
+                FileUtils.deleteDir(VEnvironment.getVirtualPrivateStorageDir(userId, packageName));
             }
             return true;
         }
@@ -341,6 +404,7 @@ private void uninstallPackageFully(PackageSetting ps) {
             VEnvironment.getOdexFile(packageName).delete();
             for (int id : VUserManagerService.get().getUserIds()) {
                 FileUtils.deleteDir(VEnvironment.getDataUserPackageDirectory(id, packageName));
+                FileUtils.deleteDir(VEnvironment.getVirtualPrivateStorageDir(id, packageName));
             }
             PackageCacheManager.remove(packageName);
         } catch (Exception e) {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VPackageManagerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VPackageManagerService.java
index 7e86c6603..4c5a1f7af 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VPackageManagerService.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VPackageManagerService.java
@@ -20,11 +20,13 @@
 
 import com.lody.virtual.client.core.VirtualCore;
 import com.lody.virtual.client.fixer.ComponentFixer;
-import com.lody.virtual.client.stub.StubManifest;
+import com.lody.virtual.client.stub.VASettings;
 import com.lody.virtual.helper.compat.ObjectsCompat;
 import com.lody.virtual.os.VUserHandle;
 import com.lody.virtual.remote.VParceledListSlice;
+import com.lody.virtual.server.IPackageInstaller;
 import com.lody.virtual.server.IPackageManager;
+import com.lody.virtual.server.pm.installer.VPackageInstallerService;
 import com.lody.virtual.server.pm.parser.PackageParserEx;
 import com.lody.virtual.server.pm.parser.VPackage;
 
@@ -96,7 +98,7 @@ public int compare(ProviderInfo p1, ProviderInfo p2) {
 
     public VPackageManagerService() {
         Intent intent = new Intent();
-        intent.setClassName(VirtualCore.get().getHostPkg(), StubManifest.RESOLVER_ACTIVITY);
+        intent.setClassName(VirtualCore.get().getHostPkg(), VASettings.RESOLVER_ACTIVITY);
         mResolveInfo = VirtualCore.get().getUnHookPackageManager().resolveActivity(intent, 0);
     }
 
@@ -738,6 +740,20 @@ public int getPackageUid(String packageName, int userId) {
         }
     }
 
+    @Override
+    public String getNameForUid(int uid) {
+        int appId = VUserHandle.getAppId(uid);
+        synchronized (mPackages) {
+            for (VPackage p : mPackages.values()) {
+                PackageSetting ps = (PackageSetting) p.mExtras;
+                if (ps.appId == appId) {
+                    return ps.packageName;
+                }
+            }
+            return null;
+        }
+    }
+
 
     @Override
     public List<String> querySharedPackages(String packageName) {
@@ -767,6 +783,11 @@ public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws
         }
     }
 
+    @Override
+    public IPackageInstaller getPackageInstaller() {
+        return VPackageInstallerService.get();
+    }
+
     void createNewUser(int userId, File userPath) {
         for (VPackage p : mPackages.values()) {
             PackageSetting setting = (PackageSetting) p.mExtras;
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/SessionParams.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/SessionParams.java
index 09dec8e31..3ad7e5a38 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/SessionParams.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/SessionParams.java
@@ -55,7 +55,7 @@ public SessionParams(int mode) {
     }
 
 
-    public PackageInstaller.SessionParams a() {
+    public PackageInstaller.SessionParams build() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(mode);
             SessionParamsMarshmallow.installFlags.set(params, installFlags);
@@ -86,7 +86,7 @@ public PackageInstaller.SessionParams a() {
         return params;
     }
 
-    public static SessionParams a(PackageInstaller.SessionParams sessionParams) {
+    public static SessionParams create(PackageInstaller.SessionParams sessionParams) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             SessionParams params = new SessionParams(SessionParamsMarshmallow.mode.get(sessionParams));
             params.installFlags = SessionParamsMarshmallow.installFlags.get(sessionParams);
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/VPackageInstallerService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/VPackageInstallerService.java
index 79258fd10..20be4d587 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/VPackageInstallerService.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/VPackageInstallerService.java
@@ -71,6 +71,7 @@ protected VPackageInstallerService create() {
     private VPackageInstallerService() {
         mContext = VirtualCore.get().getContext();
         mInstallThread = new HandlerThread("PackageInstaller");
+        mInstallThread.start();
         mInstallHandler = new Handler(mInstallThread.getLooper());
         mCallbacks = new Callbacks(mInstallThread.getLooper());
     }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java
index fb93d4ae3..57b375772 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/PackageParserEx.java
@@ -18,6 +18,7 @@
 import android.text.TextUtils;
 
 import com.lody.virtual.client.core.VirtualCore;
+import com.lody.virtual.client.env.Constants;
 import com.lody.virtual.client.fixer.ComponentFixer;
 import com.lody.virtual.helper.collection.ArrayMap;
 import com.lody.virtual.helper.compat.PackageParserCompat;
@@ -47,10 +48,30 @@ public class PackageParserEx {
 
     private static final ArrayMap<String, String[]> sSharedLibCache = new ArrayMap<>();
 
+    private static final String FAKE_SIG = "308203553082023da0030201020204378edaaa300d06092a864886f70d01010b0500305a310d300b0603550406130466616b65310d300b0603550408130466616b65310d300b0603550407130466616b65310d300b060355040a130466616b65310d300b060355040b130466616b65310d300b0603550403130466616b653020170d3138303533303034343434385a180f32313237313230353034343434385a305a310d300b0603550406130466616b65310d300b0603550408130466616b65310d300b0603550407130466616b65310d300b060355040a130466616b65310d300b060355040b130466616b65310d300b0603550403130466616b6530820122300d06092a864886f70d01010105000382010f003082010a0282010100b766ff6afd8a53edd4cee4985bc90e0c515157b5e9f731818961f7250d0d1ac7c7fb80eb5aeb8c28478732e8ff38cff574bfa0eba8039f73af1532f939c4ef9684719efbaba2dd3c583a20907c1c55248a63098c6da23dcfc877763d5fe6061dddd399cf2f49e3250e23f9e687a4d182bcd0662179ba4c9983448e34b4c83e5abbf4f87e87add9157c75fd40de3416744507a3517915f35b6fcad78766e8e1879df8ab823a6ffa335e4790f6e29c87393732025b63ce3a38e42cb0d48cdceb902f191d7d45823db9a0678895e8bfc59b2af7526ca4c2dc3dbe7e70c7c840e666b9629d36e5ddf1d9a80c37f1ab1bc1fb30432914008fbde95d5d3db7853565510203010001a321301f301d0603551d0e04160414d8513e1ae21c64e9ebeee3507e24ea375eef958e300d06092a864886f70d01010b0500038201010088bf20b36428558359536dddcfff16fe233656a92364cb544d8acc43b0859f880a8da339dd430616085edf035e4e6e6dd2281ceb14adde2f05e9ac58d547a09083eece0c6d405289cb7918f85754ee545eefe35e30c103cad617905e94eb4fb68e6920a60d30577855f9feb6e3a664856f74aa9f824aa7d4a3adf85e162c67b9a4261e3185f038ead96112ae3e574d280425e90567352fb82bc9173302122025eaecfabd94d0f9be69a85c415f7cf7759c9651734300952027b316c37aaa1b2418865a3fc7b6bd1072c92ccaacdaa1cf9586d9b8310ceee066ce68859107dfc45ccce729ad9e75b53b584fa37dcd64da8673b1279c6c5861ed3792deac156c8a";
+
     public static VPackage parsePackage(File packageFile) throws Throwable {
         PackageParser parser = PackageParserCompat.createParser(packageFile);
         PackageParser.Package p = PackageParserCompat.parsePackage(parser, packageFile, 0);
-        PackageParserCompat.collectCertificates(parser, p, PackageParser.PARSE_IS_SYSTEM);
+        if (p.requestedPermissions.contains("android.permission.FAKE_PACKAGE_SIGNATURE")
+                && p.mAppMetaData != null
+                && p.mAppMetaData.containsKey("fake-signature")) {
+            String sig = p.mAppMetaData.getString("fake-signature");
+            p.mSignatures = new Signature[]{new Signature(sig)};
+            VLog.d(TAG, "Using fake-signature feature on : " + p.packageName);
+        } else {
+            try {
+                PackageParserCompat.collectCertificates(parser, p, PackageParser.PARSE_IS_SYSTEM);
+            } catch (Throwable e) {
+                VLog.e(TAG, "collectCertificates failed", e);
+                if (VirtualCore.get().getContext().getFileStreamPath(Constants.FAKE_SIGNATURE_FLAG).exists()) {
+                    VLog.w(TAG, "Using fake signature: " + p.packageName);
+                    p.mSignatures = new Signature[]{new Signature(FAKE_SIG)};
+                } else {
+                    throw e;
+                }
+            }
+        }
         return buildPackageCache(p);
     }
 
@@ -164,7 +185,24 @@ private static VPackage buildPackageCache(PackageParser.Package p) {
             }
         }
         cache.applicationInfo = p.applicationInfo;
-        cache.mSignatures = p.mSignatures;
+        if (Build.VERSION.SDK_INT < 28) {
+            cache.mSignatures = p.mSignatures;
+        } else {
+            Object signingDetails = mirror.android.content.pm.PackageParser.Package.mSigningDetails.get(p);
+            boolean hasPastSigningCertificates = mirror.android.content.pm.PackageParser.SigningDetails.hasPastSigningCertificates.call(signingDetails);
+            if (hasPastSigningCertificates) {
+                cache.mSignatures = new Signature[1];
+                cache.mSignatures[0] = mirror.android.content.pm.PackageParser.SigningDetails.pastSigningCertificates.get(signingDetails)[0];
+            } else {
+                boolean hasSignatures = mirror.android.content.pm.PackageParser.SigningDetails.hasSignatures.call(signingDetails);
+                if (hasSignatures) {
+                    Signature[] signatures = mirror.android.content.pm.PackageParser.SigningDetails.signatures.get(signingDetails);
+                    int numberOfSigs = signatures.length;
+                    cache.mSignatures = new Signature[numberOfSigs];
+                    System.arraycopy(signatures, 0, cache.mSignatures, 0, numberOfSigs);
+                }
+            }
+        }
         cache.mAppMetaData = p.mAppMetaData;
         cache.packageName = p.packageName;
         cache.mPreferredOrder = p.mPreferredOrder;
@@ -225,9 +263,11 @@ private static void initApplicationAsUser(ApplicationInfo ai, int userId) {
             ApplicationInfoL.scanPublicSourceDir.set(ai, ai.dataDir);
         }
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            ApplicationInfoN.deviceEncryptedDataDir.set(ai, ai.dataDir);
+            if(Build.VERSION.SDK_INT < 26) {
+                ApplicationInfoN.deviceEncryptedDataDir.set(ai, ai.dataDir);
+                ApplicationInfoN.credentialEncryptedDataDir.set(ai, ai.dataDir);
+            }
             ApplicationInfoN.deviceProtectedDataDir.set(ai, ai.dataDir);
-            ApplicationInfoN.credentialEncryptedDataDir.set(ai, ai.dataDir);
             ApplicationInfoN.credentialProtectedDataDir.set(ai, ai.dataDir);
         }
     }
@@ -285,6 +325,11 @@ public static PackageInfo generatePackageInfo(VPackage p, int flags, long firstI
         pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
         pi.firstInstallTime = firstInstallTime;
         pi.lastUpdateTime = lastUpdateTime;
+        if (p.requestedPermissions != null && !p.requestedPermissions.isEmpty()) {
+            String[] requestedPermissions = new String[p.requestedPermissions.size()];
+            p.requestedPermissions.toArray(requestedPermissions);
+            pi.requestedPermissions = requestedPermissions;
+        }
         if ((flags & PackageManager.GET_GIDS) != 0) {
             pi.gids = PackageParserCompat.GIDS;
         }
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/VPackage.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/VPackage.java
index 88e47a18f..d62a3e4d4 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/VPackage.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/parser/VPackage.java
@@ -1,5 +1,6 @@
 package com.lody.virtual.server.pm.parser;
 
+import android.Manifest;
 import android.content.ComponentName;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
@@ -19,6 +20,8 @@
 import android.os.Parcelable;
 
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * @author Lody
@@ -449,6 +452,49 @@ protected InstrumentationComponent(Parcel src) {
     }
 
     public static class PermissionComponent extends Component<IntentInfo> {
+
+        // https://developer.android.com/guide/topics/security/permissions?hl=zh-cn
+        public static Set<String> DANGEROUS_PERMISSION = new HashSet<String>() {{
+            // CALENDAR group
+            add(Manifest.permission.READ_CALENDAR);
+            add(Manifest.permission.WRITE_CALENDAR);
+
+            // CAMERA
+            add(Manifest.permission.CAMERA);
+
+            // CONTACTS
+            add(Manifest.permission.READ_CONTACTS);
+            add(Manifest.permission.WRITE_CONTACTS);
+            add(Manifest.permission.GET_ACCOUNTS);
+
+            // LOCATION
+            add(Manifest.permission.ACCESS_FINE_LOCATION);
+            add(Manifest.permission.ACCESS_COARSE_LOCATION);
+
+            // PHONE
+            add(Manifest.permission.READ_PHONE_STATE);
+            add(Manifest.permission.CALL_PHONE);
+            add(Manifest.permission.READ_CALL_LOG);
+            add(Manifest.permission.WRITE_CALL_LOG);
+            add(Manifest.permission.ADD_VOICEMAIL);
+            add(Manifest.permission.USE_SIP);
+            add(Manifest.permission.PROCESS_OUTGOING_CALLS);
+
+            // SENSORS
+            add(Manifest.permission.BODY_SENSORS);
+
+            // SMS
+            add(Manifest.permission.SEND_SMS);
+            add(Manifest.permission.RECEIVE_SMS);
+            add(Manifest.permission.READ_SMS);
+            add(Manifest.permission.RECEIVE_WAP_PUSH);
+            add(Manifest.permission.RECEIVE_MMS);
+
+            // STORAGE
+            add(Manifest.permission.READ_EXTERNAL_STORAGE);
+            add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+        }};
+
         public PermissionInfo info;
 
         public PermissionComponent(PackageParser.Permission p) {
diff --git a/VirtualApp/lib/src/main/java/com/lody/virtual/server/vs/VirtualStorageService.java b/VirtualApp/lib/src/main/java/com/lody/virtual/server/vs/VirtualStorageService.java
index 8eedbc776..01a353588 100644
--- a/VirtualApp/lib/src/main/java/com/lody/virtual/server/vs/VirtualStorageService.java
+++ b/VirtualApp/lib/src/main/java/com/lody/virtual/server/vs/VirtualStorageService.java
@@ -49,7 +49,7 @@ private VSConfig getOrCreateVSConfigLocked(String packageName, int userId) {
         VSConfig config = userMap.get(packageName);
         if (config == null) {
             config = new VSConfig();
-            config.enable = true;
+            config.enable = false;
             userMap.put(packageName, config);
         }
         return config;
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/ActivityThread.java b/VirtualApp/lib/src/main/java/mirror/android/app/ActivityThread.java
index 8003df8c6..fef55d46d 100644
--- a/VirtualApp/lib/src/main/java/mirror/android/app/ActivityThread.java
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/ActivityThread.java
@@ -3,6 +3,7 @@
 
 import android.app.*;
 import android.app.Activity;
+import android.app.servertransaction.TransactionExecutor;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -48,6 +49,9 @@ public class ActivityThread {
     public static RefMethod<Void> sendActivityResult;
     public static RefMethod<Binder> getApplicationThread;
 
+    // Android 9.0
+    public static RefObject<TransactionExecutor> mTransactionExecutor;
+
     public static Object installProvider(Object mainThread, Context context, ProviderInfo providerInfo, Object holder) {
         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
             return installProvider.call(mainThread, context, holder, providerInfo, false, true);
@@ -61,6 +65,7 @@ public static class ActivityClientRecord {
         public static RefObject<ActivityInfo> activityInfo;
         public static RefObject<Intent> intent;
         public static RefObject<IBinder> token;
+        public static RefObject<?> packageInfo;
     }
 
     public static class ProviderClientRecord {
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/ContextImplKitkat.java b/VirtualApp/lib/src/main/java/mirror/android/app/ContextImplKitkat.java
index 8618f7160..6f92c656d 100644
--- a/VirtualApp/lib/src/main/java/mirror/android/app/ContextImplKitkat.java
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/ContextImplKitkat.java
@@ -10,4 +10,5 @@ public class ContextImplKitkat {
     public static RefObject<File[]> mExternalCacheDirs;
     public static RefObject<File[]> mExternalFilesDirs;
     public static RefObject<String> mOpPackageName;
+    public static RefObject<Object> mDisplayAdjustments;
 }
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/IApplicationThreadOreo.java b/VirtualApp/lib/src/main/java/mirror/android/app/IApplicationThreadOreo.java
index f2beee5a2..8bbcc643a 100644
--- a/VirtualApp/lib/src/main/java/mirror/android/app/IApplicationThreadOreo.java
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/IApplicationThreadOreo.java
@@ -4,7 +4,9 @@
 import android.os.IInterface;
 
 import mirror.MethodParams;
+import mirror.MethodReflectParams;
 import mirror.RefClass;
+import mirror.RefMethod;
 import mirror.RefStaticMethod;
 
 /**
@@ -13,11 +15,14 @@
 
 public class IApplicationThreadOreo {
 
+    public static Class<?> TYPE = RefClass.load(IApplicationThreadOreo.class, "android.app.IApplicationThread");
+
     public static final class Stub {
         public static Class<?> TYPE = RefClass.load(IApplicationThreadOreo.Stub.class, "android.app.IApplicationThread$Stub");
         @MethodParams({IBinder.class})
         public static RefStaticMethod<IInterface> asInterface;
     }
 
-
+    @MethodReflectParams({"android.os.IBinder", "android.content.pm.ParceledListSlice"})
+    public static RefMethod<Void> scheduleServiceArgs;
 }
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/IServiceConnectionO.java b/VirtualApp/lib/src/main/java/mirror/android/app/IServiceConnectionO.java
new file mode 100644
index 000000000..b29bf7baa
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/IServiceConnectionO.java
@@ -0,0 +1,15 @@
+package mirror.android.app;
+
+import android.content.ComponentName;
+import android.os.IBinder;
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefMethod;
+
+public class IServiceConnectionO {
+    public static Class<?> TYPE = RefClass.load(IServiceConnectionO.class, "android.app.IServiceConnection");
+
+    @MethodParams({ComponentName.class, IBinder.class, boolean.class})
+    public static RefMethod<Void> connected;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/IUsageStatsManager.java b/VirtualApp/lib/src/main/java/mirror/android/app/IUsageStatsManager.java
new file mode 100644
index 000000000..00446b482
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/IUsageStatsManager.java
@@ -0,0 +1,22 @@
+package mirror.android.app;
+
+import android.os.IBinder;
+import android.os.IInterface;
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefStaticMethod;
+
+/**
+ * Created by caokai on 2017/9/8.
+ */
+
+public class IUsageStatsManager {
+    public static Class<?> TYPE = RefClass.load(IUsageStatsManager.class, "android.app.usage.IUsageStatsManager");
+
+    public static class Stub {
+        public static Class<?> TYPE = RefClass.load(IUsageStatsManager.Stub.class, "android.app.usage.IUsageStatsManager$Stub");
+        @MethodParams({IBinder.class})
+        public static RefStaticMethod<IInterface> asInterface;
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/LoadedApk.java b/VirtualApp/lib/src/main/java/mirror/android/app/LoadedApk.java
index 5c112bb0e..8d2d7fc38 100644
--- a/VirtualApp/lib/src/main/java/mirror/android/app/LoadedApk.java
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/LoadedApk.java
@@ -1,26 +1,34 @@
 package mirror.android.app;
 
-import java.lang.ref.WeakReference;
-
 import android.app.Application;
+import android.app.IServiceConnection;
 import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
+import android.os.Handler;
 import android.os.IInterface;
 
+import java.lang.ref.WeakReference;
+
+import mirror.MethodParams;
 import mirror.RefClass;
-import mirror.RefObject;
 import mirror.RefMethod;
-import mirror.MethodParams;
+import mirror.RefObject;
 
 public class LoadedApk {
     public static Class Class = RefClass.load(LoadedApk.class, "android.app.LoadedApk");
     public static RefObject<ApplicationInfo> mApplicationInfo;
     @MethodParams({boolean.class, Instrumentation.class})
     public static RefMethod<Application> makeApplication;
+    @MethodParams({ServiceConnection.class, Context.class, Handler.class, int.class})
+    public static RefMethod<IServiceConnection> getServiceDispatcher;
+    @MethodParams({Context.class, ServiceConnection.class})
+    public static RefMethod<IServiceConnection> forgetServiceDispatcher;
+
+    public static RefMethod<ClassLoader> getClassLoader;
 
     public static class ReceiverDispatcher {
         public static Class Class = RefClass.load(ReceiverDispatcher.class, "android.app.LoadedApk$ReceiverDispatcher");
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/LoadedApkICS.java b/VirtualApp/lib/src/main/java/mirror/android/app/LoadedApkICS.java
new file mode 100644
index 000000000..0cc032450
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/LoadedApkICS.java
@@ -0,0 +1,10 @@
+package mirror.android.app;
+
+
+import mirror.RefClass;
+import mirror.RefObject;
+
+public class LoadedApkICS {
+    public static Class<?> Class = RefClass.load(LoadedApkICS.class, "android.app.LoadedApk");
+    public static RefObject<Object> mCompatibilityInfo;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/LoadedApkKitkat.java b/VirtualApp/lib/src/main/java/mirror/android/app/LoadedApkKitkat.java
new file mode 100644
index 000000000..344abcd58
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/LoadedApkKitkat.java
@@ -0,0 +1,10 @@
+package mirror.android.app;
+
+
+import mirror.RefClass;
+import mirror.RefObject;
+
+public class LoadedApkKitkat {
+    public static Class<?> Class = RefClass.load(LoadedApkKitkat.class, "android.app.LoadedApk");
+    public static RefObject<Object> mDisplayAdjustments;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/ServiceStartArgs.java b/VirtualApp/lib/src/main/java/mirror/android/app/ServiceStartArgs.java
new file mode 100644
index 000000000..17cf85465
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/ServiceStartArgs.java
@@ -0,0 +1,24 @@
+package mirror.android.app;
+
+import android.content.Intent;
+
+import mirror.MethodParams;
+import mirror.RefBoolean;
+import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefInt;
+import mirror.RefObject;
+
+/**
+ * @author Lody
+ */
+public class ServiceStartArgs {
+    public static Class<?> TYPE = RefClass.load(ServiceStartArgs.class, "android.app.ServiceStartArgs");
+    @MethodParams({boolean.class, int.class, int.class, Intent.class})
+    public static RefConstructor<Object> ctor;
+    public static RefBoolean taskRemoved;
+    public static RefInt startId;
+    public static RefInt flags;
+    public static RefObject<Intent> args;
+
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/admin/IDevicePolicyManager.java b/VirtualApp/lib/src/main/java/mirror/android/app/admin/IDevicePolicyManager.java
new file mode 100644
index 000000000..9790a771a
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/admin/IDevicePolicyManager.java
@@ -0,0 +1,22 @@
+package mirror.android.app.admin;
+
+import android.os.IBinder;
+import android.os.IInterface;
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefStaticMethod;
+
+/**
+ * Created by wy on 2017/10/20.
+ */
+
+public class IDevicePolicyManager {
+    public static Class<?> TYPE = RefClass.load(IDevicePolicyManager.class, "android.app.admin.IDevicePolicyManager");
+
+    public static class  Stub {
+        public static Class<?> TYPE = RefClass.load(Stub.class, "android.app.admin.IDevicePolicyManager$Stub");
+        @MethodParams({IBinder.class})
+        public static RefStaticMethod<IInterface> asInterface;
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/app/job/JobWorkItem.java b/VirtualApp/lib/src/main/java/mirror/android/app/job/JobWorkItem.java
new file mode 100644
index 000000000..5d44da593
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/app/job/JobWorkItem.java
@@ -0,0 +1,34 @@
+package mirror.android.app.job;
+
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.os.Build;
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefMethod;
+import mirror.RefObject;
+
+/**
+ * @author Lody
+ */
+
+@TargetApi(Build.VERSION_CODES.O)
+public class JobWorkItem {
+    public static Class<?> TYPE = RefClass.load(JobWorkItem.class, android.app.job.JobWorkItem.class);
+
+//    final Intent mIntent;
+//    int mDeliveryCount;
+//    int mWorkId;
+//    Object mGrants;
+    @MethodParams({Intent.class})
+    public static RefConstructor<android.app.job.JobWorkItem> ctor;
+
+    public static RefObject<Intent> mIntent;
+    public static RefObject<Integer> mDeliveryCount;
+    public static RefObject<Integer> mWorkId;
+    public static RefObject<Object> mGrants;
+
+    public static RefMethod<Intent> getIntent;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/content/BroadcastReceiver.java b/VirtualApp/lib/src/main/java/mirror/android/content/BroadcastReceiver.java
index f658669bb..2b1ad877c 100644
--- a/VirtualApp/lib/src/main/java/mirror/android/content/BroadcastReceiver.java
+++ b/VirtualApp/lib/src/main/java/mirror/android/content/BroadcastReceiver.java
@@ -13,13 +13,13 @@
 import mirror.RefObject;
 
 public class BroadcastReceiver {
-    public static Class<?> Class = RefClass.load(BroadcastReceiver.class, android.content.BroadcastReceiver.class);
+    public static Class<?> TYPE = RefClass.load(BroadcastReceiver.class, android.content.BroadcastReceiver.class);
     public static RefMethod<android.content.BroadcastReceiver.PendingResult> getPendingResult;
     @MethodParams({android.content.BroadcastReceiver.PendingResult.class})
     public static RefMethod<Void> setPendingResult;
 
     public static class PendingResult {
-        public static Class<?> Class = RefClass.load(PendingResult.class, android.content.BroadcastReceiver.PendingResult.class);
+        public static Class<?> TYPE = RefClass.load(PendingResult.class, android.content.BroadcastReceiver.PendingResult.class);
         @MethodParams({int.class, String.class, Bundle.class, int.class, boolean.class, boolean.class, IBinder.class})
         public static RefConstructor<android.content.BroadcastReceiver.PendingResult> ctor;
         public static RefBoolean mAbortBroadcast;
@@ -34,7 +34,7 @@ public static class PendingResult {
     }
 
     public static class PendingResultJBMR1 {
-        public static Class<?> Class = RefClass.load(PendingResultJBMR1.class, android.content.BroadcastReceiver.PendingResult.class);
+        public static Class<?> TYPE = RefClass.load(PendingResultJBMR1.class, android.content.BroadcastReceiver.PendingResult.class);
         @MethodParams({int.class, String.class, Bundle.class, int.class, boolean.class, boolean.class, IBinder.class, int.class})
         public static RefConstructor<android.content.BroadcastReceiver.PendingResult> ctor;
         public static RefBoolean mAbortBroadcast;
@@ -50,7 +50,7 @@ public static class PendingResultJBMR1 {
     }
 
     public static class PendingResultMNC {
-        public static Class<?> Class = RefClass.load(PendingResultMNC.class, android.content.BroadcastReceiver.PendingResult.class);
+        public static Class<?> TYPE = RefClass.load(PendingResultMNC.class, android.content.BroadcastReceiver.PendingResult.class);
         @MethodParams({int.class, String.class, Bundle.class, int.class, boolean.class, boolean.class, IBinder.class, int.class, int.class})
         public static RefConstructor<android.content.BroadcastReceiver.PendingResult> ctor;
         public static RefBoolean mAbortBroadcast;
diff --git a/VirtualApp/lib/src/main/java/mirror/android/content/SyncAdapterType.java b/VirtualApp/lib/src/main/java/mirror/android/content/SyncAdapterType.java
new file mode 100644
index 000000000..04d81ea78
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/content/SyncAdapterType.java
@@ -0,0 +1,12 @@
+package mirror.android.content;
+
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefConstructor;
+
+public class SyncAdapterType {
+    public static Class<?> TYPE = RefClass.load(SyncAdapterType.class, android.content.SyncAdapterType.class);
+    @MethodParams({String.class, String.class, boolean.class, boolean.class, boolean.class, boolean.class, String.class})
+    public static RefConstructor<android.content.SyncAdapterType> ctor;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/content/SyncAdapterTypeN.java b/VirtualApp/lib/src/main/java/mirror/android/content/SyncAdapterTypeN.java
new file mode 100644
index 000000000..146e29ca8
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/content/SyncAdapterTypeN.java
@@ -0,0 +1,13 @@
+package mirror.android.content;
+
+import android.content.SyncAdapterType;
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefConstructor;
+
+public class SyncAdapterTypeN {
+    public static Class<?> Class = RefClass.load(SyncAdapterTypeN.class, SyncAdapterType.class);
+    @MethodParams({String.class, String.class, boolean.class, boolean.class, boolean.class, boolean.class, String.class, String.class})
+    public static RefConstructor<SyncAdapterType> ctor;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/content/SyncInfo.java b/VirtualApp/lib/src/main/java/mirror/android/content/SyncInfo.java
new file mode 100644
index 000000000..ed6060013
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/content/SyncInfo.java
@@ -0,0 +1,13 @@
+package mirror.android.content;
+
+import android.accounts.Account;
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefConstructor;
+
+public class SyncInfo {
+    public static Class<?> TYPE = RefClass.load(SyncInfo.class, android.content.SyncInfo.class);
+    @MethodParams({int.class, Account.class, String.class, long.class})
+    public static RefConstructor<android.content.SyncInfo> ctor;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/content/SyncRequest.java b/VirtualApp/lib/src/main/java/mirror/android/content/SyncRequest.java
new file mode 100644
index 000000000..055d9d86a
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/content/SyncRequest.java
@@ -0,0 +1,22 @@
+package mirror.android.content;
+
+
+import android.accounts.Account;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Bundle;
+
+import mirror.RefBoolean;
+import mirror.RefClass;
+import mirror.RefLong;
+import mirror.RefObject;
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+public class SyncRequest {
+    public static Class<?> TYPE = RefClass.load(SyncRequest.class, android.content.SyncRequest.class);
+    public static RefObject<Account> mAccountToSync;
+    public static RefObject<String> mAuthority;
+    public static RefObject<Bundle> mExtras;
+    public static RefBoolean mIsPeriodic;
+    public static RefLong mSyncRunTimeSecs;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/content/pm/IShortcutService.java b/VirtualApp/lib/src/main/java/mirror/android/content/pm/IShortcutService.java
index 560e1ee0d..34ee13b2b 100644
--- a/VirtualApp/lib/src/main/java/mirror/android/content/pm/IShortcutService.java
+++ b/VirtualApp/lib/src/main/java/mirror/android/content/pm/IShortcutService.java
@@ -1,7 +1,9 @@
 package mirror.android.content.pm;
 
+import android.os.IBinder;
 import android.os.IInterface;
 
+import mirror.MethodParams;
 import mirror.RefClass;
 import mirror.RefStaticMethod;
 
@@ -12,7 +14,8 @@
 public class IShortcutService {
 
     public static final class Stub {
-        public static Class<?> TYPE = RefClass.load(IShortcutService.class, "android.content.pm.IShortcutService$Stub");
+        public static Class<?> TYPE = RefClass.load(IShortcutService.Stub.class, "android.content.pm.IShortcutService$Stub");
+        @MethodParams({IBinder.class})
         public static RefStaticMethod<IInterface> asInterface;
     }
 }
diff --git a/VirtualApp/lib/src/main/java/mirror/android/content/pm/PackageParser.java b/VirtualApp/lib/src/main/java/mirror/android/content/pm/PackageParser.java
index 10408edea..38a4ac02e 100644
--- a/VirtualApp/lib/src/main/java/mirror/android/content/pm/PackageParser.java
+++ b/VirtualApp/lib/src/main/java/mirror/android/content/pm/PackageParser.java
@@ -63,6 +63,7 @@ public static class Package {
         public static RefObject<List> receivers;
         public static RefObject<List<String>> requestedPermissions;
         public static RefObject<List> services;
+        public static RefObject<Object> mSigningDetails;
     }
 
     public static class Activity {
@@ -99,4 +100,13 @@ public static class Component {
         public static RefObject<ComponentName> componentName;
         public static RefObject<List<IntentFilter>> intents;
     }
+
+    public static class SigningDetails {
+        public static Class<?> TYPE = RefClass.load(SigningDetails.class, "android.content.pm.PackageParser$SigningDetails");
+        public static RefObject<Signature[]> signatures;
+        public static RefObject<Signature[]> pastSigningCertificates;
+
+        public static RefMethod<Boolean> hasPastSigningCertificates;
+        public static RefMethod<Boolean> hasSignatures;
+    }
 }
diff --git a/VirtualApp/lib/src/main/java/mirror/android/content/pm/PackageParserP28.java b/VirtualApp/lib/src/main/java/mirror/android/content/pm/PackageParserP28.java
new file mode 100644
index 000000000..e9d0fc5db
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/content/pm/PackageParserP28.java
@@ -0,0 +1,16 @@
+package mirror.android.content.pm;
+
+import mirror.MethodReflectParams;
+import mirror.RefClass;
+import mirror.RefStaticMethod;
+
+/**
+ * @author weishu
+ * @date 2018/6/7.
+ */
+
+public class PackageParserP28 {
+    public static Class<?> TYPE = RefClass.load(PackageParserP28.class, "android.content.pm.PackageParser");
+    @MethodReflectParams({"android.content.pm.PackageParser$Package", "boolean"})
+    public static RefStaticMethod<Void> collectCertificates;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/content/pm/ShortcutInfo.java b/VirtualApp/lib/src/main/java/mirror/android/content/pm/ShortcutInfo.java
new file mode 100644
index 000000000..ca00e7fb2
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/content/pm/ShortcutInfo.java
@@ -0,0 +1,20 @@
+package mirror.android.content.pm;
+
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.PersistableBundle;
+
+import mirror.RefClass;
+import mirror.RefObject;
+
+/**
+ * author: weishu on 18/3/3.
+ */
+
+public class ShortcutInfo {
+    public static Class<?> TYPE = RefClass.load(ShortcutInfo.class, "android.content.pm.ShortcutInfo");
+    public static RefObject<String> mPackageName;
+    public static RefObject<Icon> mIcon;
+    public static RefObject<Intent[]> mIntents;
+    public static RefObject<PersistableBundle[]> mIntentPersistableExtrases;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/hardware/fingerprint/IFingerprintService.java b/VirtualApp/lib/src/main/java/mirror/android/hardware/fingerprint/IFingerprintService.java
new file mode 100644
index 000000000..340951b57
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/hardware/fingerprint/IFingerprintService.java
@@ -0,0 +1,20 @@
+package mirror.android.hardware.fingerprint;
+
+import android.os.*;
+
+import mirror.*;
+
+/**
+ * Created by natsuki on 12/10/2017.
+ */
+
+public class IFingerprintService {
+
+    public static Class<?> TYPE = RefClass.load(IFingerprintService.class, "android.hardware.fingerprint.IFingerprintService");
+
+    public static class Stub {
+        public static Class<?> TYPE = RefClass.load(Stub.class, "android.hardware.fingerprint.IFingerprintService$Stub");
+        @MethodParams({IBinder.class})
+        public static RefStaticMethod<IInterface> asInterface;
+    }
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/location/LocationManager.java b/VirtualApp/lib/src/main/java/mirror/android/location/LocationManager.java
new file mode 100644
index 000000000..50fac4569
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/location/LocationManager.java
@@ -0,0 +1,82 @@
+package mirror.android.location;
+
+import android.location.Location;
+import android.location.LocationListener;
+import android.os.Bundle;
+
+import java.util.HashMap;
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefMethod;
+import mirror.RefObject;
+
+public class LocationManager {
+    public static Class<?> TYPE = RefClass.load(LocationManager.class, "android.location.LocationManager");
+    public static RefObject<HashMap> mGnssNmeaListeners;
+    public static RefObject<HashMap> mGnssStatusListeners;
+    public static RefObject<HashMap> mGpsNmeaListeners;
+    public static RefObject<HashMap> mGpsStatusListeners;
+    public static RefObject<HashMap> mListeners;
+    public static RefObject<HashMap> mNmeaListeners;
+
+    public static class GnssStatusListenerTransport {
+        public static Class<?> TYPE = RefClass.load(GnssStatusListenerTransport.class, "android.location.LocationManager$GnssStatusListenerTransport");
+        public static RefObject<Object> mGpsListener;
+        public static RefObject<Object> mGpsNmeaListener;
+        @MethodParams({int.class})
+        public static RefMethod<Void> onFirstFix;
+        public static RefMethod<Void> onGnssStarted;
+        @MethodParams({long.class, String.class})
+        public static RefMethod<Void> onNmeaReceived;
+        @MethodParams({int.class, int[].class, float[].class, float[].class, float[].class, float[].class})
+        public static RefMethod<Void> onSvStatusChanged;
+        public static RefObject<Object> this$0;
+    }
+
+    public static class GpsStatusListenerTransport {
+        public static Class<?> TYPE = RefClass.load(GpsStatusListenerTransport.class, "android.location.LocationManager$GpsStatusListenerTransport");
+        public static RefObject<Object> mListener;
+        public static RefObject<Object> mNmeaListener;
+        @MethodParams({int.class})
+        public static RefMethod<Void> onFirstFix;
+        public static RefMethod<Void> onGpsStarted;
+        @MethodParams({long.class, String.class})
+        public static RefMethod<Void> onNmeaReceived;
+        @MethodParams({int.class, int[].class, float[].class, float[].class, float[].class, int.class, int.class, int.class})
+        public static RefMethod<Void> onSvStatusChanged;
+        public static RefObject<Object> this$0;
+    }
+
+    public static class GpsStatusListenerTransportOPPO_R815T {
+        public static Class<?> TYPE = RefClass.load(GpsStatusListenerTransportOPPO_R815T.class, "android.location.LocationManager$GpsStatusListenerTransport");
+        @MethodParams({int.class, int[].class, float[].class, float[].class, float[].class, int[].class, int[].class, int[].class, int.class})
+        public static RefMethod<Void> onSvStatusChanged;
+    }
+
+    public static class GpsStatusListenerTransportSumsungS5 {
+        public static Class<?> TYPE = RefClass.load(GpsStatusListenerTransportSumsungS5.class, "android.location.LocationManager$GpsStatusListenerTransport");
+        @MethodParams({int.class, int[].class, float[].class, float[].class, float[].class, int.class, int.class, int.class, int[].class})
+        public static RefMethod<Void> onSvStatusChanged;
+    }
+
+    public static class GpsStatusListenerTransportVIVO {
+        public static Class<?> TYPE = RefClass.load(GpsStatusListenerTransportVIVO.class, "android.location.LocationManager$GpsStatusListenerTransport");
+        @MethodParams({int.class, int[].class, float[].class, float[].class, float[].class, int.class, int.class, int.class, long[].class})
+        public static RefMethod<Void> onSvStatusChanged;
+    }
+
+    public static class ListenerTransport {
+        public static Class<?> TYPE = RefClass.load(ListenerTransport.class, "android.location.LocationManager$ListenerTransport");
+        public static RefObject<LocationListener> mListener;
+        @MethodParams({Location.class})
+        public static RefMethod<Void> onLocationChanged;
+        @MethodParams({String.class})
+        public static RefMethod<Void> onProviderDisabled;
+        @MethodParams({String.class})
+        public static RefMethod<Void> onProviderEnabled;
+        @MethodParams({String.class, int.class, Bundle.class})
+        public static RefMethod<Void> onStatusChanged;
+        public static RefObject<Object> this$0;
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/net/NetworkInfo.java b/VirtualApp/lib/src/main/java/mirror/android/net/NetworkInfo.java
new file mode 100644
index 000000000..7942f2509
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/net/NetworkInfo.java
@@ -0,0 +1,25 @@
+package mirror.android.net;
+
+import mirror.MethodParams;
+import mirror.RefBoolean;
+import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefInt;
+import mirror.RefObject;
+
+/**
+ * @author Lody
+ */
+
+public class NetworkInfo {
+    public static Class<?> TYPE = RefClass.load(NetworkInfo.class, android.net.NetworkInfo.class);
+    @MethodParams({int.class, int.class, String.class, String.class})
+    public static RefConstructor<android.net.NetworkInfo> ctor;
+    @MethodParams({int.class})
+    public static RefConstructor<android.net.NetworkInfo> ctorOld;
+    public static RefInt mNetworkType;
+    public static RefObject<String> mTypeName;
+    public static RefObject<android.net.NetworkInfo.State> mState;
+    public static RefObject<android.net.NetworkInfo.DetailedState> mDetailedState;
+    public static RefBoolean mIsAvailable;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/net/wifi/WifiInfo.java b/VirtualApp/lib/src/main/java/mirror/android/net/wifi/WifiInfo.java
index 2319062c3..dab93221c 100644
--- a/VirtualApp/lib/src/main/java/mirror/android/net/wifi/WifiInfo.java
+++ b/VirtualApp/lib/src/main/java/mirror/android/net/wifi/WifiInfo.java
@@ -1,9 +1,27 @@
 package mirror.android.net.wifi;
 
+import android.net.wifi.SupplicantState;
+
+import java.net.InetAddress;
+
 import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefInt;
 import mirror.RefObject;
 
 public class WifiInfo {
     public static Class<?> TYPE = RefClass.load(WifiInfo.class, android.net.wifi.WifiInfo.class);
+    public static RefConstructor<android.net.wifi.WifiInfo> ctor;
     public static RefObject<String> mMacAddress;
+    public static RefInt mNetworkId;
+    public static RefInt mLinkSpeed;
+    public static RefInt mFrequency;
+    public static RefInt mRssi;
+    public static RefObject<SupplicantState> mSupplicantState;
+    public static RefObject<InetAddress> mIpAddress;
+    public static RefObject<Object> mWifiSsid;
+    public static RefObject<String> mBSSID;
+    public static RefObject<String> mSSID;
+
+
 }
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/net/wifi/WifiSsid.java b/VirtualApp/lib/src/main/java/mirror/android/net/wifi/WifiSsid.java
new file mode 100644
index 000000000..7dbc31fa7
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/net/wifi/WifiSsid.java
@@ -0,0 +1,17 @@
+package mirror.android.net.wifi;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+
+import mirror.RefClass;
+import mirror.RefStaticMethod;
+
+/**
+ * @author Lody
+ */
+@TargetApi(Build.VERSION_CODES.KITKAT)
+public class WifiSsid {
+    public static final Class<?> TYPE = RefClass.load(WifiSsid.class, "android.net.wifi.WifiSsid");
+    public static RefStaticMethod<Object> createFromAsciiEncoded;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/os/BaseBundle.java b/VirtualApp/lib/src/main/java/mirror/android/os/BaseBundle.java
new file mode 100644
index 000000000..c272a0032
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/os/BaseBundle.java
@@ -0,0 +1,12 @@
+package mirror.android.os;
+
+import android.os.Parcel;
+
+import mirror.RefClass;
+import mirror.RefObject;
+
+
+public class BaseBundle {
+    public static Class<?> TYPE = RefClass.load(BaseBundle.class, "android.os.BaseBundle");
+    public static RefObject<Parcel> mParcelledData;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/os/Build.java b/VirtualApp/lib/src/main/java/mirror/android/os/Build.java
new file mode 100644
index 000000000..d27352632
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/os/Build.java
@@ -0,0 +1,11 @@
+package mirror.android.os;
+
+
+import mirror.RefClass;
+import mirror.RefStaticObject;
+
+public class Build {
+    public static Class<?> TYPE = RefClass.load(Build.class, android.os.Build.class);
+    public static RefStaticObject<String> DEVICE;
+    public static RefStaticObject<String> SERIAL;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/os/BundleICS.java b/VirtualApp/lib/src/main/java/mirror/android/os/BundleICS.java
new file mode 100644
index 000000000..4ac77bef3
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/os/BundleICS.java
@@ -0,0 +1,12 @@
+package mirror.android.os;
+
+import android.os.Parcel;
+
+import mirror.RefClass;
+import mirror.RefObject;
+
+
+public class BundleICS {
+    public static Class<?> TYPE = RefClass.load(BundleICS.class, "android.os.Bundle");
+    public static RefObject<Parcel> mParcelledData;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/os/UserHandle.java b/VirtualApp/lib/src/main/java/mirror/android/os/UserHandle.java
new file mode 100644
index 000000000..4dfdcc29c
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/os/UserHandle.java
@@ -0,0 +1,22 @@
+package mirror.android.os;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefMethod;
+
+/**
+ * author: weishu on 18/2/11.
+ */
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+public class UserHandle {
+    public static Class<?> TYPE = RefClass.load(UserHandle.class, android.os.UserHandle.class);
+    @MethodParams({int.class})
+    public static RefConstructor<android.os.UserHandle> ctor;
+    public static RefMethod<Integer> getIdentifier;
+
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/os/storage/IStorageManager.java b/VirtualApp/lib/src/main/java/mirror/android/os/storage/IStorageManager.java
new file mode 100644
index 000000000..d99a588d5
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/os/storage/IStorageManager.java
@@ -0,0 +1,18 @@
+package mirror.android.os.storage;
+
+import android.os.IBinder;
+import android.os.IInterface;
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefStaticMethod;
+
+public class IStorageManager {
+    public static Class<?> Class = RefClass.load(IStorageManager.class, "android.os.storage.IStorageManager");
+
+    public static class Stub {
+        public static Class<?> Class = RefClass.load(Stub.class, "android.os.storage.IStorageManager$Stub");
+        @MethodParams({IBinder.class})
+        public static RefStaticMethod<IInterface> asInterface;
+    }
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/providers/Downloads.java b/VirtualApp/lib/src/main/java/mirror/android/providers/Downloads.java
deleted file mode 100644
index 737cbc047..000000000
--- a/VirtualApp/lib/src/main/java/mirror/android/providers/Downloads.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package mirror.android.providers;
-
-import android.net.Uri;
-
-import mirror.RefClass;
-import mirror.RefStaticObject;
-
-/**
- * @author Lody
- */
-
-public class Downloads {
-    public static RefStaticObject<String> COLUMN_ALLOWED_NETWORK_TYPES;
-    public static RefStaticObject<String> COLUMN_ALLOW_ROAMING;
-    public static RefStaticObject<String> COLUMN_DELETED;
-    public static RefStaticObject<String> COLUMN_DESCRIPTION;
-    public static RefStaticObject<String> COLUMN_DESTINATION;
-    public static RefStaticObject<String> COLUMN_FILE_NAME_HINT;
-    public static RefStaticObject<String> COLUMN_IS_PUBLIC_API;
-    public static RefStaticObject<String> COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI;
-    public static RefStaticObject<String> COLUMN_MEDIA_SCANNED;
-    public static RefStaticObject<String> COLUMN_MIME_TYPE;
-    public static RefStaticObject<String> COLUMN_NOTIFICATION_PACKAGE;
-    public static RefStaticObject<String> COLUMN_TITLE;
-    public static RefStaticObject<String> COLUMN_URI;
-    public static RefStaticObject<String> COLUMN_VISIBILITY;
-    public static RefStaticObject<Uri> CONTENT_URI;
-    public static Class<?> TYPE = RefClass.load(Downloads.class, "android.provider.Downloads$Impl");
-    public static RefStaticObject<Integer> DESTINATION_CACHE_PARTITION_PURGEABLE;
-    public static RefStaticObject<Integer> DESTINATION_FILE_URI;
-
-    public static class Impl {
-        public static class RequestHeaders {
-            public static Class<?> TYPE = RefClass.load(RequestHeaders.class, "android.provider.Downloads$Impl$RequestHeaders");
-            public static RefStaticObject<String> INSERT_KEY_PREFIX;
-        }
-
-    }
-}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/telephony/CellIdentityCdma.java b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellIdentityCdma.java
new file mode 100644
index 000000000..941a08ae2
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellIdentityCdma.java
@@ -0,0 +1,21 @@
+package mirror.android.telephony;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefInt;
+
+/**
+ * @author Lody
+ */
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+public class CellIdentityCdma {
+    public static Class<?> TYPE = RefClass.load(CellIdentityCdma.class, android.telephony.CellIdentityCdma.class);
+    public static RefConstructor<android.telephony.CellIdentityCdma> ctor;
+    public static RefInt mNetworkId;
+    public static RefInt mSystemId;
+    public static RefInt mBasestationId;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/telephony/CellIdentityGsm.java b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellIdentityGsm.java
new file mode 100644
index 000000000..ec42c9c40
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellIdentityGsm.java
@@ -0,0 +1,23 @@
+package mirror.android.telephony;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefInt;
+
+/**
+ * @author Lody
+ */
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+public class CellIdentityGsm {
+    public static Class<?> TYPE = RefClass.load(CellIdentityGsm.class, android.telephony.CellIdentityGsm.class);
+    public static RefConstructor<android.telephony.CellIdentityGsm> ctor;
+    public static RefInt mMcc;
+    public static RefInt mMnc;
+    public static RefInt mLac;
+    public static RefInt mCid;
+
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/telephony/CellInfoCdma.java b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellInfoCdma.java
new file mode 100644
index 000000000..0c7de3880
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellInfoCdma.java
@@ -0,0 +1,21 @@
+package mirror.android.telephony;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.telephony.CellSignalStrengthCdma;
+
+import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefObject;
+
+/**
+ * @author Lody
+ */
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+public class CellInfoCdma {
+    public static Class<?> TYPE = RefClass.load(CellInfoCdma.class, android.telephony.CellInfoCdma.class);
+    public static RefConstructor<android.telephony.CellInfoCdma> ctor;
+    public static RefObject<android.telephony.CellIdentityCdma> mCellIdentityCdma;
+    public static RefObject<CellSignalStrengthCdma> mCellSignalStrengthCdma;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/telephony/CellInfoGsm.java b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellInfoGsm.java
new file mode 100644
index 000000000..a67bbb698
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellInfoGsm.java
@@ -0,0 +1,21 @@
+package mirror.android.telephony;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefObject;
+
+/**
+ * @author Lody
+ */
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+public class CellInfoGsm {
+    public static Class<?> TYPE = RefClass.load(CellInfoGsm.class, android.telephony.CellInfoGsm.class);
+    public static RefConstructor<android.telephony.CellInfoGsm> ctor;
+    public static RefObject<android.telephony.CellIdentityGsm> mCellIdentityGsm;
+    public static RefObject<android.telephony.CellSignalStrengthGsm> mCellSignalStrengthGsm;
+
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/telephony/CellSignalStrengthCdma.java b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellSignalStrengthCdma.java
new file mode 100644
index 000000000..2d6cf6a39
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellSignalStrengthCdma.java
@@ -0,0 +1,23 @@
+package mirror.android.telephony;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefInt;
+
+/**
+ * @author Lody
+ */
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+public class CellSignalStrengthCdma {
+    public static Class<?> TYPE = RefClass.load(CellSignalStrengthCdma.class, android.telephony.CellSignalStrengthCdma.class);
+    public static RefConstructor<android.telephony.CellSignalStrengthCdma> ctor;
+    public static RefInt mCdmaDbm;
+    public static RefInt mCdmaEcio;
+    public static RefInt mEvdoDbm;
+    public static RefInt mEvdoEcio;
+    public static RefInt mEvdoSnr;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/telephony/CellSignalStrengthGsm.java b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellSignalStrengthGsm.java
new file mode 100644
index 000000000..b40f7dc51
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/telephony/CellSignalStrengthGsm.java
@@ -0,0 +1,20 @@
+package mirror.android.telephony;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import mirror.RefClass;
+import mirror.RefConstructor;
+import mirror.RefInt;
+
+/**
+ * @author Lody
+ */
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+public class CellSignalStrengthGsm {
+    public static Class<?> TYPE = RefClass.load(CellSignalStrengthGsm.class, android.telephony.CellSignalStrengthGsm.class);
+    public static RefConstructor<android.telephony.CellSignalStrengthGsm> ctor;
+    public static RefInt mSignalStrength;
+    public static RefInt mBitErrorRate;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/telephony/NeighboringCellInfo.java b/VirtualApp/lib/src/main/java/mirror/android/telephony/NeighboringCellInfo.java
new file mode 100644
index 000000000..e50147004
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/telephony/NeighboringCellInfo.java
@@ -0,0 +1,15 @@
+package mirror.android.telephony;
+
+import mirror.RefClass;
+import mirror.RefInt;
+
+/**
+ * @author Lody
+ */
+
+public class NeighboringCellInfo {
+    public static Class<?> TYPE = RefClass.load(NeighboringCellInfo.class, android.telephony.NeighboringCellInfo.class);
+    public static RefInt mLac;
+    public static RefInt mCid;
+    public static RefInt mRssi;
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/android/view/CompatibilityInfoHolder.java b/VirtualApp/lib/src/main/java/mirror/android/view/CompatibilityInfoHolder.java
new file mode 100644
index 000000000..c149afc29
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/view/CompatibilityInfoHolder.java
@@ -0,0 +1,12 @@
+package mirror.android.view;
+
+
+import mirror.MethodReflectParams;
+import mirror.RefClass;
+import mirror.RefMethod;
+
+public class CompatibilityInfoHolder {
+    public static Class<?> Class = RefClass.load(CompatibilityInfoHolder.class, "android.view.CompatibilityInfoHolder");
+    @MethodReflectParams({"android.content.res.CompatibilityInfo"})
+    public static RefMethod<Void> set;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/view/DisplayAdjustments.java b/VirtualApp/lib/src/main/java/mirror/android/view/DisplayAdjustments.java
new file mode 100644
index 000000000..190010cac
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/view/DisplayAdjustments.java
@@ -0,0 +1,12 @@
+package mirror.android.view;
+
+
+import mirror.MethodReflectParams;
+import mirror.RefClass;
+import mirror.RefMethod;
+
+public class DisplayAdjustments {
+    public static Class<?> Class = RefClass.load(DisplayAdjustments.class, "android.view.DisplayAdjustments");
+    @MethodReflectParams({"android.content.res.CompatibilityInfo"})
+    public static RefMethod<Void> setCompatibilityInfo;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/java/mirror/android/view/IAutoFillManager.java b/VirtualApp/lib/src/main/java/mirror/android/view/IAutoFillManager.java
new file mode 100644
index 000000000..2669e4497
--- /dev/null
+++ b/VirtualApp/lib/src/main/java/mirror/android/view/IAutoFillManager.java
@@ -0,0 +1,24 @@
+package mirror.android.view;
+
+import android.os.IBinder;
+import android.os.IInterface;
+
+import mirror.MethodParams;
+import mirror.RefClass;
+import mirror.RefStaticMethod;
+
+/**
+ * @author 陈磊.
+ */
+
+public class IAutoFillManager {
+
+    public static Class<?> TYPE = RefClass.load(IAutoFillManager.class, "android.view.autofill.IAutoFillManager");
+
+    public static class Stub {
+        public static Class<?> TYPE = RefClass.load(Stub.class, "android.view.autofill.IAutoFillManager$Stub");
+        @MethodParams(IBinder.class)
+        public static RefStaticMethod<IInterface> asInterface;
+    }
+
+}
diff --git a/VirtualApp/lib/src/main/java/mirror/dalvik/system/VMRuntime.java b/VirtualApp/lib/src/main/java/mirror/dalvik/system/VMRuntime.java
index 2ae926db9..3b688d745 100644
--- a/VirtualApp/lib/src/main/java/mirror/dalvik/system/VMRuntime.java
+++ b/VirtualApp/lib/src/main/java/mirror/dalvik/system/VMRuntime.java
@@ -15,4 +15,5 @@ public class VMRuntime {
         @MethodParams({int.class})
         public static RefMethod<Void> setTargetSdkVersion;
         public static RefMethod<Boolean> is64Bit;
+        public static RefStaticMethod<String> getCurrentInstructionSet;
 }
diff --git a/VirtualApp/lib/src/main/jni/Android.mk b/VirtualApp/lib/src/main/jni/Android.mk
index 9889d47b9..ef2b533d9 100644
--- a/VirtualApp/lib/src/main/jni/Android.mk
+++ b/VirtualApp/lib/src/main/jni/Android.mk
@@ -1,66 +1,29 @@
 LOCAL_PATH := $(call my-dir)
+MAIN_LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
-LOCAL_MODULE := va-native
+LOCAL_MODULE := va++
 
-LOCAL_CFLAGS := -Wno-error=format-security -fpermissive
+LOCAL_CFLAGS := -Wno-error=format-security -fpermissive -DLOG_TAG=\"VA++\"
 LOCAL_CFLAGS += -fno-rtti -fno-exceptions
 
-LOCAL_C_INCLUDES += $(LOCAL_PATH)
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/Foundation
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/MSHook
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/GodinHook
+LOCAL_C_INCLUDES += $(MAIN_LOCAL_PATH)
+LOCAL_C_INCLUDES += $(MAIN_LOCAL_PATH)/Foundation
+LOCAL_C_INCLUDES += $(MAIN_LOCAL_PATH)/Jni
 
-ifeq ($(TARGET_ARCH_ABI),x86)
-    ARCH_FILES := \
-        MSHook/MSHook.cpp \
-        MSHook/x86_64.cpp \
-        MSHook/ARM.cpp \
-        MSHook/Debug.cpp \
-        MSHook/Hooker.cpp \
-        MSHook/PosixMemory.cpp \
-        MSHook/Thumb.cpp \
-        MSHook/util.cpp \
-        MSHook/x86.cpp \
-
-else ifeq ($(TARGET_ARCH_ABI),x86_64)
-    ARCH_FILES := \
-        MSHook/MSHook.cpp \
-        MSHook/x86_64.cpp \
-        MSHook/ARM.cpp \
-        MSHook/Debug.cpp \
-        MSHook/Hooker.cpp \
-        MSHook/PosixMemory.cpp \
-        MSHook/Thumb.cpp \
-        MSHook/util.cpp \
-        MSHook/x86.cpp \
-
-else
-    ARCH_FILES := \
-        GodinHook/mem_helper.cpp \
-        GodinHook/instruction/instruction_helper.cpp \
-        GodinHook/instruction/arm_instruction.cpp \
-        GodinHook/instruction/thumb_instruction.cpp \
-        GodinHook/native_hook.cpp \
-        GodinHook/thread_helper.cpp \
-        MSHook/MSHook.cpp \
-        MSHook/x86_64.cpp \
-        MSHook/ARM.cpp \
-        MSHook/Debug.cpp \
-        MSHook/Hooker.cpp \
-        MSHook/PosixMemory.cpp \
-        MSHook/Thumb.cpp \
-        MSHook/util.cpp \
-        MSHook/x86.cpp \
-
-endif
-
-
-LOCAL_SRC_FILES := Core.cpp \
+LOCAL_SRC_FILES := Jni/VAJni.cpp \
 				   Foundation/IOUniformer.cpp \
 				   Foundation/VMPatch.cpp \
-				   $(ARCH_FILES) \
-
-
-LOCAL_LDLIBS := -llog
+				   Foundation/SymbolFinder.cpp \
+				   Foundation/Path.cpp \
+				   Foundation/SandboxFs.cpp \
+				   Foundation/fake_dlfcn.cpp \
+				   Substrate/hde64.c \
+                   Substrate/SubstrateDebug.cpp \
+                   Substrate/SubstrateHook.cpp \
+                   Substrate/SubstratePosixMemory.cpp \
+
+LOCAL_LDLIBS := -llog -latomic
+LOCAL_STATIC_LIBRARIES := fb
 
 include $(BUILD_SHARED_LIBRARY)
+include $(MAIN_LOCAL_PATH)/fb/Android.mk
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/Application.mk b/VirtualApp/lib/src/main/jni/Application.mk
index 6e05cde29..0e53eba8c 100644
--- a/VirtualApp/lib/src/main/jni/Application.mk
+++ b/VirtualApp/lib/src/main/jni/Application.mk
@@ -1,4 +1,6 @@
-APP_ABI := armeabi armeabi-v7a x86
+APP_ABI := armeabi-v7a x86
 APP_PLATFORM := android-14
-APP_STL := stlport_static
-APP_OPTIM := release
\ No newline at end of file
+APP_STL := c++_static
+APP_OPTIM := release
+VA_ROOT          := $(call my-dir)
+NDK_MODULE_PATH  := $(NDK_MODULE_PATH):$(VA_ROOT)
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/Core.cpp b/VirtualApp/lib/src/main/jni/Core.cpp
deleted file mode 100644
index 21db85e67..000000000
--- a/VirtualApp/lib/src/main/jni/Core.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-//
-// VirtualApp Native Project
-//
-#include "Core.h"
-
-
-JavaVM *gVm;
-jclass gClass;
-
-
-void Java_nativeHookNative(JNIEnv *env, jclass jclazz, jobjectArray javaMethods,
-                           jstring packageName,
-                           jboolean isArt, jint apiLevel, jint cameraMethodType) {
-    patchAndroidVM(javaMethods, packageName, isArt, apiLevel, cameraMethodType);
-}
-
-
-void Java_nativeStartUniformer(JNIEnv *env, jclass jclazz, jint apiLevel, jint previewApiLevel) {
-    IOUniformer::startUniformer(apiLevel, previewApiLevel);
-}
-
-void Java_nativeReadOnly(JNIEnv *env, jclass jclazz, jstring _path) {
-    const char *path = env->GetStringUTFChars(_path, NULL);
-    IOUniformer::readOnly(path);
-}
-
-void Java_nativeRedirect(JNIEnv *env, jclass jclazz, jstring orgPath, jstring newPath) {
-    const char *org_path = env->GetStringUTFChars(orgPath, NULL);
-    const char *new_path = env->GetStringUTFChars(newPath, NULL);
-    IOUniformer::redirect(org_path, new_path);
-}
-
-jstring Java_nativeQuery(JNIEnv *env, jclass jclazz, jstring orgPath) {
-    const char *org_path = env->GetStringUTFChars(orgPath, NULL);
-    const char *redirected_path = IOUniformer::query(org_path);
-    return env->NewStringUTF(redirected_path);
-}
-
-jstring Java_nativeRestore(JNIEnv *env, jclass jclazz, jstring redirectedPath) {
-    const char *redirected_path = env->GetStringUTFChars(redirectedPath, NULL);
-    const char *org_path = IOUniformer::restore(redirected_path);
-    return env->NewStringUTF(org_path);
-}
-
-
-static JNINativeMethod gMethods[] = {
-        NATIVE_METHOD((void *) Java_nativeStartUniformer, "nativeStartUniformer", "(II)V"),
-        NATIVE_METHOD((void *) Java_nativeReadOnly, "nativeReadOnly", "(Ljava/lang/String;)V"),
-        NATIVE_METHOD((void *) Java_nativeRedirect, "nativeRedirect",
-                      "(Ljava/lang/String;Ljava/lang/String;)V"),
-        NATIVE_METHOD((void *) Java_nativeQuery, "nativeGetRedirectedPath",
-                      "(Ljava/lang/String;)Ljava/lang/String;"),
-        NATIVE_METHOD((void *) Java_nativeRestore, "nativeRestoreRedirectedPath",
-                      "(Ljava/lang/String;)Ljava/lang/String;"),
-
-        NATIVE_METHOD((void *) Java_nativeHookNative, "nativeHookNative",
-                      "(Ljava/lang/Object;Ljava/lang/String;ZII)V"),
-};
-
-
-JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
-    JNIEnv *env;
-    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
-        return JNI_ERR;
-    }
-    jclass javaClass = env->FindClass(JAVA_CLASS);
-    if (javaClass == NULL) {
-        LOGE("Error: Unable to find the IOHook class.");
-        return JNI_ERR;
-    }
-    if (env->RegisterNatives(javaClass, gMethods, NELEM(gMethods)) < 0) {
-        LOGE("Error: Unable to register the native methods.");
-        return JNI_ERR;
-    }
-    gVm = vm;
-    gClass = (jclass) env->NewGlobalRef(javaClass);
-    env->DeleteLocalRef(javaClass);
-    return JNI_VERSION_1_6;
-}
-
-
-JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
-    JNIEnv *env;
-    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
-        return;
-    }
-    env->DeleteGlobalRef((jobject) gVm);
-    env->DeleteGlobalRef((jobject) gClass);
-}
-
diff --git a/VirtualApp/lib/src/main/jni/Foundation/IOUniformer.cpp b/VirtualApp/lib/src/main/jni/Foundation/IOUniformer.cpp
index 16979b053..d30d19c11 100644
--- a/VirtualApp/lib/src/main/jni/Foundation/IOUniformer.cpp
+++ b/VirtualApp/lib/src/main/jni/Foundation/IOUniformer.cpp
@@ -1,151 +1,113 @@
 //
 // VirtualApp Native Project
 //
-#include <util.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fb/include/fb/ALog.h>
+#include <Substrate/CydiaSubstrate.h>
+
 #include "IOUniformer.h"
-#include "native_hook.h"
+#include "SandboxFs.h"
+#include "Path.h"
+#include "SymbolFinder.h"
+
+bool iu_loaded = false;
 
-static list<std::string> ReadOnlyPathMap;
-static std::map<std::string/*orig_path*/, std::string/*new_path*/> IORedirectMap;
-static std::map<std::string/*orig_path*/, std::string/*new_path*/> RootIORedirectMap;
+void IOUniformer::init_env_before_all() {
+    if (iu_loaded)
+        return;
+    char *api_level_chars = getenv("V_API_LEVEL");
+    char *preview_api_level_chars = getenv("V_PREVIEW_API_LEVEL");
+    if (api_level_chars) {
+        ALOGE("Enter init before all.");
+        int api_level = atoi(api_level_chars);
+        int preview_api_level;
+        preview_api_level = atoi(preview_api_level_chars);
+        char keep_env_name[25];
+        char forbid_env_name[25];
+        char replace_src_env_name[25];
+        char replace_dst_env_name[25];
+        int i = 0;
+        while (true) {
+            sprintf(keep_env_name, "V_KEEP_ITEM_%d", i);
+            char *item = getenv(keep_env_name);
+            if (!item) {
+                break;
+            }
+            add_keep_item(item);
+            i++;
+        }
+        i = 0;
+        while (true) {
+            sprintf(forbid_env_name, "V_FORBID_ITEM_%d", i);
+            char *item = getenv(forbid_env_name);
+            if (!item) {
+                break;
+            }
+            add_forbidden_item(item);
+            i++;
+        }
+        i = 0;
+        while (true) {
+            sprintf(replace_src_env_name, "V_REPLACE_ITEM_SRC_%d", i);
+            char *item_src = getenv(replace_src_env_name);
+            if (!item_src) {
+                break;
+            }
+            sprintf(replace_dst_env_name, "V_REPLACE_ITEM_DST_%d", i);
+            char *item_dst = getenv(replace_dst_env_name);
+            add_replace_item(item_src, item_dst);
+            i++;
+        }
+        startUniformer(getenv("V_SO_PATH"),api_level, preview_api_level);
+        iu_loaded = true;
+    }
+}
 
 
-/**
- *
- * NOTICE:
- *   We use MSHook to hook symbol on x86 & X64.
- *   But on ARM, we use GodinHook to instead of it.
- */
 static inline void
-hook_template(void *handle, const char *symbol, void *new_func, void **old_func) {
+hook_function(void *handle, const char *symbol, void *new_func, void **old_func) {
     void *addr = dlsym(handle, symbol);
     if (addr == NULL) {
-        LOGW("Error: unable to find the Symbol : %s.", symbol);
         return;
     }
-#if defined(__i386__) || defined(__x86_64__)
-    inlineHookDirect((unsigned int) (addr), new_func, old_func);
-#else
-    GodinHook::NativeHook::registeredHook((size_t) addr, (size_t) new_func, (size_t **) old_func);
-#endif
+    MSHookFunction(addr, new_func, old_func);
 }
 
 
 void onSoLoaded(const char *name, void *handle);
 
-
-static inline bool startWith(const std::string &str, const std::string &prefix) {
-    return str.compare(0, prefix.length(), prefix) == 0;
-}
-
-
-static inline bool endWith(const std::string &str, const char &suffix) {
-    return *(str.end() - 1) == suffix;
-}
-
-static void add_pair(const char *_orig_path, const char *_new_path) {
-    std::string origPath = std::string(_orig_path);
-    std::string newPath = std::string(_new_path);
-    IORedirectMap.insert(std::pair<std::string, std::string>(origPath, newPath));
-    if (endWith(origPath, '/')) {
-        RootIORedirectMap.insert(
-                std::pair<std::string, std::string>(
-                        origPath.substr(0, origPath.length() - 1),
-                        newPath.substr(0, newPath.length() - 1))
-        );
-    }
-}
-
-
-const char *match_redirected_path(const char *_path) {
-    std::string path(_path);
-    if (path.length() <= 1) {
-        return _path;
-    }
-    std::map<std::string, std::string>::iterator iterator;
-    iterator = RootIORedirectMap.find(path);
-    if (iterator != RootIORedirectMap.end()) {
-        return strdup(iterator->second.c_str());
-    }
-
-    for (iterator = IORedirectMap.begin(); iterator != IORedirectMap.end(); iterator++) {
-        const std::string &prefix = iterator->first;
-        const std::string &new_prefix = iterator->second;
-        if (startWith(path, prefix)) {
-            std::string new_path = new_prefix + path.substr(prefix.length(), path.length());
-            return strdup(new_path.c_str());
-        }
-    }
-    return _path;
-}
-
-
 void IOUniformer::redirect(const char *orig_path, const char *new_path) {
-    LOGI("Start Java_nativeRedirect : from %s to %s", orig_path, new_path);
-    add_pair(orig_path, new_path);
+    add_replace_item(orig_path, new_path);
 }
 
 const char *IOUniformer::query(const char *orig_path) {
-    return match_redirected_path(orig_path);
+    int res;
+    return relocate_path(orig_path, &res);
 }
 
-void IOUniformer::readOnly(const char *_path) {
-    std::string path(_path);
-    ReadOnlyPathMap.push_back(path);
+void IOUniformer::whitelist(const char *_path) {
+    add_keep_item(_path);
 }
 
-bool isReadOnlyPath(const char *_path) {
-    std::string path(_path);
-    list<std::string>::iterator it;
-    for (it = ReadOnlyPathMap.begin(); it != ReadOnlyPathMap.end(); ++it) {
-        if (startWith(path, *it)) {
-            return true;
-        }
-    }
-    return false;
+void IOUniformer::forbid(const char *_path) {
+    add_forbidden_item(_path);
 }
 
 
-const char *IOUniformer::restore(const char *_path) {
-    if (_path == NULL) {
-        return NULL;
-    }
-    std::string path(_path);
-    if (path.length() <= 1) {
-        return _path;
-    }
-    std::map<std::string, std::string>::iterator iterator;
-    iterator = RootIORedirectMap.find(path);
-    if (iterator != RootIORedirectMap.end()) {
-        return strdup(iterator->second.c_str());
-    }
-    for (iterator = RootIORedirectMap.begin(); iterator != RootIORedirectMap.end(); iterator++) {
-        const std::string &origin = iterator->first;
-        const std::string &redirected = iterator->second;
-        if (path == redirected) {
-            return strdup(origin.c_str());
-        }
-    }
-
-    for (iterator = IORedirectMap.begin(); iterator != IORedirectMap.end(); iterator++) {
-        const std::string &prefix = iterator->first;
-        const std::string &new_prefix = iterator->second;
-        if (startWith(path, new_prefix)) {
-            std::string origin_path = prefix + path.substr(new_prefix.length(), path.length());
-            return strdup(origin_path.c_str());
-        }
-    }
-    return _path;
+const char *IOUniformer::reverse(const char *_path) {
+    return reverse_relocate_path(_path);
 }
 
 
 __BEGIN_DECLS
 
-
+#define FREE(ptr, org_ptr) { if ((void*) ptr != NULL && (void*) ptr != (void*) org_ptr) { free((void*) ptr); } }
 
 // int faccessat(int dirfd, const char *pathname, int mode, int flags);
 HOOK_DEF(int, faccessat, int dirfd, const char *pathname, int mode, int flags) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_faccessat, dirfd, redirect_path, mode, flags);
     FREE(redirect_path, pathname);
     return ret;
@@ -154,20 +116,16 @@ HOOK_DEF(int, faccessat, int dirfd, const char *pathname, int mode, int flags) {
 
 // int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags);
 HOOK_DEF(int, fchmodat, int dirfd, const char *pathname, mode_t mode, int flags) {
-    const char *redirect_path = match_redirected_path(pathname);
-    if (isReadOnlyPath(redirect_path)) {
-        return -1;
-    }
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_fchmodat, dirfd, redirect_path, mode, flags);
     FREE(redirect_path, pathname);
     return ret;
 }
 // int fchmod(const char *pathname, mode_t mode);
 HOOK_DEF(int, fchmod, const char *pathname, mode_t mode) {
-    const char *redirect_path = match_redirected_path(pathname);
-    if (isReadOnlyPath(redirect_path)) {
-        return -1;
-    }
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_chmod, redirect_path, mode);
     FREE(redirect_path, pathname);
     return ret;
@@ -176,14 +134,27 @@ HOOK_DEF(int, fchmod, const char *pathname, mode_t mode) {
 
 // int fstatat(int dirfd, const char *pathname, struct stat *buf, int flags);
 HOOK_DEF(int, fstatat, int dirfd, const char *pathname, struct stat *buf, int flags) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_fstatat64, dirfd, redirect_path, buf, flags);
     FREE(redirect_path, pathname);
     return ret;
 }
+
+// int fstatat64(int dirfd, const char *pathname, struct stat *buf, int flags);
+HOOK_DEF(int, fstatat64, int dirfd, const char *pathname, struct stat *buf, int flags) {
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
+    int ret = syscall(__NR_fstatat64, dirfd, redirect_path, buf, flags);
+    FREE(redirect_path, pathname);
+    return ret;
+}
+
+
 // int fstat(const char *pathname, struct stat *buf, int flags);
 HOOK_DEF(int, fstat, const char *pathname, struct stat *buf) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_fstat64, redirect_path, buf);
     FREE(redirect_path, pathname);
     return ret;
@@ -192,14 +163,16 @@ HOOK_DEF(int, fstat, const char *pathname, struct stat *buf) {
 
 // int mknodat(int dirfd, const char *pathname, mode_t mode, dev_t dev);
 HOOK_DEF(int, mknodat, int dirfd, const char *pathname, mode_t mode, dev_t dev) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_mknodat, dirfd, redirect_path, mode, dev);
     FREE(redirect_path, pathname);
     return ret;
 }
 // int mknod(const char *pathname, mode_t mode, dev_t dev);
 HOOK_DEF(int, mknod, const char *pathname, mode_t mode, dev_t dev) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_mknod, redirect_path, mode, dev);
     FREE(redirect_path, pathname);
     return ret;
@@ -209,7 +182,8 @@ HOOK_DEF(int, mknod, const char *pathname, mode_t mode, dev_t dev) {
 // int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);
 HOOK_DEF(int, utimensat, int dirfd, const char *pathname, const struct timespec times[2],
          int flags) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_utimensat, dirfd, redirect_path, times, flags);
     FREE(redirect_path, pathname);
     return ret;
@@ -218,10 +192,8 @@ HOOK_DEF(int, utimensat, int dirfd, const char *pathname, const struct timespec
 
 // int fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags);
 HOOK_DEF(int, fchownat, int dirfd, const char *pathname, uid_t owner, gid_t group, int flags) {
-    const char *redirect_path = match_redirected_path(pathname);
-    if (isReadOnlyPath(redirect_path)) {
-        return -1;
-    }
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_fchownat, dirfd, redirect_path, owner, group, flags);
     FREE(redirect_path, pathname);
     return ret;
@@ -229,7 +201,8 @@ HOOK_DEF(int, fchownat, int dirfd, const char *pathname, uid_t owner, gid_t grou
 
 // int chroot(const char *pathname);
 HOOK_DEF(int, chroot, const char *pathname) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_chroot, redirect_path);
     FREE(redirect_path, pathname);
     return ret;
@@ -238,11 +211,10 @@ HOOK_DEF(int, chroot, const char *pathname) {
 
 // int renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
 HOOK_DEF(int, renameat, int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
-    const char *redirect_path_old = match_redirected_path(oldpath);
-    const char *redirect_path_new = match_redirected_path(newpath);
-    if (isReadOnlyPath(redirect_path_old) || isReadOnlyPath(redirect_path_new)) {
-        return -1;
-    }
+    int res_old;
+    int res_new;
+    const char *redirect_path_old = relocate_path(oldpath, &res_old);
+    const char *redirect_path_new = relocate_path(newpath, &res_new);
     int ret = syscall(__NR_renameat, olddirfd, redirect_path_old, newdirfd, redirect_path_new);
     FREE(redirect_path_old, oldpath);
     FREE(redirect_path_new, newpath);
@@ -250,11 +222,10 @@ HOOK_DEF(int, renameat, int olddirfd, const char *oldpath, int newdirfd, const c
 }
 // int rename(const char *oldpath, const char *newpath);
 HOOK_DEF(int, rename, const char *oldpath, const char *newpath) {
-    const char *redirect_path_old = match_redirected_path(oldpath);
-    const char *redirect_path_new = match_redirected_path(newpath);
-    if (isReadOnlyPath(redirect_path_old) || isReadOnlyPath(redirect_path_new)) {
-        return -1;
-    }
+    int res_old;
+    int res_new;
+    const char *redirect_path_old = relocate_path(oldpath, &res_old);
+    const char *redirect_path_new = relocate_path(newpath, &res_new);
     int ret = syscall(__NR_rename, redirect_path_old, redirect_path_new);
     FREE(redirect_path_old, oldpath);
     FREE(redirect_path_new, newpath);
@@ -264,20 +235,16 @@ HOOK_DEF(int, rename, const char *oldpath, const char *newpath) {
 
 // int unlinkat(int dirfd, const char *pathname, int flags);
 HOOK_DEF(int, unlinkat, int dirfd, const char *pathname, int flags) {
-    const char *redirect_path = match_redirected_path(pathname);
-    if (isReadOnlyPath(redirect_path)) {
-        return -1;
-    }
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_unlinkat, dirfd, redirect_path, flags);
     FREE(redirect_path, pathname);
     return ret;
 }
 // int unlink(const char *pathname);
 HOOK_DEF(int, unlink, const char *pathname) {
-    const char *redirect_path = match_redirected_path(pathname);
-    if (isReadOnlyPath(redirect_path)) {
-        return -1;
-    }
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_unlink, redirect_path);
     FREE(redirect_path, pathname);
     return ret;
@@ -286,8 +253,10 @@ HOOK_DEF(int, unlink, const char *pathname) {
 
 // int symlinkat(const char *oldpath, int newdirfd, const char *newpath);
 HOOK_DEF(int, symlinkat, const char *oldpath, int newdirfd, const char *newpath) {
-    const char *redirect_path_old = match_redirected_path(oldpath);
-    const char *redirect_path_new = match_redirected_path(newpath);
+    int res_old;
+    int res_new;
+    const char *redirect_path_old = relocate_path(oldpath, &res_old);
+    const char *redirect_path_new = relocate_path(newpath, &res_new);
     int ret = syscall(__NR_symlinkat, redirect_path_old, newdirfd, redirect_path_new);
     FREE(redirect_path_old, oldpath);
     FREE(redirect_path_new, newpath);
@@ -295,11 +264,10 @@ HOOK_DEF(int, symlinkat, const char *oldpath, int newdirfd, const char *newpath)
 }
 // int symlink(const char *oldpath, const char *newpath);
 HOOK_DEF(int, symlink, const char *oldpath, const char *newpath) {
-    const char *redirect_path_old = match_redirected_path(oldpath);
-    const char *redirect_path_new = match_redirected_path(newpath);
-    if (isReadOnlyPath(redirect_path_old) || isReadOnlyPath(newpath)) {
-        return -1;
-    }
+    int res_old;
+    int res_new;
+    const char *redirect_path_old = relocate_path(oldpath, &res_old);
+    const char *redirect_path_new = relocate_path(newpath, &res_new);
     int ret = syscall(__NR_symlink, redirect_path_old, redirect_path_new);
     FREE(redirect_path_old, oldpath);
     FREE(redirect_path_new, newpath);
@@ -310,11 +278,10 @@ HOOK_DEF(int, symlink, const char *oldpath, const char *newpath) {
 // int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags);
 HOOK_DEF(int, linkat, int olddirfd, const char *oldpath, int newdirfd, const char *newpath,
          int flags) {
-    const char *redirect_path_old = match_redirected_path(oldpath);
-    const char *redirect_path_new = match_redirected_path(newpath);
-    if (isReadOnlyPath(redirect_path_old) || isReadOnlyPath(newpath)) {
-        return -1;
-    }
+    int res_old;
+    int res_new;
+    const char *redirect_path_old = relocate_path(oldpath, &res_old);
+    const char *redirect_path_new = relocate_path(newpath, &res_new);
     int ret = syscall(__NR_linkat, olddirfd, redirect_path_old, newdirfd, redirect_path_new, flags);
     FREE(redirect_path_old, oldpath);
     FREE(redirect_path_new, newpath);
@@ -322,8 +289,10 @@ HOOK_DEF(int, linkat, int olddirfd, const char *oldpath, int newdirfd, const cha
 }
 // int link(const char *oldpath, const char *newpath);
 HOOK_DEF(int, link, const char *oldpath, const char *newpath) {
-    const char *redirect_path_old = match_redirected_path(oldpath);
-    const char *redirect_path_new = match_redirected_path(newpath);
+    int res_old;
+    int res_new;
+    const char *redirect_path_old = relocate_path(oldpath, &res_old);
+    const char *redirect_path_new = relocate_path(newpath, &res_new);
     int ret = syscall(__NR_link, redirect_path_old, redirect_path_new);
     FREE(redirect_path_old, oldpath);
     FREE(redirect_path_new, newpath);
@@ -333,7 +302,8 @@ HOOK_DEF(int, link, const char *oldpath, const char *newpath) {
 
 // int utimes(const char *filename, const struct timeval *tvp);
 HOOK_DEF(int, utimes, const char *pathname, const struct timeval *tvp) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_utimes, redirect_path, tvp);
     FREE(redirect_path, pathname);
     return ret;
@@ -342,10 +312,8 @@ HOOK_DEF(int, utimes, const char *pathname, const struct timeval *tvp) {
 
 // int access(const char *pathname, int mode);
 HOOK_DEF(int, access, const char *pathname, int mode) {
-    const char *redirect_path = match_redirected_path(pathname);
-    if (mode & W_OK && isReadOnlyPath(redirect_path)) {
-        return -1;
-    }
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_access, redirect_path, mode);
     FREE(redirect_path, pathname);
     return ret;
@@ -354,10 +322,8 @@ HOOK_DEF(int, access, const char *pathname, int mode) {
 
 // int chmod(const char *path, mode_t mode);
 HOOK_DEF(int, chmod, const char *pathname, mode_t mode) {
-    const char *redirect_path = match_redirected_path(pathname);
-    if (isReadOnlyPath(redirect_path)) {
-        return -1;
-    }
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_chmod, redirect_path, mode);
     FREE(redirect_path, pathname);
     return ret;
@@ -366,7 +332,8 @@ HOOK_DEF(int, chmod, const char *pathname, mode_t mode) {
 
 // int chown(const char *path, uid_t owner, gid_t group);
 HOOK_DEF(int, chown, const char *pathname, uid_t owner, gid_t group) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_chown, redirect_path, owner, group);
     FREE(redirect_path, pathname);
     return ret;
@@ -375,7 +342,8 @@ HOOK_DEF(int, chown, const char *pathname, uid_t owner, gid_t group) {
 
 // int lstat(const char *path, struct stat *buf);
 HOOK_DEF(int, lstat, const char *pathname, struct stat *buf) {
-    char *redirect_path = const_cast<char *>(match_redirected_path(pathname));
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_lstat64, redirect_path, buf);
     FREE(redirect_path, pathname);
     return ret;
@@ -384,7 +352,8 @@ HOOK_DEF(int, lstat, const char *pathname, struct stat *buf) {
 
 // int stat(const char *path, struct stat *buf);
 HOOK_DEF(int, stat, const char *pathname, struct stat *buf) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_stat64, redirect_path, buf);
     FREE(redirect_path, pathname);
     return ret;
@@ -393,14 +362,16 @@ HOOK_DEF(int, stat, const char *pathname, struct stat *buf) {
 
 // int mkdirat(int dirfd, const char *pathname, mode_t mode);
 HOOK_DEF(int, mkdirat, int dirfd, const char *pathname, mode_t mode) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_mkdirat, dirfd, redirect_path, mode);
     FREE(redirect_path, pathname);
     return ret;
 }
 // int mkdir(const char *pathname, mode_t mode);
 HOOK_DEF(int, mkdir, const char *pathname, mode_t mode) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_mkdir, redirect_path, mode);
     FREE(redirect_path, pathname);
     return ret;
@@ -409,7 +380,8 @@ HOOK_DEF(int, mkdir, const char *pathname, mode_t mode) {
 
 // int rmdir(const char *pathname);
 HOOK_DEF(int, rmdir, const char *pathname) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_rmdir, redirect_path);
     FREE(redirect_path, pathname);
     return ret;
@@ -418,15 +390,17 @@ HOOK_DEF(int, rmdir, const char *pathname) {
 
 // int readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz);
 HOOK_DEF(int, readlinkat, int dirfd, const char *pathname, char *buf, size_t bufsiz) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_readlinkat, dirfd, redirect_path, buf, bufsiz);
     FREE(redirect_path, pathname);
     return ret;
 }
 // ssize_t readlink(const char *path, char *buf, size_t bufsiz);
 HOOK_DEF(ssize_t, readlink, const char *pathname, char *buf, size_t bufsiz) {
-    const char *redirect_path = match_redirected_path(pathname);
-    ssize_t ret = static_cast<ssize_t>(syscall(__NR_readlink, redirect_path, buf, bufsiz));
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
+    ssize_t ret = syscall(__NR_readlink, redirect_path, buf, bufsiz);
     FREE(redirect_path, pathname);
     return ret;
 }
@@ -434,7 +408,8 @@ HOOK_DEF(ssize_t, readlink, const char *pathname, char *buf, size_t bufsiz) {
 
 // int __statfs64(const char *path, size_t size, struct statfs *stat);
 HOOK_DEF(int, __statfs64, const char *pathname, size_t size, struct statfs *stat) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_statfs64, redirect_path, size, stat);
     FREE(redirect_path, pathname);
     return ret;
@@ -443,15 +418,20 @@ HOOK_DEF(int, __statfs64, const char *pathname, size_t size, struct statfs *stat
 
 // int truncate(const char *path, off_t length);
 HOOK_DEF(int, truncate, const char *pathname, off_t length) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_truncate, redirect_path, length);
     FREE(redirect_path, pathname);
     return ret;
 }
 
+#define RETURN_IF_FORBID if(res == FORBID) return -1;
+
 // int truncate64(const char *pathname, off_t length);
 HOOK_DEF(int, truncate64, const char *pathname, off_t length) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
+    RETURN_IF_FORBID
     int ret = syscall(__NR_truncate64, redirect_path, length);
     FREE(redirect_path, pathname);
     return ret;
@@ -460,7 +440,9 @@ HOOK_DEF(int, truncate64, const char *pathname, off_t length) {
 
 // int chdir(const char *path);
 HOOK_DEF(int, chdir, const char *pathname) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
+    RETURN_IF_FORBID
     int ret = syscall(__NR_chdir, redirect_path);
     FREE(redirect_path, pathname);
     return ret;
@@ -470,89 +452,207 @@ HOOK_DEF(int, chdir, const char *pathname) {
 // int __getcwd(char *buf, size_t size);
 HOOK_DEF(int, __getcwd, char *buf, size_t size) {
     int ret = syscall(__NR_getcwd, buf, size);
+    if (!ret) {
+
+    }
     return ret;
 }
 
 
 // int __openat(int fd, const char *pathname, int flags, int mode);
 HOOK_DEF(int, __openat, int fd, const char *pathname, int flags, int mode) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_openat, fd, redirect_path, flags, mode);
     FREE(redirect_path, pathname);
     return ret;
 }
 // int __open(const char *pathname, int flags, int mode);
 HOOK_DEF(int, __open, const char *pathname, int flags, int mode) {
-    const char *redirect_path = match_redirected_path(pathname);
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_open, redirect_path, flags, mode);
     FREE(redirect_path, pathname);
     return ret;
 }
 
+// int __statfs (__const char *__file, struct statfs *__buf);
+HOOK_DEF(int, __statfs, __const char *__file, struct statfs *__buf) {
+    int res;
+    const char *redirect_path = relocate_path(__file, &res);
+    int ret = syscall(__NR_statfs, redirect_path, __buf);
+    FREE(redirect_path, __file);
+    return ret;
+}
+
 // int lchown(const char *pathname, uid_t owner, gid_t group);
 HOOK_DEF(int, lchown, const char *pathname, uid_t owner, gid_t group) {
-    const char *redirect_path = match_redirected_path(pathname);
-    if (isReadOnlyPath(redirect_path)) {
-        return -1;
-    }
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
     int ret = syscall(__NR_lchown, redirect_path, owner, group);
     FREE(redirect_path, pathname);
     return ret;
 }
 
-// int (*origin_execve)(const char *pathname, char *const argv[], char *const envp[]);
-HOOK_DEF(int, execve, const char *pathname, char *const argv[], char *const envp[]) {
+int inline getArrayItemCount(char *const array[]) {
+    int i;
+    for (i = 0; array[i]; ++i);
+    return i;
+}
 
-    /**
-     * TODO (RUC):
-     * Fix the LD_PRELOAD.
-     * Now we just fill it.
-     */
-    if (!strcmp(pathname, "dex2oat")) {
-        for (int i = 0; envp[i] != NULL; ++i) {
-            if (!strncmp(envp[i], "LD_PRELOAD=", 11)) {
-                const_cast<char **>(envp)[i] = getenv("LD_PRELOAD");
-            }
+
+char **build_new_env(char *const envp[]) {
+    char *provided_ld_preload = NULL;
+    int provided_ld_preload_index = -1;
+    int orig_envp_count = getArrayItemCount(envp);
+
+    for (int i = 0; i < orig_envp_count; i++) {
+        if (strstr(envp[i], "LD_PRELOAD")) {
+            provided_ld_preload = envp[i];
+            provided_ld_preload_index = i;
+        }
+    }
+    char ld_preload[200];
+    char *so_path = getenv("V_SO_PATH");
+    if (provided_ld_preload) {
+        sprintf(ld_preload, "LD_PRELOAD=%s:%s", so_path, provided_ld_preload + 11);
+    } else {
+        sprintf(ld_preload, "LD_PRELOAD=%s", so_path);
+    }
+    int new_envp_count = orig_envp_count
+                         + get_keep_item_count()
+                         + get_forbidden_item_count()
+                         + get_replace_item_count() * 2 + 1;
+    if (provided_ld_preload) {
+        new_envp_count--;
+    }
+    char **new_envp = (char **) malloc(new_envp_count * sizeof(char *));
+    int cur = 0;
+    new_envp[cur++] = ld_preload;
+    for (int i = 0; i < orig_envp_count; ++i) {
+        if (i != provided_ld_preload_index) {
+            new_envp[cur++] = envp[i];
+        }
+    }
+    for (int i = 0; environ[i]; ++i) {
+        if (environ[i][0] == 'V' && environ[i][1] == '_') {
+            new_envp[cur++] = environ[i];
+        }
+    }
+    new_envp[cur] = NULL;
+    return new_envp;
+}
+
+char **build_new_argv(char *const envp[]) {
+    char *provided_ld_preload = NULL;
+    int provided_ld_preload_index = -1;
+    int orig_envp_count = getArrayItemCount(envp);
+
+    for (int i = 0; i < orig_envp_count; i++) {
+        if (strstr(envp[i], "compiler-filter")) {
+            provided_ld_preload = envp[i];
+            provided_ld_preload_index = i;
+        }
+    }
+    char ld_preload[40];
+    if (provided_ld_preload) {
+        sprintf(ld_preload, "--compiler-filter=%s", "everything");
+    }
+
+    char *api_level_char = getenv("V_API_LEVEL");
+    int api_level = atoi(api_level_char);
+
+    int new_envp_count = orig_envp_count + 4;
+    char **new_envp = (char **) malloc(new_envp_count * sizeof(char *));
+    int cur = 0;
+    for (int i = 0; i < orig_envp_count; ++i) {
+        if (i != provided_ld_preload_index) {
+            new_envp[cur++] = envp[i];
+        } else {
+            new_envp[i] = ld_preload;
+            cur++;
         }
     }
 
-    LOGD("execve: %s, LD_PRELOAD: %s.", pathname, getenv("LD_PRELOAD"));
-    for (int i = 0; argv[i] != NULL; ++i) {
-        LOGD("argv[%i] : %s", i, argv[i]);
+    if (api_level >= 22) {
+        new_envp[cur++] = (char *) "--compile-pic";
     }
-    for (int i = 0; envp[i] != NULL; ++i) {
-        LOGD("envp[%i] : %s", i, envp[i]);
+    if (api_level >= 23) {
+        new_envp[cur++] = (char *) (api_level > 25 ? "--inline-max-code-units=0" : "--inline-depth-limit=0");
+    }
+    if (api_level >= 28) {
+        new_envp[cur++] = (char *) "--debuggable";
+    }
+    new_envp[cur] = NULL;
+
+//    int n = getArrayItemCount(new_envp);
+//    for (int i = 0; i < n; i++) {
+//        ALOGE("dex2oat : %s", new_envp[i]);
+//    }
+
+    return new_envp;
+}
+
+// int (*origin_execve)(const char *pathname, char *const argv[], char *const envp[]);
+HOOK_DEF(int, execve, const char *pathname, char *argv[], char *const envp[]) {
+    /**
+     * CANNOT LINK EXECUTABLE "/system/bin/cat": "/data/app/io.virtualapp-1/lib/arm/libva-native.so" is 32-bit instead of 64-bit.
+     *
+     * We will support 64Bit to adopt it.
+     */
+    // ALOGE("execve : %s", pathname); // any output can break exec. See bug: https://issuetracker.google.com/issues/109448553
+    int res;
+    const char *redirect_path = relocate_path(pathname, &res);
+    char *ld = getenv("LD_PRELOAD");
+    if (ld) {
+        if (strstr(ld, "libNimsWrap.so") || strstr(ld, "stamina.so")) {
+            int ret = syscall(__NR_execve, redirect_path, argv, envp);
+            FREE(redirect_path, pathname);
+            return ret;
+        }
+    }
+    if (strstr(pathname, "dex2oat")) {
+        char **new_envp = build_new_env(envp);
+        char **new_argv = build_new_argv(argv);
+        int ret = syscall(__NR_execve, redirect_path, new_argv, new_envp);
+        FREE(redirect_path, pathname);
+        free(new_envp);
+        free(new_argv);
+        return ret;
     }
-    const char *redirect_path = match_redirected_path(pathname);
     int ret = syscall(__NR_execve, redirect_path, argv, envp);
     FREE(redirect_path, pathname);
     return ret;
 }
 
+
 HOOK_DEF(void*, dlopen, const char *filename, int flag) {
-    const char *redirect_path = match_redirected_path(filename);
+    int res;
+    const char *redirect_path = relocate_path(filename, &res);
     void *ret = orig_dlopen(redirect_path, flag);
     onSoLoaded(filename, ret);
-    LOGD("dlopen : %s, return : %p.", redirect_path, ret);
+    ALOGD("dlopen : %s, return : %p.", redirect_path, ret);
     FREE(redirect_path, filename);
     return ret;
 }
 
 HOOK_DEF(void*, do_dlopen_V19, const char *filename, int flag, const void *extinfo) {
-    const char *redirect_path = match_redirected_path(filename);
+    int res;
+    const char *redirect_path = relocate_path(filename, &res);
     void *ret = orig_do_dlopen_V19(redirect_path, flag, extinfo);
     onSoLoaded(filename, ret);
-    LOGD("do_dlopen : %s, return : %p.", redirect_path, ret);
+    ALOGD("do_dlopen : %s, return : %p.", redirect_path, ret);
     FREE(redirect_path, filename);
     return ret;
 }
 
 HOOK_DEF(void*, do_dlopen_V24, const char *name, int flags, const void *extinfo,
          void *caller_addr) {
-    const char *redirect_path = match_redirected_path(name);
+    int res;
+    const char *redirect_path = relocate_path(name, &res);
     void *ret = orig_do_dlopen_V24(redirect_path, flags, extinfo, caller_addr);
     onSoLoaded(name, ret);
-    LOGD("do_dlopen : %s, return : %p.", redirect_path, ret);
+    ALOGD("do_dlopen : %s, return : %p.", redirect_path, ret);
     FREE(redirect_path, name);
     return ret;
 }
@@ -561,24 +661,21 @@ HOOK_DEF(void*, do_dlopen_V24, const char *name, int flags, const void *extinfo,
 
 //void *dlsym(void *handle,const char *symbol)
 HOOK_DEF(void*, dlsym, void *handle, char *symbol) {
-    LOGD("dlsym : %p %s.", handle, symbol);
+    ALOGD("dlsym : %p %s.", handle, symbol);
     return orig_dlsym(handle, symbol);
 }
 
 // int kill(pid_t pid, int sig);
 HOOK_DEF(int, kill, pid_t pid, int sig) {
-    LOGD(">>>>> kill >>> pid: %d, sig: %d.", pid, sig);
-    extern JavaVM *gVm;
-    extern jclass gClass;
-    JNIEnv *env = NULL;
-    gVm->GetEnv((void **) &env, JNI_VERSION_1_4);
-    gVm->AttachCurrentThread(&env, NULL);
-    jmethodID method = env->GetStaticMethodID(gClass, "onKillProcess", "(II)V");
-    env->CallStaticVoidMethod(gClass, method, pid, sig);
+    ALOGD(">>>>> kill >>> pid: %d, sig: %d.", pid, sig);
     int ret = syscall(__NR_kill, pid, sig);
     return ret;
 }
 
+HOOK_DEF(pid_t, vfork) {
+    return fork();
+}
+
 __END_DECLS
 // end IO DEF
 
@@ -586,76 +683,90 @@ __END_DECLS
 void onSoLoaded(const char *name, void *handle) {
 }
 
+int findSymbol(const char *name, const char *libn,
+               unsigned long *addr) {
+    return find_name(getpid(), name, libn, addr);
+}
 
 void hook_dlopen(int api_level) {
     void *symbol = NULL;
-    if (api_level > 23) {
+    if (api_level > 25) {
+        if (findSymbol("__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv", "linker",
+                       (unsigned long *) &symbol) == 0) {
+            MSHookFunction(symbol, (void *) new_do_dlopen_V24,
+                           (void **) &orig_do_dlopen_V24);
+        }
+    } else if (api_level > 23) {
         if (findSymbol("__dl__Z9do_dlopenPKciPK17android_dlextinfoPv", "linker",
                        (unsigned long *) &symbol) == 0) {
-            inlineHookDirect((unsigned int) symbol, (void *) new_do_dlopen_V24,
-                             (void **) &orig_do_dlopen_V24);
+            MSHookFunction(symbol, (void *) new_do_dlopen_V24,
+                          (void **) &orig_do_dlopen_V24);
         }
     } else if (api_level >= 19) {
         if (findSymbol("__dl__Z9do_dlopenPKciPK17android_dlextinfo", "linker",
                        (unsigned long *) &symbol) == 0) {
-            inlineHookDirect((unsigned int) symbol, (void *) new_do_dlopen_V19,
-                             (void **) &orig_do_dlopen_V19);
+            MSHookFunction(symbol, (void *) new_do_dlopen_V19,
+                          (void **) &orig_do_dlopen_V19);
         }
     } else {
         if (findSymbol("__dl_dlopen", "linker",
                        (unsigned long *) &symbol) == 0) {
-            inlineHookDirect((unsigned int) symbol, (void *) new_dlopen, (void **) &orig_dlopen);
+            MSHookFunction(symbol, (void *) new_dlopen, (void **) &orig_dlopen);
         }
     }
-    if (!symbol) {
-        HOOK_SYMBOL(RTLD_DEFAULT, dlopen);
-    }
 }
 
 
-
-void IOUniformer::startUniformer(int api_level, int preview_api_level) {
-    HOOK_SYMBOL(RTLD_DEFAULT, kill);
-    HOOK_SYMBOL(RTLD_DEFAULT, __getcwd);
-    HOOK_SYMBOL(RTLD_DEFAULT, truncate);
-    HOOK_SYMBOL(RTLD_DEFAULT, __statfs64);
-    HOOK_SYMBOL(RTLD_DEFAULT, execve);
-    HOOK_SYMBOL(RTLD_DEFAULT, __open);
-    if ((api_level < 25) || (api_level == 25 && preview_api_level == 0)) {
-        HOOK_SYMBOL(RTLD_DEFAULT, utimes);
-        HOOK_SYMBOL(RTLD_DEFAULT, mkdir);
-        HOOK_SYMBOL(RTLD_DEFAULT, chmod);
-        HOOK_SYMBOL(RTLD_DEFAULT, lstat);
-        HOOK_SYMBOL(RTLD_DEFAULT, link);
-        HOOK_SYMBOL(RTLD_DEFAULT, symlink);
-        HOOK_SYMBOL(RTLD_DEFAULT, mknod);
-        HOOK_SYMBOL(RTLD_DEFAULT, rmdir);
-        HOOK_SYMBOL(RTLD_DEFAULT, chown);
-        HOOK_SYMBOL(RTLD_DEFAULT, rename);
-        HOOK_SYMBOL(RTLD_DEFAULT, stat);
-        HOOK_SYMBOL(RTLD_DEFAULT, chdir);
-        HOOK_SYMBOL(RTLD_DEFAULT, access);
-        HOOK_SYMBOL(RTLD_DEFAULT, readlink);
-        HOOK_SYMBOL(RTLD_DEFAULT, unlink);
+void IOUniformer::startUniformer(const char *so_path, int api_level, int preview_api_level) {
+    char api_level_chars[5];
+    setenv("V_SO_PATH", so_path, 1);
+    sprintf(api_level_chars, "%i", api_level);
+    setenv("V_API_LEVEL", api_level_chars, 1);
+    sprintf(api_level_chars, "%i", preview_api_level);
+    setenv("V_PREVIEW_API_LEVEL", api_level_chars, 1);
+
+    void *handle = dlopen("libc.so", RTLD_NOW);
+    if (handle) {
+        HOOK_SYMBOL(handle, faccessat);
+        HOOK_SYMBOL(handle, __openat);
+        HOOK_SYMBOL(handle, fchmodat);
+        HOOK_SYMBOL(handle, fchownat);
+        HOOK_SYMBOL(handle, renameat);
+        HOOK_SYMBOL(handle, fstatat64);
+        HOOK_SYMBOL(handle, __statfs);
+        HOOK_SYMBOL(handle, __statfs64);
+        HOOK_SYMBOL(handle, mkdirat);
+        HOOK_SYMBOL(handle, mknodat);
+        HOOK_SYMBOL(handle, truncate);
+        HOOK_SYMBOL(handle, linkat);
+        HOOK_SYMBOL(handle, readlinkat);
+        HOOK_SYMBOL(handle, unlinkat);
+        HOOK_SYMBOL(handle, symlinkat);
+        HOOK_SYMBOL(handle, utimensat);
+        HOOK_SYMBOL(handle, __getcwd);
+//        HOOK_SYMBOL(handle, __getdents64);
+        HOOK_SYMBOL(handle, chdir);
+        HOOK_SYMBOL(handle, execve);
+        if (api_level <= 20) {
+            HOOK_SYMBOL(handle, access);
+            HOOK_SYMBOL(handle, __open);
+            HOOK_SYMBOL(handle, stat);
+            HOOK_SYMBOL(handle, lstat);
+            HOOK_SYMBOL(handle, fstatat);
+            HOOK_SYMBOL(handle, chmod);
+            HOOK_SYMBOL(handle, chown);
+            HOOK_SYMBOL(handle, rename);
+            HOOK_SYMBOL(handle, rmdir);
+            HOOK_SYMBOL(handle, mkdir);
+            HOOK_SYMBOL(handle, mknod);
+            HOOK_SYMBOL(handle, link);
+            HOOK_SYMBOL(handle, unlink);
+            HOOK_SYMBOL(handle, readlink);
+            HOOK_SYMBOL(handle, symlink);
+//            HOOK_SYMBOL(handle, getdents);
+//            HOOK_SYMBOL(handle, execv);
+        }
+        dlclose(handle);
     }
-    HOOK_SYMBOL(RTLD_DEFAULT, fstatat);
-    HOOK_SYMBOL(RTLD_DEFAULT, fchmodat);
-    HOOK_SYMBOL(RTLD_DEFAULT, symlinkat);
-    HOOK_SYMBOL(RTLD_DEFAULT, readlinkat);
-    HOOK_SYMBOL(RTLD_DEFAULT, unlinkat);
-    HOOK_SYMBOL(RTLD_DEFAULT, linkat);
-    HOOK_SYMBOL(RTLD_DEFAULT, utimensat);
-    HOOK_SYMBOL(RTLD_DEFAULT, __openat);
-    HOOK_SYMBOL(RTLD_DEFAULT, faccessat);
-    HOOK_SYMBOL(RTLD_DEFAULT, mkdirat);
-    HOOK_SYMBOL(RTLD_DEFAULT, renameat);
-    HOOK_SYMBOL(RTLD_DEFAULT, fchownat);
-    HOOK_SYMBOL(RTLD_DEFAULT, mknodat);
-//    hook_dlopen(api_level);
-
-#if defined(__i386__) || defined(__x86_64__)
-    // Do nothing
-#else
-    GodinHook::NativeHook::hookAllRegistered();
-#endif
+    // hook_dlopen(api_level);
 }
diff --git a/VirtualApp/lib/src/main/jni/Foundation/IOUniformer.h b/VirtualApp/lib/src/main/jni/Foundation/IOUniformer.h
index 7d6cf697f..0b0f8c8a3 100644
--- a/VirtualApp/lib/src/main/jni/Foundation/IOUniformer.h
+++ b/VirtualApp/lib/src/main/jni/Foundation/IOUniformer.h
@@ -16,26 +16,30 @@
 #include<dirent.h>
 #include <sys/syscall.h>
 
-#include <MSHook.h>
-#include "Helper.h"
+#include "Jni/Helper.h"
 
 
-#define HOOK_SYMBOL(handle, func) hook_template(handle, #func, (void*) new_##func, (void**) &orig_##func)
+#define HOOK_SYMBOL(handle, func) hook_function(handle, #func, (void*) new_##func, (void**) &orig_##func)
 #define HOOK_DEF(ret, func, ...) \
   ret (*orig_##func)(__VA_ARGS__); \
   ret new_##func(__VA_ARGS__)
 
 
 namespace IOUniformer {
-    void startUniformer(int api_level, int preview_api_level);
 
-    void redirect(const char*orig_path, const char*new_path);
+    void init_env_before_all();
 
-    void readOnly(const char*path);
+    void startUniformer(const char *so_path, int api_level, int preview_api_level);
+
+    void redirect(const char *orig_path, const char *new_path);
+
+    void whitelist(const char *path);
 
     const char *query(const char *orig_path);
 
-    const char *restore(const char *redirected_path);
+    const char *reverse(const char *redirected_path);
+
+    void forbid(const char *path);
 }
 
 #endif //NDK_HOOK_H
diff --git a/VirtualApp/lib/src/main/jni/Foundation/Path.cpp b/VirtualApp/lib/src/main/jni/Foundation/Path.cpp
new file mode 100644
index 000000000..e09431318
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Foundation/Path.cpp
@@ -0,0 +1,72 @@
+#include "Path.h"
+#define MAX_PATH_SIZE 4096
+
+/* returns last slash position in @s or -1 if there is no one */
+int get_last_slash_pos(char *s) {
+    int last_slash = -1;
+    char *slash = strrchr(s, '/');
+    if (slash)
+        last_slash = slash - s;
+    return last_slash;
+}
+
+char *canonicalize_filename(const char *str) {
+    int prev_last_slash = -1;
+    int last_slash = -1;
+    int i = 0;
+    int j = 0;
+    char c;
+    char cprev = '\0';
+    char result[MAX_PATH_SIZE] = {0};
+
+    if (!str)
+        return NULL;
+
+    for (; i < MAX_PATH_SIZE && str[i]; ++i) {
+        c = str[i];
+
+        switch (c) {
+            case '/':
+                if (cprev == '/'/** || j == 0*/) {
+                    // eat repeating and leading slashes
+                    ;
+                } else {
+                    result[j++] = c;
+                    prev_last_slash = last_slash;
+                    last_slash = j - 1;
+                }
+                break;
+            case '.':
+                if (cprev == '.') {
+                    int start_position = 0;
+                    if (prev_last_slash > 0) {
+                        start_position = prev_last_slash;
+                        // handle following duplicate slash on next iteration, if any
+                        cprev = '/';
+                    }
+                    while (j > start_position)
+                        result[j--] = '\0';
+                    result[j] = '\0';
+
+                    // we lost last slash positions, calculate them
+                    prev_last_slash = -1;
+                    last_slash = get_last_slash_pos(result);
+                    if (last_slash != -1) {
+                        // trying to find previous last slash position
+                        result[last_slash] = ' ';
+                        prev_last_slash = get_last_slash_pos(result);
+                        result[last_slash] = '/';
+                    }
+                } else {
+                    // assume it is a valid to have dot in names
+                    result[j++] = c;
+                }
+                break;
+            default:
+                result[j++] = c;
+                break;
+        }
+        cprev = c;
+    }
+    return strndup(result, MAX_PATH_SIZE - 1);
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/Foundation/Path.h b/VirtualApp/lib/src/main/jni/Foundation/Path.h
new file mode 100644
index 000000000..65a4cdd24
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Foundation/Path.h
@@ -0,0 +1,15 @@
+//
+// VirtualApp Native Project
+//
+
+#ifndef FOUNDATION_PATH
+#define FOUNDATION_PATH
+
+#include <string.h>
+
+int get_last_slash_pos(char *s);
+
+char* canonicalize_filename(const char *str);
+
+
+#endif //FOUNDATION_PATH
diff --git a/VirtualApp/lib/src/main/jni/Foundation/SandboxFs.cpp b/VirtualApp/lib/src/main/jni/Foundation/SandboxFs.cpp
new file mode 100644
index 000000000..359f87a26
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Foundation/SandboxFs.cpp
@@ -0,0 +1,194 @@
+#include <stdlib.h>
+#include "SandboxFs.h"
+#include "Path.h"
+
+PathItem *keep_items;
+PathItem *forbidden_items;
+ReplaceItem *replace_items;
+int keep_item_count;
+int forbidden_item_count;
+int replace_item_count;
+
+int add_keep_item(const char *path) {
+    char keep_env_name[25];
+    sprintf(keep_env_name, "V_KEEP_ITEM_%d", keep_item_count);
+    setenv(keep_env_name, path, 1);
+    keep_items = (PathItem *) realloc(keep_items,
+                                      keep_item_count * sizeof(PathItem) + sizeof(PathItem));
+    PathItem &item = keep_items[keep_item_count];
+    item.path = strdup(path);
+    item.size = strlen(path);
+    return ++keep_item_count;
+}
+
+int add_forbidden_item(const char *path) {
+    char forbidden_env_name[25];
+    sprintf(forbidden_env_name, "V_FORBID_ITEM_%d", forbidden_item_count);
+    setenv(forbidden_env_name, path, 1);
+    forbidden_items = (PathItem *) realloc(forbidden_items,
+                                           forbidden_item_count * sizeof(PathItem) +
+                                           sizeof(PathItem));
+    PathItem &item = forbidden_items[forbidden_item_count];
+    item.path = strdup(path);
+    item.size = strlen(path);
+    item.is_folder = (path[strlen(path) - 1] == '/');
+    return ++forbidden_item_count;
+}
+
+int add_replace_item(const char *orig_path, const char *new_path) {
+    char src_env_name[25];
+    char dst_env_name[25];
+    sprintf(src_env_name, "V_REPLACE_ITEM_SRC_%d", replace_item_count);
+    sprintf(dst_env_name, "V_REPLACE_ITEM_DST_%d", replace_item_count);
+    setenv(src_env_name, orig_path, 1);
+    setenv(dst_env_name, new_path, 1);
+
+    replace_items = (ReplaceItem *) realloc(replace_items,
+                                            replace_item_count * sizeof(ReplaceItem) +
+                                            sizeof(ReplaceItem));
+    ReplaceItem &item = replace_items[replace_item_count];
+    item.orig_path = strdup(orig_path);
+    item.orig_size = strlen(orig_path);
+    item.new_path = strdup(new_path);
+    item.new_size = strlen(new_path);
+    item.is_folder = (orig_path[strlen(orig_path) - 1] == '/');
+    return ++replace_item_count;
+}
+
+
+PathItem *get_keep_items() {
+    return keep_items;
+}
+
+PathItem *get_forbidden_item() {
+    return forbidden_items;
+}
+
+ReplaceItem *get_replace_items() {
+    return replace_items;
+}
+
+int get_keep_item_count() {
+    return keep_item_count;
+}
+
+int get_forbidden_item_count() {
+    return forbidden_item_count;
+}
+
+int get_replace_item_count() {
+    return replace_item_count;
+}
+
+inline bool match_path(bool is_folder, size_t size, const char *item_path, const char *path) {
+    if (is_folder) {
+        if (strlen(path) < size) {
+            // ignore the last '/'
+            return strncmp(item_path, path, size - 1) == 0;
+        }
+    }
+    return strncmp(item_path, path, size) == 0;
+}
+
+
+const char *relocate_path(const char *path, int *result) {
+    if (path == NULL) {
+        *result = NOT_MATCH;
+        return NULL;
+    }
+    for (int i = 0; i < keep_item_count; ++i) {
+        PathItem &item = keep_items[i];
+        if (match_path(item.is_folder, item.size, item.path, path)) {
+            *result = KEEP;
+            return path;
+        }
+    }
+    for (int i = 0; i < forbidden_item_count; ++i) {
+        PathItem &item = forbidden_items[i];
+        if (match_path(item.is_folder, item.size, item.path, path)) {
+            *result = FORBID;
+            // Permission denied
+            errno = 13;
+            return NULL;
+        }
+    }
+    for (int i = 0; i < replace_item_count; ++i) {
+        ReplaceItem &item = replace_items[i];
+        if (match_path(item.is_folder, item.orig_size, item.orig_path, path)) {
+            *result = MATCH;
+            int len = strlen(path);
+            if (len < item.orig_size) {
+                //remove last /
+                std::string redirect_path(item.new_path, 0, item.new_size - 1);
+                return strdup(redirect_path.c_str());
+            } else {
+                std::string redirect_path(item.new_path);
+                redirect_path += path + item.orig_size;
+                return strdup(redirect_path.c_str());
+            }
+        }
+    }
+    *result = NOT_MATCH;
+    return path;
+}
+
+
+int relocate_path_inplace(char *_path, size_t size, int *result) {
+    const char *redirect_path = relocate_path(_path, result);
+    if (redirect_path && redirect_path != _path) {
+        if (strlen(redirect_path) <= size) {
+            strcpy(_path, redirect_path);
+        } else {
+            return -1;
+        }
+        free((void *) redirect_path);
+    }
+    return 0;
+}
+
+
+const char *reverse_relocate_path(const char *_path) {
+    if (_path == NULL) {
+        return NULL;
+    }
+    char *path = canonicalize_filename(_path);
+    for (int i = 0; i < keep_item_count; ++i) {
+        PathItem &item = keep_items[i];
+        if (strcmp(item.path, path) == 0) {
+            free(path);
+            return _path;
+        }
+    }
+    for (int i = 0; i < replace_item_count; ++i) {
+        ReplaceItem &item = replace_items[i];
+        if (match_path(item.is_folder, item.new_size, item.new_path, path)) {
+            int len = strlen(path);
+            if (len < item.new_size) {
+                //remove last /
+                std::string reverse_path(item.orig_path, 0, item.orig_size - 1);
+                free(path);
+                return strdup(reverse_path.c_str());
+            } else {
+                std::string reverse_path(item.orig_path);
+                reverse_path += path + item.new_size;
+                free(path);
+                return strdup(reverse_path.c_str());
+            }
+        }
+    }
+    return _path;
+}
+
+
+int reverse_relocate_path_inplace(char *_path, size_t size) {
+    const char *redirect_path = reverse_relocate_path(_path);
+    if (redirect_path && redirect_path != _path) {
+        if (strlen(redirect_path) <= size) {
+            strcpy(_path, redirect_path);
+        } else {
+            return -1;
+        }
+        free((void *) redirect_path);
+    }
+    return 0;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/Foundation/SandboxFs.h b/VirtualApp/lib/src/main/jni/Foundation/SandboxFs.h
new file mode 100644
index 000000000..6a8fb78e3
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Foundation/SandboxFs.h
@@ -0,0 +1,56 @@
+#ifndef SANDBOX_FS_H
+#define SANDBOX_FS_H
+
+#include <string>
+#include <errno.h>
+
+typedef struct PathItem {
+    char *path;
+    bool is_folder;
+    size_t size;
+} PathItem;
+
+typedef struct ReplaceItem {
+    char *orig_path;
+    size_t orig_size;
+    char *new_path;
+    size_t new_size;
+    bool is_folder;
+} ReplaceItem;
+
+enum RelocateResult {
+    MATCH,
+    NOT_MATCH,
+    FORBID,
+    KEEP
+};
+
+
+const char *relocate_path(const char *_path, int *result);
+
+int relocate_path_inplace(char *_path, size_t size, int *result);
+
+const char *reverse_relocate_path(const char *_path);
+
+int reverse_relocate_path_inplace(char *_path, size_t size);
+
+int add_keep_item(const char *path);
+
+int add_forbidden_item(const char *path);
+
+int add_replace_item(const char *orig_path, const char *new_path);
+
+PathItem *get_keep_items();
+
+PathItem *get_forbidden_item();
+
+ReplaceItem *get_replace_items();
+
+int get_keep_item_count();
+
+int get_forbidden_item_count();
+
+int get_replace_item_count();
+
+
+#endif //SANDBOX_FS_H
diff --git a/VirtualApp/lib/src/main/jni/Foundation/SymbolFinder.cpp b/VirtualApp/lib/src/main/jni/Foundation/SymbolFinder.cpp
new file mode 100644
index 000000000..25d6d2d1c
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Foundation/SymbolFinder.cpp
@@ -0,0 +1,428 @@
+#include <stdio.h>
+#include <elf.h>
+#include <Jni/Helper.h>
+#include <malloc.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <fb/include/fb/ALog.h>
+#include "SymbolFinder.h"
+
+/* memory map for libraries */
+#define MAX_NAME_LEN 256
+#define MEMORY_ONLY  "[memory]"
+struct mm {
+    char name[MAX_NAME_LEN];
+    unsigned long start, end;
+};
+
+typedef struct symtab *symtab_t;
+struct symlist {
+    Elf32_Sym *sym; /* symbols */
+    char *str; /* symbol strings */
+    unsigned num; /* number of symbols */
+};
+struct symtab {
+    struct symlist *st; /* "static" symbols */
+    struct symlist *dyn; /* dynamic symbols */
+};
+
+static void* xmalloc(size_t size) {
+    void *p;
+    p = malloc(size);
+    if (!p) {
+        printf("Out of memory\n");
+        exit(1);
+    }
+    return p;
+}
+
+static int my_pread(int fd, void *buf, size_t count, off_t offset) {
+    lseek(fd, offset, SEEK_SET);
+    return read(fd, buf, count);
+}
+
+static struct symlist* get_syms(int fd, Elf32_Shdr *symh, Elf32_Shdr *strh) {
+    struct symlist *sl, *ret;
+    int rv;
+
+    ret = NULL;
+    sl = (struct symlist *) xmalloc(sizeof(struct symlist));
+    sl->str = NULL;
+    sl->sym = NULL;
+
+    /* sanity */
+    if (symh->sh_size % sizeof(Elf32_Sym)) {
+        //printf("elf_error\n");
+        goto out;
+    }
+
+    /* symbol table */
+    sl->num = symh->sh_size / sizeof(Elf32_Sym);
+    sl->sym = (Elf32_Sym *) xmalloc(symh->sh_size);
+    rv = my_pread(fd, sl->sym, symh->sh_size, symh->sh_offset);
+    if (0 > rv) {
+        //perror("read");
+        goto out;
+    }
+    if (rv != symh->sh_size) {
+        //printf("elf error\n");
+        goto out;
+    }
+
+    /* string table */
+    sl->str = (char *) xmalloc(strh->sh_size);
+    rv = my_pread(fd, sl->str, strh->sh_size, strh->sh_offset);
+    if (0 > rv) {
+        //perror("read");
+        goto out;
+    }
+    if (rv != strh->sh_size) {
+        //printf("elf error");
+        goto out;
+    }
+
+    ret = sl;
+    out: return ret;
+}
+
+static int do_load(int fd, symtab_t symtab) {
+    int rv;
+    size_t size;
+    Elf32_Ehdr ehdr;
+    Elf32_Shdr *shdr = NULL, *p;
+    Elf32_Shdr *dynsymh, *dynstrh;
+    Elf32_Shdr *symh, *strh;
+    char *shstrtab = NULL;
+    int i;
+    int ret = -1;
+
+    /* elf header */
+    rv = read(fd, &ehdr, sizeof(ehdr));
+    if (0 > rv) {
+        ALOGD("read\n");
+        goto out;
+    }
+    if (rv != sizeof(ehdr)) {
+        ALOGD("elf error 1\n");
+        goto out;
+    }
+    if (strncmp((const char *) ELFMAG, (const char *) ehdr.e_ident, SELFMAG)) { /* sanity */
+        ALOGD("not an elf\n");
+        goto out;
+    }
+    if (sizeof(Elf32_Shdr) != ehdr.e_shentsize) { /* sanity */
+        ALOGD("elf error 2\n");
+        goto out;
+    }
+
+    /* section header table */
+    size = ehdr.e_shentsize * ehdr.e_shnum;
+    shdr = (Elf32_Shdr *) xmalloc(size);
+    rv = my_pread(fd, shdr, size, ehdr.e_shoff);
+    if (0 > rv) {
+        ALOGD("read\n");
+        goto out;
+    }
+    if (rv != size) {
+        ALOGD("elf error 3 %d %d\n", rv, size);
+        goto out;
+    }
+
+    /* section header string table */
+    size = shdr[ehdr.e_shstrndx].sh_size;
+    shstrtab = (char *) xmalloc(size);
+    rv = my_pread(fd, shstrtab, size, shdr[ehdr.e_shstrndx].sh_offset);
+    if (0 > rv) {
+        ALOGD("read\n");
+        goto out;
+    }
+    if (rv != size) {
+        ALOGD("elf error 4 %d %d\n", rv, size);
+        goto out;
+    }
+
+    /* symbol table headers */
+    symh = dynsymh = NULL;
+    strh = dynstrh = NULL;
+    for (i = 0, p = shdr; i < ehdr.e_shnum; i++, p++)
+        if (SHT_SYMTAB == p->sh_type) {
+            if (symh) {
+                ALOGD("too many symbol tables\n");
+                goto out;
+            }
+            symh = p;
+        } else if (SHT_DYNSYM == p->sh_type) {
+            if (dynsymh) {
+                ALOGD("too many symbol tables\n");
+                goto out;
+            }
+            dynsymh = p;
+        } else if (SHT_STRTAB == p->sh_type
+                   && !strncmp(shstrtab + p->sh_name, ".strtab", 7)) {
+            if (strh) {
+                ALOGD("too many string tables\n");
+                goto out;
+            }
+            strh = p;
+        } else if (SHT_STRTAB == p->sh_type
+                   && !strncmp(shstrtab + p->sh_name, ".dynstr", 7)) {
+            if (dynstrh) {
+                ALOGD("too many string tables\n");
+                goto out;
+            }
+            dynstrh = p;
+        }
+    /* sanity checks */
+    if ((!dynsymh && dynstrh) || (dynsymh && !dynstrh)) {
+        ALOGD("bad dynamic symbol table\n");
+        goto out;
+    }
+    if ((!symh && strh) || (symh && !strh)) {
+        ALOGD("bad symbol table\n");
+        goto out;
+    }
+    if (!dynsymh && !symh) {
+        ALOGD("no symbol table\n");
+        goto out;
+    }
+
+    /* symbol tables */
+    if (dynsymh)
+        symtab->dyn = get_syms(fd, dynsymh, dynstrh);
+    if (symh)
+        symtab->st = get_syms(fd, symh, strh);
+    ret = 0;
+    out: free(shstrtab);
+    free(shdr);
+    return ret;
+}
+
+static symtab_t load_symtab(char *filename) {
+    int fd;
+    symtab_t symtab;
+
+    symtab = (symtab_t) xmalloc(sizeof(*symtab));
+    memset(symtab, 0, sizeof(*symtab));
+
+    fd = open(filename, O_RDONLY);
+    if (0 > fd) {
+        ALOGE("%s open\n", __func__);
+        return NULL;
+    }
+    if (0 > do_load(fd, symtab)) {
+        ALOGE("Error ELF parsing %s\n", filename);
+        free(symtab);
+        symtab = NULL;
+    }
+    close(fd);
+    return symtab;
+}
+
+
+static int load_memmap(pid_t pid, struct mm *mm, int *nmmp) {
+    size_t buf_size = 0x40000;
+    char *p_buf = (char *) malloc(buf_size); // increase this if needed for larger "maps"
+    char name[MAX_NAME_LEN] = { 0 };
+    char *p;
+    unsigned long start, end;
+    struct mm *m;
+    int nmm = 0;
+    int fd, rv;
+    int i;
+
+    sprintf(p_buf, "/proc/%d/maps", pid);
+    fd = open(p_buf, O_RDONLY);
+    if (0 > fd) {
+        ALOGE("Can't open %s for reading\n", p_buf);
+        free(p_buf);
+        return -1;
+    }
+
+    /* Zero to ensure data is null terminated */
+    memset(p_buf, 0, buf_size);
+
+    p = p_buf;
+    while (1) {
+        rv = read(fd, p, buf_size - (p - p_buf));
+        if (0 > rv) {
+            ALOGE("%s read", __FUNCTION__);
+            free(p_buf);
+            return -1;
+        }
+        if (0 == rv)
+            break;
+        p += rv;
+        if (p - p_buf >= buf_size) {
+            ALOGE("Too many memory mapping\n");
+            free(p_buf);
+            return -1;
+        }
+    }
+    close(fd);
+
+    p = strtok(p_buf, "\n");
+    m = mm;
+    while (p) {
+        /* parse current map line */
+        rv = sscanf(p, "%08lx-%08lx %*s %*s %*s %*s %s\n", &start, &end, name);
+
+        p = strtok(NULL, "\n");
+
+        if (rv == 2) {
+            m = &mm[nmm++];
+            m->start = start;
+            m->end = end;
+            memcpy(m->name, MEMORY_ONLY, sizeof(MEMORY_ONLY));
+            continue;
+        }
+
+        /* search backward for other mapping with same name */
+        for (i = nmm - 1; i >= 0; i--) {
+            m = &mm[i];
+            if (!strcmp(m->name, name))
+                break;
+        }
+
+        if (i >= 0) {
+            if (start < m->start)
+                m->start = start;
+            if (end > m->end)
+                m->end = end;
+        } else {
+            /* new entry */
+            m = &mm[nmm++];
+            m->start = start;
+            m->end = end;
+            memcpy(m->name, name, strlen(name));
+        }
+    }
+
+    *nmmp = nmm;
+    free(p_buf);
+    return 0;
+}
+
+/* Find libc in MM, storing no more than LEN-1 chars of
+ its name in NAME and set START to its starting
+ address.  If libc cannot be found return -1 and
+ leave NAME and START untouched.  Otherwise return 0
+ and null-terminated NAME. */
+static int find_libname(const char *libn, char *name, int len, unsigned long *start,
+                        struct mm *mm, int nmm) {
+    int i;
+    struct mm *m;
+    char *p;
+    for (i = 0, m = mm; i < nmm; i++, m++) {
+        if (!strcmp(m->name, MEMORY_ONLY))
+            continue;
+        p = strrchr(m->name, '/');
+        if (!p)
+            continue;
+        p++;
+        if (strncmp(libn, p, strlen(libn)))
+            continue;
+        p += strlen(libn);
+
+        /* here comes our crude test -> 'libc.so' or 'libc-[0-9]' */
+        if (!strncmp("so", p, 2) || 1) // || (p[0] == '-' && isdigit(p[1])))
+            break;
+    }
+    if (i >= nmm)
+        /* not found */
+        return -1;
+
+    *start = m->start;
+    strncpy(name, m->name, len);
+    if (strlen(m->name) >= len)
+        name[len - 1] = '\0';
+
+    mprotect((void*) m->start, m->end - m->start,
+             PROT_READ | PROT_WRITE | PROT_EXEC);
+    return 0;
+}
+
+static int lookup2(struct symlist *sl, unsigned char type, char *name,
+                   unsigned long *val) {
+    Elf32_Sym *p;
+    int len;
+    int i;
+
+    len = strlen(name);
+    for (i = 0, p = sl->sym; i < sl->num; i++, p++) {
+        //ALOGD("name: %s %x\n", sl->str+p->st_name, p->st_value)
+        if (!strncmp(sl->str + p->st_name, name, len)
+            && *(sl->str + p->st_name + len) == 0
+            && ELF32_ST_TYPE(p->st_info) == type) {
+            //if (p->st_value != 0) {
+            *val = p->st_value;
+            return 0;
+            //}
+        }
+    }
+    return -1;
+}
+
+static int lookup_sym(symtab_t s, unsigned char type, char *name,
+                      unsigned long *val) {
+    if (s->dyn && !lookup2(s->dyn, type, name, val))
+        return 0;
+    if (s->st && !lookup2(s->st, type, name, val))
+        return 0;
+    return -1;
+}
+
+static int lookup_func_sym(symtab_t s, char *name, unsigned long *val) {
+    return lookup_sym(s, STT_FUNC, name, val);
+}
+
+int find_name(pid_t pid, const char *name, const char *libn,
+              unsigned long *addr) {
+    struct mm mm[1000] = { 0 };
+    unsigned long libcaddr;
+    int nmm;
+    char libc[1024] = { 0 };
+    symtab_t s;
+
+    if (0 > load_memmap(pid, mm, &nmm)) {
+        ALOGD("cannot read memory map\n");
+        return -1;
+    }
+    if (0
+        > find_libname((char *) libn, (char *) libc, sizeof(libc),
+                       &libcaddr, mm, nmm)) {
+        ALOGD("cannot find lib: %s\n", libn);
+        return -1;
+    }
+    //ALOGD("lib: >%s<\n", libc)
+    s = load_symtab(libc);
+    if (!s) {
+        ALOGD("cannot read symbol table\n");
+        return -1;
+    }
+    if (0 > lookup_func_sym(s, (char *) name, addr)) {
+        ALOGD("cannot find function: %s\n", name);
+        return -1;
+    }
+    *addr += libcaddr;
+    return 0;
+}
+
+int find_libbase(pid_t pid, const char *libn, unsigned long *addr) {
+    struct mm mm[1000] = { 0 };
+    unsigned long libcaddr;
+    int nmm;
+    char libc[1024] = { 0 };
+    symtab_t s;
+
+    if (0 > load_memmap(pid, mm, &nmm)) {
+        ALOGD("cannot read memory map\n");
+        return -1;
+    }
+    if (0 > find_libname(libn, libc, sizeof(libc), &libcaddr, mm, nmm)) {
+        ALOGD("cannot find lib\n");
+        return -1;
+    }
+    *addr = libcaddr;
+    return 0;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/MSHook/util.h b/VirtualApp/lib/src/main/jni/Foundation/SymbolFinder.h
similarity index 81%
rename from VirtualApp/lib/src/main/jni/MSHook/util.h
rename to VirtualApp/lib/src/main/jni/Foundation/SymbolFinder.h
index 8e210d7f1..28e806559 100644
--- a/VirtualApp/lib/src/main/jni/MSHook/util.h
+++ b/VirtualApp/lib/src/main/jni/Foundation/SymbolFinder.h
@@ -1,5 +1,5 @@
-#ifndef HOOK_UTIL_H_
-#define HOOK_UTIL_H_
+#ifndef SYMBOL_FINDER
+#define SYMBOL_FINDER
 
 #include <unistd.h>
 
diff --git a/VirtualApp/lib/src/main/jni/Foundation/VMPatch.cpp b/VirtualApp/lib/src/main/jni/Foundation/VMPatch.cpp
index 1e16c31b2..a798a81a3 100644
--- a/VirtualApp/lib/src/main/jni/Foundation/VMPatch.cpp
+++ b/VirtualApp/lib/src/main/jni/Foundation/VMPatch.cpp
@@ -1,38 +1,46 @@
 //
 // VirtualApp Native Project
 //
+#include <Jni/VAJni.h>
+#include <Substrate/CydiaSubstrate.h>
 #include "VMPatch.h"
+#include "fake_dlfcn.h"
 
-typedef void (*Bridge_DalvikBridgeFunc)(const void **, void *, const void *, void *);
+namespace FunctionDef {
+    typedef void (*Function_DalvikBridgeFunc)(const void **, void *, const void *, void *);
 
-typedef jobject (*Native_openDexNativeFunc)(JNIEnv *, jclass, jstring, jstring, jint);
+    typedef jobject (*Function_openDexNativeFunc)(JNIEnv *, jclass, jstring, jstring, jint);
 
-typedef jobject (*Native_openDexNativeFunc_N)(JNIEnv *, jclass, jstring, jstring, jint, jobject,
-                                              jobject);
+    typedef jobject (*Native_openDexNativeFunc_N)(JNIEnv *, jclass, jstring, jstring, jint, jobject,
+                                                  jobject);
 
 
-typedef jint (*Native_cameraNativeSetupFunc_T1)(JNIEnv *, jobject, jobject, jint, jstring);
+    typedef jint (*Function_cameraNativeSetupFunc_T1)(JNIEnv *, jobject, jobject, jint, jstring);
 
-typedef jint (*Native_cameraNativeSetupFunc_T2)(JNIEnv *, jobject, jobject, jint, jint, jstring);
+    typedef jint (*Function_cameraNativeSetupFunc_T2)(JNIEnv *, jobject, jobject, jint, jint,
+                                                      jstring);
 
-typedef jint (*Native_cameraNativeSetupFunc_T3)(JNIEnv *, jobject, jobject, jint, jint, jstring,
-                                                jboolean);
+    typedef jint (*Function_cameraNativeSetupFunc_T3)(JNIEnv *, jobject, jobject, jint, jint,
+                                                      jstring,
+                                                      jboolean);
 
-typedef jint (*Native_cameraNativeSetupFunc_T4)(JNIEnv *, jobject, jobject, jint, jstring,
-                                                jboolean);
+    typedef jint (*Function_cameraNativeSetupFunc_T4)(JNIEnv *, jobject, jobject, jint, jstring,
+                                                      jboolean);
 
-typedef jint (*Native_getCallingUid)(JNIEnv *, jclass);
+    typedef jint (*Function_getCallingUid)(JNIEnv *, jclass);
 
-typedef jint (*Native_audioRecordNativeCheckPermission)(JNIEnv *, jobject, jstring);
+    typedef jint (*Function_audioRecordNativeCheckPermission)(JNIEnv *, jobject, jstring);
+}
+
+using namespace FunctionDef;
 
 
 static struct {
 
-    bool isArt;
-    int nativeOffset;
-    char *hostPackageName;
-    jint apiLevel;
-    jclass binder_class;
+    bool is_art;
+    int native_offset;
+    char *host_packageName;
+    jint api_level;
     jmethodID method_onGetCallingUid;
     jmethodID method_onOpenDexFileNative;
 
@@ -42,63 +50,47 @@ static struct {
 
     void *(*GetStringFromCstr)(const char *);
 
+    int (*native_getCallingUid)(int);
 
-    void *sym_IPCThreadState_self;
-    void *sym_IPCThreadState_getCallingUid;
+    int (*IPCThreadState_self)(void);
 
-    Native_getCallingUid orig_getCallingUid;
+    Function_getCallingUid jni_orig_getCallingUid;
+    Function_DalvikBridgeFunc orig_cameraNativeSetup_dvm;
 
     int cameraMethodType;
-    Bridge_DalvikBridgeFunc orig_cameraNativeSetup_dvm;
     union {
-        Native_cameraNativeSetupFunc_T1 t1;
-        Native_cameraNativeSetupFunc_T2 t2;
-        Native_cameraNativeSetupFunc_T3 t3;
-        Native_cameraNativeSetupFunc_T4 t4;
+        Function_cameraNativeSetupFunc_T1 t1;
+        Function_cameraNativeSetupFunc_T2 t2;
+        Function_cameraNativeSetupFunc_T3 t3;
+        Function_cameraNativeSetupFunc_T4 t4;
     } orig_native_cameraNativeSetupFunc;
 
-    Bridge_DalvikBridgeFunc orig_openDexFile_dvm;
+    Function_DalvikBridgeFunc orig_openDexFile_dvm;
     union {
-        Native_openDexNativeFunc beforeN;
+        Function_openDexNativeFunc beforeN;
         Native_openDexNativeFunc_N afterN;
-    } orig_native_openDexNativeFunc;
-
-    Native_audioRecordNativeCheckPermission orig_native_audioRecordNativeCheckPermission;
-
-} gOffset;
+    } orig_openDexNativeFunc_art;
 
+    Function_audioRecordNativeCheckPermission orig_audioRecordNativeCheckPermission;
 
-extern JavaVM *gVm;
-extern jclass gClass;
+} patchEnv;
 
 
-void mark() {
-    // Do nothing
-};
-
-jint getCallingUid(JNIEnv *env, jclass jclazz) {
+jint getCallingUid(alias_ref<jclass> clazz) {
     jint uid;
-    if (gOffset.isArt) {
-        uid = gOffset.orig_getCallingUid(env, jclazz);
+    if (patchEnv.is_art) {
+        uid = patchEnv.jni_orig_getCallingUid(Environment::ensureCurrentThreadIsAttached(),
+                                              clazz.get());
     } else {
-        int (*org_getCallingUid)(int) = (int (*)(int)) gOffset.sym_IPCThreadState_getCallingUid;
-        int (*func_self)(void) = (int (*)(void)) gOffset.sym_IPCThreadState_self;
-        uid = org_getCallingUid(func_self());
+        uid = patchEnv.native_getCallingUid(patchEnv.IPCThreadState_self());
     }
-    uid = env->CallStaticIntMethod(gClass, gOffset.method_onGetCallingUid, uid);
+    uid = Environment::ensureCurrentThreadIsAttached()->CallStaticIntMethod(nativeEngineClass.get(),
+                                                                            patchEnv.method_onGetCallingUid,
+                                                                            uid);
     return uid;
 }
 
 
-static JNINativeMethod gMarkMethods[] = {
-        NATIVE_METHOD((void *) mark, "nativeMark", "()V"),
-};
-
-JNINativeMethod gUidMethods[] = {
-        NATIVE_METHOD((void *) getCallingUid, "getCallingUid", "()I"),
-};
-
-
 static jobject new_native_openDexNativeFunc(JNIEnv *env, jclass jclazz, jstring javaSourceName,
                                             jstring javaOutputName, jint options) {
     jclass stringClass = env->FindClass("java/lang/String");
@@ -110,13 +102,13 @@ static jobject new_native_openDexNativeFunc(JNIEnv *env, jclass jclazz, jstring
     if (javaOutputName) {
         env->SetObjectArrayElement(array, 1, javaOutputName);
     }
-    env->CallStaticVoidMethod(gClass, gOffset.method_onOpenDexFileNative, array);
+    env->CallStaticVoidMethod(nativeEngineClass.get(), patchEnv.method_onOpenDexFileNative, array);
 
     jstring newSource = (jstring) env->GetObjectArrayElement(array, 0);
     jstring newOutput = (jstring) env->GetObjectArrayElement(array, 1);
 
-    return gOffset.orig_native_openDexNativeFunc.beforeN(env, jclazz, newSource, newOutput,
-                                                         options);
+    return patchEnv.orig_openDexNativeFunc_art.beforeN(env, jclazz, newSource, newOutput,
+                                                       options);
 }
 
 static jobject new_native_openDexNativeFunc_N(JNIEnv *env, jclass jclazz, jstring javaSourceName,
@@ -131,27 +123,23 @@ static jobject new_native_openDexNativeFunc_N(JNIEnv *env, jclass jclazz, jstrin
     if (javaOutputName) {
         env->SetObjectArrayElement(array, 1, javaOutputName);
     }
-    env->CallStaticVoidMethod(gClass, gOffset.method_onOpenDexFileNative, array);
+    env->CallStaticVoidMethod(nativeEngineClass.get(), patchEnv.method_onOpenDexFileNative, array);
 
     jstring newSource = (jstring) env->GetObjectArrayElement(array, 0);
     jstring newOutput = (jstring) env->GetObjectArrayElement(array, 1);
 
-    return gOffset.orig_native_openDexNativeFunc.afterN(env, jclazz, newSource, newOutput, options,
-                                                        loader, elements);
+    return patchEnv.orig_openDexNativeFunc_art.afterN(env, jclazz, newSource, newOutput, options,
+                                                      loader, elements);
 }
 
 
 static void
 new_bridge_openDexNativeFunc(const void **args, void *pResult, const void *method, void *self) {
-    JNIEnv *env = NULL;
-    gVm->GetEnv((void **) &env, JNI_VERSION_1_6);
-    gVm->AttachCurrentThread(&env, NULL);
 
-    typedef char *(*GetCstrFromString)(void *);
-    typedef void *(*GetStringFromCstr)(const char *);
+    JNIEnv *env = Environment::ensureCurrentThreadIsAttached();
 
-    const char *source = args[0] == NULL ? NULL : gOffset.GetCstrFromString((void *) args[0]);
-    const char *output = args[1] == NULL ? NULL : gOffset.GetCstrFromString((void *) args[1]);
+    const char *source = args[0] == NULL ? NULL : patchEnv.GetCstrFromString((void *) args[0]);
+    const char *output = args[1] == NULL ? NULL : patchEnv.GetCstrFromString((void *) args[1]);
 
     jstring orgSource = source == NULL ? NULL : env->NewStringUTF(source);
     jstring orgOutput = output == NULL ? NULL : env->NewStringUTF(output);
@@ -164,8 +152,7 @@ new_bridge_openDexNativeFunc(const void **args, void *pResult, const void *metho
     if (orgOutput) {
         env->SetObjectArrayElement(array, 1, orgOutput);
     }
-
-    env->CallStaticVoidMethod(gClass, gOffset.method_onOpenDexFileNative, array);
+    env->CallStaticVoidMethod(nativeEngineClass.get(), patchEnv.method_onOpenDexFileNative, array);
 
     jstring newSource = (jstring) env->GetObjectArrayElement(array, 0);
     jstring newOutput = (jstring) env->GetObjectArrayElement(array, 1);
@@ -173,8 +160,8 @@ new_bridge_openDexNativeFunc(const void **args, void *pResult, const void *metho
     const char *_newSource = newSource == NULL ? NULL : env->GetStringUTFChars(newSource, NULL);
     const char *_newOutput = newOutput == NULL ? NULL : env->GetStringUTFChars(newOutput, NULL);
 
-    args[0] = _newSource == NULL ? NULL : gOffset.GetStringFromCstr(_newSource);
-    args[1] = _newOutput == NULL ? NULL : gOffset.GetStringFromCstr(_newOutput);
+    args[0] = _newSource == NULL ? NULL : patchEnv.GetStringFromCstr(_newSource);
+    args[1] = _newOutput == NULL ? NULL : patchEnv.GetStringFromCstr(_newOutput);
 
     if (source && orgSource) {
         env->ReleaseStringUTFChars(orgSource, source);
@@ -183,90 +170,90 @@ new_bridge_openDexNativeFunc(const void **args, void *pResult, const void *metho
         env->ReleaseStringUTFChars(orgOutput, output);
     }
 
-    gOffset.orig_openDexFile_dvm(args, pResult, method, self);
+    patchEnv.orig_openDexFile_dvm(args, pResult, method, self);
 }
 
 static jint new_native_cameraNativeSetupFunc_T1(JNIEnv *env, jobject thiz, jobject camera_this,
                                                 jint cameraId, jstring packageName) {
 
-    jstring host = env->NewStringUTF(gOffset.hostPackageName);
+    jstring host = env->NewStringUTF(patchEnv.host_packageName);
 
-    return gOffset.orig_native_cameraNativeSetupFunc.t1(env, thiz, camera_this,
-                                                        cameraId,
-                                                        host);
+    return patchEnv.orig_native_cameraNativeSetupFunc.t1(env, thiz, camera_this,
+                                                         cameraId,
+                                                         host);
 }
 
 static jint new_native_cameraNativeSetupFunc_T2(JNIEnv *env, jobject thiz, jobject camera_this,
                                                 jint cameraId, jint halVersion,
                                                 jstring packageName) {
 
-    jstring host = env->NewStringUTF(gOffset.hostPackageName);
+    jstring host = env->NewStringUTF(patchEnv.host_packageName);
 
-    return gOffset.orig_native_cameraNativeSetupFunc.t2(env, thiz, camera_this, cameraId,
-                                                        halVersion, host);
+    return patchEnv.orig_native_cameraNativeSetupFunc.t2(env, thiz, camera_this, cameraId,
+                                                         halVersion, host);
 }
 
 static jint new_native_cameraNativeSetupFunc_T3(JNIEnv *env, jobject thiz, jobject camera_this,
                                                 jint cameraId, jint halVersion,
                                                 jstring packageName, jboolean option) {
 
-    jstring host = env->NewStringUTF(gOffset.hostPackageName);
+    jstring host = env->NewStringUTF(patchEnv.host_packageName);
 
-    return gOffset.orig_native_cameraNativeSetupFunc.t3(env, thiz, camera_this, cameraId,
-                                                        halVersion, host, option);
+    return patchEnv.orig_native_cameraNativeSetupFunc.t3(env, thiz, camera_this, cameraId,
+                                                         halVersion, host, option);
 }
 
 static jint new_native_cameraNativeSetupFunc_T4(JNIEnv *env, jobject thiz, jobject camera_this,
                                                 jint cameraId,
                                                 jstring packageName, jboolean option) {
 
-    jstring host = env->NewStringUTF(gOffset.hostPackageName);
+    jstring host = env->NewStringUTF(patchEnv.host_packageName);
 
-    return gOffset.orig_native_cameraNativeSetupFunc.t4(env, thiz, camera_this, cameraId, host,
-                                                        option);
+    return patchEnv.orig_native_cameraNativeSetupFunc.t4(env, thiz, camera_this, cameraId, host,
+                                                         option);
 }
 
 
 static jint
 new_native_audioRecordNativeCheckPermission(JNIEnv *env, jobject thiz, jstring _packagename) {
-    jstring host = env->NewStringUTF(gOffset.hostPackageName);
-    return gOffset.orig_native_audioRecordNativeCheckPermission(env, thiz, host);
+    jstring host = env->NewStringUTF(patchEnv.host_packageName);
+    return patchEnv.orig_audioRecordNativeCheckPermission(env, thiz, host);
 }
 
 
 static void
 new_bridge_cameraNativeSetupFunc(const void **args, void *pResult, const void *method, void *self) {
-    JNIEnv *env = NULL;
-    gVm->GetEnv((void **) &env, JNI_VERSION_1_6);
-    gVm->AttachCurrentThread(&env, NULL);
     // args[0] = this
-    switch (gOffset.cameraMethodType) {
+    switch (patchEnv.cameraMethodType) {
         case 1:
-            args[4] = gOffset.GetStringFromCstr(gOffset.hostPackageName);
+            args[4] = patchEnv.GetStringFromCstr(patchEnv.host_packageName);
             break;
         case 2:
-            args[5] = gOffset.GetStringFromCstr(gOffset.hostPackageName);
+            args[5] = patchEnv.GetStringFromCstr(patchEnv.host_packageName);
             break;
         case 3:
-            args[5] = gOffset.GetStringFromCstr(gOffset.hostPackageName);
+            args[5] = patchEnv.GetStringFromCstr(patchEnv.host_packageName);
             break;
         case 4:
-            args[4] = gOffset.GetStringFromCstr(gOffset.hostPackageName);
+            args[4] = patchEnv.GetStringFromCstr(patchEnv.host_packageName);
             break;
     }
-    gOffset.orig_cameraNativeSetup_dvm(args, pResult, method, self);
+    patchEnv.orig_cameraNativeSetup_dvm(args, pResult, method, self);
 }
 
+void mark() {
+    // Do nothing
+};
+
 
-void measureNativeOffset(JNIEnv *env, bool isArt) {
+void measureNativeOffset(bool isArt) {
 
-    jmethodID mtd_nativeHook = env->GetStaticMethodID(gClass, gMarkMethods[0].name,
-                                                      gMarkMethods[0].signature);
+    jmethodID markMethod = nativeEngineClass->getStaticMethod<void(void)>("nativeMark").getId();
 
-    size_t startAddress = (size_t) mtd_nativeHook;
+    size_t startAddress = (size_t) markMethod;
     size_t targetAddress = (size_t) mark;
-    if (isArt && gOffset.art_work_around_app_jni_bugs) {
-        targetAddress = (size_t) gOffset.art_work_around_app_jni_bugs;
+    if (isArt && patchEnv.art_work_around_app_jni_bugs) {
+        targetAddress = (size_t) patchEnv.art_work_around_app_jni_bugs;
     }
 
     int offset = 0;
@@ -278,51 +265,49 @@ void measureNativeOffset(JNIEnv *env, bool isArt) {
         }
         offset += 4;
         if (offset >= 100) {
-            LOGE("Error: Unable to find the jni function.");
+            ALOGE("Error: Unable to find the jni function.");
             break;
         }
     }
     if (found) {
-        gOffset.nativeOffset = offset;
+        patchEnv.native_offset = offset;
         if (!isArt) {
-            gOffset.nativeOffset += (sizeof(int) + sizeof(void *));
+            patchEnv.native_offset += (sizeof(int) + sizeof(void *));
         }
     }
 }
 
 
-inline void replaceGetCallingUid(JNIEnv *env, jboolean isArt) {
-
-
+inline void replaceGetCallingUid(jboolean isArt) {
+    auto binderClass = findClassLocal("android/os/Binder");
     if (isArt) {
-        size_t mtd_getCallingUid = (size_t) env->GetStaticMethodID(gOffset.binder_class,
-                                                                   "getCallingUid", "()I");
-        int nativeFuncOffset = gOffset.nativeOffset;
+        size_t mtd_getCallingUid = (size_t) binderClass->getStaticMethod<jint(void)>(
+                "getCallingUid").getId();
+        int nativeFuncOffset = patchEnv.native_offset;
         void **jniFuncPtr = (void **) (mtd_getCallingUid + nativeFuncOffset);
-        gOffset.orig_getCallingUid = (Native_getCallingUid) (*jniFuncPtr);
+        patchEnv.jni_orig_getCallingUid = (Function_getCallingUid) (*jniFuncPtr);
         *jniFuncPtr = (void *) getCallingUid;
     } else {
-        env->RegisterNatives(gOffset.binder_class, gUidMethods, NELEM(gUidMethods));
+        binderClass->registerNatives({makeNativeMethod("getCallingUid", getCallingUid)});
     }
-
 }
 
 inline void
-replaceOpenDexFileMethod(JNIEnv *env, jobject javaMethod, jboolean isArt, int apiLevel) {
+replaceOpenDexFileMethod(jobject javaMethod, jboolean isArt, int apiLevel) {
 
-    size_t mtd_openDexNative = (size_t) env->FromReflectedMethod(javaMethod);
-    int nativeFuncOffset = gOffset.nativeOffset;
+    size_t mtd_openDexNative = (size_t) Environment::current()->FromReflectedMethod(javaMethod);
+    int nativeFuncOffset = patchEnv.native_offset;
     void **jniFuncPtr = (void **) (mtd_openDexNative + nativeFuncOffset);
 
     if (!isArt) {
-        gOffset.orig_openDexFile_dvm = (Bridge_DalvikBridgeFunc) (*jniFuncPtr);
+        patchEnv.orig_openDexFile_dvm = (Function_DalvikBridgeFunc) (*jniFuncPtr);
         *jniFuncPtr = (void *) new_bridge_openDexNativeFunc;
     } else {
-        if (apiLevel < ANDROID_N) {
-            gOffset.orig_native_openDexNativeFunc.beforeN = (Native_openDexNativeFunc) (*jniFuncPtr);
+        if (apiLevel < 24) {
+            patchEnv.orig_openDexNativeFunc_art.beforeN = (Function_openDexNativeFunc) (*jniFuncPtr);
             *jniFuncPtr = (void *) new_native_openDexNativeFunc;
         } else {
-            gOffset.orig_native_openDexNativeFunc.afterN = (Native_openDexNativeFunc_N) (*jniFuncPtr);
+            patchEnv.orig_openDexNativeFunc_art.afterN = (Native_openDexNativeFunc_N) (*jniFuncPtr);
             *jniFuncPtr = (void *) new_native_openDexNativeFunc_N;
         }
     }
@@ -331,34 +316,34 @@ replaceOpenDexFileMethod(JNIEnv *env, jobject javaMethod, jboolean isArt, int ap
 
 
 inline void
-replaceCameraNativeSetupMethod(JNIEnv *env, jobject javaMethod, jboolean isArt, int apiLevel) {
+replaceCameraNativeSetupMethod(jobject javaMethod, jboolean isArt, int apiLevel) {
 
     if (!javaMethod) {
         return;
     }
-    size_t mtd_cameraNativeSetup = (size_t) env->FromReflectedMethod(javaMethod);
-    int nativeFuncOffset = gOffset.nativeOffset;
+    size_t mtd_cameraNativeSetup = (size_t) Environment::current()->FromReflectedMethod(javaMethod);
+    int nativeFuncOffset = patchEnv.native_offset;
     void **jniFuncPtr = (void **) (mtd_cameraNativeSetup + nativeFuncOffset);
 
     if (!isArt) {
-        gOffset.orig_cameraNativeSetup_dvm = (Bridge_DalvikBridgeFunc) (*jniFuncPtr);
+        patchEnv.orig_cameraNativeSetup_dvm = (Function_DalvikBridgeFunc) (*jniFuncPtr);
         *jniFuncPtr = (void *) new_bridge_cameraNativeSetupFunc;
     } else {
-        switch (gOffset.cameraMethodType) {
+        switch (patchEnv.cameraMethodType) {
             case 1:
-                gOffset.orig_native_cameraNativeSetupFunc.t1 = (Native_cameraNativeSetupFunc_T1) (*jniFuncPtr);
+                patchEnv.orig_native_cameraNativeSetupFunc.t1 = (Function_cameraNativeSetupFunc_T1) (*jniFuncPtr);
                 *jniFuncPtr = (void *) new_native_cameraNativeSetupFunc_T1;
                 break;
             case 2:
-                gOffset.orig_native_cameraNativeSetupFunc.t2 = (Native_cameraNativeSetupFunc_T2) (*jniFuncPtr);
+                patchEnv.orig_native_cameraNativeSetupFunc.t2 = (Function_cameraNativeSetupFunc_T2) (*jniFuncPtr);
                 *jniFuncPtr = (void *) new_native_cameraNativeSetupFunc_T2;
                 break;
             case 3:
-                gOffset.orig_native_cameraNativeSetupFunc.t3 = (Native_cameraNativeSetupFunc_T3) (*jniFuncPtr);
+                patchEnv.orig_native_cameraNativeSetupFunc.t3 = (Function_cameraNativeSetupFunc_T3) (*jniFuncPtr);
                 *jniFuncPtr = (void *) new_native_cameraNativeSetupFunc_T3;
                 break;
             case 4:
-                gOffset.orig_native_cameraNativeSetupFunc.t4 = (Native_cameraNativeSetupFunc_T4) (*jniFuncPtr);
+                patchEnv.orig_native_cameraNativeSetupFunc.t4 = (Function_cameraNativeSetupFunc_T4) (*jniFuncPtr);
                 *jniFuncPtr = (void *) new_native_cameraNativeSetupFunc_T4;
                 break;
         }
@@ -368,13 +353,13 @@ replaceCameraNativeSetupMethod(JNIEnv *env, jobject javaMethod, jboolean isArt,
 
 
 void
-replaceAudioRecordNativeCheckPermission(JNIEnv *env, jobject javaMethod, jboolean isArt, int api) {
+replaceAudioRecordNativeCheckPermission(jobject javaMethod, jboolean isArt, int api) {
     if (!javaMethod || !isArt) {
         return;
     }
-    jmethodID methodStruct = env->FromReflectedMethod(javaMethod);
-    void **funPtr = (void **) (reinterpret_cast<size_t>(methodStruct) + gOffset.nativeOffset);
-    gOffset.orig_native_audioRecordNativeCheckPermission = (Native_audioRecordNativeCheckPermission) (*funPtr);
+    jmethodID methodStruct = Environment::current()->FromReflectedMethod(javaMethod);
+    void **funPtr = (void **) (reinterpret_cast<size_t>(methodStruct) + patchEnv.native_offset);
+    patchEnv.orig_audioRecordNativeCheckPermission = (Function_audioRecordNativeCheckPermission) (*funPtr);
     *funPtr = (void *) new_native_audioRecordNativeCheckPermission;
 }
 
@@ -385,68 +370,113 @@ replaceAudioRecordNativeCheckPermission(JNIEnv *env, jobject javaMethod, jboolea
  * @param isArt Dalvik or Art
  * @param apiLevel Api level from Java
  */
-void patchAndroidVM(jobjectArray javaMethods, jstring packageName, jboolean isArt, jint apiLevel,
-                    jint cameraMethodType) {
+void hookAndroidVM(JArrayClass<jobject> javaMethods,
+                   jstring packageName, jboolean isArt, jint apiLevel,
+                   jint cameraMethodType) {
 
-    JNIEnv *env = NULL;
-    gVm->GetEnv((void **) &env, JNI_VERSION_1_6);
-    gVm->AttachCurrentThread(&env, NULL);
+    JNIEnv *env = Environment::current();
 
-    if (env->RegisterNatives(gClass, gMarkMethods, NELEM(gMarkMethods)) < 0) {
+    JNINativeMethod methods[] = {
+            NATIVE_METHOD((void *) mark, "nativeMark", "()V"),
+    };
+    if (env->RegisterNatives(nativeEngineClass.get(), methods, 1) < 0) {
         return;
     }
-    gOffset.isArt = isArt;
-    gOffset.cameraMethodType = cameraMethodType;
-    gOffset.hostPackageName = (char *) env->GetStringUTFChars(packageName, NULL);
-    gOffset.apiLevel = apiLevel;
-    void *soInfo = getVMHandle();
-    gOffset.binder_class = env->FindClass("android/os/Binder");
-    gOffset.method_onGetCallingUid = env->GetStaticMethodID(gClass, "onGetCallingUid", "(I)I");
-    gOffset.method_onOpenDexFileNative = env->GetStaticMethodID(gClass, "onOpenDexFileNative",
-                                                                "([Ljava/lang/String;)V");
+    patchEnv.is_art = isArt;
+    patchEnv.cameraMethodType = cameraMethodType;
+    patchEnv.host_packageName = (char *) env->GetStringUTFChars(packageName,
+                                                                NULL);
+    patchEnv.api_level = apiLevel;
+    void *soInfo = getDvmOrArtSOHandle();
+    patchEnv.method_onGetCallingUid = nativeEngineClass->getStaticMethod<jint(jint)>(
+            "onGetCallingUid").getId();
+    patchEnv.method_onOpenDexFileNative = env->GetStaticMethodID(nativeEngineClass.get(),
+                                                                 "onOpenDexFileNative",
+                                                                 "([Ljava/lang/String;)V");
 
     if (isArt) {
-        gOffset.art_work_around_app_jni_bugs = dlsym(soInfo, "art_work_around_app_jni_bugs");
+        patchEnv.art_work_around_app_jni_bugs = dlsym(soInfo, "art_work_around_app_jni_bugs");
     } else {
-        gOffset.sym_IPCThreadState_self = dlsym(RTLD_DEFAULT, "_ZN7android14IPCThreadState4selfEv");
-        gOffset.sym_IPCThreadState_getCallingUid = dlsym(RTLD_DEFAULT,
-                                                         "_ZNK7android14IPCThreadState13getCallingUidEv");
-        if (gOffset.sym_IPCThreadState_getCallingUid == NULL) {
-            gOffset.sym_IPCThreadState_getCallingUid = dlsym(RTLD_DEFAULT,
-                                                             "_ZN7android14IPCThreadState13getCallingUidEv");
+        // workaround for dlsym returns null when system has libhoudini
+        void *h = dlopen("/system/lib/libandroid_runtime.so", RTLD_LAZY);
+        {
+            patchEnv.IPCThreadState_self = (int (*)(void)) dlsym(RTLD_DEFAULT,
+                                                                 "_ZN7android14IPCThreadState4selfEv");
+            patchEnv.native_getCallingUid = (int (*)(int)) dlsym(RTLD_DEFAULT,
+                                                                 "_ZNK7android14IPCThreadState13getCallingUidEv");
+            if (patchEnv.IPCThreadState_self == NULL) {
+                patchEnv.IPCThreadState_self = (int (*)(void)) dlsym(RTLD_DEFAULT,
+                                                                     "_ZN7android14IPCThreadState13getCallingUidEv");
+            }
+        }
+        if (h != NULL) {
+            dlclose(h);
         }
 
-        gOffset.GetCstrFromString = (char *(*)(void *)) dlsym(soInfo,
-                                                              "_Z23dvmCreateCstrFromStringPK12StringObject");
-        if (!gOffset.GetCstrFromString) {
-            gOffset.GetCstrFromString = (char *(*)(void *)) dlsym(soInfo,
-                                                                  "dvmCreateCstrFromString");
+        patchEnv.GetCstrFromString = (char *(*)(void *)) dlsym(soInfo,
+                                                               "_Z23dvmCreateCstrFromStringPK12StringObject");
+        if (!patchEnv.GetCstrFromString) {
+            patchEnv.GetCstrFromString = (char *(*)(void *)) dlsym(soInfo,
+                                                                   "dvmCreateCstrFromString");
         }
-        gOffset.GetStringFromCstr = (void *(*)(const char *)) dlsym(soInfo,
-                                                                    "_Z23dvmCreateStringFromCstrPKc");
-        if (!gOffset.GetStringFromCstr) {
-            gOffset.GetStringFromCstr = (void *(*)(const char *)) dlsym(soInfo,
-                                                                        "dvmCreateStringFromCstr");
+        patchEnv.GetStringFromCstr = (void *(*)(const char *)) dlsym(soInfo,
+                                                                     "_Z23dvmCreateStringFromCstrPKc");
+        if (!patchEnv.GetStringFromCstr) {
+            patchEnv.GetStringFromCstr = (void *(*)(const char *)) dlsym(soInfo,
+                                                                         "dvmCreateStringFromCstr");
         }
     }
-    measureNativeOffset(env, isArt);
-    replaceGetCallingUid(env, isArt);
-    replaceOpenDexFileMethod(env, env->GetObjectArrayElement(javaMethods, OPEN_DEX), isArt,
+    measureNativeOffset(isArt);
+    replaceGetCallingUid(isArt);
+    replaceOpenDexFileMethod(javaMethods.getElement(OPEN_DEX).get(), isArt,
                              apiLevel);
-    replaceCameraNativeSetupMethod(env, env->GetObjectArrayElement(javaMethods, CAMERA_SETUP),
+    replaceCameraNativeSetupMethod(javaMethods.getElement(CAMERA_SETUP).get(),
                                    isArt, apiLevel);
-    replaceAudioRecordNativeCheckPermission(env, env->GetObjectArrayElement(javaMethods,
-                                                                            VIVO_AUDIORECORD_NATIVE_CHECK_PERMISSION),
+    replaceAudioRecordNativeCheckPermission(javaMethods.getElement(
+            AUDIO_NATIVE_CHECK_PERMISSION).get(),
                                             isArt, apiLevel);
 }
 
-void *getVMHandle() {
-    char soName[15] = {0};
-    __system_property_get("persist.sys.dalvik.vm.lib.2", soName);
-    if (soName[0] == '\x0') {
-        __system_property_get("persist.sys.dalvik.vm.lib", soName);
+bool processNothing(void* thiz, void* new_methods){ return true; }
+bool (*orig_ProcessProfilingInfo)(void*, void*);
+
+bool compileNothing(void* thiz, void* thread, void* method, bool osr) { return false; }
+bool (*orig_CompileNothing)(void* thiz, void* thread, void* method, bool osr);
+
+void disableJit(int apiLevel) {
+#ifdef __arm__
+    void *libart = fake_dlopen("/system/lib/libart.so", RTLD_NOW);
+    if (libart) {
+        // disable profile.
+        void *processProfilingInfo = NULL;
+        const char *processProfileInfoFunc =
+                apiLevel < 26 ? "_ZN3art12ProfileSaver20ProcessProfilingInfoEPt" :
+                "_ZN3art12ProfileSaver20ProcessProfilingInfoEbPt";
+        processProfilingInfo = fake_dlsym(libart, processProfileInfoFunc);
+        ALOGE("processProfileingInfo: %p", processProfilingInfo);
+        if (processProfilingInfo) {
+            MSHookFunction(processProfilingInfo, (void*)processNothing, (void**)&orig_ProcessProfilingInfo);
+        }
+
+        // disable jit
+        void *compileMethod = NULL;
+        compileMethod = fake_dlsym(libart,
+                                   "_ZN3art3jit3Jit13CompileMethodEPNS_9ArtMethodEPNS_6ThreadEb");
+        ALOGE("compileMethod: %p", compileMethod);
+        if (compileMethod) {
+            MSHookFunction(compileMethod, (void*) compileNothing, (void**) &orig_CompileNothing);
+        }
+    }
+#endif
+}
+
+void *getDvmOrArtSOHandle() {
+    char so_name[25] = {0};
+    __system_property_get("persist.sys.dalvik.vm.lib.2", so_name);
+    if (strlen(so_name) == 0) {
+        __system_property_get("persist.sys.dalvik.vm.lib", so_name);
     }
-    void *soInfo = dlopen(soName, 0);
+    void *soInfo = dlopen(so_name, 0);
     if (!soInfo) {
         soInfo = RTLD_DEFAULT;
     }
diff --git a/VirtualApp/lib/src/main/jni/Foundation/VMPatch.h b/VirtualApp/lib/src/main/jni/Foundation/VMPatch.h
index b480af66d..a291a81df 100644
--- a/VirtualApp/lib/src/main/jni/Foundation/VMPatch.h
+++ b/VirtualApp/lib/src/main/jni/Foundation/VMPatch.h
@@ -2,8 +2,8 @@
 // VirtualApp Native Project
 //
 
-#ifndef NDK_HOOK_NATIVE_H
-#define NDK_HOOK_NATIVE_H
+#ifndef FOUNDATION_PATH
+#define FOUNDATION_PATH
 
 
 #include <jni.h>
@@ -11,16 +11,21 @@
 #include <stddef.h>
 #include <fcntl.h>
 #include <sys/system_properties.h>
+#include <fb/include/fb/ALog.h>
+#include <fb/include/fb/fbjni.h>
+#include "Jni/Helper.h"
 
-#include "Helper.h"
+using namespace facebook::jni;
 
 enum METHODS {
-    OPEN_DEX = 0, CAMERA_SETUP, VIVO_AUDIORECORD_NATIVE_CHECK_PERMISSION
+    OPEN_DEX = 0, CAMERA_SETUP, AUDIO_NATIVE_CHECK_PERMISSION
 };
 
-void patchAndroidVM(jobjectArray javaMethods, jstring packageName, jboolean isArt, jint apiLevel, jint cameraMethodType);
+void hookAndroidVM(JArrayClass<jobject> javaMethods,
+                   jstring packageName, jboolean isArt, jint apiLevel, jint cameraMethodType);
 
-void *getVMHandle();
+void *getDvmOrArtSOHandle();
 
+void disableJit(int apiLevel);
 
 #endif //NDK_HOOK_NATIVE_H
diff --git a/VirtualApp/lib/src/main/jni/Foundation/fake_dlfcn.cpp b/VirtualApp/lib/src/main/jni/Foundation/fake_dlfcn.cpp
new file mode 100644
index 000000000..9e8eec6f5
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Foundation/fake_dlfcn.cpp
@@ -0,0 +1,191 @@
+// Copyright (c) 2016 avs333
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+//		of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+//		to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//		copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+//		The above copyright notice and this permission notice shall be included in all
+//		copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// 		AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <elf.h>
+#include <android/log.h>
+#include "fake_dlfcn.h"
+
+#define TAG_NAME	"test2:fake_dlfcn"
+
+#define log_info(fmt,args...) __android_log_print(ANDROID_LOG_INFO, TAG_NAME, (const char *) fmt, ##args)
+#define log_err(fmt,args...) __android_log_print(ANDROID_LOG_ERROR, TAG_NAME, (const char *) fmt, ##args)
+
+//#ifdef LOG_DBG
+//#define log_dbg log_info
+//#else
+#define log_dbg(...)
+#define log_info(...)
+//#define log_err(...)
+//#endif
+
+#ifdef __arm__
+#define Elf_Ehdr Elf32_Ehdr
+#define Elf_Shdr Elf32_Shdr
+#define Elf_Sym  Elf32_Sym
+#elif defined(__aarch64__)
+#define Elf_Ehdr Elf64_Ehdr
+#define Elf_Shdr Elf64_Shdr
+#define Elf_Sym  Elf64_Sym
+#elif  defined(__i386__)
+#define Elf_Ehdr Elf32_Ehdr
+#define Elf_Shdr Elf32_Shdr
+#define Elf_Sym  Elf32_Sym
+#else
+//#error "Arch unknown, please port me"
+#endif
+
+struct ctx {
+    void *load_addr;
+    void *dynstr;
+    void *dynsym;	
+    int nsyms;
+    off_t bias;
+};
+
+extern "C" {
+int fake_dlclose(void *handle) {
+	if (handle) {
+		struct ctx *ctx = (struct ctx *) handle;
+		if (ctx->dynsym) free(ctx->dynsym);    /* we're saving dynsym and dynstr */
+		if (ctx->dynstr) free(ctx->dynstr);    /* from library file just in case */
+		free(ctx);
+	}
+	return 0;
+}
+
+/* flags are ignored */
+
+void *fake_dlopen(const char *libpath, int flags) {
+	FILE *maps;
+	char buff[256];
+	struct ctx *ctx = 0;
+	off_t load_addr, size;
+	int k, fd = -1, found = 0;
+	void *shoff;
+	Elf_Ehdr *elf = (Elf_Ehdr *) MAP_FAILED;
+
+#define fatal(fmt, args...) do { log_err(fmt,##args); goto err_exit; } while(0)
+
+	maps = fopen("/proc/self/maps", "r");
+	if (!maps) fatal("failed to open maps");
+
+	while (!found && fgets(buff, sizeof(buff), maps))
+		if (strstr(buff, "r-xp") && strstr(buff, libpath)) found = 1;
+
+	fclose(maps);
+
+	if (!found) fatal("%s not found in my userspace", libpath);
+
+	if (sscanf(buff, "%lx", &load_addr) != 1)
+		fatal("failed to read load address for %s", libpath);
+
+	log_info("%s loaded in Android at 0x%08lx", libpath, load_addr);
+
+	/* Now, mmap the same library once again */
+
+	fd = open(libpath, O_RDONLY);
+	if (fd < 0) fatal("failed to open %s", libpath);
+
+	size = lseek(fd, 0, SEEK_END);
+	if (size <= 0) fatal("lseek() failed for %s", libpath);
+
+	elf = (Elf_Ehdr *) mmap(0, size, PROT_READ, MAP_SHARED, fd, 0);
+	close(fd);
+	fd = -1;
+
+	if (elf == MAP_FAILED) fatal("mmap() failed for %s", libpath);
+
+	ctx = (struct ctx *) calloc(1, sizeof(struct ctx));
+	if (!ctx) fatal("no memory for %s", libpath);
+
+	ctx->load_addr = (void *) load_addr;
+	shoff = ((char *) elf) + elf->e_shoff;
+
+	for (k = 0; k < elf->e_shnum; k++, shoff = (char*)shoff + elf->e_shentsize) {
+
+		Elf_Shdr *sh = (Elf_Shdr *) shoff;
+		log_dbg("%s: k=%d shdr=%p type=%x", __func__, k, sh, sh->sh_type);
+
+		switch (sh->sh_type) {
+
+			case SHT_DYNSYM:
+				if (ctx->dynsym) fatal("%s: duplicate DYNSYM sections", libpath); /* .dynsym */
+				ctx->dynsym = malloc(sh->sh_size);
+				if (!ctx->dynsym) fatal("%s: no memory for .dynsym", libpath);
+				memcpy(ctx->dynsym, ((char *) elf) + sh->sh_offset, sh->sh_size);
+				ctx->nsyms = (sh->sh_size / sizeof(Elf_Sym));
+				break;
+
+			case SHT_STRTAB:
+				if (ctx->dynstr) break;    /* .dynstr is guaranteed to be the first STRTAB */
+				ctx->dynstr = malloc(sh->sh_size);
+				if (!ctx->dynstr) fatal("%s: no memory for .dynstr", libpath);
+				memcpy(ctx->dynstr, ((char *) elf) + sh->sh_offset, sh->sh_size);
+				break;
+
+			case SHT_PROGBITS:
+				if (!ctx->dynstr || !ctx->dynsym) break;
+				/* won't even bother checking against the section name */
+				ctx->bias = (off_t) sh->sh_addr - (off_t) sh->sh_offset;
+				k = elf->e_shnum;  /* exit for */
+				break;
+		}
+	}
+
+	munmap(elf, size);
+	elf = 0;
+
+	if (!ctx->dynstr || !ctx->dynsym) fatal("dynamic sections not found in %s", libpath);
+
+#undef fatal
+
+	log_dbg("%s: ok, dynsym = %p, dynstr = %p", libpath, ctx->dynsym, ctx->dynstr);
+
+	return ctx;
+
+	err_exit:
+	if (fd >= 0) close(fd);
+	if (elf != MAP_FAILED) munmap(elf, size);
+	fake_dlclose(ctx);
+	return 0;
+}
+
+void *fake_dlsym(void *handle, const char *name) {
+	int k;
+	struct ctx *ctx = (struct ctx *) handle;
+	Elf_Sym *sym = (Elf_Sym *) ctx->dynsym;
+	char *strings = (char *) ctx->dynstr;
+
+	for (k = 0; k < ctx->nsyms; k++, sym++)
+		if (strcmp(strings + sym->st_name, name) == 0) {
+			/*  NB: sym->st_value is an offset into the section for relocatables,
+            but a VMA for shared libs or exe files, so we have to subtract the bias */
+			void *ret = (char*)ctx->load_addr + sym->st_value - ctx->bias;
+			log_info("%s found at %p", name, ret);
+			return ret;
+		}
+	return 0;
+}
+}
+
+
diff --git a/VirtualApp/lib/src/main/jni/Foundation/fake_dlfcn.h b/VirtualApp/lib/src/main/jni/Foundation/fake_dlfcn.h
new file mode 100644
index 000000000..a64a8fc86
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Foundation/fake_dlfcn.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2016 avs333
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+//		of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+//		to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//		copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+//		The above copyright notice and this permission notice shall be included in all
+//		copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// 		AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef DEXPOSED_DLFCN_H
+#define DEXPOSED_DLFCN_H
+
+#include <cstdlib>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" {
+
+    void *fake_dlopen(const char *libpath, int flags);
+    void *fake_dlsym(void *handle, const char *name);
+
+};
+#endif //DEXPOSED_DLFCN_H
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/godin_type.h b/VirtualApp/lib/src/main/jni/GodinHook/godin_type.h
deleted file mode 100644
index 91fd09023..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/godin_type.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef GODINTYPE_H
-#define GODINTYPE_H
-
-
-typedef signed char             int8_t;
-typedef short int               int16_t;
-typedef int                     int32_t;
-# if __WORDSIZE == 64
-typedef long int                int64_t;
-# else
-__extension__
-typedef long long int           int64_t;
-# endif
-#endif
-
-/* Unsigned.  */
-typedef unsigned char           uint8_t;
-typedef unsigned short int      uint16_t;
-#ifndef __uint32_t_defined
-typedef unsigned int            uint32_t;
-# define __uint32_t_defined
-#endif
-#if __WORDSIZE == 64
-typedef unsigned long int       uint64_t;
-#else
-__extension__
-typedef unsigned long long int  uint64_t;
-
-#endif // GODINTYPE_H
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/hookinfo.h b/VirtualApp/lib/src/main/jni/GodinHook/hookinfo.h
deleted file mode 100644
index 853db19d1..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/hookinfo.h
+++ /dev/null
@@ -1,136 +0,0 @@
-#ifndef HOOKINFO_H
-#define HOOKINFO_H
-
-//#include <godin_type.h>
-#include <stdint.h>
-#include <iostream>
-namespace GodinHook {
-
-
-  enum HookStatus{
-    ERRSTATUS = 0,    /*!< 错误状态 */
-    REGISTERED,
-    HOOKED,           /*!< 已经被hook */
-  };
-
-  enum FunctionType{
-    ERRTYEPE  = 0,    /*!< 错误类型 */
-    ARM,              /*!< ARM指令集 */
-    THUMB ,           /*!< thumb指令集 */
-    ARM64,            /*!< ARMV8指令集*/
-  };
-
-class HookInfo{
-
-
-public :
-
-  /**
-   * @brief HookInfo
-   *  唯一的构造函数
-   * @param originalAddr
-   *  原方法地址
-   * @param hookAddr
-   *  新方法地址
-   * @param callOriginalAddr
-   *  存储回调原方法的地址
-   */
-  HookInfo(size_t originalAddr,size_t hookAddr,size_t ** callOriginalAddr)
-    :original_addr_(originalAddr),hook_addr_(hookAddr),call_original_addr_(callOriginalAddr),
-    original_stub_back_(NULL),back_len_(0),call_original_ins_(NULL),hook_status_(ERRSTATUS),
-    original_function_type_(ERRTYEPE),hook_function_type_(ERRTYEPE),count(0){}
-
-private:
-  /// 构造函数中负责初始化这三个字段
-  size_t original_addr_;                    /*!< 原方法地址 */
-  size_t hook_addr_;                        /*!< 新方法地址 */
-  size_t **call_original_addr_;             /*!< 回调原方法 */
-
-  /// registerAndHook初始化这三个字段,
-  /// unhook回收资源时要用到
-  uint8_t *original_stub_back_;             /*!< 存储原方法被stub覆盖的机器码 */
-  int    back_len_;                         /*!< 原方法被stub覆盖的机器指令大小,亦即stub大小 */
-  uint8_t *call_original_ins_;              /*!< 存储经过修正过的原方法被stub覆盖的机器指令,以及追加跳转至原方法剩余机器指令的跳转指令*/
-
-  HookStatus hook_status_;                  /*!< 当前hook状态*/
-  FunctionType original_function_type_;     /*!< 原方法指令集类型*/
-  FunctionType hook_function_type_;         /*!< 新方法指令集类型*/
-
-public:
-  /// 为了保证线程安全
-  int orig_boundaries[8];
-  int trampoline_boundaries[32];
-  int count;
-
-public:
-
-  void setOriginalAddr(size_t addr){
-    this->original_addr_ = addr;
-  }
-  size_t getOriginalAddr(){
-    return this->original_addr_;
-  }
-
-  void setHookAddr(size_t addr){
-    this->hook_addr_ = addr;
-  }
-  size_t getHookAddr(){
-    return this->hook_addr_;
-  }
-
-  void setCallOriginalAddr(size_t ** addr){
-     this->call_original_addr_ = addr;
-  }
-  size_t ** getCallOriginalAddr(){
-    return this->call_original_addr_;
-  }
-
-
-
-  void setOriginalStubBack(uint8_t* addr){
-    this->original_stub_back_ = addr;
-  }
- uint8_t* getOriginalStubBack(){
-    return this->original_stub_back_;
-  }
-
-  void setBackLen(int len){
-    this->back_len_ = len;
-  }
-  size_t getBackLen(){
-    return this->back_len_;
-  }
-
-  void setCallOriginalIns(uint8_t* addr){
-     this->call_original_ins_ = addr;
-  }
-  uint8_t* getCallOriginalIns(){
-    return this->call_original_ins_;
-  }
-
-
-  void setHookStatus(HookStatus status){
-    this->hook_status_ = status;
-  }
-  HookStatus getHookStatus(){
-    return this->hook_status_;
-  }
-
-  void setOriginalFunctionType(FunctionType type){
-    this->original_function_type_ = type;
-  }
-  FunctionType getOriginalFunctiontype(){
-    return this->original_function_type_;
-  }
-
-  void setHookFunctionType(FunctionType type){
-    this->hook_function_type_ = type;
-  }
-  FunctionType getHookFunctionType(){
-    return this->hook_function_type_;
-  }
-
-};
-
-}
-#endif // HOOKINFO_H
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/instruction/arm_instruction.cpp b/VirtualApp/lib/src/main/jni/GodinHook/instruction/arm_instruction.cpp
deleted file mode 100644
index 9c271a03a..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/instruction/arm_instruction.cpp
+++ /dev/null
@@ -1,229 +0,0 @@
-
-#include "arm_instruction.h"
-#include <stdlib.h>
-#include <string.h>
-#include "../mem_helper.h"
-#include <unistd.h>//android cacheflush
-
-
-#define MAX_REPAIR_INS_LEN 64
-
-uint8_t GodinHook::ArmInstruction::ldr[4]={
-  0x04,0xf0,0x1f,0xe5,
-};
-static void clearcache(char* begin, char *end)
-{
-    const int syscall = 0xf0002;
-    __asm __volatile (
-        "mov     r0, %0\n"
-        "mov     r1, %1\n"
-        "mov     r7, %2\n"
-        "mov     r2, #0x0\n"
-        "svc     0x00000000\n"
-        :
-        :"r" (begin), "r" (end), "r" (syscall)
-        :"r0","r1","r7"
-        );
-}
-void GodinHook::ArmInstruction::createStub(HookInfo * info)
-{
-  size_t originalAddress = info->getOriginalAddr();
-  size_t targetAddress = info->getHookAddr();
-  int len = sizeofStub();
-  /// 去保护,并修改指令
-  if(MemHelper::unProtectMemory(originalAddress,len)){
-     memcpy((void*)originalAddress,ldr,4);
-     memcpy((void*)(originalAddress+4),&targetAddress,4);
-  }else
-    return;
-  //clearcache(originalAddress,originalAddress+len);
-  /// 加保护
-  MemHelper::protectMemory(originalAddress,len);
-  /// 刷新指令缓存
-  cacheflush(originalAddress,originalAddress+len,0);
-
-
-
-}
-
-int GodinHook::ArmInstruction::getRepairInstruction(size_t instruction)
-{
-  if ((instruction & 0xFE000000) == 0xFA000000) {
-          return BLX_ARM;
-  }
-  if ((instruction & 0xF000000) == 0xB000000) {
-          return BL_ARM;
-  }
-  if ((instruction & 0xF000000) == 0xA000000) {
-          return B_ARM;
-  }
-  if ((instruction & 0xFF000FF) == 0x120001F) {
-          return BX_ARM;
-  }
-  if ((instruction & 0xFEF0010) == 0x8F0000) {
-          return ADD_ARM;
-  }
-  if ((instruction & 0xFFF0000) == 0x28F0000) {
-          return ADR1_ARM;
-  }
-  if ((instruction & 0xFFF0000) == 0x24F0000) {
-          return ADR2_ARM;
-  }
-  if ((instruction & 0xE5F0000) == 0x41F0000) {
-          return LDR_ARM;
-  }
-  if ((instruction & 0xFE00FFF) == 0x1A0000F) {
-          return MOV_ARM;
-  }
-  return UNDEFINE;
-}
-
-void GodinHook::ArmInstruction::repairBackInstructionsOfStub(HookInfo * info,size_t * calloriginal)
-{
-  size_t originalAddress = info->getOriginalAddr();
-  uint8_t *back = info->getOriginalStubBack();
-  size_t * ins = (size_t *)back;
-  size_t * repair = calloriginal;
-  if(NULL == repair)
-    return;
-  int pos = 0;
-
-  /// 得到原始指令起始处的pc值
-  size_t originalPc = originalAddress + 8;
-  size_t originalLr = originalAddress +sizeofStub();
-
-
-  /**
-   * 需要修正的是那些机器指令内部存储的是要操作数据基于当前PC的偏移值;
-   * 修正思路,计算出绝对地址,构造跳转指令。
-   */
-  for(int i=0;i<sizeofStub()/(sizeof(size_t));i++){
-  /// 为了线程安全
-  info->orig_boundaries[info->count] = i * sizeof(uint32_t);
-  info->trampoline_boundaries[info->count] = pos * sizeof(uint32_t);
-  info->count +=1;
-
-
-  int type = getRepairInstruction(ins[i]);
-  size_t x = 0;
-  int top_bit;
-  size_t imm32;
-  size_t value;
-  switch(type){
-    case BLX_ARM:
-    case BL_ARM:
-      repair[pos++] = 0xE28FE004;	// ADD LR, PC, #4
-    case B_ARM:
-    case BX_ARM:
-    {
-      repair[pos++] = 0xE51FF004;  	// LDR PC, [PC, #-4]
-      if(BLX_ARM == type)
-        x = (((ins[i]) & 0xFFFFFF) << 2) | (((ins[i]) & 0x1000000) >> 23);
-      else if (type == BL_ARM || type == B_ARM){
-          x = ((ins[i]) & 0xFFFFFF) << 2;
-        }
-      else {
-          x = 0;
-      }
-      top_bit = x >> 25;
-      imm32 = top_bit ? (x | (0xFFFFFFFF << 26)) : x;
-      if (type == BLX_ARM) {
-          value = originalPc + imm32 + 1;
-        }
-      else {
-          value = originalPc + imm32;
-        }
-      repair[pos++] = value;
-      break;
-    }
-    case ADD_ARM:
-    {
-        int rd;
-        int rm;
-        int r;
-        rd = ((ins[i]) & 0xF000) >> 12;
-        rm = (ins[i]) & 0xF;
-        for (r = 12; ; --r) {
-            if (r != rd && r != rm) {
-                break;
-              }
-          }
-        repair[pos++] = 0xE52D0004 | (r << 12);	// PUSH {Rr}
-        repair[pos++] = 0xE59F0008 | (r << 12);	// LDR Rr, [PC, #8]
-        repair[pos++] = ((ins[i]) & 0xFFF0FFFF) | (r << 16);
-        repair[pos++] = 0xE49D0004 | (r << 12);	// POP {Rr}
-        repair[pos++] = 0xE28FF000;	// ADD PC, PC
-        repair[pos++] = originalPc;
-        break;
-    }
-    case ADR1_ARM:
-    case ADR2_ARM:
-    case LDR_ARM:
-    case MOV_ARM:
-    {
-        int r;
-        uint32_t value;
-
-        r = ((ins[i]) & 0xF000) >> 12;
-
-        if (type == ADR1_ARM || type == ADR2_ARM || type == LDR_ARM) {
-            uint32_t imm32;
-
-            imm32 = (ins[i]) & 0xFFF;
-            if (type == ADR1_ARM) {
-                value = originalPc + imm32;
-              }
-            else if (type == ADR2_ARM) {
-                value = originalPc - imm32;
-              }
-            else if (type == LDR_ARM) {
-                int is_add;
-
-                is_add = ((ins[i]) & 0x800000) >> 23;
-                if (is_add) {
-                    value = ((size_t *) (originalPc + imm32))[0];
-                  }
-                else {
-                    value = ((size_t *) (originalPc - imm32))[0];
-                  }
-              }
-          }
-        else {
-            value = originalPc;
-          }
-        repair[pos++] = 0xE51F0000 | (r << 12);	// LDR Rr, [PC]
-        repair[pos++] = 0xE28FF000;	// ADD PC, PC
-        repair[pos++] = value;
-        break;
-    }
-    default:
-    {
-      ///无需修正
-      repair[pos++] = ins[i];
-    }
-  }
-  originalPc +=sizeof(size_t);
-  //ins++;
- }
-  repair[pos++] = 0xe51ff004;	// LDR PC, [PC, #-4]
-  repair[pos++] = originalLr;
-}
-
-void *GodinHook::ArmInstruction::createCallOriginalIns(HookInfo * info)
-{
-  void * fun = MemHelper::createExecMemory();
-
-
-  //int len = sizeofStub();
-  /**
-   *修正指令,需要对备份的机器指令中与pc相关的进行修正
-   */
-
-  ///创建指令
-  //memcpy(fun,back,len);
-  //memcpy((size_t)fun+len,ldr,4);
-  //memcpy((void*)((size_t)fun+len+4),&originalAddress,4);
-
-  repairBackInstructionsOfStub(info, (size_t *) fun);
-  return fun;
-}
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/instruction/arm_instruction.h b/VirtualApp/lib/src/main/jni/GodinHook/instruction/arm_instruction.h
deleted file mode 100644
index d19f6c140..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/instruction/arm_instruction.h
+++ /dev/null
@@ -1,71 +0,0 @@
-#ifndef ARM_INSTRUCTION_H
-#define ARM_INSTRUCTION_H
-
-#include "instruction_helper.h"
-#include <stdint.h>
-namespace GodinHook {
-
-  class ArmInstruction:public InstructionHelper{
-
-
-
-  public:
-
-    ~ArmInstruction(){}
-
-    /**
-     * @brief sizeofStub
-     *  计算stub指令所需空间大小,单位字节
-     * @return
-     *  返回stub所需字节数
-     */
-    int sizeofStub()
-    {
-      return 8;
-    }
-
-    /**
-     * @brief createStub
-     *  构造stub指令,亦即构造跳转指令,特别注意需要刷新指令缓存
-     * @param originalAddress
-     *  原方法地址
-     * @param targetAddress
-     *  跳转的目标地址
-     * @return
-     *  存储stub指令的空间地址
-     */
-    void  createStub(HookInfo * info);
-
-
-    int getRepairInstruction(size_t ins);
-
-    void  repairBackInstructionsOfStub(HookInfo * info,size_t * calloriginal);
-    void *createCallOriginalIns(HookInfo * info);
-
-   private:
-    static uint8_t ldr[4];
-    enum RepairIns{
-      BLX_ARM,      /*!< BLX <label>*/
-      BL_ARM,       /*!< BL <label>*/
-      B_ARM,        /*!< B <label>*/
-      BX_ARM,       /*!< BX PC*/
-      ADD_ARM,      /*!< ADD Rd, PC, Rm (Rd != PC, Rm != PC)*/
-      ADR1_ARM,     /*!< ADR Rd, <label>*/
-      ADR2_ARM,     /*!< ADR Rd, <label>*/
-      MOV_ARM,      /*!< MOV Rd, PC*/
-      LDR_ARM,      /*!<LDR Rt, <label>*/
-
-      UNDEFINE,
-    };
-
-
-
-
-
-
-
-  };
-
-
-}
-#endif // ARM_INSTRUCTION_H
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/instruction/instruction_helper.cpp b/VirtualApp/lib/src/main/jni/GodinHook/instruction/instruction_helper.cpp
deleted file mode 100644
index 15a5880ff..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/instruction/instruction_helper.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-
-#include "instruction_helper.h"
-#include <stdlib.h>
-#include <string.h>
-
-
-uint8_t *GodinHook::InstructionHelper::getBackOfStub(size_t targetAddress)
-{
-  int len = sizeofStub();
-  uint8_t * back = (uint8_t *) calloc(1, (size_t) len);
-  if(NULL == back)
-    return NULL;
-  memcpy(back, (const void *) targetAddress, (size_t) len);
-  return back;
-}
-
-///TODO 添加对ARM 64 指令集支持
-GodinHook::FunctionType GodinHook::InstructionHelper::getFunctionType(size_t functionAddr)
-{
-  if(0 == functionAddr)
-    return ERRTYEPE;
-    if(functionAddr % 4 == 0)
-      return ARM;
-    else if(functionAddr % 4 == 1)
-      return THUMB;
-
-    return THUMB;
-}
-
-size_t GodinHook::InstructionHelper::valueToMem(size_t addr)
-{
-  return addr &(~0x1L);
-}
-
-size_t GodinHook::InstructionHelper::valueToPc(size_t addr)
-{
-  return valueToMem(addr)+1;
-}
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/instruction/instruction_helper.h b/VirtualApp/lib/src/main/jni/GodinHook/instruction/instruction_helper.h
deleted file mode 100644
index 52e385843..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/instruction/instruction_helper.h
+++ /dev/null
@@ -1,113 +0,0 @@
-#ifndef INSTRUCTIONHELPER_H
-#define INSTRUCTIONHELPER_H
-
-//#include <godin_type.h>
-#include <stdint.h>
-#include "../hookinfo.h"
-#include <iostream>
-#include "../hookinfo.h"
-
-
-namespace GodinHook {
-
-
-/**
- * @brief 指令集基类
- *
- * 其直接子类负责具体的指令集,比如thumb2,arm,arm64.
- * 该类为抽象类,其子类需负责实现其中的纯虚函数。
- */
-class InstructionHelper{
-public:
-  /**
-   * @brief ~InstructionHelper
-   *  虚构造函数,析构时可以调用其子类的析构函数,防止析构不彻底
-   */
-  virtual ~InstructionHelper(){}
-
-  /**
-  * @brief createStub
-  *   在原方法机器码起始处创建跳转至新方法的跳转指令
-  *
-  * @param originalAddress
-  *   原方法地址,需考虑兼容32/64
-  * @param targetAddress
-  *   新方法地址,需考虑兼容32/64
-  */
-  virtual void createStub(HookInfo * info)=0;
-
-
-  virtual void * createCallOriginalIns(HookInfo * info)=0;
-
-  virtual int getRepairInstruction(size_t ins)=0;
-
-  virtual void repairBackInstructionsOfStub(HookInfo * info,size_t * calloriginal)=0;
-
-
-  virtual void isResetStubSize(size_t originalAddress){}
-
- /**
-   * @brief sizeofStub
-   *  计算stub机器指令所占空间
-   *
-   * @return
-   *  stub机器指令所占字节大小
-   */
-  virtual int sizeofStub()=0;
-
-
-  /**
-   * @brief getBackOfStub
-   * 备份被stub覆盖的机器指令,该函数内部申请的堆内存空间,需要调用者手动释放。
-   *
-   * @param targetAddress
-   * 机器指令起始地址
-   * @return
-   * 返回存储备份指令的数组地址
-   */
-  uint8_t * getBackOfStub(size_t targetAddress );
-
-  /**
-   * @brief getFunctionType
-   *   获得该函数的指令集类型
-   *
-   * @param functionAddr
-   *   必须是一个函数的地址
-   *
-   * @return
-   *   该函数指令集类型,ARM,THUMB or ARMV8
-   */
-  static FunctionType getFunctionType(size_t functionAddr);
-  
-  /**
-   * @brief valueToMem
-   *  修正地址,以便内存操作
-   * @param addr
-   *  函数地址info->getOriginalAddr()
-   * @return 
-   *  修正后的值
-   */
-  static size_t valueToMem(size_t addr);
-  
-  
-  /**
-   * @brief valueToPc
-   *  修正地址,以便正确运行
-   * @param addr
-   *  机器码地址
-   * @return 
-   *  修正后的值
-   */
-  static size_t valueToPc(size_t addr);
-
-
-
-
-
-
-private:
-  //static const unsigned char TargetJump[16];
-};
-
-}
-#endif // INSTRUCTIONHELPER_H
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/instruction/thumb_instruction.cpp b/VirtualApp/lib/src/main/jni/GodinHook/instruction/thumb_instruction.cpp
deleted file mode 100644
index 62a8c424b..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/instruction/thumb_instruction.cpp
+++ /dev/null
@@ -1,467 +0,0 @@
-
-#include "thumb_instruction.h"
-#include "../mem_helper.h"
-#include <unistd.h>//android cacheflush
-
-#define ALIGN_PC(pc)	(pc & 0xFFFFFFFC)
-
-void GodinHook::ThumbInstruction::createStub(HookInfo * info)
-{
-  size_t originalAddress = info->getOriginalAddr();
-  size_t targetAddress = info->getHookAddr();
-  int i = 0;
-  if(NULL == originalAddress || NULL == targetAddress)
-    return ;
-  /// 修正地址
-  size_t original = valueToMem(originalAddress);
- if(MemHelper::unProtectMemory(original,stub_len_)){
-    /// 判断是否需要添加nop指令
-    if(isPcNeedAlgin(original)){
-    ((uint16_t*)original)[i++] = 0xbf00;
-    //printf("--need pc align!!!!\n");
-    }
-    /// 构造跳转指令
-    ((uint16_t*)original)[i++] = 0xF8DF;
-    ((uint16_t*)original)[i++] = 0xF000;
-    ((uint16_t*)original)[i++] = targetAddress & 0xFFFF;
-    ((uint16_t*)original)[i++] = targetAddress >> 16;
-  }else
-    return ;
-    MemHelper::protectMemory(original,stub_len_);
-    /// 刷新指令缓存
-    cacheflush(original,original+stub_len_,0);
-}
-
-void *GodinHook::ThumbInstruction::createCallOriginalIns(HookInfo * info)
-{
-  void * fun = MemHelper::createExecMemory();
-
-
-  //int len = sizeofStub();
-  /**
-   *修正指令,需要对备份的机器指令中与pc相关的进行修正
-   */
-  repairBackInstructionsOfStub(info, (size_t *) fun);
-  return fun;
-}
-
-int GodinHook::ThumbInstruction::getRepairInstruction(size_t instruction)
-{
-  if((instruction >> 16) == 0){
-      if ((instruction & 0xF000) == 0xD000) {
-          return B1_THUMB16;
-        }
-      if ((instruction & 0xF800) == 0xE000) {
-          return B2_THUMB16;
-        }
-      if ((instruction & 0xFFF8) == 0x4778) {
-          return BX_THUMB16;
-        }
-      if ((instruction & 0xFF78) == 0x4478) {
-          return ADD_THUMB16;
-        }
-      if ((instruction & 0xFF78) == 0x4678) {
-          return MOV_THUMB16;
-        }
-      if ((instruction & 0xF800) == 0xA000) {
-          return ADR_THUMB16;
-        }
-      if ((instruction & 0xF800) == 0x4800) {
-          return LDR_THUMB16;
-        }
-   }else{
-      if ((instruction & 0xF800D000) == 0xF000C000) {
-          return BLX_THUMB32;
-        }
-      if ((instruction & 0xF800D000) == 0xF000D000) {
-          return BL_THUMB32;
-        }
-      if ((instruction & 0xF800D000) == 0xF0008000) {
-          return B1_THUMB32;
-        }
-      if ((instruction & 0xF800D000) == 0xF0009000) {
-          return B2_THUMB32;
-        }
-      if ((instruction & 0xFBFF8000) == 0xF2AF0000) {
-          return ADR1_THUMB32;
-        }
-      if ((instruction & 0xFBFF8000) == 0xF20F0000) {
-          return ADR2_THUMB32;
-        }
-      if ((instruction & 0xFF7F0000) == 0xF85F0000) {
-          return LDR_THUMB32;
-        }
-      if ((instruction & 0xFFFF00F0) == 0xE8DF0000) {
-          return TBB_THUMB32;
-        }
-      if ((instruction & 0xFFFF00F0) == 0xE8DF0010) {
-          return TBH_THUMB32;
-        }
-   }
-  return UNDEFINE;
-}
-
-int GodinHook::ThumbInstruction::repairThumb32Instruction(uint32_t pc, uint16_t high_instruction, uint16_t low_instruction, uint16_t *respair)
-{
-	uint32_t instruction;
-	int type;
-	int idx;
-	int offset;
-
-	instruction = (high_instruction << 16) | low_instruction;
-	type = getRepairInstruction(instruction);
-	idx = 0;
-	if (type == BLX_THUMB32 || type == BL_THUMB32 || type == B1_THUMB32 || type == B2_THUMB32) {
-		uint32_t j1;
-		uint32_t j2;
-		uint32_t s;
-		uint32_t i1;
-		uint32_t i2;
-		uint32_t x;
-		uint32_t imm32;
-		uint32_t value;
-
-		j1 = (low_instruction & 0x2000) >> 13;
-		j2 = (low_instruction & 0x800) >> 11;
-		s = (high_instruction & 0x400) >> 10;
-		i1 = !(j1 ^ s);
-		i2 = !(j2 ^ s);
-
-		if (type == BLX_THUMB32 || type == BL_THUMB32) {
-			respair[idx++] = 0xF20F;
-			respair[idx++] = 0x0E09;	// ADD.W LR, PC, #9
-		}
-		else if (type == B1_THUMB32) {
-			respair[idx++] = 0xD000 | ((high_instruction & 0x3C0) << 2);
-			respair[idx++] = 0xE003;	// B PC, #6
-		}
-		respair[idx++] = 0xF8DF;
-		respair[idx++] = 0xF000;	// LDR.W PC, [PC]
-		if (type == BLX_THUMB32) {
-			x = (s << 24) | (i1 << 23) | (i2 << 22) | ((high_instruction & 0x3FF) << 12) | ((low_instruction & 0x7FE) << 1);
-			imm32 = s ? (x | (0xFFFFFFFF << 25)) : x;
-			value = pc + imm32;
-		}
-		else if (type == BL_THUMB32) {
-			x = (s << 24) | (i1 << 23) | (i2 << 22) | ((high_instruction & 0x3FF) << 12) | ((low_instruction & 0x7FF) << 1);
-			imm32 = s ? (x | (0xFFFFFFFF << 25)) : x;
-			value = pc + imm32;
-			value = valueToPc(value);
-		}
-		else if (type == B1_THUMB32) {
-			x = (s << 20) | (j2 << 19) | (j1 << 18) | ((high_instruction & 0x3F) << 12) | ((low_instruction & 0x7FF) << 1);
-			imm32 = s ? (x | (0xFFFFFFFF << 21)) : x;
-			value = pc + imm32;
-			value = valueToPc(value);
-		}
-		else if (type == B2_THUMB32) {
-			x = (s << 24) | (i1 << 23) | (i2 << 22) | ((high_instruction & 0x3FF) << 12) | ((low_instruction & 0x7FF) << 1);
-			imm32 = s ? (x | (0xFFFFFFFF << 25)) : x;
-			value = pc + imm32;
-			value = valueToPc(value);
-		}
-		respair[idx++] = value & 0xFFFF;
-		respair[idx++] = value >> 16;
-		offset = idx;
-	}
-	else if (type == ADR1_THUMB32 || type == ADR2_THUMB32 || type == LDR_THUMB32) {
-		int r;
-		uint32_t imm32;
-		uint32_t value;
-
-		if (type == ADR1_THUMB32 || type == ADR2_THUMB32) {
-			uint32_t i;
-			uint32_t imm3;
-			uint32_t imm8;
-
-			r = (low_instruction & 0xF00) >> 8;
-			i = (high_instruction & 0x400) >> 10;
-			imm3 = (low_instruction & 0x7000) >> 12;
-			imm8 = instruction & 0xFF;
-
-			imm32 = (i << 31) | (imm3 << 30) | (imm8 << 27);
-
-			if (type == ADR1_THUMB32) {
-				value = ALIGN_PC(pc) + imm32;
-			}
-			else {
-				value = ALIGN_PC(pc) - imm32;
-			}
-		}
-		else {
-			int is_add;
-			uint32_t *addr;
-
-			is_add = (high_instruction & 0x80) >> 7;
-			r = low_instruction >> 12;
-			imm32 = low_instruction & 0xFFF;
-
-			if (is_add) {
-				addr = (uint32_t *) (ALIGN_PC(pc) + imm32);
-			}
-			else {
-				addr = (uint32_t *) (ALIGN_PC(pc) - imm32);
-			}
-
-			value = addr[0];
-		}
-
-		respair[0] = 0x4800 | (r << 8);	// LDR Rr, [PC]
-		respair[1] = 0xE001;	// B PC, #2
-		respair[2] = value & 0xFFFF;
-		respair[3] = value >> 16;
-		offset = 4;
-	}
-
-	else if (type == TBB_THUMB32 || type == TBH_THUMB32) {
-	     printf("99999999999999999");
-		int rm;
-		int r;
-		int rx;
-
-		rm = low_instruction & 0xF;
-
-		for (r = 7;; --r) {
-			if (r != rm) {
-			  break;
-			}
-		}
-
-		for (rx = 7; ; --rx) {
-			if (rx != rm && rx != r) {
-				break;
-			}
-		}
-
-		respair[0] = 0xB400 | (1 << rx);	// PUSH {Rx}
-		respair[1] = 0x4805 | (r << 8);	// LDR Rr, [PC, #20]
-		respair[2] = 0x4600 | (rm << 3) | rx;	// MOV Rx, Rm
-		if (type == TBB_THUMB32) {
-			respair[3] = 0xEB00 | r;
-			respair[4] = 0x0000 | (rx << 8) | rx;	// ADD.W Rx, Rr, Rx
-			respair[5] = 0x7800 | (rx << 3) | rx; 	// LDRB Rx, [Rx]
-		}
-		else if (type == TBH_THUMB32) {
-			respair[3] = 0xEB00 | r;
-			respair[4] = 0x0040 | (rx << 8) | rx;	// ADD.W Rx, Rr, Rx, LSL #1
-			respair[5] = 0x8800 | (rx << 3) | rx; 	// LDRH Rx, [Rx]
-		}
-		respair[6] = 0xEB00 | r;
-		respair[7] = 0x0040 | (r << 8) | rx;	// ADD Rr, Rr, Rx, LSL #1
-		respair[8] = 0x3001 | (r << 8);	// ADD Rr, #1
-		respair[9] = 0xBC00 | (1 << rx);	// POP {Rx}
-		respair[10] = 0x4700 | (r << 3);	// BX Rr
-		respair[11] = 0xBF00;
-		respair[12] = pc & 0xFFFF;
-		respair[13] = pc >> 16;
-		offset = 14;
-	}
-	else {
-		respair[0] = high_instruction;
-		respair[1] = low_instruction;
-		offset = 2;
-	}
-
-	return offset;
-}
-
-int GodinHook::ThumbInstruction::repairThumb16Instruction(uint32_t pc, uint16_t instruction, uint16_t *respair)
-{
-  int type;
-  int offset;
-  type = getRepairInstruction(instruction);
-  if (type == B1_THUMB16 || type == B2_THUMB16 || type == BX_THUMB16) {
-      uint32_t x;
-      int top_bit;
-      uint32_t imm32;
-      uint32_t value;
-      int idx;
-
-      idx = 0;
-      if (type == B1_THUMB16) {
-          x = (instruction & 0xFF) << 1;
-          top_bit = x >> 8;
-          imm32 = top_bit ? (x | (0xFFFFFFFF << 8)) : x;
-          value = pc + imm32;
-          respair[idx++] = instruction & 0xFF00;  // B<cond> 0
-          respair[idx++] = 0xE003;                // B PC, #6
-        }
-      else if (type == B2_THUMB16) {
-          x = (instruction & 0x7FF) << 1;
-          top_bit = x >> 11;
-          imm32 = top_bit ? (x | (0xFFFFFFFF << 11)) : x;
-          value = pc + imm32;
-
-        }
-      else if (type == BX_THUMB16) {
-          value = pc;
-        }
-
-      respair[idx++] = 0xF8DF;
-      respair[idx++] = 0xF000;	// LDR.W PC, [PC]
-      respair[idx++] = valueToPc(value) & 0xFFFF;
-      respair[idx++] = valueToPc(value) >> 16;
-      offset = idx;
-    }
-  else if (type == ADD_THUMB16) {
-      int rdn;
-      int rm;
-      int r;
-
-      rdn = ((instruction & 0x80) >> 4) | (instruction & 0x7);
-
-      for (r = 7; ; --r) {
-          if (r != rdn) {
-              break;
-            }
-        }
-
-      respair[0] = 0xB400 | (1 << r);	// PUSH {Rr}
-      respair[1] = 0x4802 | (r << 8);	// LDR Rr, [PC, #8]
-      respair[2] = (instruction & 0xFF87) | (r << 3);
-      respair[3] = 0xBC00 | (1 << r);	// POP {Rr}
-      respair[4] = 0xE002;	// B PC, #4
-      respair[5] = 0xBF00;
-      respair[6] = pc & 0xFFFF;
-      respair[7] = pc >> 16;
-      offset = 8;
-    }
-  else if (type == MOV_THUMB16 || type == ADR_THUMB16 || type == LDR_THUMB16) {
-      int r;
-      uint32_t value;
-
-      if (type == MOV_THUMB16) {
-          r = instruction & 0x7;
-          value = pc;
-        }
-      else if (type == ADR_THUMB16) {
-          r = (instruction & 0x700) >> 8;
-          value = ALIGN_PC(pc) + (instruction & 0xFF) << 2;
-        }
-      else {
-          r = (instruction & 0x700) >> 8;
-          value = ((uint32_t *) (ALIGN_PC(pc) + ((instruction & 0xFF) << 2)))[0];
-        }
-
-      respair[0] = 0x4800 | (r << 8);	// LDR Rd, [PC]
-      respair[1] = 0xE001;	// B PC, #2
-      respair[2] = value & 0xFFFF;
-      respair[3] = value >> 16;
-      offset = 4;
-    }
-  else {
-      respair[0] = instruction;
-      respair[1] = 0xBF00;  // NOP 方便接下来构造thumb2指令
-      offset = 2;
-    }
-
-  return offset;
-}
-void GodinHook::ThumbInstruction::repairBackInstructionsOfStub(HookInfo * info, size_t *calloriginal)
-{
-  size_t originalAddress = info->getOriginalAddr();
-  uint8_t *back = info->getOriginalStubBack();
-
-  uint16_t * ins = (uint16_t *)back;
-  uint16_t * repair = (uint16_t *)calloriginal;
-  int backlen = sizeofStub();
-  if(NULL == repair)
-    return;
-  int pos = 0;
-  int repair_pos = 0;
-
-  /// 得到原始指令起始处的pc值
-  size_t originalPc = valueToMem(originalAddress) + 4;
-  size_t originalLr = 0;
-
-  /**
-   * 需要修正的是那些机器指令内部存储的是要操作数据基于当前PC的偏移值;
-   * 修正思路,计算出绝对地址,构造跳转指令。
-   * 这里还要判断是常规的16位thumb指令,还是32位的thumb2指令
-   */
-
-  while(true){
-    int offset = 0;
-    /// 为了线程安全
-    info->orig_boundaries[info->count] = pos * sizeof(uint16_t);
-    info->trampoline_boundaries[info->count] = repair_pos * sizeof(uint16_t);
-    info->count +=1;
-
-    if(isThumb2Instruction(ins[pos])){
-      offset = repairThumb32Instruction(originalPc,ins[pos],ins[pos+1],&repair[repair_pos]);
-      originalPc += 4;
-      repair_pos += offset;
-      pos += 2;
-    }else{
-    /// thumb16
-      offset = repairThumb16Instruction(originalPc,ins[pos],&repair[repair_pos]);
-      originalPc += 2;
-      repair_pos += offset;
-      pos +=1;
-    }
-    if(pos >= sizeofStub()/2){
-        break;
-    }
-
-  }
-
-  /// 为了保险起见,再一次做判断
-  if((size_t)(&repair[repair_pos]) % 4 != 0){
-      repair[repair_pos ] = 0xBF00;
-      repair_pos +=1;
-//      printf("-------------------->>> 0x%x\n",&repair[repair_pos]);
-//      printf("-------------------->>> 0x%x\n",&repair[0]);
-//      printf("-------------------->>> %d\n",repair_pos);
-  }
-//  printf("-------------------->>> %d\n",repair_pos);
-  originalLr = valueToMem(originalAddress) + sizeofStub() + 1;
-  repair[repair_pos ] = 0xF8DF;
-  repair[repair_pos +1] = 0xF000;	// LDR.W PC, [PC]
-  repair[repair_pos +2] = originalLr & 0xFFFF;
-  repair[repair_pos +3] = originalLr >> 16;
-}
-
-int GodinHook::ThumbInstruction::sizeofStub()
-{
-  return stub_len_;
-}
-
-void GodinHook::ThumbInstruction::isResetStubSize(size_t originalAddress)
-{
-  size_t original = valueToMem(originalAddress);
-  uint16_t * ins = (uint16_t *)original;
-  if(!isPcNeedAlgin(original)){
-//      printf("ins[3]===0x%x\n",ins[3]);
-//      printf("ins[4]===0x%x\n",ins[4]);
-      if(((ins[3] & 0xf000)==0xf000) && ((ins[4] & 0xc000)==0xc000))
-        setStubSize(10);//调用原函数时,需要特殊处理
-      else
-        setStubSize(8);
-  }
-  else{
-      if(((ins[4] & 0xf000)==0xf000) && ((ins[5] & 0xc000)==0xc000))
-         setStubSize(12);
-      else
-         setStubSize(10);//调用原函数时,需要特殊处理
-  }
-
-}
-
-bool GodinHook::ThumbInstruction::isPcNeedAlgin(size_t address)
-{
-  if(NULL == address)
-    return false;
-  /// 此时表示bit[1]为1
-  if(address % 4 != 0)
-    return true;
-  else
-    return false;
-}
-
-bool GodinHook::ThumbInstruction::isThumb2Instruction(uint16_t ins)
-{
-  if(((ins >> 11)>=0x1d) && ((ins >> 11)<= 0x1f))
-    return true;
-  else
-    return false;
-}
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/instruction/thumb_instruction.h b/VirtualApp/lib/src/main/jni/GodinHook/instruction/thumb_instruction.h
deleted file mode 100644
index fc1305dcd..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/instruction/thumb_instruction.h
+++ /dev/null
@@ -1,111 +0,0 @@
-#ifndef THUMB2_INSTRUCTION_H
-#define THUMB2_INSTRUCTION_H
-
-#include "instruction_helper.h"
-
-
-
-namespace GodinHook {
-  /**
-   * @brief The Thumb2Instruction class
-   * 仅支持ARMv5T之后支持thumb2指令集;
-   *
-   * 另外thumb2指令集中的ldr指令需要4字节对齐;
-   * 传入pc中的指令地址末尾bit要为1;
-   *
-   */
-  class ThumbInstruction :public InstructionHelper{
-
-
-    // InstructionHelper interface
-  public:
-   ThumbInstruction():stub_len_(12){}
-
-   /**
-    * @brief sizeofStub
-    *  计算stub指令所需空间大小,单位字节
-    * @return
-    *  返回stub所需字节数
-    */
-    void createStub(HookInfo * info);
-
-
-    void *createCallOriginalIns(HookInfo * info);
-    int getRepairInstruction(size_t ins);
-    void repairBackInstructionsOfStub(HookInfo * info,size_t * calloriginal);
-    /**
-     * @brief sizeofStub
-     * 计算thumb中stub块大小;
-     *
-     * @return
-     * 返回stub块所需字节数
-     */
-    int sizeofStub();
-    void isResetStubSize(size_t originalAddress);
-  private:
-    int repairThumb32Instruction(uint32_t pc, uint16_t high_instruction, uint16_t low_instruction, uint16_t *respair);
-    int repairThumb16Instruction(uint32_t pc, uint16_t instruction, uint16_t *respair);
-    bool isPcNeedAlgin(size_t address);
-
-    /**
-     * @brief isThumb2Instruction
-     * 判断当前指令是thumb还是thumb2
-     * @param ins
-     * 指令
-     * @return
-     * 返回true是thumb2;
-     * 返回false是thumb;
-     */
-    bool isThumb2Instruction(uint16_t ins);
-
-    void setStubSize(int len)
-    {
-      stub_len_ = len;
-    }
-
-    int stub_len_;
-    enum RepairIns{
-          // B <label>
-          B1_THUMB16 = 0,
-          // B <label>
-          B2_THUMB16,
-          // BX PC
-          BX_THUMB16,
-          // ADD <Rdn>, PC (Rd != PC, Rn != PC) 在对ADD进行修正时,
-          //采用了替换PC为Rr的方法,当Rd也为PC时,由于之前更改了Rr的值,
-          //可能会影响跳转后的正常功能。
-          ADD_THUMB16,
-          // MOV Rd, PC
-          MOV_THUMB16,
-          // ADR Rd, <label>
-          ADR_THUMB16,
-          // LDR Rt, <label>
-          LDR_THUMB16,
-
-          // BLX <label>
-          BLX_THUMB32,
-          // BL <label>
-          BL_THUMB32,
-          // B.W <label>
-          B1_THUMB32,
-          // B.W <label>
-          B2_THUMB32,
-          // ADR.W Rd, <label>
-          ADR1_THUMB32,
-          // ADR.W Rd, <label>
-          ADR2_THUMB32,
-          // LDR.W Rt, <label>
-          LDR_THUMB32,
-          // TBB [PC, Rm]
-          TBB_THUMB32,
-          // TBH [PC, Rm, LSL #1]
-          TBH_THUMB32,
-
-          UNDEFINE,
-    };
-  };
-}
-
-
-
-#endif // THUMB2_INSTRUCTION_H
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/mem_helper.cpp b/VirtualApp/lib/src/main/jni/GodinHook/mem_helper.cpp
deleted file mode 100644
index 26107dfa8..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/mem_helper.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-#include <mem_helper.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <stdlib.h>
-#include <asm/unistd.h>
-#include <sys/syscall.h>
-
-
-using namespace std;
-
-#define maps "/proc/self/maps"
-#define MAX_BUF 512
-
-bool GodinHook::MemHelper::isFunctionAddr(size_t addr)
-{
-  char buf[MAX_BUF]={0};
-
-     FILE * fp = fopen(maps,"r");
-
-     if(NULL == fp){
-         return false;
-     }
-
-     while(fgets(buf,MAX_BUF,fp)){
-
-         /*
-          *可执行程序和so库对应的属性段是“r-xp”
-          * */
-         if(strstr(buf,"r-xp")!=NULL){
-            size_t startAddr = strtoul(strtok(buf,"-"),NULL,16);
-            size_t endAddr   = strtoul(strtok(NULL," "),NULL,16);
-             if(addr>=startAddr && addr<=endAddr){
-                 fclose(fp);
-                 //printf("startAddr = 0x%x \n",startAddr);
-                 //printf("endAddr = 0x%x \n",endAddr);
-                 return true;
-             }
-
-         }
-     }
-
-     fclose(fp);
-     perror("this functionAddr is not a function!\n");
-     return false;
-}
-
-bool GodinHook::MemHelper::unProtectMemory(size_t addr, int size)
-{
-  /// 获得当前系统的内存页大小
-  int pageSize = sysconf(_SC_PAGESIZE);
-
-  /// 计算所在内存页中的偏移
-  int align = addr % pageSize;
-
-  int ret  = mprotect((void*)(addr-align),(size_t)(size+align),PROT_READ|PROT_WRITE|PROT_EXEC);
-  if(-1 == ret){
-      perror("mprotect");
-      return false;
-    }
-  return true;
-
-}
-
-bool GodinHook::MemHelper::protectMemory(size_t addr, int size)
-{
-  /// 获得当前系统的内存页大小
-  int pageSize = sysconf(_SC_PAGESIZE);
-
-  /// 计算所在内存页的偏移
-  int align = addr % pageSize;
-
-  int ret  = syscall(__NR_mprotect,(void*)(addr-align),(size_t)(size+align),PROT_READ|PROT_EXEC);
-  if(-1 == ret){
-      perror("mprotect");
-      return false;
-    }
-  return true;
-
-}
-
-void *GodinHook::MemHelper::createExecMemory()
-{
-  /// 获得当前系统的内存页大小
-  int pageSize = sysconf(_SC_PAGESIZE);
-
-  return mmap(NULL,pageSize,PROT_READ | PROT_WRITE | PROT_EXEC,MAP_ANONYMOUS | MAP_PRIVATE,0,0);
-}
-
-void GodinHook::MemHelper::freeExecMemory(void *address)
-{
-  /// 获得当前系统的内存页大小
-  int pageSize = sysconf(_SC_PAGESIZE);
-  munmap(address,pageSize);
-}
-
-
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/mem_helper.h b/VirtualApp/lib/src/main/jni/GodinHook/mem_helper.h
deleted file mode 100644
index 2fbff7642..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/mem_helper.h
+++ /dev/null
@@ -1,68 +0,0 @@
-#ifndef MEMHELPER_H
-#define MEMHELPER_H
-
-#include <fstream>
-#include <sys/mman.h>
-
-namespace GodinHook {
-  class MemHelper{
-  public:
-    /**
-     * @brief isFunctionAddr
-     *  判断所给地址是否是一个函数地址
-     * @param addr
-     *
-     * @return
-     *  是函数地址返回true,反之返回false。
-     */
-    static bool isFunctionAddr(size_t addr);
-
-    /**
-     * @brief unProtectMemory
-     *  将起始地址处开始的size大小去保护,即添加写权限。
-     *  此函数可能真正去保护的部分要比size大。
-     *
-     * @param addr
-     *  起始地址
-     * @param size
-     *  要去保护的内存空间大小
-     *
-     * @return
-     *  执行成功返回true,失败返回false
-     */
-    static bool unProtectMemory(size_t addr,int size);
-
-    /**
-     * @brief protectMemory
-     *  将起始地址处开始的size大小添加保护,即取消写权限。
-     *  此函数可能真正添加保护的部分要比size大。
-     *
-     * @param addr
-     *  起始地址
-     * @param size
-     *  要添加保护的内存空间大小
-     *
-     * @return
-     *  执行成功返回true,失败返回false
-     */
-    static bool protectMemory(size_t addr,int size);
-
-    /**
-     * @brief createExecMemory
-     * 创建一个内存页大小的可执行内存区域
-     * @return
-     * 可执行区域的起始地址
-     */
-    static void * createExecMemory();
-
-    /**
-     * @brief freeExecMemory
-     * 释放一个内存页大小的可执行区域
-     * @param address
-     * 要释放的可执行内存起始地址
-     */
-    static void freeExecMemory(void * address);
-
-  };
-}
-#endif // MEMHELPER_H
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/native_hook.cpp b/VirtualApp/lib/src/main/jni/GodinHook/native_hook.cpp
deleted file mode 100644
index f0cc09557..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/native_hook.cpp
+++ /dev/null
@@ -1,338 +0,0 @@
-
-
-#include "native_hook.h"
-#include "mem_helper.h"
-#include "instruction/instruction_helper.h"
-#include "instruction/arm_instruction.h"
-#include "instruction/thumb_instruction.h"
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include "thread_helper.h"
-
-
-
-GodinHook::NativeHook::hook_map GodinHook::NativeHook::hook_map_;
-
-int GodinHook::NativeHook::registeredHook(size_t originalFunAddress, size_t newFunAddress, size_t **callOriginal)
-{
-  bool flag = false;
-
-  /// 首先判断original和new为函数地址
-  if (!MemHelper::isFunctionAddr(originalFunAddress) || !MemHelper::isFunctionAddr(newFunAddress))
-    return GODINHOOK_ERROR_NOT_EXECUTABLE;
-
-  /// 尝试得到HookInfo
-  HookInfo * info = NULL;
-  info = getHookInfo(originalFunAddress);
-
-  if(NULL != info){
-      /// 判断original的hook状态
-      HookStatus hookStatus = info->getHookStatus();
-      if(HOOKED == hookStatus)
-        return GODINHOOK_ERROR_ALREADY_HOOKED;
-      if(REGISTERED == hookStatus)
-        return GODINHOOK_ERROR_ALREADY_REGISTERED;
-  }
-
-  /// info == NULL
-  /// 没有注册过,需要创建和初始化HookInfo对象,并添加到hook_map_中
-  info = new HookInfo(originalFunAddress,newFunAddress,callOriginal);
-  if(NULL == info)
-    return GODINHOOK_ERROR_MEMORY;
-
-  /// 设置original方法指令集
-  FunctionType type = InstructionHelper::getFunctionType(originalFunAddress);
-  if(ERRTYEPE == type)
-    return false;
-  info->setOriginalFunctionType(type);
-
-  /// 设置hook方法指令集
-  info->setHookFunctionType(InstructionHelper::getFunctionType(newFunAddress));
-
-
-  /// 创建指令集处理对象
-  InstructionHelper *insHelper = NULL ;
-  if(ARM == type){
-      insHelper =  new ArmInstruction();
-     printf("arm----------------\n");
-  }else if(THUMB == type){
-     insHelper = new ThumbInstruction();
-     insHelper->isResetStubSize(originalFunAddress);
-     printf("thumb---------len-----%d--\n",insHelper->sizeofStub());
-  }else if(ARM64 == type){
-      // TODO
-  }
-  /// 备份stub覆盖的指令
-  uint8_t *back = insHelper->getBackOfStub(InstructionHelper::valueToMem(originalFunAddress));
-  if(NULL == back){
-    free(insHelper);
-    return GODINHOOK_ERROR_MEMORY;
-  }
-  /// 设置hookinfo
-  info->setBackLen(insHelper->sizeofStub());
-  info->setOriginalStubBack(back);
-
-
-  /// 创建call original instruction ins
-
-  void * callOriginalFun = insHelper->createCallOriginalIns(info);
-  if(NULL == callOriginalFun){
-    free(back);
-    free(insHelper);
-    return GODINHOOK_ERROR_MEMORY;
-  }
-  info->setCallOriginalIns((uint8_t *) callOriginalFun);
-
-  /// 修改hookstatus
-  addHookInfo(info);
-  info->setHookStatus(REGISTERED);
-
-  free(insHelper);
-  return GODINHOOK_OK;
-}
-
-
-
-int GodinHook::NativeHook::hook(size_t originalFunAddress)
-{
-
-  HookInfo * info = getHookInfo(originalFunAddress);
-
-  if(NULL == info)
-    return GODINHOOK_ERROR_NOT_REGISTERED;
-  if(info->getHookStatus() == HOOKED)
-    return GODINHOOK_ERROR_ALREADY_HOOKED;
-  else if(info->getHookStatus() == REGISTERED){
-
-    /// 暂停当前其他线程;
-    /// 并且纠正正在运行被hook的函数的线程的PC
-
-    pid_t pid = ThreadHealper::freezzAndRepairThread(info,ACTION_ENABLE);
-
-    /// 进行hook
-    if(Hook(info)){
-      /// 恢复当前其他线程
-      ThreadHealper::unFreeze(pid);
-      return GODINHOOK_OK;
-    }
-    else{
-      /// 恢复当前其他线程
-      ThreadHealper::unFreeze(pid);
-      return GODINHOOK_ERROR_MEMORY;
-    }
-  }else
-    return GODINHOOK_ERROR_UNKNOWN;
-}
-
-void *GodinHook::NativeHook::isAlreadyHooked(size_t originalFunAddress)
-{
-  hook_map::iterator it = hook_map_.find(originalFunAddress);
-  if(it == hook_map_.end())
-    return NULL;
-  else {
-      HookInfo * info = it->second;
-      if(NULL != info && (info->getHookAddr() != NULL))
-       return (void *) info->getHookAddr();
-  }
-  return NULL;
-}
-
-int GodinHook::NativeHook::getHookedCount()
-{
-  return hook_map_.size();
-}
-
-bool GodinHook::NativeHook::unHook(size_t originalFunAddress)
-{
-  hook_map::iterator it = hook_map_.find(originalFunAddress);
-  if(it == hook_map_.end())
-    return true;
-  else{
-       HookInfo * info = it->second;
-       if(info != NULL || info->getHookStatus() == HOOKED){
-          size_t addr = InstructionHelper::valueToMem(originalFunAddress);
-
-          pid_t pid;
-          int i;
-          /// 冻结线程
-          pid = ThreadHealper::freezzAndRepairThread(info, ACTION_DISABLE);
-          /// 去保护,还原,加保护
-          if(MemHelper::unProtectMemory(addr,info->getBackLen())){
-              memcpy((void *) addr, info->getOriginalStubBack(), info->getBackLen());
-              MemHelper::protectMemory(addr,info->getBackLen());
-
-           /// 刷新指令缓存
-           cacheflush(addr, addr+info->getBackLen(), 0);
-
-           ///恢复线程
-           ThreadHealper::unFreeze(pid);
-           /// 释放资源
-            if(info->getCallOriginalIns() != NULL)
-              MemHelper::freeExecMemory(info->getCallOriginalIns());
-            if(info->getOriginalStubBack() !=NULL )
-              free(info->getOriginalStubBack());
-            if(info->getCallOriginalAddr() !=NULL )
-              (*(info->getCallOriginalAddr()))=NULL;
-            hook_map_.erase(it);
-            free(info);
-            info = NULL;
-            return true;
-        }else
-            return false;
-
-      }
-  }
-  return false;
-}
-
-GodinHook::HookInfo **GodinHook::NativeHook::getAllHookInfo()
-{
-  int count = getHookedCount();
-
-  HookInfo ** infos = (HookInfo **) calloc(count, sizeof(HookInfo*));
-
-  hook_map::iterator it = hook_map_.begin();
-  for(int i=0;it!=hook_map_.end();++it,++i){
-    infos[i] = it->second;
-  }
-  return infos;
-}
-
-void GodinHook::NativeHook::hookAllRegistered()
-{
-  pid_t pid;
-  int i;
-  pid = ThreadHealper::freezzAndRepairThread(NULL, ACTION_ENABLE);
-  HookInfo ** infos = NativeHook::getAllHookInfo();
-  for (i = 0; i < getHookedCount(); ++i) {
-      if (infos[i]->getHookStatus() == REGISTERED) {
-         Hook(infos[i]);
-        }
-    }
-  ThreadHealper::unFreeze(pid);
-}
-
-void GodinHook::NativeHook::unHookAll()
-{
-  pid_t pid;
-  int i;
-
-  pid = ThreadHealper::freezzAndRepairThread(NULL, ACTION_DISABLE);
-  HookInfo ** infos = NativeHook::getAllHookInfo();
-  int count = getHookedCount();
-  for (i = 0; i < count; ++i) {
-      if (infos[i]->getHookStatus() == HOOKED) {
-        // printf("-------unhookall count %d\n",getHookedCount());
-         UnHook(infos[i]);
-        }
-    }
-  ThreadHealper::unFreeze(pid);
-  free(infos);
-}
-
-GodinHook::HookStatus GodinHook::NativeHook::getFunctionStatus(size_t functionAddr)
-{
-   hook_map::iterator it = hook_map_.find(functionAddr);
-   if(it == hook_map_.end())
-     return ERRSTATUS;
-   else {
-       HookInfo * info = it->second;
-       if(NULL != info)
-        return info->getHookStatus();
-   }
-   return ERRSTATUS;
-}
-
-void GodinHook::NativeHook::addHookInfo(GodinHook::HookInfo *info)
-{
-  if(NULL == info)
-   return;
-  hook_map_.insert(hook_map::value_type(info->getOriginalAddr(),info));
-}
-
-bool GodinHook::NativeHook::Hook(HookInfo *info)
-{
-
-  /// 获取original方法指令集
-  FunctionType type = info->getOriginalFunctiontype();
-  if(ERRTYEPE == type)
-    return false;
-
-  /// 创建指令集处理对象
-  InstructionHelper *insHelper = NULL ;
-  if(ARM == type){
-      insHelper =  new ArmInstruction();
-  }else if(THUMB == type){
-     insHelper = new ThumbInstruction();
-     insHelper->isResetStubSize(info->getOriginalAddr());
-  }else if(ARM64 == type){
-
-  }
-
-  /// 创建stub
-  insHelper->createStub(info);
-
-  /// 创建call original instruction
-  /// 此时说明使用者不需要回调原方法
-  if(NULL == info->getCallOriginalAddr())
-    return true;
-  else{
-      void * callOriginal =info->getCallOriginalIns();
-      if(THUMB == type)
-        *(info->getCallOriginalAddr()) = (size_t *) InstructionHelper::valueToPc((size_t)callOriginal);
-      else
-        *(info->getCallOriginalAddr()) = (size_t *) callOriginal;
-  }
-  /// 设置状态
-  info->setHookStatus(HOOKED);
-  /// 再次刷新指令缓存
-  cacheflush((long) InstructionHelper::valueToMem(info->getOriginalAddr()),
-             (long) (InstructionHelper::valueToMem(info->getOriginalAddr()) + 12), 0);
-  free(insHelper);
-  return true;
-}
-
-bool GodinHook::NativeHook::UnHook(GodinHook::HookInfo *info)
-{
-  size_t addr = InstructionHelper::valueToMem(info->getOriginalAddr());
-  /// 去保护,还原,加保护
-  if(MemHelper::unProtectMemory(addr,info->getBackLen())){
-        memcpy((void *) addr, info->getOriginalStubBack(), info->getBackLen());
-        MemHelper::protectMemory(addr,info->getBackLen());
-
-        /// 刷新指令缓存
-        cacheflush(addr, (long) (addr + info->getBackLen()), 0);
-
-        /// 释放资源
-        if(info->getCallOriginalIns() != NULL)
-          MemHelper::freeExecMemory(info->getCallOriginalIns());
-        if(info->getOriginalStubBack() !=NULL )
-          free(info->getOriginalStubBack());
-        if(info->getCallOriginalAddr() !=NULL )
-          (*(info->getCallOriginalAddr()))=NULL;
-
-        hook_map::iterator it = hook_map_.find(info->getOriginalAddr());
-        if(it != hook_map_.end())
-          hook_map_.erase(it);
-        free(info);
-        info = NULL;
-        return true;
-   }else
-    return false;
-}
-
-GodinHook::HookInfo *GodinHook::NativeHook::getHookInfo(size_t functionAddr)
-{
-  hook_map::iterator it = hook_map_.find(functionAddr);
-  if(it == hook_map_.end())
-    return NULL;
-  else {
-      HookInfo * info = it->second;
-      if(NULL != info)
-       return info;
-  }
-  return NULL;
-}
-
-
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/native_hook.h b/VirtualApp/lib/src/main/jni/GodinHook/native_hook.h
deleted file mode 100644
index 7b812d5d9..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/native_hook.h
+++ /dev/null
@@ -1,165 +0,0 @@
-#ifndef NATIVE_HOOK_H
-#define NATIVE_HOOK_H
-
-#include <map>
-#include <string>
-#include <hookinfo.h>
-
-using namespace std;
-
-namespace  GodinHook {
-
-  enum GODINHOOK_STATUS {
-    GODINHOOK_ERROR_UNKNOWN = -1,
-    GODINHOOK_OK = 0,
-    GODINHOOK_ERROR_NOT_INITIALIZED,
-    GODINHOOK_ERROR_NOT_EXECUTABLE,
-    GODINHOOK_ERROR_NOT_REGISTERED,
-    GODINHOOK_ERROR_NOT_HOOKED,
-    GODINHOOK_ERROR_ALREADY_REGISTERED,
-    GODINHOOK_ERROR_ALREADY_HOOKED,
-    GODINHOOK_ERROR_SO_NOT_FOUND,
-    GODINHOOK_ERROR_FUNCTION_NOT_FOUND,
-    GODINHOOK_ERROR_MEMORY
-  };
-
-  class NativeHook{
-
-  public:
-
-    /**
-     * @brief registeredHook
-     *  注册成功后,就对相关的指令进行了修正,以及创建好回调原方法的环境,主要是为了线程安全做准备。
-     *
-     * @param originalFunAddress
-     *  原函数地址
-     * @param newFunAddress
-     *  新函数地址
-     * @param callOriginal
-     *  存储回调原函数的地址
-     *
-     * @return
-     *  返回 GODINHOOK_OK,表示注册成功;
-     *  返回 GODINHOOK_ERROR_ALREADY_REGISTERED,表示已经注册;
-     *  返回 GODINHOOK_ERROR_ALREADY_HOOKED,表示已经hook.
-     *  返回其他,则注册失败。
-     */
-    static int registeredHook(size_t originalFunAddress,size_t newFunAddress,size_t ** callOriginal);
-
-    /**
-     * @brief hook
-     *  对外提供的hook函数的接口,执行成功即完成了hook。
-     *
-     *  此方法中要对当前进程中的线程进行检查,并对正在运行被hook的
-     *  函数的线程,进行必要的修正。
-     *
-     * @param originalFunAddress
-     *  原函数地址
-     *
-     * @return
-     *  返回 GODINHOOK_OK,表示hook成功;
-     *  返回 GODINHOOK_ERROR_ALREADY_HOOKED,表示已经hook.
-     *  返回其他,则hook失败
-     */
-    static int hook(size_t originalFunAddress);
-
-    /**
-     * @brief isAlreadyHooked
-     * 判断该方法是否被hook了
-     * @param originalFunAddress
-     * 方法地址
-     * @return
-     * NULL,表明未被HOOK;
-     * 否则返回hook函数的地址;
-     */
-    static void* isAlreadyHooked(size_t originalFunAddress);
-
-    /**
-     * @brief getHookedCount
-     * 当前被hook的方法的数量
-     * @return
-     * 返回当前被hook的方法的数量
-     */
-    static int getHookedCount();
-
-    /**
-     * @brief unHook
-     * 卸载hook
-     * @param originalFunAddress
-     * 原方法地址
-     * @return
-     * true表示卸载成功;
-     * 反之卸载失败。
-     */
-    static bool unHook(size_t originalFunAddress);
-
-
-    /**
-     * @brief getAllHookInfo
-     * 得到当前所有的HookInfo,需要手动释放返回值指向的空间
-     * @return
-     * 返回存储有当前所有HookInfo地址的数组
-     */
-    static HookInfo ** getAllHookInfo();
-
-    /**
-     * @brief hookAllRegistered
-     * 将注册的所有方法进行hook操作
-     */
-    static void hookAllRegistered();
-
-    /**
-     * @brief unHookAll
-     * 卸载所有的hook
-     */
-    static void unHookAll();
-
-
-
-  private:
-    /**
-     * @brief getFunctionStatus
-     *  获取函数当前的状态
-     *
-     * @param functionAddr
-     *  必须是一个函数的地址
-     *
-     * @return
-     * UNREGISTERED,REGISTERED,HOOKED or UNHOOK.
-     */
-    static HookStatus getFunctionStatus(size_t functionAddr);
-
-    /**
-     * @brief addHookInfo
-     *  向hook_map_中添加新成员
-     * @param info
-     *  要加入的hook_map_的info
-     */
-    static void addHookInfo(HookInfo * info);
-
-    /**
-     * @brief registerAndHook
-     * 注册并进行hook操作
-     * @param info
-     * HookInfo类型的对象指针
-     * @return
-     * 成功返回true;
-     * 失败返回false.
-     */
-    static bool Hook(HookInfo* info);
-
-    static bool UnHook(HookInfo* info);
-
-
-    static HookInfo * getHookInfo(size_t functionAddr);
-
-  private:
-
-    /// 记录哪些方法被hook,key为原方法的地址
-    typedef map<size_t,HookInfo*> hook_map;
-    static hook_map hook_map_;
-  };
-
-}
-
-#endif // NATIVE_HOOK_H
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/thread_helper.cpp b/VirtualApp/lib/src/main/jni/GodinHook/thread_helper.cpp
deleted file mode 100644
index 597dbfecf..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/thread_helper.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-#include "thread_helper.h"
-#include <sys/types.h>
-#include <dirent.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include "instruction/instruction_helper.h"
-#include "native_hook.h"
-
-pid_t GodinHook::ThreadHealper::freezzAndRepairThread(GodinHook::HookInfo *info, int action)
-{
-  int count;
-  pid_t tids[1024];
-  pid_t pid;
-
-  pid = -1;
-  count = getAllTids(getpid(), tids);
-  if (count > 0) {
-
-      /// 创建进程
-      pid = fork();
-
-      /// 子进程进行冻结和修复工作
-      if (pid == 0) {
-          int i;
-
-          for (i = 0; i < count; ++i) {
-              if (ptrace(PTRACE_ATTACH, tids[i], NULL, NULL) == 0) {
-                  //子进程进入暂停,立即返回
-                  waitpid(tids[i], NULL, WUNTRACED);
-                  repairThreadPc(tids[i], info, action);
-                }
-            }
-          //把信号发送给(进程)自身.
-          raise(SIGSTOP);
-
-          // 收到SIGCONT后,解除冻结状态
-          for (i = 0; i < count; ++i) {
-              ptrace(PTRACE_DETACH, tids[i], NULL, NULL);
-            }
-
-          exit(0);
-        }
-      else if (pid > 0) {
-          //等待前面创建的子进程暂停
-          waitpid(pid, NULL, WUNTRACED);
-        }
-    }
-
-  return pid;
-}
-
-void GodinHook::ThreadHealper::unFreeze(pid_t pid)
-{
-  if(pid < 0)
-    return;
-
-  /// 向执行冻结工作的进程发送信号,使其继续执行
-  kill(pid,SIGCONT);
-
-  /// 等待子进程退出
-  waitpid(pid,NULL,0);
-
-}
-
-int GodinHook::ThreadHealper::getAllTids(pid_t pid, pid_t *tids)
-{
-  char dir_path[32];
-  DIR *dir;
-  int i;
-  struct dirent *entry;
-  pid_t tid;
-
-  if (pid < 0) {
-      snprintf(dir_path, sizeof(dir_path), "/proc/self/task");
-    }
-  else {
-      snprintf(dir_path, sizeof(dir_path), "/proc/%d/task", pid);
-    }
-
-  dir = opendir(dir_path);
-  if (dir == NULL) {
-      return 0;
-    }
-
-  i = 0;
-  while((entry = readdir(dir)) != NULL) {
-      tid = atoi(entry->d_name);
-      if (tid != 0 && tid != getpid()) {
-          tids[i++] = tid;
-        }
-    }
-  closedir(dir);
-  return i;
-}
-
-void GodinHook::ThreadHealper::repairThreadPc(pid_t tid, GodinHook::HookInfo *info, int action)
-{
-  struct pt_regs regs;
-
-  if((NULL != info) || NativeHook::getHookedCount()>0){
-      if (ptrace(PTRACE_GETREGS, tid, NULL, &regs) == 0) {
-          if (info == NULL) {
-              int pos;
-              HookInfo ** infos = NativeHook::getAllHookInfo();
-              for (pos = 0; pos < NativeHook::getHookedCount(); ++pos) {
-                  if (doRepairThreadPC(infos[pos], &regs, action) == true) {
-                      break;
-                    }
-                }
-              free(infos);
-            }
-          else {
-              doRepairThreadPC(info, &regs, action);
-            }
-
-          ptrace(PTRACE_SETREGS, tid, NULL, &regs);
-        }
-   }
-}
-
-bool GodinHook::ThreadHealper::doRepairThreadPC(GodinHook::HookInfo *info, pt_regs *regs, int action)
-{
-  int offset;
-  int i;
-  switch (action)
-    {
-    /// 进行hook的时候,线程执行到正在被hook的函数,将其纠正到hook框架构建的调用原方法的对应机器指令上
-    case ACTION_ENABLE:
-      offset = regs->ARM_pc - InstructionHelper::valueToMem(info->getOriginalAddr());
-      for (i = 0; i < info->count; ++i) {
-          if (offset == info->orig_boundaries[i]) {
-              regs->ARM_pc = (uint32_t) info->getCallOriginalIns()+ info->trampoline_boundaries[i];
-              return true;
-            }
-        }
-      break;
-    ///  进行unhook的时候,线程正在执行被hook函数的新函数,将其纠正到unhook之后原方法的机器指令上
-    case ACTION_DISABLE:
-      offset = regs->ARM_pc - (int) info->getCallOriginalIns();
-      for (i = 0; i < info->count; ++i) {
-          if (offset == info->trampoline_boundaries[i]) {
-              regs->ARM_pc = InstructionHelper::valueToMem(info->getOriginalAddr()) + info->orig_boundaries[i];
-              return true;
-            }
-        }
-      break;
-    }
-
-  return false;
-}
-
-
diff --git a/VirtualApp/lib/src/main/jni/GodinHook/thread_helper.h b/VirtualApp/lib/src/main/jni/GodinHook/thread_helper.h
deleted file mode 100644
index f41781963..000000000
--- a/VirtualApp/lib/src/main/jni/GodinHook/thread_helper.h
+++ /dev/null
@@ -1,60 +0,0 @@
-#ifndef THREAD_HEALPER_H
-#define THREAD_HEALPER_H
-
-#include <sys/wait.h>
-#include <sys/ptrace.h>
-#include "hookinfo.h"
-
-
-namespace GodinHook {
-
-  #define ACTION_ENABLE         0
-  #define ACTION_DISABLE	1
-
-  class ThreadHealper{
-
-  public:
-    /**
-     * @brief freezzAndRepairThread
-     * 冻结和修复线程
-     * @param info
-     * 当HookInfo为NULL时,表示检查所有的HookInfo
-     * @param action
-     * ACTION_ENABLE或者ACTION_DISABLE
-     * @return
-     * 返回执行冻结和修复工作的进程ID
-     */
-    static pid_t freezzAndRepairThread(HookInfo * info,int action);
-
-    /**
-     * @brief unFreeze
-     * 解除冻结状态,线程恢复运行
-     * @param pid
-     * 执行冻结工作的进程
-     */
-    static void unFreeze(pid_t pid);
-  private:
-
-    /**
-     * @brief getAllTids
-     * 获取当前进程中除主线程之外的所有线程
-     * @param pid
-     * 当前进程
-     * @param tids
-     * 存储线程号的数组
-     * @return
-     * 除主线程之外的其他线程的数量
-     */
-    static int getAllTids(pid_t pid, pid_t *tids);
-
-    static void repairThreadPc(pid_t tid, HookInfo *info, int action);
-
-    static bool doRepairThreadPC(HookInfo *info, struct pt_regs *regs, int action);
-
-
-
-  };
-
-}
-
-#endif // THREAD_HEALPER_H
diff --git a/VirtualApp/lib/src/main/jni/Helper.h b/VirtualApp/lib/src/main/jni/Helper.h
deleted file mode 100644
index 0b4be53d4..000000000
--- a/VirtualApp/lib/src/main/jni/Helper.h
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// VirtualApp Native Project
-//
-
-#ifndef NDK_LOG_H
-#define NDK_LOG_H
-
-#include <android/log.h>
-
-#define TAG "VA-Native"
-
-#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,  TAG, __VA_ARGS__)
-#define LOGDT(T, ...) __android_log_print(ANDROID_LOG_DEBUG,  T, __VA_ARGS__)
-#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,  TAG, __VA_ARGS__)
-#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,  TAG, __VA_ARGS__)
-#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
-
-#define FREE(ptr, org_ptr) { if ((void*) ptr != NULL && (void*) ptr != (void*) org_ptr) { free((void*) ptr); } }
-
-#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
-
-#define NATIVE_METHOD(func_ptr, func_name, signature) { func_name, signature, reinterpret_cast<void*>(func_ptr) }
-
-#define JAVA_CLASS "com/lody/virtual/client/NativeEngine"
-
-#define ANDROID_JBMR2    18
-#define ANDROID_L        21
-#define ANDROID_N        24
-
-
-#endif //NDK_LOG_H
diff --git a/VirtualApp/lib/src/main/jni/HookZz/.gitignore b/VirtualApp/lib/src/main/jni/HookZz/.gitignore
new file mode 100644
index 000000000..93f0552df
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/.gitignore
@@ -0,0 +1,60 @@
+.DS_Store
+build/
+darwin.ios.release.mk
+.idea/
+.vscode/
+cmake-build-debug/
+MachoParser.xcworkspace/
+
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
diff --git a/VirtualApp/lib/src/main/jni/HookZz/.gitmodules b/VirtualApp/lib/src/main/jni/HookZz/.gitmodules
new file mode 100644
index 000000000..20dc32052
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "tools/deps/MachoParser"]
+	path = tools/deps/MachoParser
+	url = https://github.com/jmpews/MachoParser.git
diff --git a/VirtualApp/lib/src/main/jni/HookZz/Android.mk b/VirtualApp/lib/src/main/jni/HookZz/Android.mk
new file mode 100644
index 000000000..e56863372
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/Android.mk
@@ -0,0 +1,35 @@
+#
+# ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./android.mk APP_ABI=armeabi(armeabi-v7a/arm64-v8a)
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+ZZ_INCLUDE := $(LOCAL_PATH)/include \
+			$(LOCAL_PATH)/src
+
+ZZ_SRC := $(wildcard $(LOCAL_PATH)/src/*.c) \
+			$(wildcard $(LOCAL_PATH)/src/zzdeps/common/*.c) \
+			$(wildcard $(LOCAL_PATH)/src/zzdeps/linux/*.c) \
+			$(wildcard $(LOCAL_PATH)/src/zzdeps/posix/*.c) \
+			$(wildcard $(LOCAL_PATH)/src/platforms/backend-linux/*.c) \
+			$(wildcard $(LOCAL_PATH)/src/platforms/backend-posix/*.c)
+
+ifeq ($(TARGET_ARCH), arm)
+	ZZ_SRC += $(wildcard $(LOCAL_PATH)/src/platforms/arch-arm/*.c) \
+			$(wildcard $(LOCAL_PATH)/src/platforms/backend-arm/*.c)
+else ifeq ($(TARGET_ARCH), arm64)
+	ZZ_SRC += $(wildcard $(LOCAL_PATH)/src/platforms/arch-arm64/*.c) \
+			$(wildcard $(LOCAL_PATH)/src/platforms/backend-arm64/*.c)
+else ifeq ($(TARGET_ARCH), x86)
+	ZZ_SRC += $(wildcard $(LOCAL_PATH)/src/platforms/arch-x86/*.c) \
+			$(wildcard $(LOCAL_PATH)/src/platforms/backend-x86/*.c)
+endif
+
+LOCAL_MODULE := hookzz
+LOCAL_C_INCLUDES := $(ZZ_INCLUDE)
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_SRC_FILES := 	$(ZZ_SRC)
+
+include $(BUILD_STATIC_LIBRARY)
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/LICENSE b/VirtualApp/lib/src/main/jni/HookZz/LICENSE
new file mode 100644
index 000000000..f49a4e16e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/Makefile b/VirtualApp/lib/src/main/jni/HookZz/Makefile
new file mode 100644
index 000000000..a8cd29d72
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/Makefile
@@ -0,0 +1,184 @@
+NO_COLOR=\x1b[0m
+OK_COLOR=\x1b[32;01m
+ERROR_COLOR=\x1b[31;01m
+WARN_COLOR=\x1b[33;01m
+
+HOOKZZ_NAME := hookzz
+HOOKZZ_DIR := $(abspath .)
+LOCAL_PATH := $(abspath .)
+OUTPUT_DIR := $(abspath build)
+
+CFLAGS ?= -O0 -g
+CXXFLAGS = $(CFLAGS) -stdlib=libc++ -std=c++11 -gmodules
+LDFLAGS ?=
+LIBS_CFLAGS ?= -fPIC
+
+HOST ?= $(shell uname -s)
+HOST_ARCH ?= $(shell uname -m)
+
+ifeq ($(HOST), Darwin)
+
+endif
+
+ZZ_SRCS_PATH := $(abspath $(LOCAL_PATH)/src)
+ZZ_DEPS_PATH := $(abspath $(LOCAL_PATH)/src/zzdeps)
+ZZ_CAPSTONE_DEPS_PATH := $(abspath $(LOCAL_PATH)/deps/capstone)
+
+ZZ_COMMON_SRCS := $(wildcard $(ZZ_SRCS_PATH)/*.c)
+ZZ_SRCS := $(ZZ_COMMON_SRCS) \
+			$(wildcard $(ZZ_SRCS_PATH)/platforms/backend-posix/*.c)
+
+# zzdeps
+ZZ_DEPS_SRCS := $(wildcard $(ZZ_DEPS_PATH)/common/*.c) \
+		$(wildcard $(ZZ_DEPS_PATH)/posix/*.c) 
+
+BACKEND ?= ios
+ARCH ?= arm64
+
+ifeq ($(BACKEND), ios)
+	ifeq ($(ARCH), arm)
+		ZZ_ARCH := armv7
+	else ifeq ($(ARCH), arm64)
+		ZZ_ARCH := arm64
+	endif
+
+	ZZ_BACKEND := ios
+	ZZ_GXX_BIN := $(shell xcrun --sdk iphoneos --find clang++)
+	ZZ_GCC_BIN := $(shell xcrun --sdk iphoneos --find clang)
+	ZZ_SDK_ROOT := $(shell xcrun --sdk iphoneos --show-sdk-path)
+	ZZ_AR_BIN := $(shell which ar)
+	ZZ_RANLIB_BIN := $(shell which ranlib)
+
+	ZZ_DEPS_SRCS += $(wildcard $(ZZ_DEPS_PATH)/darwin/*.c)
+	ZZ_SRCS += $(wildcard $(ZZ_SRCS_PATH)/platforms/backend-darwin/*.c)
+	
+	ZZ_CFLAGS := -g -fPIC -shared -dynamiclib
+	ZZ_DLL := lib$(HOOKZZ_NAME).dylib
+
+	CFLAGS += -arch $(ZZ_ARCH)
+	
+	ZZ_GCC_SOURCE := $(ZZ_GCC_BIN) -isysroot $(ZZ_SDK_ROOT)
+	ZZ_GXX_SOURCE := $(ZZ_GXX_BIN) -isysroot $(ZZ_SDK_ROOT)
+	ZZ_GCC_TEST := $(ZZ_GCC_BIN) -isysroot $(ZZ_SDK_ROOT)
+	ZZ_GXX_TEST := $(ZZ_GXX_BIN) -isysroot $(ZZ_SDK_ROOT)
+else ifeq ($(BACKEND), macos)
+	ifeq ($(ARCH), x86)
+		ZZ_ARCH := i386
+	else ifeq ($(ARCH), x86_64)
+		ZZ_ARCH := x86_64
+	endif
+
+	ZZ_BACKEND := macos
+	ZZ_GXX_BIN := $(shell xcrun --sdk macosx --find clang++)
+	ZZ_GCC_BIN := $(shell xcrun --sdk macosx --find clang)
+	ZZ_SDK_ROOT := $(shell xcrun --sdk macosx --show-sdk-path)
+	ZZ_AR_BIN := $(shell which ar)
+	ZZ_RANLIB_BIN := $(shell which ranlib)
+
+	ZZ_DEPS_SRCS += $(wildcard $(ZZ_DEPS_PATH)/darwin/*.c)
+	ZZ_SRCS += $(wildcard $(ZZ_SRCS_PATH)/platforms/backend-darwin/*.c)
+	
+	ZZ_CFLAGS := -g -fPIC -shared -dynamiclib
+	ZZ_DLL := lib$(HOOKZZ_NAME).dylib
+
+	CFLAGS += -arch $(ZZ_ARCH)
+	
+	ZZ_GCC_SOURCE := $(ZZ_GCC_BIN) -isysroot $(ZZ_SDK_ROOT)
+	ZZ_GXX_SOURCE := $(ZZ_GXX_BIN) -isysroot $(ZZ_SDK_ROOT)
+	ZZ_GCC_TEST := $(ZZ_GCC_BIN) -isysroot $(ZZ_SDK_ROOT)
+	ZZ_GXX_TEST := $(ZZ_GXX_BIN) -isysroot $(ZZ_SDK_ROOT)
+else ifeq ($(BACKEND), android)
+	ZZ_BACKEND := android
+
+	ifeq ($(ARCH), arm)
+		ZZ_ARCH := armv7
+		ZZ_API_LEVEL := android-19
+		ZZ_CROSS_PREFIX := arm-linux-androideabi-
+		ZZ_BIN_CROSS_PREFIX := arm-linux-androideabi-
+	else ifeq ($(ARCH), arm64)
+		ZZ_ARCH := arm64
+		ZZ_API_LEVEL := android-21
+		ZZ_CROSS_PREFIX := aarch64-linux-android-
+		ZZ_BIN_CROSS_PREFIX := aarch64-linux-android-
+	else ifeq ($(ARCH), x86)
+		ZZ_ARCH := x86
+		ZZ_API_LEVEL := android-21
+		ZZ_CROSS_PREFIX := x86-
+		ZZ_BIN_CROSS_PREFIX := i686-linux-android-
+
+	endif
+
+	HOST_DIR := $(shell echo $(HOST) | tr A-Z a-z)-$(HOST_ARCH)
+	ZZ_NDK_HOME := $(shell dirname `which ndk-build`)
+	ZZ_SDK_ROOT := $(ZZ_NDK_HOME)/platforms/$(ZZ_API_LEVEL)/arch-$(ARCH)
+	ZZ_GCC_BIN := $(ZZ_NDK_HOME)/toolchains/$(ZZ_CROSS_PREFIX)4.9/prebuilt/$(HOST_DIR)/bin/$(ZZ_BIN_CROSS_PREFIX)gcc
+	ZZ_GXX_BIN := $(ZZ_NDK_HOME)/toolchains/$(ZZ_CROSS_PREFIX)4.9/prebuilt/$(HOST_DIR)/bin/$(ZZ_BIN_CROSS_PREFIX)g++
+	ZZ_AR_BIN := $(ZZ_NDK_HOME)/toolchains/$(ZZ_CROSS_PREFIX)4.9/prebuilt/$(HOST_DIR)/bin/$(ZZ_BIN_CROSS_PREFIX)ar
+	ZZ_RANLIB_BIN := $(ZZ_NDK_HOME)/toolchains/$(ZZ_CROSS_PREFIX)4.9/prebuilt/$(HOST_DIR)/bin/$(ZZ_BIN_CROSS_PREFIX)ranlib
+
+	ZZ_DEPS_SRCS += $(wildcard $(ZZ_DEPS_PATH)/linux/*.c)
+	ZZ_SRCS += $(wildcard $(ZZ_SRCS_PATH)/platforms/backend-linux/*.c)
+
+	ZZ_CFLAGS := -g -fPIC -shared
+	ZZ_DLL := lib$(HOOKZZ_NAME).so
+
+	ZZ_GCC_SOURCE := $(ZZ_GCC_BIN) --sysroot=$(ZZ_SDK_ROOT)
+	ZZ_GXX_SOURCE := $(ZZ_GXX_BIN) --sysroot=$(ZZ_SDK_ROOT)
+	ZZ_GCC_TEST := $(ZZ_GCC_BIN) --sysroot=$(ZZ_SDK_ROOT)
+	ZZ_GXX_TEST := $(ZZ_GXX_BIN) --sysroot=$(ZZ_SDK_ROOT)
+endif
+
+ZZ_SRCS += $(wildcard $(ZZ_SRCS_PATH)/platforms/arch-$(ARCH)/*.c) \
+		$(wildcard $(ZZ_SRCS_PATH)/platforms/backend-$(ARCH)/*.c)
+
+ifeq ($(ARCH), arm64)
+ZZ_SS += $(wildcard $(ZZ_SRCS_PATH)/platforms/backend-$(ARCH)/*.s)
+endif
+	
+ZZ_EXPORT_INCLUDE := -I$(LOCAL_PATH)/include
+
+ZZ_SRCS_INCLUDE := $(ZZ_EXPORT_INCLUDE) \
+		-I$(ZZ_SRCS_PATH)
+
+ZZ_DEPS_OBJS := $(ZZ_DEPS_SRCS:.c=.o)
+
+OUTPUT_DIR := $(OUTPUT_DIR)/$(ZZ_BACKEND)-$(ZZ_ARCH)
+
+ZZ_GCC_SOURCE += $(ZZ_SRCS_INCLUDE)
+ZZ_GCC_TEST += $(ZZ_INCLUDE)
+
+
+LDFLAGS += $(ZZ_LIB)
+
+ZZ_SS_OBJS := $(ZZ_SS:.s=.o)
+ZZ_SRCS_OBJS := $(ZZ_SRCS:.c=.o)
+ZZ_OBJS := $(ZZ_SRCS_OBJS) $(ZZ_DEPS_OBJS) $(ZZ_SS_OBJS)
+
+# ATTENTION !!!
+# 1. simple `ar` can't make a 'static library', need `ar -x` to extract `libcapstone.ios.arm64.a` and then `ar rcs` to pack as `.a`
+# 2. must `rm -rf  $(OUTPUT_DIR)/libhookzz.static.a`, very important!!!
+$(HOOKZZ_NAME) : $(ZZ_OBJS)
+	@mkdir -p $(OUTPUT_DIR)
+	@rm -rf $(OUTPUT_DIR)/*
+
+	@$(ZZ_GCC_SOURCE) $(ZZ_CFLAGS) $(CFLAGS) $(LDFLAGS) $(ZZ_OBJS) -o $(OUTPUT_DIR)/$(ZZ_DLL)
+	@$(ZZ_AR_BIN) -rcs $(OUTPUT_DIR)/lib$(HOOKZZ_NAME).static.a $(ZZ_OBJS)
+
+	@echo "$(OK_COLOR)build success for $(ARCH)-$(BACKEND)-hookzz! $(NO_COLOR)"
+
+$(ZZ_SRCS_OBJS): %.o : %.c
+	@$(ZZ_GCC_SOURCE) $(CFLAGS) -c $< -o $@
+	@echo "$(OK_COLOR)generate [$@]! $(NO_COLOR)"
+
+$(ZZ_DEPS_OBJS): %.o : %.c
+	@$(ZZ_GCC_SOURCE) $(CFLAGS) -c $< -o $@
+	@echo "$(OK_COLOR)generate [$@]! $(NO_COLOR)"
+
+$(ZZ_SS_OBJS): %.o : %.s
+	@$(ZZ_GCC_SOURCE) $(CFLAGS) -c $< -o $@
+	@echo "$(OK_COLOR)generate [$@]! $(NO_COLOR)"
+
+clean:
+	@rm -rf $(shell find ./src -name "*\.o" | xargs echo)
+	@rm -rf $(OUTPUT_DIR)
+	@echo "$(OK_COLOR)clean all *.o success!$(NO_COLOR)"
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/README.md b/VirtualApp/lib/src/main/jni/HookZz/README.md
new file mode 100644
index 000000000..c14c9889f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/README.md
@@ -0,0 +1,199 @@
+# What is HookZz ?
+
+**a hook framwork for arm/arm64/ios/android**
+
+ref to: [frida-gum](https://github.com/frida/frida-gum) and [minhook](https://github.com/TsudaKageyu/minhook) and [substrate](https://github.com/jevinskie/substrate).
+
+**special thanks to [frida-gum](https://github.com/frida/frida-gum) perfect code and modular architecture, frida is aircraft carrier, HookZz is boat, but still with some tricks**
+
+**thanks for @lauos with contributing android code**
+
+# Features
+
+- **solidify inlinehook without Jailbreak [new-90%]**
+
+- **GOT hook with HookZz(i.e. change fishhook to inlinehook), better for APM [new-0%]**
+
+- [HookZz-Modules help you to hook.](https://github.com/jmpews/HookZzModules)
+
+- the power to access registers directly
+
+- hook function with `replace_call`
+
+- hook function with `pre_call` and `post_call`
+
+- hook **address(a piece of code)** with `pre_call` and `half_call`
+
+- (almost)only **one instruction** to hook(i.e. hook **short funciton, even only one instruction**) [arm/thumb/arm64]
+
+- runtime code patch, without codesign limit
+
+- it's cute
+
+# Getting Started
+
+[Move to HookZz Getting Started](https://jmpews.github.io/zzpp/getting-started/) **[need update]**
+
+# How it works ?
+
+[Move to HookFrameworkDesign.md](https://github.com/jmpews/HookZz/blob/master/docs/HookFrameworkDesign.md) **[need update]**
+
+# Why I do this?
+
+1. for arsenal - [zzdeps](https://github.com/jmpews/zzdeps)
+2. for low-level control
+
+# Who use this?
+
+**[VirtualApp](https://github.com/asLody/VirtualApp) An open source implementation of MultiAccount.(Support 4.0 - 8.0)**
+
+**[AppleTrace](https://github.com/everettjf/AppleTrace) Trace tool for iOS/macOS (similar to systrace for Android)**
+
+# Docs
+
+[Move to HookZz docs](https://jmpews.github.io/zzpp/hookzz-docs/) **[need update]**
+
+# Example
+
+[Move to HookZz example](https://jmpews.github.io/zzpp/hookzz-example/) **[need update]**
+
+# Modules
+
+[Move to HookZzModules](https://github.com/jmpews/HookZzModules) **[need update]**
+
+# Compile
+
+## build for arm64-ios
+
+#### 1. build `libhookzz.dylib` and `libhookzz.static.a`
+
+```
+jmpews at jmpewsdeMBP in ~/Desktop/SpiderZz/project/HookZz
+λ : >>> make clean; make BACKEND=ios ARCH=arm64
+clean all *.o success!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/allocator.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/interceptor.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/memory.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/stack.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/trampoline.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/backend-posix/thread-posix.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/backend-darwin/memory-darwin.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm64/instructions.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm64/reader-arm64.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm64/regs-arm64.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm64/relocator-arm64.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm64/writer-arm64.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/backend-arm64/thunker-arm64.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/backend-arm64/trampoline-arm64.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/zzdeps/common/memory-utils-common.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/zzdeps/posix/memory-utils-posix.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/zzdeps/posix/thread-utils-posix.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/zzdeps/darwin/macho-utils-darwin.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/zzdeps/darwin/memory-utils-darwin.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/backend-arm64/interceptor-template-arm64.o]!
+build success for arm64-ios-hookzz!
+```
+
+check `build/ios-arm64/*`.
+
+#### 2. build tests dylib
+
+```
+jmpews at jmpewsdeMBP in ~/Desktop/SpiderZz/project/HookZz/tests/arm64-ios
+λ : >>> make clean; make
+clean all *.o success!
+build [test_hook_oc.dylib] success for arm64-ios!
+build [test_hook_address.dylib] success for arm64-ios!
+build [test_hook_printf.dylib] success for arm64-ios!
+build [test] success for arm64-ios-hookzz!
+```
+
+check `build/ios-arm64/*`.
+
+## build for arm-ios
+
+ignore...
+
+## build for arm-android
+
+#### 1. build `libhookzz.so` and `libhookzz.static.a`
+
+```
+jmpews at jmpewsdeMBP in ~/Desktop/SpiderZz/project/HookZz
+λ : >>> make clean; make BACKEND=android ARCH=arm
+clean all *.o success!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/allocator.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/interceptor.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/memory.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/stack.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/trampoline.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/backend-posix/thread-posix.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/backend-linux/memory-linux.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm/instructions.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm/reader-arm.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm/reader-thumb.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm/regs-arm.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm/relocator-arm.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm/relocator-thumb.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm/writer-arm.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/arch-arm/writer-thumb.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/backend-arm/thunker-arm.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/platforms/backend-arm/trampoline-arm.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/zzdeps/common/memory-utils-common.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/zzdeps/posix/memory-utils-posix.o]!
+generate [/Users/jmpews/Desktop/SpiderZz/project/HookZz/src/zzdeps/posix/thread-utils-posix.o]!
+build success for arm-android-hookzz!
+```
+
+and check `build/android-armv7`
+
+#### build tests ELF
+
+```
+jmpews at jmpewsdeMBP in ~/Desktop/SpiderZz/project/HookZz/tests/arm-android
+λ : >>> make clean; make
+clean all *.o success!
+build [test_hook_open_arm.dylib] success for armv7-ios!
+build [test_hook_address_thumb.dylib] success for armv7-ios!
+build [test_hook_printf.dylib] success for armv7-ios!
+build [test] success for armv7-android-hookzz!
+```
+
+and check `build/android-armv7/*`
+
+# Quick Example
+
+#### `test_hook_printf.c` output for arm64-ios
+
+test hook `printf` with `try_near_jump` option , and `ZzEnableDebugMode()` with `replace_call`, `pre_call`, `post_call`.
+
+```
+ZzThunkerBuildThunk:
+LogInfo: enter_thunk at 0x100162c20, use enter_thunk_template.
+
+ZzThunkerBuildThunk:
+LogInfo: leave_thunk at 0x1001500f4, length: 240.
+
+ZzBuildEnterTrampoline:
+LogInfo: on_enter_trampoline at 0x1001502d8, length: 44. hook-entry: 0x145e0c720. and will jump to enter_thunk(0x100162c20).
+
+ZzBuildEnterTransferTrampoline:
+LogInfo: on_enter_transfer_trampoline at 0x180f1f414, length: 20. and will jump to on_enter_trampoline(0x1001502d8).
+
+ZzBuildInvokeTrampoline:
+LogInfo: on_invoke_trampoline at 0x100150304, length: 24. and will jump to rest code(0x181402a60).
+ArmInstructionFix: origin instruction at 0x181402a5c, relocator end at 0x181402a60, relocator instruction nums 1
+origin_prologue: 0xf4 0x4f 0xbe 0xa9 
+
+ZzBuildLeaveTrampoline:
+LogInfo: on_leave_trampoline at 0x10015031c, length: 44. and will jump to leave_thunk(0x1001500f4).
+
+HookZzzzzzz, %d, %p, %d, %d, %d, %d, %d, %d, %d
+
+printf-pre-call
+call printf
+HookZzzzzzz, 1, 0x2, 3, 4, 5, 6, 7, 8, 9
+HookZzzzzzz, %d, %p, %d, %d, %d, %d, %d, %d, %d
+
+printf-post-call
+```
diff --git a/VirtualApp/lib/src/main/jni/HookZz/docs/HookFrameworkDesign.md b/VirtualApp/lib/src/main/jni/HookZz/docs/HookFrameworkDesign.md
new file mode 100644
index 000000000..a89469b27
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/docs/HookFrameworkDesign.md
@@ -0,0 +1,468 @@
+# HookZz
+
+画了一半的流程, 其实另一半还有 invoke trampoline, leave trampoline, 大致相同.
+
+```
+                 enter            enter
+function         trampoline       thunk
++------------+   +------------+   +------------+
+| hook patch +--->            +--->            |
++------------+   |            |   | storeReg   |
+|            |   |            |   |            |
+|            |   +------------+   | prep args  |
+|            |                    |            |
+|            |                    | call func  <---+begin_invocation_func()
+|            |                    |            |
++------------+                    | restoreReg |
+                                  |            |
+                                  +------------+
+
+```
+
+# HookFramework 架构设计 2.0
+
+一般来说可以分为以下几个模块
+
+1. 内存分配 模块
+2. 指令写 模块
+3. 指令读 模块
+4. 指令修复 模块
+5. 跳板 模块
+6. 调度器 模块
+7. 栈 模块
+
+## 1. 内存分配 模块
+
+这里主要关注三个点:
+
+1. 内存的分配
+2. 内存属性修改
+3. 内存布局获取
+
+#### 1.1 内存的分配
+
+**设计方面:** 1. 提供一个 allocator 去管理/分配内存 2. 需要封装成架构无关的 API.
+
+通常使用 posix 标准的 `mmap`, darwin 下的 mach kernel 分配内存使用 `mmap` 实际使用的是 `mach_vm_allocate`. [move to detail]( https://github.com/bminor/glibc/blob/master/sysdeps/mach/hurd/mmap.c)
+
+在入口点的 patch, 通常会使用绝对地址跳到 `trampoline`, 如果使用绝对地址跳, 将会占用 4 条指令, 如下的形式.
+
+```
+ldr x17, #0x8
+b #0xc
+.long 0x0
+.long 0x0
+br x17
+```
+
+但是如果可以使用 `B #0x?`, 实现相对地址跳(near jump), 将是最好的, 在 armv8 中的可以想在 `+-128MB` 范围内进行 `near jump`, 具体可以参考 `ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile Page: C6-550`. 所以问题转换为找到一块 `rx-` 的内存写入 enter trampline. 
+
+大概有以下几种方法可以获取到 `rx-` 内存块.
+
+1. 尝试使用 mmap 的 fixed flag 分配相近内存
+
+2. 当时获取进程内的所有动态库列表, 之后搜索每一个动态库的 `__TEXT`, 查找是否存在 code cave.(尝试搜索内存空洞(`code cave`), 搜索 `__text` 这个 `section` 其实更准确来说是搜索 `__TEXT` 这个 `segment`. 由于内存页对齐的原因以及其他原因很容易出现 `code cave`. 所以只需要搜索这个区间内的 `00` 即可, `00` 本身就是无效指令, 所以可以判断该位置无指令使用.)
+
+3. 获取当前进程的内存布局, 对所有 `rx-` 属性内存页搜索 code cave. (内存布局的获取会在1.3详细提到)
+
+```
+__asm__ {
+	// 第一次绝对地址跳, 跳转到修复模块, 执行正常流程
+	"ldr x17, #0x8\n"
+	"b #0xc\n"
+	".long\n"
+	".long\n"
+	"br x17"
+
+	// double jump, 跳转到 on_enter_trampoline
+	"ldr x17, #0x8\n"
+	"b #0xc\n"
+	".long\n"
+	".long\n"
+	"br x17"
+}
+```
+
+#### 1.2 内存属性修改
+
+通常使用 posix 标准的 `mprotect`, darwin 下的 mach kernel 修改内存属性使用的是 `mach_vm_protect`, 注意: ios 不允许引用 `#include <mach_vm.h>`, 可以用单独拷贝一份该头文件到项目下.
+
+这一部分与具体的操作系统有关. 比如 .
+
+在 lldb 中可以通过 `memory region address` 查看地址的内存属性.
+
+当然这里也存在一个巨大的坑, ios 下无法分配 `rwx` 属性的内存页. 这导致 inlinehook 无法在非越狱系统上使用, 并且只有 `MobileSafari` 才有 `VM_FLAGS_MAP_JIT` 权限. 具体解释请参下方 **[坑 - rwx 与 codesigning]**.
+
+#### 1.3 内存布局获取
+
+linux 下可以通过 `/proc/pid/maps` 获取当前进程的内存, 而且也仅有此方法.
+
+darwin 下有 `vmmap` 这个命令可以在 `macOS` 使用, darwin 下 pid 与 `task_t`, 所以具体的内存页管理都在 `task_t` 这个内核结构体里. 但是可以通过 `vm_region` 函数遍历获取当前进程的所有属性的内存页. 通常用来爆破 dyld 的地址
+
+
+#### 2. 指令写 模块
+
+先说坑,  非越狱状态下不允许设置 `rw-` 为 `r-x`, 或者  设置 `r-x` 为 `rx-`. 具体解释请参考下方坑 **[坑-rwx 与 codesigning]**.
+
+其实这里的指令写有种简单的方法, 就是在本地生成指令的16进制串, 之后直接写即可. 但这种应该是属于 hardcode.
+
+这里使用 `frida-gum` 和 `CydiaSubstrace` 都用的方法, 把需要用到的指令都写成一个小函数.
+
+例如:
+
+```
+// frida-gum/gum/arch-arm64/gumarm64writer.c
+void
+gum_arm64_zz_arm64_writer_put_ldr_reg_address (GumArm64Writer * self,
+                                      ZzARM64Reg reg,
+                                      GumAddress address)
+{
+  gum_arm64_writer_put_ldr_reg_u64 (self, reg, (zuint64) address);
+}
+
+void
+gum_arm64_writer_put_ldr_reg_u64 (GumArm64Writer * self,
+                                  ZzARM64Reg reg,
+                                  zuint64 val)
+{
+  GumArm64RegInfo ri;
+
+  gum_arm64_zz_arm64_writer_describe_reg (self, reg, &ri);
+
+  g_assert_cmpuint (ri.width, ==, 64);
+
+  gum_arm64_writer_add_literal_reference_here (self, val);
+  gum_arm64_zz_arm64_writer_put_instruction (self,
+      (ri.is_integer ? 0x58000000 : 0x5c000000) | ri.index);
+}
+
+```
+
+其实有另外一个小思路,  有一点小不足, 就是确定指令片段的长度, 但其实也有解决方法, **可以放几条特殊指令作为结尾标记**.
+
+先使用内联汇编写一个函数.
+
+```
+
+__attribute__((__naked__)) static void ctx_save() {
+  __asm__ volatile(
+
+      /* reserve space for next_hop */
+      "sub sp, sp, #(2*8)\n"
+
+      /* save {q0-q7} */
+      "sub sp, sp, #(8*16)\n"
+      "stp q6, q7, [sp, #(6*16)]\n"
+      "stp q4, q5, [sp, #(4*16)]\n"
+      "stp q2, q3, [sp, #(2*16)]\n"
+      "stp q0, q1, [sp, #(0*16)]\n"
+
+      /* save {x1-x30} */
+      "sub sp, sp, #(30*8)\n"
+      "stp fp, lr, [sp, #(28*8)]\n"
+      "stp x27, x28, [sp, #(26*8)]\n"
+      "stp x25, x26, [sp, #(24*8)]\n"
+      "stp x23, x24, [sp, #(22*8)]\n"
+      "stp x21, x22, [sp, #(20*8)]\n"
+      "stp x19, x20, [sp, #(18*8)]\n"
+      "stp x17, x18, [sp, #(16*8)]\n"
+      "stp x15, x16, [sp, #(14*8)]\n"
+      "stp x13, x14, [sp, #(12*8)]\n"
+      "stp x11, x12, [sp, #(10*8)]\n"
+      "stp x9, x10, [sp, #(8*8)]\n"
+      "stp x7, x8, [sp, #(6*8)]\n"
+      "stp x5, x6, [sp, #(4*8)]\n"
+      "stp x3, x4, [sp, #(2*8)]\n"
+      "stp x1, x2, [sp, #(0*8)]\n"
+
+      /* save sp, x0 */
+      "sub sp, sp, #(2*8)\n"
+      "add x1, sp, #(2*8 + 8*16 + 30*8 + 2*8)\n"
+      "stp x1, x0, [sp, #(0*8)]\n"
+
+      /* alignment padding + dummy PC */
+      "sub sp, sp, #(2*8)\n");
+}
+
+```
+
+之后直接复制这块函数内存数据即可, 这一般适合那种指令片段堆.
+
+```
+void ZzThunkerBuildEnterThunk(ZzWriter *writer)
+{
+
+    // pop x17
+    zz_arm64_writer_put_ldr_reg_reg_offset(writer, ZZ_ARM64_REG_X17, ZZ_ARM64_REG_SP, 0);
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 16);
+
+    zz_arm64_writer_put_bytes(writer, (void *)ctx_save, 26 * 4);
+
+    // call `function_context_begin_invocation`
+    zz_arm64_writer_put_bytes(writer, (void *)pass_enter_func_args, 4 * 4);
+    zz_arm64_writer_put_ldr_reg_address(
+        writer, ZZ_ARM64_REG_X17,
+        (zaddr)(zpointer)function_context_begin_invocation);
+    zz_arm64_writer_put_blr_reg(writer, ZZ_ARM64_REG_X17);
+
+    zz_arm64_writer_put_bytes(writer, (void *)ctx_restore, 23 * 4);
+}
+```
+
+#### 3. 指令读 模块
+
+这一部分实际上就是 `disassembler`, 这一部分可以直接使用 `capstone`, 这里需要把 `capstone` 编译成多种架构.
+
+#### 4. 指令修复 模块
+
+这里的指令修复主要是发生在 hook 函数头几条指令, 由于备份指令到另一个地址, 这就需要对所有 `PC(IP)` 相关指令进行修复. 对于确定的哪些指令需要修复可以参考 [Move to <解析ARM和x86_x64指令格式>](http://jmpews.github.io/2017/05/17/pwn/%E8%A7%A3%E6%9E%90ARM%E5%92%8Cx86_x64%E6%8C%87%E4%BB%A4%E6%A0%BC%E5%BC%8F/).
+
+大致的思路就是: 判断 `capstone` 读取到的指令 ID, 针对特定指令写一个小函数进行修复.
+
+例如在 `frida-gum` 中:
+
+```
+frida-gum/gum/arch-arm64/gumarm64relocator.c
+static gboolean
+gum_arm64_relocator_rewrite_b (GumArm64Relocator * self,
+                               GumCodeGenCtx * ctx)
+{
+  const cs_arm64_op * target = &ctx->detail->operands[0];
+
+  (void) self;
+
+  gum_arm64_zz_arm64_writer_put_ldr_reg_address (ctx->output, ZZ_ARM64_REG_X16,
+      target->imm);
+  gum_arm64_zz_arm64_writer_put_br_reg (ctx->output, ZZ_ARM64_REG_X16);
+
+  return TRUE;
+}
+```
+
+#### 5. 跳板 模块
+
+跳板模块的设计是希望各个模块的实现更浅的耦合, 跳板函数主要作用就是进行跳转, 并准备 `跳转目标` 需要的参数. 举个例子, 被 hook 的函数经过入口跳板(`enter_trampoline`), 跳转到调度函数(`enter_chunk`), 需要被 hook 的函数相关信息等, 这个就需要在构造跳板时完成.
+
+#### 6. 调度 模块
+
+可以理解为所有被 hook 的函数都必须经过的函数, 类似于 `objc_msgSend`, 在这里通过栈返回值来控制函数(`replace_call`, `pre_call`, `half_call`, `post_call`)调用顺序.
+
+本质有些类似于 `objc_msgSend` 所有的被 hook 的函数都在经过 `enter_trampoline` 跳板后, 跳转到 `enter_thunk`, 在此进行下一步的跳转判断决定, 并不是直接跳转到 `replace_call`.
+
+#### 7. 栈模块
+
+如果希望在 `pre_call` 和 `post_call`  使用同一个局部变量, 就想在同一个函数内一样. 在 `frida-js` 中也就是 `this` 这个关键字. 这就需要自建函数栈, 模拟栈的行为. 同时还要避免线程冲突, 所以需要使用 `thread local variable`, 为每一个线程中的每一个 `hook-entry` 添加线程栈, 同时为每一次调用添加函数栈. 所以这里存在两种栈. 1. 线程栈(保存了该 hook-entry 的所有当前函数调用栈) 2. 函数调用栈(本次函数调用时的栈)
+
+# 坑
+
+## `ldr` 指令
+
+在进行指令修复时, 需要需要将 PC 相关的地址转换为绝对地址, 其中涉及到保存地址到寄存器. 一般来说是使用指令 `ldr`. 也就是说如何完成该函数 `zz_arm64_writer_put_ldr_reg_address(relocate_writer, ZZ_ARM64_REG_X17, target_addr);`
+
+`frida-gum` 的实现原理是, 有一个相对地址表, 在整体一段写完后进行修复.
+
+```
+void
+gum_arm64_writer_put_ldr_reg_u64 (GumArm64Writer * self,
+                                  ZzARM64Reg reg,
+                                  zuint64 val)
+{
+  GumArm64RegInfo ri;
+
+  gum_arm64_zz_arm64_writer_describe_reg (self, reg, &ri);
+
+  g_assert_cmpuint (ri.width, ==, 64);
+
+  gum_arm64_writer_add_literal_reference_here (self, val);
+  gum_arm64_zz_arm64_writer_put_instruction (self,
+      (ri.is_integer ? 0x58000000 : 0x5c000000) | ri.index);
+}
+```
+
+在 HookZz 中的实现, 直接将地址写在指令后, 之后使用 `b` 到正常的下一条指令, 从而实现将地址保存到寄存器.
+
+```
+void zz_arm64_writer_put_ldr_reg_address(ZzWriter *self, ZzARM64Reg reg, zaddr address)
+{
+    zz_arm64_writer_put_ldr_reg_imm(self, reg, (zuint)0x8);
+    zz_arm64_writer_put_b_imm(self, (zaddr)0xc);
+    zz_arm64_writer_put_bytes(self, (zpointer)&address, sizeof(address));
+}
+```
+
+也就是下面的样子.
+
+```
+__asm__ {
+	"ldr x17, #0x8\n"
+	"b #0xc\n"
+	".long\n"
+	".long\n"
+	"br x17"
+}
+```
+
+## 寄存器污染
+
+在进行 inlinehook 需要进行各种跳转, 通常会以以下模板进行跳转.
+
+```
+0:  ldr x16, 8;
+4:  br x16;
+8:  0x12345678
+12: 0x00000000
+```
+
+问题在于这会造成 x16 寄存器被污染(arm64 中 `svc #0x80` 使用 x16 传递系统调用号) 所以这里有两种思路解决这个问题.
+
+思路一:
+
+在使用寄存器之前进行 `push`, 跳转后 `pop`, 这里存在一个问题就是在原地址的几条指令进行 `patch code` 时一定会污染一个寄存器(也不能说一定, 如果这时进行压栈, 在之后的 `invoke_trampline` 会导致函数栈发生改变, 此时有个解决方法可以 `pop` 出来, 由 hook-entry 或者其他变量暂时保存, 但这时需要处理锁的问题. )
+
+思路二:
+
+挑选合适的寄存器, 不考虑污染问题. 这时可以参考, 下面的资料, 选择 x16 or x17, 或者自己做一个实验 `otool -tv ~/Downloads/DiSpecialDriver64 > ~/Downloads/DiSpecialDriver64.txt` 通过 dump 一个 arm64 程序的指令, 来判断哪个寄存器用的最少, 但是不要使用 `x18` 寄存器, 你对该寄存器的修改是无效的.
+
+Tips: 之前还想过为对每一个寄存器都做适配, 用户可以选择当前的 `hook-entry` 选择哪一个寄存器作为临时寄存器.
+
+参考资料:
+
+```
+PAGE: 9-3
+Programmer’s Guide for ARMv8-A
+9.1 Register use in the AArch64 Procedure Call Standard 
+9.1.1 Parameters in general-purpose registers
+```
+
+这里也有一个问题,  这也是 `frida-gum` 中遇到一个问题, 就是对于 `svc #0x80` 类系统调用, 系统调用号(syscall number)的传递是利用 `x16` 寄存器进行传递的, 所以本框架使用 `x17` 寄存器, 并且在传递参数时使用 `push` & `pop`, 在跳转后恢复 `x17`, 避免了一个寄存器的使用.
+
+## `rwx` 与 `codesigning`
+
+对于非越狱, 不能分配可执行内存, 不能进行 `code patch`.
+
+两篇原理讲解 codesign 的原理
+
+```
+https://papers.put.as/papers/ios/2011/syscan11_breaking_ios_code_signing.pdf
+http://www.newosxbook.com/articles/CodeSigning.pdf
+```
+
+以及源码分析如下:
+
+crash 异常如下, 其中 `0x0000000100714000` 是 mmap 分配的页.
+
+```
+Exception Type:  EXC_BAD_ACCESS (SIGKILL - CODESIGNING)
+Exception Subtype: unknown at 0x0000000100714000
+Termination Reason: Namespace CODESIGNING, Code 0x2
+Triggered by Thread:  0
+```
+
+寻找对应的错误码
+
+```
+xnu-3789.41.3/bsd/sys/reason.h
+/*
+ * codesigning exit reasons
+ */
+#define CODESIGNING_EXIT_REASON_TASKGATED_INVALID_SIG 1
+#define CODESIGNING_EXIT_REASON_INVALID_PAGE          2
+#define CODESIGNING_EXIT_REASON_TASK_ACCESS_PORT      3
+```
+
+找到对应处理函数, 请仔细阅读注释里内容, 不做解释了.
+
+```
+# xnu-3789.41.3/osfmk/vm/vm_fault.c:2632
+
+	/* If the map is switched, and is switch-protected, we must protect
+	 * some pages from being write-faulted: immutable pages because by 
+	 * definition they may not be written, and executable pages because that
+	 * would provide a way to inject unsigned code.
+	 * If the page is immutable, we can simply return. However, we can't
+	 * immediately determine whether a page is executable anywhere. But,
+	 * we can disconnect it everywhere and remove the executable protection
+	 * from the current map. We do that below right before we do the 
+	 * PMAP_ENTER.
+	 */
+	cs_enforcement_enabled = cs_enforcement(NULL);
+
+	if(cs_enforcement_enabled && map_is_switched && 
+	   map_is_switch_protected && page_immutable(m, prot) && 
+	   (prot & VM_PROT_WRITE))
+	{
+		return KERN_CODESIGN_ERROR;
+	}
+
+	if (cs_enforcement_enabled && page_nx(m) && (prot & VM_PROT_EXECUTE)) {
+		if (cs_debug)
+			printf("page marked to be NX, not letting it be mapped EXEC\n");
+		return KERN_CODESIGN_ERROR;
+	}
+
+	if (cs_enforcement_enabled &&
+	    !m->cs_validated &&
+	    (prot & VM_PROT_EXECUTE) &&
+	    !(caller_prot & VM_PROT_EXECUTE)) {
+		/*
+		 * FOURK PAGER:
+		 * This page has not been validated and will not be
+		 * allowed to be mapped for "execute".
+		 * But the caller did not request "execute" access for this
+		 * fault, so we should not raise a code-signing violation
+		 * (and possibly kill the process) below.
+		 * Instead, let's just remove the "execute" access request.
+		 * 
+		 * This can happen on devices with a 4K page size if a 16K
+		 * page contains a mix of signed&executable and
+		 * unsigned&non-executable 4K pages, making the whole 16K
+		 * mapping "executable".
+		 */
+		prot &= ~VM_PROT_EXECUTE;
+	}
+
+	/* A page could be tainted, or pose a risk of being tainted later.
+	 * Check whether the receiving process wants it, and make it feel
+	 * the consequences (that hapens in cs_invalid_page()).
+	 * For CS Enforcement, two other conditions will 
+	 * cause that page to be tainted as well: 
+	 * - pmapping an unsigned page executable - this means unsigned code;
+	 * - writeable mapping of a validated page - the content of that page
+	 *   can be changed without the kernel noticing, therefore unsigned
+	 *   code can be created
+	 */
+	if (!cs_bypass &&
+	    (m->cs_tainted ||
+	     (cs_enforcement_enabled &&
+	      (/* The page is unsigned and wants to be executable */
+	       (!m->cs_validated && (prot & VM_PROT_EXECUTE))  ||
+	       /* The page should be immutable, but is in danger of being modified
+		* This is the case where we want policy from the code directory -
+		* is the page immutable or not? For now we have to assume that 
+		* code pages will be immutable, data pages not.
+		* We'll assume a page is a code page if it has a code directory 
+		* and we fault for execution.
+		* That is good enough since if we faulted the code page for
+		* writing in another map before, it is wpmapped; if we fault
+		* it for writing in this map later it will also be faulted for executing 
+		* at the same time; and if we fault for writing in another map
+		* later, we will disconnect it from this pmap so we'll notice
+		* the change.
+		*/
+	      (page_immutable(m, prot) && ((prot & VM_PROT_WRITE) || m->wpmapped))
+	      ))
+		    )) 
+	{
+```
+
+#### 其他文章:
+
+http://ddeville.me/2014/04/dynamic-linking
+
+> Later on, whenever a page fault occurs the vm_fault function in `vm_fault.c` is called. During the page fault the signature is validated if necessary. The signature will need to be validated if the page is mapped in user space, if the page belongs to a code-signed object, if the page will be writable or simply if it has not previously been validated. Validation happens in the `vm_page_validate_cs` function inside vm_fault.c (the validation process and how it is enforced continually and not only at load time is interesting, see Charlie Miller’s book for more details).
+
+> If for some reason the page cannot be validated, the kernel checks whether the `CS_KILL` flag has been set and kills the process if necessary. There is a major distinction between iOS and OS X regarding this flag. All iOS processes have this flag set whereas on OS X, although code signing is checked it is not set and thus not enforced.
+
+> In our case we can safely assume that the (missing) code signature couldn’t be verified leading to the kernel killing the process.
+
+---
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/docs/hookzz-docs.md b/VirtualApp/lib/src/main/jni/HookZz/docs/hookzz-docs.md
new file mode 100644
index 000000000..f4d6f8afe
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/docs/hookzz-docs.md
@@ -0,0 +1,161 @@
+# HookZz docs
+
+> [Move to HookZz Getting Started](https://jmpews.github.io/zzpp/getting-started/)
+
+> [Move to HookZz Example](https://jmpews.github.io/zzpp/hookzz-example/)
+
+> [Move to HookZz docs](https://jmpews.github.io/zzpp/hookzz-docs/)
+
+> [Move to HookZzModules](https://github.com/jmpews/HookZzModules)
+
+> [Move to HookZzWebsite](https://jmpews.github.io/zzpp/)
+
+
+## Export 4 core function
+
+#### 1. `ZzBuildHook`
+
+build hook with `replace_call`, `pre_call`, `post_call`, but not enable. the definition of `PRECALL` and `POSTCALL` type is below.
+
+```
+@target_ptr: hook function address
+@replace_ptr: replace function address
+@origin_ptr: origin function address work with @replace_tpr
+@pre_call_ptr: pre function call address
+@post_call_ptr: post funciton call address
+
+ZZSTATUS ZzBuildHook(zpointer target_ptr, zpointer replace_ptr, zpointer *origin_ptr, PRECALL pre_call_ptr, POSTCALL post_call_ptr);
+```
+
+#### 2. `ZzBuildHookAddress`
+
+build hook address(a piece of code) with `pre_call`, `half_call`. the definition of `PRECALL` and `POSTCALL` type is below.
+
+```
+@target_start_ptr: hook instruction start address
+@target_end_ptr: hook instruction end address
+@pre_call_ptr: pre function call address
+@half_call_ptr: half function call address
+
+ZZSTATUS ZzBuildHookAddress(zpointer target_start_ptr, zpointer target_end_ptr, PRECALL pre_call_ptr, HALFCALL half_call_ptr);
+```
+
+#### 3. `ZzEnableHook`
+
+enable hook with `code patch` at target_ptr.
+
+```
+@target_ptr: target address
+
+ZZSTATUS ZzEnableHook(zpointer target_ptr);
+```
+
+#### 4. `ZzRuntimeCodePatch`
+
+runtime code patch without codesign limit, and will work better with [MachoParser](https://github.com/jmpews/MachoParser).
+
+```
+@address: patch address
+@codedata: code patch data
+@codedata: code ptach data size
+
+ZZSTATUS ZzRuntimeCodePatch(zaddr address, zpointer codedata, zuint codedata_size);
+```
+
+**[Move to AntiDebugBypass Example](https://github.com/jmpews/HookZzModules/blob/master/AntiDebugBypass/AntiDebugBypass.mm#L270)**
+
+## Export 3 core type:
+
+#### 1. `PRECALL`, `POSTCALL`, `HALFCALL`
+
+**For `RegState`:**
+
+without the explicit argument, use `RegState` to replace, you can access all the registers. 
+
+**For `ThreadStack`:**
+
+Contains all of the current `CallStack`.
+
+
+**For `CallStack`:**
+
+if you want use variable in `pre_call` and `post_call`(`half_call`), just like the trick variable `self`, you need `CallStack *stack`.
+
+```
+typedef struct _CallStack
+{
+    long call_id;
+} CallStack;
+
+typedef struct _ThreadStack
+{
+    long thread_id;
+    zsize size;
+} ThreadStack;
+
+
+typedef void (*PRECALL)(RegState *rs, ThreadStack *threadstack, CallStack *callstack);
+typedef void (*POSTCALL)(RegState *rs, ThreadStack *threadstack, CallStack *callstack);
+typedef void (*HALFCALL)(RegState *rs, ThreadStack *threadstack, CallStack *callstack);
+
+```
+
+#### 2. `RegState`
+
+current all cpu register state.
+
+```
+typedef union FPReg_ {
+    __int128_t q;
+    struct {
+        double d1; // Holds the double (LSB).
+        double d2;
+    } d;
+    struct {
+        float f1; // Holds the float (LSB).
+        float f2;
+        float f3;
+        float f4;
+    } f;
+} FPReg;
+
+typedef struct _RegState {
+    uint64_t pc;
+    uint64_t sp;
+
+    union {
+        uint64_t x[29];
+        struct {
+            uint64_t x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28;
+        } regs;
+    } general;
+
+    uint64_t fp;
+    uint64_t lr;
+
+    union {
+        FPReg q[8];
+        FPReg q0,q1,q2,q3,q4,q5,q6,q7;
+    } floating;
+} RegState;
+```
+
+#### 3. `CallStack`
+
+export 2 method user to get/set `callstack`
+
+```
+// get value with the key
+zpointer ZzGetCallStackData(CallStack *callstack_ptr, char *key);
+
+// set value with key.
+zbool ZzSetCallStackData(CallStack *callstack_ptr, char *key, zpointer value_ptr, zsize value_size);
+```
+
+but for convenience, the macro is better.
+
+```
+#define STACK_CHECK_KEY(callstack, key) (bool)ZzGetCallStackData(callstack, key)
+#define STACK_GET(callstack, key, type) *(type *)ZzGetCallStackData(callstack, key)
+#define STACK_SET(callstack, key, value, type) ZzSetCallStackData(callstack, key, &(value), sizeof(type))
+```
diff --git a/VirtualApp/lib/src/main/jni/HookZz/docs/hookzz-example.md b/VirtualApp/lib/src/main/jni/HookZz/docs/hookzz-example.md
new file mode 100644
index 000000000..47016f7cf
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/docs/hookzz-example.md
@@ -0,0 +1,271 @@
+# HookZz example
+
+> [Move to HookZz Getting Started](https://jmpews.github.io/zzpp/getting-started/)
+
+> [Move to HookZz Example](https://jmpews.github.io/zzpp/hookzz-example/)
+
+> [Move to HookZz docs](https://jmpews.github.io/zzpp/hookzz-docs/)
+
+> [Move to HookZzModules](https://github.com/jmpews/HookZzModules)
+
+> [Move to HookZzWebsite](https://jmpews.github.io/zzpp/)
+
+
+## Simple Example
+
+#### 1. `ZzBuildHookAddress` & `ZzRuntimeCodePatch`
+
+hook at address, and specify the hook length. (i.e. hook a piece of code.)
+
+```c
+#include "hookzz.h"
+#include <stdio.h>
+#include <unistd.h>
+
+static void hack_this_function()
+{
+#ifdef __arm64__
+    __asm__("mov X0, #0\n"
+            "mov w16, #20\n"
+            "svc #0x80");
+#endif
+}
+
+static void sorry_to_exit()
+{
+#ifdef __arm64__
+    __asm__("mov X0, #0\n"
+            "mov w16, #1\n"
+            "svc #0x80");
+#endif
+}
+
+void getpid_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    unsigned long request = *(unsigned long *)(&rs->general.regs.x16);
+    printf("request(x16) is: %ld\n", request);
+    printf("x0 is: %ld\n", (long)rs->general.regs.x0);
+}
+
+void getpid_half_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    pid_t x0 = (pid_t)(rs->general.regs.x0);
+    printf("getpid() return at x0 is: %d\n", x0);
+}
+
+__attribute__((constructor)) void test_hook_address()
+{
+    void *hack_this_function_ptr = (void *)hack_this_function;
+    ZzBuildHookAddress(hack_this_function_ptr + 8, hack_this_function_ptr + 12, getpid_pre_call, getpid_half_call);
+    ZzEnableHook((void *)hack_this_function_ptr + 8);
+
+
+    void *sorry_to_exit_ptr = (void *)sorry_to_exit;
+    unsigned long nop_bytes = 0xD503201F;
+    ZzRuntimeCodePatch((unsigned long)sorry_to_exit_ptr + 8, (zpointer)&nop_bytes, 4);
+
+    hack_this_function();
+    sorry_to_exit();
+
+    printf("hack success -.0\n");
+}
+```
+
+breakpoint with lldb.
+
+```
+(lldb) disass -n hack_this_function
+test_hook_address.dylib`hack_this_function:
+    0x1000b0280 <+0>:  mov    x0, #0x0
+    0x1000b0284 <+4>:  mov    w16, #0x14
+    0x1000b0288 <+8>:  svc    #0x80
+    0x1000b028c <+12>: ret    
+
+(lldb) disass -n sorry_to_exit
+test_hook_address.dylib`sorry_to_exit:
+    0x1000b0290 <+0>:  mov    x0, #0x0
+    0x1000b0294 <+4>:  mov    w16, #0x1
+    0x1000b0298 <+8>:  svc    #0x80
+    0x1000b029c <+12>: ret    
+
+(lldb) c
+Process 41414 resuming
+request(x16) is: 20
+x0 is: 0
+getpid() return at x0 is: 41414
+hack success -.0
+(lldb) disass -n hack_this_function
+test_hook_address.dylib`hack_this_function:
+    0x1000b0280 <+0>:  mov    x0, #0x0
+    0x1000b0284 <+4>:  mov    w16, #0x14
+    0x1000b0288 <+8>:  b      0x1001202cc
+    0x1000b028c <+12>: ret    
+
+(lldb) disass -n sorry_to_exit
+test_hook_address.dylib`sorry_to_exit:
+    0x1000b0290 <+0>:  mov    x0, #0x0
+    0x1000b0294 <+4>:  mov    w16, #0x1
+    0x1000b0298 <+8>:  nop    
+    0x1000b029c <+12>: ret  
+```
+
+
+#### 2. use `ZzBuildHook` to hook `OC-Method`
+
+```c
+#include "hookzz.h"
+#import <Foundation/Foundation.h>
+#import <objc/runtime.h>
+#import <mach-o/dyld.h>
+#import <dlfcn.h>
+
+@interface HookZz : NSObject
+
+@end
+
+@implementation HookZz
+
++ (void)load {
+  [self zzMethodSwizzlingHook];
+}
+
+void objcMethod_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+  zpointer t = 0x1234; 
+  STACK_SET(callstack ,"key_x", t, void *);
+  STACK_SET(callstack ,"key_y", t, zpointer);
+  NSLog(@"hookzz OC-Method: -[UIViewController %s]",
+        (zpointer)(rs->general.regs.x1));
+}
+
+void objcMethod_post_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+  zpointer x = STACK_GET(callstack, "key_x", void *);
+  zpointer y = STACK_GET(callstack, "key_y", zpointer);
+  NSLog(@"function over, and get 'key_x' is: %p", x);
+  NSLog(@"function over, and get 'key_y' is: %p", y);
+}
+
++ (void)zzMethodSwizzlingHook {
+  Class hookClass = objc_getClass("UIViewController");
+  SEL oriSEL = @selector(viewWillAppear:);
+  Method oriMethod = class_getInstanceMethod(hookClass, oriSEL);
+  IMP oriImp = method_getImplementation(oriMethod);
+
+  ZzBuildHook((void *)oriImp, NULL, NULL, objcMethod_pre_call, objcMethod_post_call);
+  ZzEnableHook((void *)oriImp);
+
+}
+
+@end
+```
+
+breakpoint with lldb.
+
+```
+(lldb) disass -n "-[UIViewController viewWillAppear:]" -c 3
+UIKit`-[UIViewController viewWillAppear:]:
+    0x18881c10c <+0>: adrp   x8, 126868
+    0x18881c110 <+4>: ldrsw  x8, [x8, #0x280]
+    0x18881c114 <+8>: ldr    x9, [x0, x8]
+
+(lldb) c
+Process 41637 resuming
+(lldb) c
+Process 41637 resuming
+(lldb) c
+Process 41637 resuming
+2017-08-30 02:01:58.954875+0800 T007[41637:10198806] hookzz OC-Method: -[UIViewController viewWillAppear:]
+2017-08-30 02:01:58.956558+0800 T007[41637:10198806] function over, and get 'key_x' is: 0x1234
+2017-08-30 02:01:58.956654+0800 T007[41637:10198806] function over, and get 'key_y' is: 0x1234
+(lldb) disass -n "-[UIViewController viewWillAppear:]" -c 3
+UIKit`-[UIViewController viewWillAppear:]:
+    0x18881c10c <+0>: b      0x1810b0b4c
+    0x18881c110 <+4>: ldrsw  x8, [x8, #0x280]
+    0x18881c114 <+8>: ldr    x9, [x0, x8]
+```
+
+#### 3. hook variadic function
+
+```c
+#include "hookzz.h"
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+int (*orig_printf)(const char * restrict format, ...);
+int fake_printf(const char * restrict format, ...) {
+    puts("call printf");
+
+    char *stack[16];
+    va_list args;
+    va_start(args, format);
+    memcpy(stack, args, 8 * 16);
+    va_end(args);
+
+    // how to hook variadic function? fake a original copy stack.
+    // [move to detail-1](http://jmpews.github.io/2017/08/29/pwn/%E7%9F%AD%E5%87%BD%E6%95%B0%E5%92%8C%E4%B8%8D%E5%AE%9A%E5%8F%82%E6%95%B0%E7%9A%84hook/)
+    // [move to detail-2](https://github.com/jmpews/HookZzModules/tree/master/AntiDebugBypass)
+    int x = orig_printf(format, stack[0], stack[1], stack[2], stack[3], stack[4], stack[5], stack[6], stack[7], stack[8], stack[9], stack[10], stack[11], stack[12], stack[13], stack[14], stack[15]);
+    return x;
+}
+
+void printf_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    puts((char *)rs->general.regs.x0);
+    STACK_SET(callstack, "format", rs->general.regs.x0, char *);
+    puts("printf-pre-call");
+}
+
+void printf_post_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    if(STACK_CHECK_KEY(callstack, "format")) {
+        char *format = STACK_GET(callstack, "format", char *);
+        puts(format);
+    }
+    puts("printf-post-call");
+}
+
+__attribute__((constructor)) void test_hook_printf()
+{
+    void *printf_ptr = (void *)printf;
+
+    ZzBuildHook((void *)printf_ptr, (void *)fake_printf, (void **)&orig_printf, printf_pre_call, printf_post_call);
+    ZzEnableHook((void *)printf_ptr);
+    printf("HookZzzzzzz, %d, %p, %d, %d, %d, %d, %d, %d, %d\n",1, (void *)2, 3, (char)4, (char)5, (char)6 , 7, 8 , 9);
+}
+```
+
+breakpoint with lldb.
+
+```
+(lldb) disass -s 0x1815f61d8 -c 3
+libsystem_c.dylib`printf:
+    0x1815f61d8 <+0>: sub    sp, sp, #0x30             ; =0x30 
+    0x1815f61dc <+4>: stp    x20, x19, [sp, #0x10]
+    0x1815f61e0 <+8>: stp    x29, x30, [sp, #0x20]
+(lldb) c
+Process 41408 resuming
+HookZzzzzzz, %d, %p, %d, %d, %d, %d, %d, %d, %d
+
+printf-pre-call
+call printf
+HookZzzzzzz, 1, 0x2, 3, 4, 5, 6, 7, 8, 9
+HookZzzzzzz, %d, %p, %d, %d, %d, %d, %d, %d, %d
+
+printf-post-call
+(lldb) disass -s 0x1815f61d8 -c 3
+libsystem_c.dylib`printf:
+    0x1815f61d8 <+0>: b      0x1795f61d8
+    0x1815f61dc <+4>: stp    x20, x19, [sp, #0x10]
+    0x1815f61e0 <+8>: stp    x29, x30, [sp, #0x20]
+
+```
+
+## Advanced Example
+
+#### 1. AntiDebugBypass
+
+**[Move to AntiDebugBypass Detail](https://github.com/jmpews/HookZzModules/tree/master/AntiDebugBypass)**
+
+#### 5. hook `objc_msgSend`
+
+**[Move to hook_objc_msgSend Detail](https://github.com/jmpews/HookZzModules/tree/master/hook_objc_msgSend)**
+
+#### 6. hook `MGCopyAnswer`
+
+**[Move to hook_MGCopyAnswer Detail](https://github.com/jmpews/HookZzModules/tree/master/hook_MGCopyAnswer)**
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/docs/hookzz-getting-started.md b/VirtualApp/lib/src/main/jni/HookZz/docs/hookzz-getting-started.md
new file mode 100644
index 000000000..d670cf994
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/docs/hookzz-getting-started.md
@@ -0,0 +1,97 @@
+# HookZz Getting Started
+
+> [Move to HookZz Getting Started](https://jmpews.github.io/zzpp/getting-started/)
+
+> [Move to HookZz Example](https://jmpews.github.io/zzpp/hookzz-example/)
+
+> [Move to HookZz docs](https://jmpews.github.io/zzpp/hookzz-docs/)
+
+> [Move to HookZzModules](https://github.com/jmpews/HookZzModules)
+
+> [Move to HookZzWebsite](https://jmpews.github.io/zzpp/)
+
+## 1. build hookzz dylib
+
+clone the repo `git clone https://github.com/jmpews/HookZz` and build for `darwin.ios`. btw, you can set the log infomation in `hookzz.h`.
+
+```
+λ : >>> make -f darwin.ios.mk darwin.ios
+generate [src/allocator.o]!
+generate [src/interceptor.o]!
+generate [src/memory.o]!
+generate [src/stack.o]!
+generate [src/thread.o]!
+generate [src/trampoline.o]!
+generate [src/platforms/posix/thread-posix.o]!
+generate [src/platforms/darwin/memory-darwin.o]!
+generate [src/platforms/arm64/reader.o]!
+generate [src/platforms/arm64/relocator-arm64.o]!
+generate [src/platforms/arm64/thunker-arm64.o]!
+generate [src/platforms/arm64/writer-arm64.o]!
+generate [src/zzdeps/darwin/macho-utils-darwin.o]!
+generate [src/zzdeps/darwin/memory-utils-darwin.o]!
+generate [src/zzdeps/common/memory-utils-common.o]!
+generate [src/zzdeps/posix/memory-utils-posix.o]!
+generate [src/zzdeps/posix/thread-utils-posix.o]!
+build success for arm64(IOS)!
+```
+
+check the dylibs in `build` directory. `libhookzz.dylib` is shared library, and `libhookzz.static.a` is static library.
+
+```
+λ : >>> ls build
+libhookzz.dylib    libhookzz.static.a
+```
+
+## 2. build the test demo dylib
+
+a demo dylib to hook `[UIViewController viewWillAppear]`
+
+before build demo dylib, specify the hookzz library path(shared or static).
+
+1. build with commandline.
+
+```
+`xcrun --sdk iphoneos --find clang` -isysroot `xcrun --sdk iphoneos --show-sdk-path` -g -gmodules -I/path/HookZz/include  -L/path/HookZz/build -lhookzz.static -framework Foundation -dynamiclib -arch arm64 test_hook_oc.m -o test_hook_oc.dylib
+```
+
+2. build with `make -f darwin.ios.mk test`
+
+```
+λ : >>> make -f darwin.ios.mk test
+build success for arm64(IOS)!
+build [test_hook_oc.dylib] success for arm64(ios)!
+build [test_hook_address.dylib] success for arm64(ios)!
+build [test_hook_printf.dylib] success for arm64(ios)!
+build [test] success for arm64(IOS)!
+```
+
+```
+λ : >>> ls build
+libhookzz.dylib         test_hook_address.dylib test_hook_printf.dylib
+libhookzz.static.a      test_hook_oc.dylib
+```
+
+## 3. test your demo dylib
+
+build new ios app project. and then `Build Phases -> New Run Script Phase` add a run script.
+
+```
+cd ${BUILT_PRODUCTS_DIR}
+cd ${FULL_PRODUCT_NAME}
+
+cp /path/HookZz/build/test_hook_oc.dylib ./
+/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --timestamp=none test_hook_oc.dylib
+/Users/jmpews/Desktop/SpiderZz/Pwntools/Darwin/bin/optool install -c load -p "@executable_path/test_hook_oc.dylib" -t ${EXECUTABLE_NAME}
+```
+
+last thing, run the app ,you will get the such output(with open `GLOBAL_DEBUG`, `GLOBAL_INFO`)
+
+```
+target 0x188525804 near jump to 0x183c9141c
+target 0x188525804 call begin-invocation
+2017-08-20 16:41:26.155 T007[409:129805] hookzz OC-Method: -[ViewController viewWillAppear:]
+0x188525804 call end-invocation
+2017-08-20 16:41:26.157 T007[409:129805] function over, and get 'key_x' is: 0x1234
+2017-08-20 16:41:26.157 T007[409:129805] function over, and get 'key_y' is: 0x1234
+```
diff --git a/VirtualApp/lib/src/main/jni/HookZz/include/hookzz.h b/VirtualApp/lib/src/main/jni/HookZz/include/hookzz.h
new file mode 100644
index 000000000..34eb953d5
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/include/hookzz.h
@@ -0,0 +1,182 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef hook_zz_h
+#define hook_zz_h
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#define DEBUG_MODE 0
+
+#ifndef zz_type
+#define zz_type
+
+typedef void *zpointer;
+typedef unsigned long zsize;
+typedef unsigned long zaddr;
+
+typedef uint64_t zuint64;
+typedef uint32_t zuint32;
+typedef uint16_t zuint16;
+typedef uint8_t zuint8;
+
+typedef int32_t zint32;
+typedef int16_t zint16;
+typedef int8_t zint8;
+
+typedef unsigned long zuint;
+typedef long zint;
+typedef unsigned char zbyte;
+typedef bool zbool;
+
+#endif
+
+#if defined(FALSE)
+#else
+#define FALSE 0
+#define TRUE 1
+#endif
+
+#ifndef zz_register_type
+#define zz_register_type
+#if defined(__arm64__) || defined(__aarch64__)
+typedef union FPReg_ {
+    __int128_t q;
+    struct {
+        double d1;
+        double d2;
+    } d;
+    struct {
+        float f1;
+        float f2;
+        float f3;
+        float f4;
+    } f;
+} FPReg;
+
+typedef struct _RegState {
+    uint64_t sp;
+
+    union {
+        uint64_t x[29];
+        struct {
+            uint64_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21,
+                x22, x23, x24, x25, x26, x27, x28;
+        } regs;
+    } general;
+
+    uint64_t fp;
+    uint64_t lr;
+
+    union {
+        FPReg q[8];
+        FPReg q0, q1, q2, q3, q4, q5, q6, q7;
+    } floating;
+} RegState;
+#elif defined(__arm__)
+typedef struct _RegState {
+    zuint32 sp;
+
+    union {
+        zuint32 r[13];
+        struct {
+            zuint32 r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12;
+        } regs;
+    } general;
+
+    zuint32 lr;
+} RegState;
+#elif defined(__i386__)
+typedef struct _RegState {
+} RegState;
+#elif defined(__x86_64__)
+typedef struct _RegState {
+} RegState;
+#endif
+#endif
+
+typedef enum _ZZSTATUS {
+    ZZ_UNKOWN = -1,
+    ZZ_DONE = 0,
+    ZZ_SUCCESS,
+    ZZ_FAILED,
+    ZZ_DONE_HOOK,
+    ZZ_DONE_INIT,
+    ZZ_DONE_ENABLE,
+    ZZ_ALREADY_HOOK,
+    ZZ_ALREADY_INIT,
+    ZZ_ALREADY_ENABLED,
+    ZZ_NEED_INIT,
+    ZZ_NO_BUILD_HOOK
+} ZZSTATUS;
+
+typedef struct _CallStack {
+    zsize call_id;
+    struct _ThreadStack *threadstack;
+} CallStack;
+
+typedef struct _ThreadStack {
+    zsize thread_id;
+    zsize size;
+} ThreadStack;
+
+typedef void (*PRECALL)(RegState *rs, ThreadStack *threadstack, CallStack *callstack);
+typedef void (*POSTCALL)(RegState *rs, ThreadStack *threadstack, CallStack *callstack);
+typedef void (*HALFCALL)(RegState *rs, ThreadStack *threadstack, CallStack *callstack);
+
+// ------- export API -------
+
+zpointer ZzGetCallStackData(CallStack *callstack_ptr, char *key);
+zbool ZzSetCallStackData(CallStack *callstack_ptr, char *key, zpointer value_ptr, zsize value_size);
+
+#define STACK_CHECK_KEY(callstack, key) (bool)ZzGetCallStackData(callstack, key)
+#define STACK_GET(callstack, key, type) *(type *)ZzGetCallStackData(callstack, key)
+#define STACK_SET(callstack, key, value, type) ZzSetCallStackData(callstack, key, &(value), sizeof(type))
+
+ZZSTATUS ZzBuildHook(zpointer target_ptr, zpointer replace_call_ptr, zpointer *origin_ptr, PRECALL pre_call_ptr,
+                     POSTCALL post_call_ptr, zbool try_near_jump);
+ZZSTATUS ZzBuildHookAddress(zpointer target_start_ptr, zpointer target_end_ptr, PRECALL pre_call_ptr,
+                            HALFCALL half_call_ptr, zbool try_near_jump);
+ZZSTATUS ZzEnableHook(zpointer target_ptr);
+
+ZZSTATUS ZzHook(zpointer target_ptr, zpointer replace_ptr, zpointer *origin_ptr, PRECALL pre_call_ptr,
+                POSTCALL post_call_ptr, zbool try_near_jump);
+ZZSTATUS ZzHookPrePost(zpointer target_ptr, PRECALL pre_call_ptr, POSTCALL post_call_ptr);
+ZZSTATUS ZzHookReplace(zpointer target_ptr, zpointer replace_ptr, zpointer *origin_ptr);
+ZZSTATUS ZzHookAddress(zpointer target_start_ptr, zpointer target_end_ptr, PRECALL pre_call_ptr,
+                       HALFCALL half_call_ptr);
+
+void ZzEnableDebugMode(void);
+
+ZZSTATUS ZzRuntimeCodePatch(zaddr address, zpointer codedata, zsize codedata_size);
+
+// ------- export API -------
+
+#if defined(__arm64__) || defined(__aarch64__)
+#if defined(__APPLE__) && defined(__MACH__)
+#include <TargetConditionals.h>
+#if TARGET_OS_IPHONE
+#define TARGET_IS_IOS 1
+#endif
+#endif
+#endif
+#ifdef TARGET_IS_IOS
+ZZSTATUS ZzSolidifyHook(zpointer target_fileoff, zpointer replace_call_ptr, zpointer *origin_ptr, PRECALL pre_call_ptr,
+                        POSTCALL post_call_ptr);
+#endif
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/allocator.c b/VirtualApp/lib/src/main/jni/HookZz/src/allocator.c
new file mode 100644
index 000000000..2dbbe6ed4
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/allocator.c
@@ -0,0 +1,298 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "allocator.h"
+#include "zzdeps/zz.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define DEFAULT_ALLOCATOR_CAPACITY 4
+
+ZzAllocator *ZzNewAllocator() {
+
+    if (!ZzMemoryIsSupportAllocateRXPage())
+        return NULL;
+
+    ZzAllocator *allocator;
+    allocator = (ZzAllocator *)malloc(sizeof(ZzAllocator));
+    allocator->memory_pages = (ZzMemoryPage **)malloc(sizeof(ZzMemoryPage *) * DEFAULT_ALLOCATOR_CAPACITY);
+    if (!allocator->memory_pages)
+        return NULL;
+    allocator->size = 0;
+    allocator->capacity = DEFAULT_ALLOCATOR_CAPACITY;
+    return allocator;
+}
+
+ZzMemoryPage *ZzNewMemoryPage() {
+    zsize page_size = ZzMemoryGetPageSzie();
+    zpointer page_ptr = NULL;
+    zpointer cave_ptr = NULL;
+    ZzMemoryPage *page = NULL;
+
+    page_ptr = ZzMemoryAllocatePages(1);
+    if (!page_ptr) {
+        return NULL;
+    }
+    if (!ZzMemoryProtectAsExecutable((zaddr)page_ptr, page_size)) {
+        Xerror("ZzMemoryProtectAsExecutable error at %p", page_ptr);
+#if defined(DEBUG_MODE)
+        debug_break();
+#endif
+        exit(1);
+    }
+
+    page = (ZzMemoryPage *)malloc(sizeof(ZzMemoryPage));
+    page->base = page_ptr;
+    page->curr_pos = page_ptr;
+    page->size = page_size;
+    page->used_size = 0;
+    return page;
+}
+
+ZzMemoryPage *ZzNewNearMemoryPage(zaddr address, zsize redirect_range_size) {
+    zsize page_size = ZzMemoryGetPageSzie();
+    zpointer page_ptr = NULL;
+    zpointer cave_ptr = NULL;
+    ZzMemoryPage *page = NULL;
+
+    page_ptr = ZzMemoryAllocateNearPages(address, redirect_range_size, 1);
+    if (!page_ptr) {
+        return NULL;
+    }
+
+    if (!ZzMemoryProtectAsExecutable((zaddr)page_ptr, page_size)) {
+        Xerror("ZzMemoryProtectAsExecutable error at %p", page_ptr);
+#if defined(DEBUG_MODE)
+        debug_break();
+#endif
+        exit(1);
+    }
+
+    page = (ZzMemoryPage *)malloc(sizeof(ZzMemoryPage));
+
+    page->base = page_ptr;
+
+    if ((zaddr)page_ptr > address && ((zaddr)page_ptr + page_size) > (address + redirect_range_size)) {
+        page->size = (address + redirect_range_size) - (zaddr)page_ptr;
+        page->used_size = 0;
+        page->curr_pos = page_ptr;
+    } else if ((zaddr)page_ptr < address && (zaddr)page_ptr < (address - redirect_range_size)) {
+        page->size = page_size;
+        page->used_size = (address - redirect_range_size) - (zaddr)page_ptr;
+        page->curr_pos = (zpointer)(address - redirect_range_size);
+    } else {
+        page->size = page_size;
+        page->used_size = 0;
+        page->curr_pos = page_ptr;
+    }
+    page->isCodeCave = FALSE;
+    return page;
+}
+
+ZzMemoryPage *ZzNewNearCodeCave(zaddr address, zsize redirect_range_size, zsize code_slice_size) {
+    zsize page_size = ZzMemoryGetPageSzie();
+    zpointer cave_ptr = NULL;
+    ZzMemoryPage *page = NULL;
+    zsize cave_size = code_slice_size;
+
+    cave_ptr = ZzMemorySearchCodeCave(address, redirect_range_size, cave_size);
+
+    if (!cave_ptr)
+        return NULL;
+
+    page = (ZzMemoryPage *)malloc(sizeof(ZzMemoryPage));
+    page->base = cave_ptr;
+    page->curr_pos = cave_ptr;
+    page->size = cave_size;
+    page->used_size = 0;
+    page->isCodeCave = TRUE;
+    return page;
+}
+
+ZZSTATUS ZzAddMemoryPage(ZzAllocator *allocator, ZzMemoryPage *page) {
+    if (!allocator)
+        return ZZ_FAILED;
+    if (allocator->size >= allocator->capacity) {
+        ZzMemoryPage **pages = realloc(allocator->memory_pages, sizeof(ZzMemoryPage) * (allocator->capacity) * 2);
+        if (!pages) {
+            return ZZ_FAILED;
+        }
+        allocator->capacity = allocator->capacity * 2;
+        allocator->memory_pages = pages;
+    }
+    allocator->memory_pages[allocator->size++] = page;
+    return ZZ_SUCCESS;
+}
+
+//  1. try allocate from the history pages
+//  2. try allocate a new page
+//  3. add it to the page manager
+ZzCodeSlice *ZzNewCodeSlice(ZzAllocator *allocator, zsize code_slice_size) {
+    ZzCodeSlice *code_slice = NULL;
+    ZzMemoryPage *page = NULL;
+    int i;
+
+    for (i = 0; i < allocator->size; i++) {
+        page = allocator->memory_pages[i];
+        // 1. page is initialized
+        // 2. can't be codecave
+        // 3. the rest memory of this page is enough for code_slice_size
+        // 4. the page address is near
+
+        if ((zaddr)page->curr_pos % 4) {
+            int t = 4 - (zaddr)page->curr_pos % 4;
+            page->used_size += t;
+            page->curr_pos += t;
+        }
+
+        if (page->base && !page->isCodeCave && (page->size - page->used_size) > code_slice_size) {
+            code_slice = (ZzCodeSlice *)malloc(sizeof(ZzCodeSlice));
+            code_slice->data = page->curr_pos;
+            code_slice->size = code_slice_size;
+
+            page->curr_pos += code_slice_size;
+            page->used_size += code_slice_size;
+            return code_slice;
+        }
+    }
+
+    page = ZzNewMemoryPage();
+    ZzAddMemoryPage(allocator, page);
+
+    if ((zaddr)page->curr_pos % 4) {
+        int t = 4 - (zaddr)page->curr_pos % 4;
+        page->used_size += t;
+        page->curr_pos += t;
+    }
+
+    code_slice = (ZzCodeSlice *)malloc(sizeof(ZzCodeSlice));
+    code_slice->data = page->curr_pos;
+    code_slice->size = code_slice_size;
+
+    page->curr_pos += code_slice_size;
+    page->used_size += code_slice_size;
+
+    return code_slice;
+}
+
+//  1. try allocate from the history pages
+//  2. try allocate a new near page
+//  3. add it to the page manager
+ZzCodeSlice *ZzNewNearCodeSlice(ZzAllocator *allocator, zaddr address, zsize redirect_range_size,
+                                zsize code_slice_size) {
+    ZzCodeSlice *code_slice = NULL;
+    ZzMemoryPage *page = NULL;
+    int i;
+    for (i = 0; i < allocator->size; i++) {
+        page = allocator->memory_pages[i];
+        // 1. page is initialized
+        // 2. can't be codecave
+        // 3. the rest memory of this page is enough for code_slice_size
+        // 4. the page address is near
+        if (page->base && !page->isCodeCave) {
+            int flag = 0;
+            zaddr split_addr = 0;
+
+            if ((zaddr)page->curr_pos < address) {
+                if (address - redirect_range_size < (zaddr)page->curr_pos) {
+                    // enough for code_slice_size
+                    if ((page->size - page->used_size) < code_slice_size)
+                        continue;
+                    flag = 1;
+                } else if (address - redirect_range_size > (zaddr)page->curr_pos &&
+                           (address - redirect_range_size) < ((zaddr)page->base + page->size)) {
+                    // enough for code_slice_size
+                    if (((zaddr)page->base + page->size) - (address - redirect_range_size) < code_slice_size)
+                        continue;
+                    split_addr = address - redirect_range_size;
+                    flag = 2;
+                }
+            } else {
+                if (address + redirect_range_size > ((zaddr)page->base + page->size)) {
+                    // enough for code_slice_size
+                    if ((page->size - page->used_size) < code_slice_size)
+                        continue;
+                    flag = 1;
+                } else if ((address + redirect_range_size) > (zaddr)page->curr_pos &&
+                           (address + redirect_range_size) < ((zaddr)page->base + page->size)) {
+                    if ((address + redirect_range_size) - (zaddr)page->curr_pos > code_slice_size)
+                        continue;
+                    flag = 1;
+                }
+            }
+
+            if (1 == flag) {
+                code_slice = (ZzCodeSlice *)malloc(sizeof(ZzCodeSlice));
+                code_slice->isCodeCave = page->isCodeCave;
+                code_slice->data = page->curr_pos;
+                code_slice->size = code_slice_size;
+
+                page->curr_pos += code_slice_size;
+                page->used_size += code_slice_size;
+                return code_slice;
+            } else if (2 == flag) {
+
+                // new page
+                ZzMemoryPage *new_page = (ZzMemoryPage *)malloc(sizeof(ZzMemoryPage));
+                new_page->base = (zpointer)split_addr;
+                new_page->size = ((zaddr)page->base + page->size) - split_addr;
+                new_page->used_size = 0;
+                new_page->curr_pos = (zpointer)split_addr;
+                ZzAddMemoryPage(allocator, new_page);
+
+                // origin page
+                page->size = split_addr - (zaddr)page->base;
+
+                code_slice = (ZzCodeSlice *)malloc(sizeof(ZzCodeSlice));
+                code_slice->isCodeCave = FALSE;
+                code_slice->data = new_page->curr_pos;
+                code_slice->size = code_slice_size;
+
+                new_page->curr_pos += code_slice_size;
+                new_page->used_size += code_slice_size;
+                return code_slice;
+            }
+        }
+    }
+
+#if 0
+    page = ZzNewNearMemoryPage(address, redirect_range_size);
+    // try allocate again, avoid the boundary page
+    if (page && (page->size - page->used_size) < code_slice_size) {
+        page = ZzNewNearMemoryPage(address, redirect_range_size);
+    }
+#endif
+    page = NULL;
+
+    if (!page) {
+        page = ZzNewNearCodeCave(address, redirect_range_size, code_slice_size);
+        if (!page)
+            return NULL;
+    }
+    if (!page)
+        return NULL;
+
+    code_slice = (ZzCodeSlice *)malloc(sizeof(ZzCodeSlice));
+    code_slice->isCodeCave = page->isCodeCave;
+    code_slice->data = page->curr_pos;
+    code_slice->size = code_slice_size;
+
+    page->curr_pos += code_slice_size;
+    page->used_size += code_slice_size;
+
+    return code_slice;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/allocator.h b/VirtualApp/lib/src/main/jni/HookZz/src/allocator.h
new file mode 100644
index 000000000..bee034096
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/allocator.h
@@ -0,0 +1,60 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef allocator_h
+#define allocator_h
+
+#include <stdint.h>
+
+// platforms
+
+// hookzz
+#include "hookzz.h"
+#include "memory.h"
+
+// zzdeps
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+
+typedef struct _codeslice {
+    zpointer data;
+    zsize size;
+    zbool is_used;
+    zbool isCodeCave;
+} ZzCodeSlice;
+
+typedef struct _ZzMemoryPage {
+    zpointer base;
+    zpointer curr_pos;
+    zsize size;
+    zsize used_size;
+    zbool isCodeCave;
+} ZzMemoryPage;
+
+typedef struct _allocator {
+    ZzMemoryPage **memory_pages;
+    zsize size;
+    zsize capacity;
+} ZzAllocator;
+
+ZzCodeSlice *ZzNewNearCodeSlice(ZzAllocator *allocator, zaddr address, zsize redirect_range_size,
+                                zsize codeslice_size);
+
+ZzCodeSlice *ZzNewCodeSlice(ZzAllocator *allocator, zsize codeslice_size);
+
+ZzAllocator *ZzNewAllocator();
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/interceptor.c b/VirtualApp/lib/src/main/jni/HookZz/src/interceptor.c
new file mode 100644
index 000000000..4e513f0c8
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/interceptor.c
@@ -0,0 +1,285 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "interceptor.h"
+#include "trampoline.h"
+#include "zzinfo.h"
+
+#define ZZHOOKENTRIES_DEFAULT 100
+ZzInterceptor *g_interceptor = NULL;
+
+ZZSTATUS ZzInitializeInterceptor(void) {
+    ZzInterceptor *interceptor = g_interceptor;
+    ZzHookFunctionEntrySet *hook_function_entry_set;
+
+    if (NULL == interceptor) {
+        interceptor = (ZzInterceptor *)malloc(sizeof(ZzInterceptor));
+        memset(interceptor, 0, sizeof(ZzInterceptor));
+
+        hook_function_entry_set = &(interceptor->hook_function_entry_set);
+        hook_function_entry_set->capacity = ZZHOOKENTRIES_DEFAULT;
+        hook_function_entry_set->entries =
+            (ZzHookFunctionEntry **)malloc(sizeof(ZzHookFunctionEntry *) * hook_function_entry_set->capacity);
+        memset(hook_function_entry_set->entries, 0, sizeof(ZzHookFunctionEntry *) * hook_function_entry_set->capacity);
+
+        if (!hook_function_entry_set->entries) {
+            return ZZ_FAILED;
+        }
+        hook_function_entry_set->size = 0;
+        g_interceptor = interceptor;
+        interceptor->is_support_rx_page = ZzMemoryIsSupportAllocateRXPage();
+        if (interceptor->is_support_rx_page) {
+            interceptor->allocator = ZzNewAllocator();
+            interceptor->backend = ZzBuildInteceptorBackend(interceptor->allocator);
+        } else {
+            interceptor->allocator = NULL;
+            interceptor->backend = NULL;
+        }
+        return ZZ_DONE_INIT;
+    }
+    return ZZ_ALREADY_INIT;
+}
+
+ZzHookFunctionEntry *ZzFindHookFunctionEntry(zpointer target_ptr) {
+    ZzInterceptor *interceptor = g_interceptor;
+    if (!interceptor)
+        return NULL;
+
+    ZzHookFunctionEntrySet *hook_function_entry_set = &(interceptor->hook_function_entry_set);
+
+    int i;
+    for (i = 0; i < hook_function_entry_set->size; ++i) {
+        if ((hook_function_entry_set->entries)[i] && target_ptr == (hook_function_entry_set->entries)[i]->target_ptr) {
+            return (hook_function_entry_set->entries)[i];
+        }
+    }
+    return NULL;
+}
+
+ZZSTATUS ZzAddHookFunctionEntry(ZzHookFunctionEntry *entry) {
+    ZzInterceptor *interceptor = g_interceptor;
+    if (!interceptor)
+        return ZZ_FAILED;
+
+    ZzHookFunctionEntrySet *hook_function_entry_set = &(interceptor->hook_function_entry_set);
+
+    if (hook_function_entry_set->size >= hook_function_entry_set->capacity) {
+        ZzHookFunctionEntry **entries = (ZzHookFunctionEntry **)realloc(
+            hook_function_entry_set->entries, sizeof(ZzHookFunctionEntry *) * hook_function_entry_set->capacity * 2);
+        if (!entries)
+            return ZZ_FAILED;
+
+        hook_function_entry_set->capacity = hook_function_entry_set->capacity * 2;
+        hook_function_entry_set->entries = entries;
+    }
+    hook_function_entry_set->entries[hook_function_entry_set->size++] = entry;
+    return ZZ_SUCCESS;
+}
+
+void ZzInitializeHookFunctionEntry(ZzHookFunctionEntry *entry, int hook_type, zpointer target_ptr,
+                                   zpointer target_end_ptr, zpointer replace_call, PRECALL pre_call, HALFCALL half_call,
+                                   POSTCALL post_call, zbool try_near_jump) {
+    ZzInterceptor *interceptor = g_interceptor;
+    ZzHookFunctionEntrySet *hook_function_entry_set = &(interceptor->hook_function_entry_set);
+
+    memset(entry, 0, sizeof(ZzHookFunctionEntry));
+
+    entry->hook_type = hook_type;
+    entry->id = hook_function_entry_set->size;
+    entry->isEnabled = 0;
+    entry->try_near_jump = try_near_jump;
+    entry->interceptor = interceptor;
+    entry->target_ptr = target_ptr;
+    entry->target_end_ptr = target_end_ptr;
+    entry->replace_call = replace_call;
+    entry->pre_call = (zpointer)pre_call;
+    entry->half_call = (zpointer)half_call;
+    entry->post_call = (zpointer)post_call;
+    entry->on_enter_trampoline = NULL;
+    entry->on_invoke_trampoline = NULL;
+    entry->on_half_trampoline = NULL;
+    entry->on_leave_trampoline = NULL;
+    entry->origin_prologue.address = target_ptr;
+    entry->thread_local_key = ZzThreadNewThreadLocalKeyPtr();
+
+    /* key function */
+    ZzBuildTrampoline(interceptor->backend, entry);
+    ZzAddHookFunctionEntry(entry);
+}
+
+ZZSTATUS ZzBuildHook(zpointer target_ptr, zpointer replace_call_ptr, zpointer *origin_ptr, PRECALL pre_call_ptr,
+                     POSTCALL post_call_ptr, zbool try_near_jump) {
+#if defined(__i386__) || defined(__x86_64__)
+    ZzInfoLog("%s", "x86 & x86_64 arch not support");
+    return ZZ_FAILED;
+#endif
+
+    ZZSTATUS status = ZZ_DONE_HOOK;
+    ZzInterceptor *interceptor = g_interceptor;
+    ZzHookFunctionEntrySet *hook_function_entry_set = NULL;
+    ZzHookFunctionEntry *entry;
+
+    if (!interceptor) {
+        ZzInitializeInterceptor();
+        if (!g_interceptor)
+            return ZZ_FAILED;
+        if (!g_interceptor->is_support_rx_page) {
+            return ZZ_FAILED;
+        }
+    }
+
+    interceptor = g_interceptor;
+    hook_function_entry_set = &(interceptor->hook_function_entry_set);
+
+    do {
+        // check is already hooked
+        if (ZzFindHookFunctionEntry(target_ptr)) {
+            status = ZZ_ALREADY_HOOK;
+            break;
+        }
+
+        entry = (ZzHookFunctionEntry *)malloc(sizeof(ZzHookFunctionEntry));
+        memset(entry, 0, sizeof(ZzHookFunctionEntry));
+
+        ZzInitializeHookFunctionEntry(entry, HOOK_FUNCTION_TYPE, target_ptr, 0, replace_call_ptr, pre_call_ptr, NULL,
+                                      post_call_ptr, try_near_jump);
+
+        if (origin_ptr)
+            *origin_ptr = entry->on_invoke_trampoline;
+
+    } while (0);
+    return status;
+}
+
+ZZSTATUS ZzBuildHookAddress(zpointer target_start_ptr, zpointer target_end_ptr, PRECALL pre_call_ptr,
+                            HALFCALL half_call_ptr, zbool try_near_jump) {
+#if defined(__i386__) || defined(__x86_64__)
+    ZzInfoLog("%s", "x86 & x86_64 arch not support");
+    return ZZ_FAILED;
+#endif
+    ZZSTATUS status = ZZ_DONE_HOOK;
+    ZzInterceptor *interceptor = g_interceptor;
+    ZzHookFunctionEntrySet *hook_function_entry_set = NULL;
+    ZzHookFunctionEntry *entry = NULL;
+
+    if (!interceptor) {
+        ZzInitializeInterceptor();
+        if (!g_interceptor)
+            return ZZ_FAILED;
+        if (!g_interceptor->is_support_rx_page) {
+            return ZZ_FAILED;
+        }
+    }
+
+    interceptor = g_interceptor;
+    hook_function_entry_set = &(interceptor->hook_function_entry_set);
+
+    do {
+        // check is already hooked
+        if (ZzFindHookFunctionEntry(target_start_ptr)) {
+            status = ZZ_ALREADY_HOOK;
+            break;
+        }
+
+        entry = (ZzHookFunctionEntry *)malloc(sizeof(ZzHookFunctionEntry));
+        memset(entry, 0, sizeof(ZzHookFunctionEntry));
+
+        ZzInitializeHookFunctionEntry(entry, HOOK_ADDRESS_TYPE, target_start_ptr, target_end_ptr, NULL, pre_call_ptr,
+                                      half_call_ptr, NULL, try_near_jump);
+
+    } while (0);
+    return status;
+}
+
+ZZSTATUS ZzEnableHook(zpointer target_ptr) {
+    ZZSTATUS status = ZZ_DONE_ENABLE;
+    ZzInterceptor *interceptor = g_interceptor;
+    ZzHookFunctionEntry *entry = ZzFindHookFunctionEntry(target_ptr);
+
+    if (!entry) {
+        status = ZZ_NO_BUILD_HOOK;
+        Xinfo(" %p not build HookFunctionEntry!", target_ptr);
+        return status;
+    }
+
+    if (entry->isEnabled) {
+        status = ZZ_ALREADY_ENABLED;
+        Xinfo("HookFunctionEntry %p already enable!", target_ptr);
+        return status;
+    }
+
+    return ZzActivateTrampoline(interceptor->backend, entry);
+}
+
+ZZSTATUS ZzHook(zpointer target_ptr, zpointer replace_ptr, zpointer *origin_ptr, PRECALL pre_call_ptr,
+                POSTCALL post_call_ptr, zbool try_near_jump) {
+    ZzBuildHook(target_ptr, replace_ptr, origin_ptr, pre_call_ptr, post_call_ptr, try_near_jump);
+    ZzEnableHook(target_ptr);
+    return ZZ_SUCCESS;
+}
+
+ZZSTATUS ZzHookPrePost(zpointer target_ptr, PRECALL pre_call_ptr, POSTCALL post_call_ptr) {
+    ZzBuildHook(target_ptr, NULL, NULL, pre_call_ptr, post_call_ptr, FALSE);
+    ZzEnableHook(target_ptr);
+    return ZZ_SUCCESS;
+}
+
+ZZSTATUS ZzHookReplace(zpointer target_ptr, zpointer replace_ptr, zpointer *origin_ptr) {
+    ZzBuildHook(target_ptr, replace_ptr, origin_ptr, NULL, NULL, FALSE);
+    ZzEnableHook(target_ptr);
+    return ZZ_SUCCESS;
+}
+
+ZZSTATUS ZzHookAddress(zpointer target_start_ptr, zpointer target_end_ptr, PRECALL pre_call_ptr,
+                       HALFCALL half_call_ptr) {
+    ZzBuildHookAddress(target_start_ptr, target_end_ptr, pre_call_ptr, half_call_ptr, FALSE);
+    ZzEnableHook(target_start_ptr);
+    return ZZ_SUCCESS;
+}
+
+#ifdef TARGET_IS_IOS
+
+ZZSTATUS ZzSolidifyHook(zpointer target_fileoff, zpointer replace_call_ptr, zpointer *origin_ptr, PRECALL pre_call_ptr,
+                        POSTCALL post_call_ptr) {
+    ZZSTATUS status = ZZ_DONE_HOOK;
+    ZzInterceptor *interceptor = g_interceptor;
+    ZzHookFunctionEntrySet *hook_function_entry_set = NULL;
+    ZzHookFunctionEntry *entry = NULL;
+
+    if (!interceptor) {
+        ZzInitializeInterceptor();
+        if (!g_interceptor)
+            return ZZ_FAILED;
+    }
+
+    interceptor = g_interceptor;
+
+    entry = (ZzHookFunctionEntry *)malloc(sizeof(ZzHookFunctionEntry));
+    entry->target_ptr = target_fileoff;
+    entry->replace_call = replace_call_ptr;
+    entry->pre_call = (zpointer)pre_call_ptr;
+    entry->post_call = (zpointer)post_call_ptr;
+
+    ZzActivateSolidifyTrampoline(entry, (zaddr)target_fileoff);
+
+    if (origin_ptr)
+        *origin_ptr = entry->on_invoke_trampoline;
+    return status;
+}
+#endif
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/interceptor.h b/VirtualApp/lib/src/main/jni/HookZz/src/interceptor.h
new file mode 100644
index 000000000..92c26fb57
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/interceptor.h
@@ -0,0 +1,95 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef interceptor_h
+#define interceptor_h
+
+// platforms
+
+// hookzz
+#include "allocator.h"
+#include "hookzz.h"
+#include "stack.h"
+#include "thread.h"
+#include "thunker.h"
+#include "writer.h"
+
+// zzdeps
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef struct _FunctionBackup {
+    zpointer address;
+    zsize size;
+    zbyte data[32];
+} FunctionBackup;
+
+struct _ZzInterceptor;
+
+/*
+ * hook entry
+ */
+
+#define HOOK_FUNCTION_TYPE 1
+#define HOOK_ADDRESS_TYPE 2
+
+struct _ZzHookFunctionEntryBackend;
+typedef struct _ZzHookFunctionEntry {
+    int hook_type;
+    unsigned long id;
+    zbool isEnabled;
+    zbool try_near_jump;
+
+    zpointer thread_local_key;
+    struct _ZzHookFunctionEntryBackend *backend;
+
+    zpointer target_ptr;
+    zpointer target_end_ptr;
+    zpointer target_half_ret_addr;
+
+    zpointer pre_call;
+    zpointer half_call;
+    zpointer post_call;
+    zpointer replace_call;
+
+    FunctionBackup origin_prologue;
+
+    zpointer on_enter_transfer_trampoline;
+    zpointer on_enter_trampoline;
+    zpointer on_half_trampoline;
+    zpointer on_invoke_trampoline;
+    zpointer on_leave_trampoline;
+
+    struct _ZzInterceptor *interceptor;
+} ZzHookFunctionEntry;
+
+typedef struct {
+    ZzHookFunctionEntry **entries;
+    zsize size;
+    zsize capacity;
+} ZzHookFunctionEntrySet;
+
+struct _ZzInterceptorBackend;
+
+typedef struct _ZzInterceptor {
+    zbool is_support_rx_page;
+    ZzHookFunctionEntrySet hook_function_entry_set;
+    struct _ZzInterceptorBackend *backend;
+    ZzAllocator *allocator;
+} ZzInterceptor;
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/memory.c b/VirtualApp/lib/src/main/jni/HookZz/src/memory.c
new file mode 100644
index 000000000..ac0613dfb
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/memory.c
@@ -0,0 +1,24 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "memory.h"
+
+ZZSTATUS ZzRuntimeCodePatch(zaddr address, zpointer codedata, zuint codedata_size) {
+    zaddr address_fixed = address & ~(zaddr)1;
+    if (!ZzMemoryPatchCode(address_fixed, codedata, codedata_size))
+        return ZZ_FAILED;
+    return ZZ_SUCCESS;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/memory.h b/VirtualApp/lib/src/main/jni/HookZz/src/memory.h
new file mode 100644
index 000000000..1884597ad
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/memory.h
@@ -0,0 +1,42 @@
+//    Copyright 2017 jmpews
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+#ifndef memory_h
+#define memory_h
+
+// platforms
+
+// hookzz
+#include "hookzz.h"
+
+// zzdeps
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+// #include "platforms/darwin/memory-darwin.h"
+// #include "zzdeps/darwin/memory-utils-darwin.h"
+
+zsize ZzMemoryGetPageSzie();
+
+zpointer ZzMemoryAllocatePages(zsize n_pages);
+zpointer ZzMemoryAllocateNearPages(zaddr address, zsize redirect_range_size, zsize n_pages);
+zpointer ZzMemoryAllocate(zsize size);
+zbool ZzMemoryPatchCode(const zaddr address, const zpointer codedata, zuint codedata_size);
+zbool ZzMemoryProtectAsExecutable(const zaddr address, zsize size);
+zbool ZzMemoryProtectAsWritable(const zaddr address, zsize size);
+zbool ZzMemoryIsSupportAllocateRXPage();
+zpointer ZzMemorySearchCodeCave(zaddr address, zsize redirect_range_size, zsize size);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/instructions.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/instructions.c
new file mode 100644
index 000000000..00ec11d1c
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/instructions.c
@@ -0,0 +1,21 @@
+#include "instructions.h"
+#include <string.h>
+
+zuint32 get_insn_sub(zuint32 insn, int start, int length) { return (insn >> start) & ((1 << length) - 1); }
+
+zbool insn_equal(zuint32 insn, char *opstr) {
+    zuint32 mask = 0, value = 0;
+    zsize length = strlen(opstr);
+    int i, j;
+    for (i = length - 1, j = 0; i >= 0 && j < length; i--, j++) {
+        if (opstr[i] == 'x') {
+            mask = mask | (0 << j);
+        } else if (opstr[i] == '0') {
+            mask = mask | (1 << j);
+        } else if (opstr[i] == '1') {
+            value = value | (1 << j);
+            mask = mask | (1 << j);
+        }
+    }
+    return (insn & mask) == value;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/instructions.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/instructions.h
new file mode 100644
index 000000000..f8b41e269
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/instructions.h
@@ -0,0 +1,51 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_arm_instructions_h
+#define platforms_arch_arm_instructions_h
+
+#include "hookzz.h"
+
+typedef enum _INSN_TYPE { ARM_INSN, THUMB_INSN, THUMB2_INSN } InsnType;
+
+typedef struct _Instruction {
+    InsnType type;
+    zaddr pc;
+    zaddr address;
+    zuint8 size;
+    union {
+        zuint32 trick_insn;
+        struct {
+            zuint16 trick_insn1;
+            zuint16 trick_insn2;
+        };
+    };
+
+    zuint32 insn;
+    zuint16 insn1;
+    zuint16 insn2;
+} ZzInstruction;
+
+typedef struct _ZzRelocateInstruction {
+    const ZzInstruction *insn_ctx;
+    zaddr relocated_offset;
+    zsize relocated_length;
+} ZzRelocateInstruction;
+
+zuint32 get_insn_sub(zuint32 insn, int start, int length);
+zbool insn_equal(zuint32 insn, char *opstr);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-arm.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-arm.c
new file mode 100644
index 000000000..1274db44f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-arm.c
@@ -0,0 +1,58 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "reader-arm.h"
+
+zpointer zz_arm_reader_read_one_instruction(ZzInstruction *insn_ctx, zpointer address) {
+    insn_ctx->type = ARM_INSN;
+    insn_ctx->address = (zaddr)address;
+    insn_ctx->pc = (zaddr)address + 8;
+    insn_ctx->insn = *(zuint32 *)address;
+    insn_ctx->size = 4;
+    return (zpointer)insn_ctx->pc;
+}
+
+// ARM Manual
+// A5 ARM Instruction Set Encoding
+// A5.3 Load/store word and unsigned byte
+ARMInsnType GetARMInsnType(zuint32 insn) {
+
+    if (insn_equal(insn, "xxxx0000100xxxxxxxxxxxxxxxx0xxxx") && (get_insn_sub(insn, 28, 4) != 0xF)) {
+        return ARM_INS_ADD_register_A1;
+    }
+
+    if (insn_equal(insn, "xxxx0101x0011111xxxxxxxxxxxxxxxx") && (get_insn_sub(insn, 28, 4) != 0xF)) {
+        return ARM_INS_LDR_literal_A1;
+    }
+
+    if (insn_equal(insn, "xxxx001010001111xxxxxxxxxxxxxxxx") && (get_insn_sub(insn, 28, 4) != 0xF)) {
+        return ARM_INS_ADR_A1;
+    }
+    if (insn_equal(insn, "xxxx001001001111xxxxxxxxxxxxxxxx") && (get_insn_sub(insn, 28, 4) != 0xF)) {
+        return ARM_INS_ADR_A2;
+    }
+    if (insn_equal(insn, "xxxx1010xxxxxxxxxxxxxxxxxxxxxxxx") && (get_insn_sub(insn, 28, 4) != 0xF)) {
+        return ARM_INS_B_A1;
+    }
+    if (insn_equal(insn, "xxxx1011xxxxxxxxxxxxxxxxxxxxxxxx") && (get_insn_sub(insn, 28, 4) != 0xF)) {
+        return ARM_INS_BLBLX_immediate_A1;
+    }
+    if (insn_equal(insn, "1111101xxxxxxxxxxxxxxxxxxxxxxxxx")) {
+        return ARM_INS_BLBLX_immediate_A2;
+    }
+
+    return ARM_UNDEF;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-arm.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-arm.h
new file mode 100644
index 000000000..7936dc901
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-arm.h
@@ -0,0 +1,43 @@
+//    Copyright 2017 jmpews
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+#ifndef platforms_arch_arm_reader_arm_h
+#define platforms_arch_arm_reader_arm_h
+
+// platforms
+#include "instructions.h"
+
+// hookzz
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef enum _ARMInsnType {
+    ARM_INS_ADD_register_A1,
+    ARM_INS_LDR_literal_A1,
+    ARM_INS_ADR_A1,
+    ARM_INS_ADR_A2,
+    ARM_INS_B_A1,
+    ARM_INS_BLBLX_immediate_A1,
+    ARM_INS_BLBLX_immediate_A2,
+    ARM_UNDEF
+} ARMInsnType;
+
+ARMInsnType GetARMInsnType(zuint32 insn);
+zpointer zz_arm_reader_read_one_instruction(ZzInstruction *insn_ctx, zpointer address);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-thumb.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-thumb.c
new file mode 100644
index 000000000..652433b79
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-thumb.c
@@ -0,0 +1,110 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "reader-thumb.h"
+
+zbool insn_is_thumb2(zuint32 insn) {
+    // PAGE: A6-221
+    // PAGE: A6-230
+
+    if (insn_equal(insn & 0x0000FFFF, "11101xxxxxxxxxxx") || insn_equal(insn & 0x0000FFFF, "11111xxxxxxxxxxx") ||
+        insn_equal(insn & 0x0000FFFF, "11110xxxxxxxxxxx")) {
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+
+zpointer zz_thumb_reader_read_one_instruction(ZzInstruction *insn_ctx, zpointer address) {
+    // ZzInstruction *insn_ctx = (ZzInstruction *)malloc(sizeof(ZzInstruction));
+    insn_ctx->pc = (zaddr)address + 4;
+    insn_ctx->address = (zaddr)address;
+    insn_ctx->insn = *(zuint32 *)address;
+
+    // PAGE: A6-221
+    if (insn_is_thumb2(insn_ctx->insn)) {
+        insn_ctx->type = THUMB2_INSN;
+        insn_ctx->size = 4;
+        insn_ctx->insn1 = insn_ctx->insn & 0x0000FFFF;
+        insn_ctx->insn2 = (insn_ctx->insn & 0xFFFF0000) >> 16;
+    } else {
+        insn_ctx->type = THUMB_INSN;
+        insn_ctx->size = 2;
+        insn_ctx->insn1 = insn_ctx->insn & 0x0000FFFF;
+        insn_ctx->insn2 = 0;
+    }
+    return (zpointer)insn_ctx->pc;
+}
+
+// ARM Manual
+// A5 ARM Instruction Set Encoding
+// A5.3 Load/store word and unsigned byte
+THUMBInsnType GetTHUMBInsnType(zuint16 insn1, zuint16 insn2) {
+
+    if (!insn_is_thumb2(insn1) && insn_equal(insn1, "1011x0x1xxxxxxxx")) {
+        return THUMB_INS_CBNZ_CBZ;
+    }
+
+    if (!insn_is_thumb2(insn1) && insn_equal(insn1, "01000100xxxxxxxx")) {
+        return THUMB_INS_ADD_register_T2;
+    }
+
+    if (!insn_is_thumb2(insn1) && insn_equal(insn1, "01001xxxxxxxxxxx")) {
+        return THUMB_INS_LDR_literal_T1;
+    }
+
+    if (insn_is_thumb2(insn1) && insn_equal(insn1, "11111000x1011111") && insn_equal(insn2, "xxxxxxxxxxxxxxxx")) {
+        return THUMB_INS_LDR_literal_T2;
+    }
+
+    if (!insn_is_thumb2(insn1) && insn_equal(insn1, "10100xxxxxxxxxxx")) {
+        return THUMB_INS_ADR_T1;
+    }
+
+    if (insn_is_thumb2(insn1) && insn_equal(insn1, "11110x1010101111") && insn_equal(insn2, "0xxxxxxxxxxxxxxx")) {
+        return THUMB_INS_ADR_T2;
+    }
+
+    if (insn_is_thumb2(insn1) && insn_equal(insn1, "11110x1000001111") && insn_equal(insn2, "0xxxxxxxxxxxxxxx")) {
+        return THUMB_INS_ADR_T3;
+    }
+
+    if (!insn_is_thumb2(insn1) && insn_equal(insn1, "1101xxxxxxxxxxxx")) {
+        return THUMB_INS_B_T1;
+    }
+
+    if (!insn_is_thumb2(insn1) && insn_equal(insn1, "11100xxxxxxxxxxx")) {
+        return THUMB_INS_B_T2;
+    }
+
+    if (insn_is_thumb2(insn1) && insn_equal(insn1, "11110xxxxxxxxxxx") && insn_equal(insn2, "10x0xxxxxxxxxxxx")) {
+        return THUMB_INS_B_T3;
+    }
+
+    if (insn_is_thumb2(insn1) && insn_equal(insn1, "11110xxxxxxxxxxx") && insn_equal(insn2, "10x1xxxxxxxxxxxx")) {
+        return THUMB_INS_B_T4;
+    }
+
+    if (insn_is_thumb2(insn1) && insn_equal(insn1, "11110xxxxxxxxxxx") && insn_equal(insn2, "11x1xxxxxxxxxxxx")) {
+        return THUMB_INS_BLBLX_immediate_T1;
+    }
+
+    if (insn_is_thumb2(insn1) && insn_equal(insn1, "11110xxxxxxxxxxx") && insn_equal(insn2, "11x0xxxxxxxxxxxx")) {
+        return THUMB_INS_BLBLX_immediate_T2;
+    }
+
+    return THUMB_UNDEF;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-thumb.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-thumb.h
new file mode 100644
index 000000000..3d1dee803
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/reader-thumb.h
@@ -0,0 +1,49 @@
+//    Copyright 2017 jmpews
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+#ifndef platforms_arch_arm_reader_thumb_h
+#define platforms_arch_arm_reader_thumb_h
+
+// platforms
+#include "instructions.h"
+
+// hookzz
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef enum _THUMBInsnType {
+    THUMB_INS_CBNZ_CBZ,
+    THUMB_INS_ADD_register_T2,
+    THUMB_INS_LDR_literal_T1,
+    THUMB_INS_LDR_literal_T2,
+    THUMB_INS_ADR_T1,
+    THUMB_INS_ADR_T2,
+    THUMB_INS_ADR_T3,
+    THUMB_INS_B_T1,
+    THUMB_INS_B_T2,
+    THUMB_INS_B_T3,
+    THUMB_INS_B_T4,
+    THUMB_INS_BLBLX_immediate_T1,
+    THUMB_INS_BLBLX_immediate_T2,
+    THUMB_UNDEF
+} THUMBInsnType;
+
+THUMBInsnType GetTHUMBInsnType(zuint16 insn1, zuint16 insn2);
+zpointer zz_thumb_reader_read_one_instruction(ZzInstruction *insn_ctx, zpointer address);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/regs-arm.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/regs-arm.c
new file mode 100644
index 000000000..05d26eb74
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/regs-arm.c
@@ -0,0 +1,40 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "regs-arm.h"
+
+void zz_arm_register_describe(ZzARMReg reg, ZzArmRegInfo *ri) {
+    if (reg >= ZZ_ARM_REG_R0 && reg <= ZZ_ARM_REG_R12) {
+        ri->width = 32;
+        ri->meta = reg;
+    } else if (reg == ZZ_ARM_REG_SP) {
+        ri->width = 32;
+        ri->meta = reg;
+    } else if (reg == ZZ_ARM_REG_LR) {
+        ri->width = 32;
+        ri->meta = reg;
+    } else if (reg == ZZ_ARM_REG_PC) {
+        ri->width = 32;
+        ri->meta = reg;
+    } else {
+        Serror("zz_arm64_register_describe error.");
+#if defined(DEBUG_MODE)
+        debug_break();
+#endif
+        ri->index = 0;
+    }
+    ri->index = reg - ZZ_ARM_REG_R0;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/regs-arm.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/regs-arm.h
new file mode 100644
index 000000000..20869b48a
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/regs-arm.h
@@ -0,0 +1,63 @@
+//    Copyright 2017 jmpews
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+#ifndef platforms_arch_arm_regs_h
+#define platforms_arch_arm_regs_h
+
+// platforms
+#include "instructions.h"
+
+// hookzz
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+// REF:
+// ARM Architecture Reference Manual
+// A2.4 Registers
+
+typedef enum _ZzReg {
+    ZZ_ARM_REG_R0 = 0,
+    ZZ_ARM_REG_R1,
+    ZZ_ARM_REG_R2,
+    ZZ_ARM_REG_R3,
+    ZZ_ARM_REG_R4,
+    ZZ_ARM_REG_R5,
+    ZZ_ARM_REG_R6,
+    ZZ_ARM_REG_R7,
+    ZZ_ARM_REG_R8,
+    ZZ_ARM_REG_R9,
+    ZZ_ARM_REG_R10,
+    ZZ_ARM_REG_R11,
+    ZZ_ARM_REG_R12,
+    ZZ_ARM_REG_R13,
+    ZZ_ARM_REG_R14,
+    ZZ_ARM_REG_R15,
+    ZZ_ARM_REG_SP = ZZ_ARM_REG_R13,
+    ZZ_ARM_REG_LR = ZZ_ARM_REG_R14,
+    ZZ_ARM_REG_PC = ZZ_ARM_REG_R15
+} ZzARMReg;
+
+typedef struct _ZzArmRegInfo {
+    zuint index;
+    zuint meta;
+    zuint width;
+} ZzArmRegInfo;
+
+void zz_arm_register_describe(ZzARMReg reg, ZzArmRegInfo *ri);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-arm.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-arm.c
new file mode 100644
index 000000000..ca7c55838
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-arm.c
@@ -0,0 +1,344 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "relocator-arm.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_RELOCATOR_INSTRUCIONS_SIZE 64
+
+void zz_arm_relocator_init(ZzArmRelocator *relocator, zpointer input_code, ZzArmWriter *output) {
+    relocator->inpos = 0;
+    relocator->outpos = 0;
+    relocator->input_start = input_code;
+    relocator->input_cur = input_code;
+    relocator->input_pc = (zaddr)input_code;
+    relocator->output = output;
+    relocator->relocate_literal_insns_size = 0;
+    relocator->try_relocated_length = 0;
+
+    relocator->input_insns = (ZzInstruction *)malloc(MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzInstruction));
+    memset(relocator->input_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzInstruction));
+    relocator->output_insns =
+        (ZzRelocateInstruction *)malloc(MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzRelocateInstruction));
+    memset(relocator->output_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzRelocateInstruction));
+    relocator->relocate_literal_insns =
+        (ZzLiteralInstruction **)malloc(MAX_LITERAL_INSN_SIZE * sizeof(ZzLiteralInstruction *));
+    memset(relocator->relocate_literal_insns, 0, MAX_LITERAL_INSN_SIZE * sizeof(ZzLiteralInstruction *));
+}
+
+void zz_arm_relocator_reset(ZzArmRelocator *self, zpointer input_code, ZzArmWriter *output) {
+    self->input_cur = input_code;
+    self->input_start = input_code;
+    self->input_pc = (zaddr)input_code;
+    self->inpos = 0;
+    self->outpos = 0;
+    self->output = output;
+    self->relocate_literal_insns_size = 0;
+    self->try_relocated_length = 0;
+
+    memset(self->input_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzInstruction));
+    memset(self->output_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzRelocateInstruction));
+    memset(self->relocate_literal_insns, 0, MAX_LITERAL_INSN_SIZE * sizeof(ZzLiteralInstruction *));
+}
+
+zsize zz_arm_relocator_read_one(ZzArmRelocator *self, ZzInstruction *instruction) {
+    ZzInstruction *insn_ctx = &self->input_insns[self->inpos];
+    ZzRelocateInstruction *re_insn_ctx = &self->output_insns[self->inpos];
+
+    re_insn_ctx->insn_ctx = insn_ctx;
+    zz_arm_reader_read_one_instruction(insn_ctx, self->input_cur);
+
+    // switch (1) {}
+
+    self->inpos++;
+
+    if (instruction != NULL)
+        *instruction = *insn_ctx;
+
+    self->input_cur += insn_ctx->size;
+    self->input_pc += insn_ctx->size;
+
+    return self->input_cur - self->input_start;
+}
+void zz_arm_relocator_try_relocate(zpointer address, zuint min_bytes, zuint *max_bytes) {
+    int tmp_size = 0;
+    zpointer target_addr;
+    ZzInstruction insn_ctx;
+    zbool early_end = FALSE;
+    target_addr = (zpointer)address;
+
+    do {
+        zz_arm_reader_read_one_instruction(&insn_ctx, target_addr);
+        switch (GetARMInsnType(insn_ctx.insn)) {
+        case ARM_INS_B_A1: {
+            zuint32 cond = get_insn_sub(insn_ctx.insn, 28, 4);
+            if (cond == 0xE)
+                early_end = TRUE;
+        }; break;
+        default:;
+        }
+        tmp_size += insn_ctx.size;
+        target_addr = target_addr + insn_ctx.size;
+    } while (tmp_size < min_bytes);
+
+    if (early_end) {
+        *max_bytes = tmp_size;
+    }
+    return;
+}
+
+zaddr zz_arm_relocator_get_insn_relocated_offset(ZzArmRelocator *self, zaddr address) {
+    const ZzInstruction *insn_ctx;
+    const ZzRelocateInstruction *re_insn_ctx;
+    int i;
+    for (i = 0; i < self->inpos; i++) {
+        re_insn_ctx = &self->output_insns[i];
+        insn_ctx = re_insn_ctx->insn_ctx;
+        if (insn_ctx->address == address && re_insn_ctx->relocated_offset) {
+            return re_insn_ctx->relocated_offset;
+        }
+    }
+    return 0;
+}
+
+void zz_arm_relocator_relocate_writer(ZzArmRelocator *relocator, zaddr code_address) {
+    ZzArmWriter *arm_writer;
+    arm_writer = relocator->output;
+    if (relocator->relocate_literal_insns_size) {
+        int i;
+        zaddr literal_address, relocated_offset, relocated_address, *literal_address_ptr;
+        for (i = 0; i < relocator->relocate_literal_insns_size; i++) {
+            literal_address_ptr = (zaddr *)relocator->relocate_literal_insns[i]->literal_address_ptr;
+            literal_address = *literal_address_ptr;
+            relocated_offset = zz_arm_relocator_get_insn_relocated_offset(relocator, literal_address);
+            if (relocated_offset) {
+                relocated_address = code_address + relocated_offset;
+                *literal_address_ptr = relocated_address;
+            }
+        }
+    }
+}
+
+void zz_arm_relocator_write_all(ZzArmRelocator *self) {
+    zuint count = 0;
+    zuint outpos = self->outpos;
+    ZzArmWriter arm_writer = *self->output;
+
+    while (zz_arm_relocator_write_one(self))
+        count++;
+}
+
+// PAGE: A8-312
+static zbool zz_arm_relocator_rewrite_ADD_register_A1(ZzArmRelocator *self, const ZzInstruction *insn_ctx,
+                                                      ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+
+    zuint32 Rn_ndx, Rd_ndx, Rm_ndx;
+    Rn_ndx = get_insn_sub(insn, 16, 4);
+    Rd_ndx = get_insn_sub(insn, 12, 4);
+    Rm_ndx = get_insn_sub(insn, 0, 4);
+
+    if (Rn_ndx != ZZ_ARM_REG_PC) {
+        return FALSE;
+    }
+    // push R7
+    zz_arm_writer_put_push_reg(self->output, ZZ_ARM_REG_R7);
+    zz_arm_writer_put_ldr_b_reg_address(self->output, ZZ_ARM_REG_R7, insn_ctx->pc);
+    zz_arm_writer_put_instruction(self->output, (insn & 0xFFF0FFFF) | ZZ_ARM_REG_R7 << 16);
+    // pop R7
+    zz_arm_writer_put_pop_reg(self->output, ZZ_ARM_REG_R7);
+    return TRUE;
+}
+
+// PAGE: A8-410
+static zbool zz_arm_relocator_rewrite_LDR_literal_A1(ZzArmRelocator *self, const ZzInstruction *insn_ctx,
+                                                     ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 imm12 = get_insn_sub(insn, 0, 12);
+    zuint32 imm32 = imm12;
+    zbool add = get_insn_sub(insn, 7 + 16, 1) == 1;
+    zaddr target_address;
+    if (add)
+        target_address = insn_ctx->pc + imm32;
+    else
+        target_address = insn_ctx->pc - imm32;
+    int Rt_ndx = get_insn_sub(insn, 12, 4);
+
+    zz_arm_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+    zz_arm_writer_put_ldr_reg_reg_imm(self->output, Rt_ndx, Rt_ndx, 0);
+
+    return TRUE;
+}
+
+// PAGE: A8-322
+static zbool zz_arm_relocator_rewrite_ADR_A1(ZzArmRelocator *self, const ZzInstruction *insn_ctx,
+                                             ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 imm12 = get_insn_sub(insn, 0, 12);
+    zuint32 imm32 = imm12;
+    zaddr target_address;
+    target_address = insn_ctx->pc + imm32;
+    int Rt_ndx = get_insn_sub(insn, 12, 4);
+
+    zz_arm_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+
+    return TRUE;
+}
+
+// PAGE: A8-322
+static zbool zz_arm_relocator_rewrite_ADR_A2(ZzArmRelocator *self, const ZzInstruction *insn_ctx,
+                                             ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 imm12 = get_insn_sub(insn, 0, 12);
+    zuint32 imm32 = imm12;
+    zaddr target_address;
+    target_address = insn_ctx->pc - imm32;
+    int Rt_ndx = get_insn_sub(insn, 12, 4);
+
+    zz_arm_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+
+    return TRUE;
+}
+
+// 0x000 : b.cond 0x0;
+// 0x004 : b 0x4
+// 0x008 : ldr pc, [pc, #0]
+// 0x00c : .long 0x0
+// 0x010 : remain code
+
+// PAGE: A8-334
+static zbool zz_arm_relocator_rewrite_B_A1(ZzArmRelocator *self, const ZzInstruction *insn_ctx,
+                                           ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 imm24 = get_insn_sub(insn, 0, 24);
+    zuint32 imm32 = imm24 << 2;
+    zaddr target_address;
+    target_address = insn_ctx->pc + imm32;
+
+    zz_arm_writer_put_instruction(self->output, (insn & 0xFF000000) | 0);
+    zz_arm_writer_put_b_imm(self->output, 0x4);
+    zz_arm_writer_put_ldr_reg_address(self->output, ZZ_ARM_REG_PC, target_address);
+
+    return TRUE;
+}
+
+// 0x000 : bl.cond 0x0;
+
+// 0x004 : b 0x10
+
+// 0x008 : ldr lr, [pc, #0]
+// 0x00c : b 0x0
+// 0x010 : .long 0x0
+
+// 0x014 : ldr pc, [pc, #0]
+// 0x018 : .long 0x0
+
+// 0x01c : remain code
+
+// PAGE: A8-348
+static zbool zz_arm_relocator_rewrite_BLBLX_immediate_A1(ZzArmRelocator *self, const ZzInstruction *insn_ctx,
+                                                         ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 imm24 = get_insn_sub(insn, 0, 24);
+    zuint32 imm32 = imm24 << 2;
+    zaddr target_address;
+    target_address = ALIGN_4(insn_ctx->pc) + imm32;
+
+    // CurrentInstrSet = thumb
+    // targetInstrSet = arm
+
+    // convert 'bl' to 'b', but save 'cond'
+    zz_arm_writer_put_instruction(self->output, (insn & 0xF0000000) | 0b1010 << 24 | 0);
+
+    ZzArmWriter ouput_bak = *self->output;
+
+    zz_arm_writer_put_b_imm(self->output, 0);
+    ZzLiteralInstruction **literal_insn_ptr = &(self->relocate_literal_insns[self->relocate_literal_insns_size++]);
+    zz_arm_writer_put_ldr_b_reg_relocate_address(self->output, ZZ_ARM_REG_LR, insn_ctx->pc - 4, literal_insn_ptr);
+    zz_arm_writer_put_ldr_reg_address(self->output, ZZ_ARM_REG_PC, target_address);
+
+    // overwrite `zz_arm_writer_put_b_imm`
+    zz_arm_writer_put_b_imm(&ouput_bak, self->output->pc - ouput_bak.pc - 8);
+    return TRUE;
+}
+
+// PAGE: A8-348
+static zbool zz_arm_relocator_rewrite_BLBLX_immediate_A2(ZzArmRelocator *self, const ZzInstruction *insn_ctx,
+                                                         ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 H = get_insn_sub(insn, 24, 1);
+    zuint32 imm24 = get_insn_sub(insn, 0, 24);
+    zuint32 imm32 = (imm24 << 2) | (H << 1);
+    zaddr target_address;
+    target_address = insn_ctx->pc + imm32;
+
+    ZzLiteralInstruction **literal_insn_ptr = &(self->relocate_literal_insns[self->relocate_literal_insns_size++]);
+    zz_arm_writer_put_ldr_b_reg_relocate_address(self->output, ZZ_ARM_REG_LR, insn_ctx->pc - 4, literal_insn_ptr);
+    zz_arm_writer_put_ldr_reg_address(self->output, ZZ_ARM_REG_PC, target_address);
+
+    return TRUE;
+}
+
+zbool zz_arm_relocator_write_one(ZzArmRelocator *self) {
+    const ZzInstruction *insn_ctx;
+    ZzRelocateInstruction *re_insn_ctx;
+    zbool rewritten = FALSE;
+
+    if (self->inpos != self->outpos) {
+        insn_ctx = &self->input_insns[self->outpos];
+        re_insn_ctx = &self->output_insns[self->outpos];
+
+        self->outpos++;
+    } else
+        return FALSE;
+
+    re_insn_ctx->relocated_offset = (zaddr)self->output->pc - (zaddr)self->output->base;
+
+    switch (GetARMInsnType(insn_ctx->insn)) {
+    case ARM_INS_ADD_register_A1:
+        rewritten = zz_arm_relocator_rewrite_ADD_register_A1(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM_INS_LDR_literal_A1:
+        rewritten = zz_arm_relocator_rewrite_LDR_literal_A1(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM_INS_ADR_A1:
+        rewritten = zz_arm_relocator_rewrite_ADR_A1(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM_INS_ADR_A2:
+        rewritten = zz_arm_relocator_rewrite_ADR_A2(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM_INS_B_A1:
+        rewritten = zz_arm_relocator_rewrite_B_A1(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM_INS_BLBLX_immediate_A1:
+        rewritten = zz_arm_relocator_rewrite_BLBLX_immediate_A1(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM_INS_BLBLX_immediate_A2:
+        rewritten = zz_arm_relocator_rewrite_BLBLX_immediate_A2(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM_UNDEF:
+        rewritten = FALSE;
+        break;
+    }
+    if (!rewritten)
+        zz_arm_writer_put_bytes(self->output, (zbyte *)&insn_ctx->insn, insn_ctx->size);
+
+    re_insn_ctx->relocated_length =
+        (zaddr)self->output->pc - (zaddr)self->output->base - (zaddr)re_insn_ctx->relocated_offset;
+    return TRUE;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-arm.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-arm.h
new file mode 100644
index 000000000..a1ee866ed
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-arm.h
@@ -0,0 +1,57 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_arm_relocator_arm_h
+#define platforms_arch_arm_relocator_arm_h
+
+// platforms
+#include "instructions.h"
+#include "reader-arm.h"
+#include "regs-arm.h"
+#include "writer-arm.h"
+
+// hookzz
+#include "writer.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef struct _ZzArmRelocator {
+    zbool try_relocated_again;
+    zsize try_relocated_length;
+    zpointer input_start;
+    zpointer input_cur;
+    zaddr input_pc;
+    zuint inpos;
+    zuint outpos;
+    ZzInstruction *input_insns;
+    ZzRelocateInstruction *output_insns;
+    ZzLiteralInstruction **relocate_literal_insns;
+    zsize relocate_literal_insns_size;
+    ZzArmWriter *output;
+} ZzArmRelocator;
+
+void zz_arm_relocator_init(ZzArmRelocator *relocator, zpointer input_code, ZzArmWriter *output);
+void zz_arm_relocator_reset(ZzArmRelocator *self, zpointer input_code, ZzArmWriter *output);
+void zz_arm_relocator_write_all(ZzArmRelocator *self);
+zsize zz_arm_relocator_read_one(ZzArmRelocator *self, ZzInstruction *instruction);
+void zz_arm_relocator_try_relocate(zpointer address, zuint min_bytes, zuint *max_bytes);
+zbool zz_arm_relocator_write_one(ZzArmRelocator *self);
+void zz_arm_relocator_relocate_writer(ZzArmRelocator *relocator, zaddr code_address);
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-thumb.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-thumb.c
new file mode 100644
index 000000000..f56f71f5e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-thumb.c
@@ -0,0 +1,522 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "relocator-thumb.h"
+
+#define MAX_RELOCATOR_INSTRUCIONS_SIZE 64
+
+void zz_thumb_relocator_init(ZzThumbRelocator *relocator, zpointer input_code, ZzThumbWriter *output) {
+
+    memset(relocator, 0, sizeof(ZzThumbRelocator));
+
+    relocator->inpos = 0;
+    relocator->outpos = 0;
+    relocator->input_start = input_code;
+    relocator->input_cur = input_code;
+    relocator->input_pc = (zaddr)input_code;
+    relocator->output = output;
+    relocator->relocate_literal_insns_size = 0;
+    relocator->try_relocated_length = 0;
+
+    relocator->input_insns = (ZzInstruction *)malloc(MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzInstruction));
+    memset(relocator->input_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzInstruction));
+    relocator->output_insns =
+        (ZzRelocateInstruction *)malloc(MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzRelocateInstruction));
+    memset(relocator->output_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzRelocateInstruction));
+    relocator->relocate_literal_insns =
+        (ZzLiteralInstruction **)malloc(MAX_LITERAL_INSN_SIZE * sizeof(ZzLiteralInstruction *));
+    memset(relocator->relocate_literal_insns, 0, MAX_LITERAL_INSN_SIZE * sizeof(ZzLiteralInstruction *));
+}
+
+void zz_thumb_relocator_reset(ZzThumbRelocator *self, zpointer input_code, ZzThumbWriter *output) {
+    self->input_cur = input_code;
+    self->input_start = input_code;
+    self->input_pc = (zaddr)input_code;
+    self->inpos = 0;
+    self->outpos = 0;
+    self->output = output;
+    self->relocate_literal_insns_size = 0;
+    self->try_relocated_length = 0;
+
+    memset(self->input_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzInstruction));
+    memset(self->output_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzRelocateInstruction));
+    memset(self->relocate_literal_insns, 0, MAX_LITERAL_INSN_SIZE * sizeof(ZzLiteralInstruction *));
+}
+
+zsize zz_thumb_relocator_read_one(ZzThumbRelocator *self, ZzInstruction *instruction) {
+    ZzInstruction *insn_ctx = &self->input_insns[self->inpos];
+    ZzRelocateInstruction *re_insn_ctx = &self->output_insns[self->inpos];
+
+    re_insn_ctx->insn_ctx = insn_ctx;
+    zz_thumb_reader_read_one_instruction(insn_ctx, self->input_cur);
+
+    // switch (1) {}
+
+    self->inpos++;
+
+    if (instruction != NULL)
+        *instruction = *insn_ctx;
+
+    self->input_cur += insn_ctx->size;
+    self->input_pc += insn_ctx->size;
+
+    return self->input_cur - self->input_start;
+}
+
+void zz_thumb_relocator_try_relocate(zpointer address, zuint min_bytes, zuint *max_bytes) {
+    int tmp_size = 0;
+    zbool is_thumb;
+    zpointer target_addr;
+    ZzInstruction insn_ctx;
+    zbool early_end = FALSE;
+    is_thumb = INSTRUCTION_IS_THUMB((zaddr)address);
+    target_addr = (zpointer)address;
+
+    do {
+        zz_thumb_reader_read_one_instruction(&insn_ctx, target_addr);
+        switch (GetTHUMBInsnType(insn_ctx.insn1, insn_ctx.insn2)) {
+        case THUMB_INS_B_T2:
+            early_end = TRUE;
+            break;
+        case THUMB_INS_B_T4:
+            early_end = TRUE;
+            break;
+        default:;
+        }
+        tmp_size += insn_ctx.size;
+        target_addr = target_addr + insn_ctx.size;
+    } while (tmp_size < min_bytes);
+
+    if (early_end) {
+        *max_bytes = tmp_size;
+    }
+    return;
+}
+
+zaddr zz_thumb_relocator_get_insn_relocated_offset(ZzThumbRelocator *self, zaddr address) {
+    const ZzInstruction *insn_ctx;
+    const ZzRelocateInstruction *re_insn_ctx;
+    int i;
+
+    for (i = 0; i < self->inpos; i++) {
+        re_insn_ctx = &self->output_insns[i];
+        insn_ctx = re_insn_ctx->insn_ctx;
+        if (insn_ctx->address == address && re_insn_ctx->relocated_offset) {
+            return re_insn_ctx->relocated_offset;
+        }
+    }
+    return 0;
+}
+
+void zz_thumb_relocator_relocate_writer(ZzThumbRelocator *relocator, zaddr code_address) {
+    ZzThumbWriter *thumb_writer;
+    thumb_writer = relocator->output;
+    if (relocator->relocate_literal_insns_size) {
+        int i;
+        zaddr literal_address, relocated_offset, relocated_address, *literal_address_ptr;
+        for (i = 0; i < relocator->relocate_literal_insns_size; i++) {
+            literal_address_ptr = (zaddr *)relocator->relocate_literal_insns[i]->literal_address_ptr;
+            literal_address = *literal_address_ptr;
+            relocated_offset = zz_thumb_relocator_get_insn_relocated_offset(relocator, literal_address & ~(zaddr)1);
+            if (relocated_offset) {
+                relocated_address = code_address + relocated_offset + 1;
+                *literal_address_ptr = relocated_address;
+            }
+        }
+    }
+}
+
+void zz_thumb_relocator_write_all(ZzThumbRelocator *self) {
+    zuint count = 0;
+    zuint outpos = self->outpos;
+    ZzThumbWriter thumb_writer = *self->output;
+    while (zz_thumb_relocator_write_one(self))
+        count++;
+}
+
+// A8-357
+// 0: cbz #0
+// 2: b #6
+// 4: ldr pc, #0
+// 8: .long ?
+// c: next insn
+static zbool zz_thumb_relocator_rewrite_CBNZ_CBZ(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                                 ZzRelocateInstruction *re_insn_ctx) {
+
+    zuint32 insn1 = insn_ctx->insn1;
+    zuint16 op, i, imm5, Rn_ndx;
+    zuint32 imm32, nonzero;
+
+    op = get_insn_sub(insn1, 11, 1);
+    i = get_insn_sub(insn1, 9, 1);
+    imm5 = get_insn_sub(insn1, 3, 5);
+    Rn_ndx = get_insn_sub(insn1, 0, 3);
+
+    imm32 = imm5 << 1 | i << (5 + 1);
+    nonzero = (op == 1);
+
+    zaddr target_address = insn_ctx->pc + imm32;
+
+    /* for align , simple solution, maybe the correct solution is get `ldr_reg_address` length and adjust the immediate
+     * of `b_imm`. */
+    if ((zaddr)self->output->pc % 4) {
+        zz_thumb_writer_put_nop(self->output);
+    }
+    zz_thumb_writer_put_instruction(self->output, (insn1 & 0b1111110100000111) | 0);
+
+    zz_thumb_writer_put_b_imm(self->output, 0x6);
+    ZzLiteralInstruction **literal_insn_ptr = &(self->relocate_literal_insns[self->relocate_literal_insns_size++]);
+    zz_thumb_writer_put_ldr_reg_relocate_address(self->output, ZZ_ARM_REG_PC, target_address + 1, literal_insn_ptr);
+
+    // zz_thumb_writer_put_b_imm(self->output, 0x10);
+    // zz_thumb_writer_put_push_reg(self->output, ZZ_ARM_REG_R0);
+    // zz_thumb_writer_put_push_reg(self->output, ZZ_ARM_REG_R0);
+    // zz_thumb_writer_put_ldr_b_reg_address(self->output, ZZ_ARM_REG_R0, target_address + 1);
+    // zz_thumb_writer_put_str_reg_reg_offset(self->output, ZZ_ARM_REG_R0, ZZ_ARM_REG_SP, 4);
+    // zz_thumb_writer_put_pop_reg(self->output, ZZ_ARM_REG_R0);
+    // zz_thumb_writer_put_pop_reg(self->output, ZZ_ARM_REG_PC);
+    return TRUE;
+}
+
+// PAGE: A8-310
+static zbool zz_thumb_relocator_rewrite_ADD_register_T2(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                                        ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+
+    zuint16 Rm_ndx, Rdn_ndx, DN, Rd_ndx;
+    Rm_ndx = get_insn_sub(insn1, 3, 4);
+    Rdn_ndx = get_insn_sub(insn1, 0, 3);
+    DN = get_insn_sub(insn1, 7, 1);
+    Rd_ndx = (DN << 3) | Rdn_ndx;
+
+    if (Rm_ndx != ZZ_ARM_REG_PC) {
+        return FALSE;
+    }
+
+    zz_thumb_writer_put_push_reg(self->output, ZZ_ARM_REG_R7);
+    zz_thumb_writer_put_ldr_b_reg_address(self->output, ZZ_ARM_REG_R7, insn_ctx->pc);
+    zz_thumb_writer_put_instruction(self->output, (insn1 & 0b1111111110000111) | ZZ_ARM_REG_R7 << 3);
+    zz_thumb_writer_put_pop_reg(self->output, ZZ_ARM_REG_R7);
+
+    return TRUE;
+}
+
+// PAGE: A8-410
+zbool zz_thumb_relocator_rewrite_LDR_literal_T1(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                                ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+    zuint32 imm8 = get_insn_sub(insn1, 0, 8);
+    zuint32 imm32 = imm8 << 2;
+    zaddr target_address = ALIGN_4(insn_ctx->pc) + imm32;
+    int Rt_ndx = get_insn_sub(insn1, 8, 3);
+
+    zz_thumb_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+    zz_thumb_writer_put_ldr_reg_reg_offset(self->output, Rt_ndx, Rt_ndx, 0);
+
+    return TRUE;
+}
+
+// PAGE: A8-410
+zbool zz_thumb_relocator_rewrite_LDR_literal_T2(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                                ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+    zuint32 insn2 = insn_ctx->insn2;
+
+    zuint32 imm12 = get_insn_sub(insn2, 0, 12);
+    zuint32 imm32 = imm12;
+
+    zbool add = get_insn_sub(insn_ctx->insn1, 7, 1) == 1;
+    zaddr target_address;
+    if (add)
+        target_address = ALIGN_4(insn_ctx->pc) + imm32;
+    else
+        target_address = ALIGN_4(insn_ctx->pc) - imm32;
+    int Rt_ndx = get_insn_sub(insn_ctx->insn2, 12, 4);
+
+    zz_thumb_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+    zz_thumb_writer_put_ldr_reg_reg_offset(self->output, Rt_ndx, Rt_ndx, 0);
+
+    return TRUE;
+}
+
+// PAGE: A8-322
+zbool zz_thumb_relocator_rewrite_ADR_T1(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                        ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+
+    zuint32 imm8 = get_insn_sub(insn1, 0, 8);
+    zuint32 imm32 = imm8 << 2;
+    zaddr target_address = insn_ctx->pc + imm32;
+    int Rt_ndx = get_insn_sub(insn1, 8, 3);
+
+    zz_thumb_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+    return TRUE;
+}
+
+// PAGE: A8-322
+zbool zz_thumb_relocator_rewrite_ADR_T2(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                        ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+    zuint32 insn2 = insn_ctx->insn2;
+
+    zuint32 imm32 =
+        get_insn_sub(insn2, 0, 8) | (get_insn_sub(insn2, 12, 3) << 8) | ((get_insn_sub(insn1, 10, 1) << (3 + 8)));
+
+    zaddr target_address;
+    target_address = insn_ctx->pc - imm32;
+    int Rt_ndx = get_insn_sub(insn_ctx->insn2, 8, 4);
+    zz_thumb_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+    return TRUE;
+}
+
+// PAGE: A8-322
+zbool zz_thumb_relocator_rewrite_ADR_T3(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                        ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+    zuint32 insn2 = insn_ctx->insn2;
+
+    zuint32 imm32 =
+        get_insn_sub(insn2, 0, 8) | (get_insn_sub(insn2, 12, 3) << 8) | ((get_insn_sub(insn1, 10, 1) << (3 + 8)));
+
+    zaddr target_address;
+    target_address = insn_ctx->pc + imm32;
+    int Rt_ndx = get_insn_sub(insn_ctx->insn2, 8, 4);
+
+    zz_thumb_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+    return TRUE;
+}
+
+// 0x000 : b.cond 0x0;
+// 0x002 : b 0x6
+// 0x004 : ldr pc, [pc, #0]
+// 0x008 : .long 0x0
+// 0x00c : remain code
+
+// PAGE: A8-334
+zbool zz_thumb_relocator_rewrite_B_T1(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                      ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+    // zuint32 insn2 = insn_ctx->insn2;
+
+    zuint32 imm8 = get_insn_sub(insn1, 0, 8);
+    zuint32 imm32 = imm8 << 1;
+    zaddr target_address = insn_ctx->pc + imm32;
+
+    /* for align , simple solution, maybe the correct solution is get `ldr_reg_address` length and adjust the immediate
+     * of `b_imm`. */
+    if ((zaddr)self->output->pc % 4) {
+        zz_thumb_writer_put_nop(self->output);
+    }
+    zz_thumb_writer_put_instruction(self->output, (insn1 & 0xFF00) | 0);
+    zz_thumb_writer_put_b_imm(self->output, 0x6);
+    zz_thumb_writer_put_ldr_reg_address(self->output, ZZ_ARM_REG_PC, target_address + 1);
+    return TRUE;
+}
+
+// PAGE: A8-334
+zbool zz_thumb_relocator_rewrite_B_T2(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                      ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+
+    zuint32 imm11 = get_insn_sub(insn1, 0, 11);
+    zuint32 imm32 = imm11 << 1;
+    zaddr target_address = insn_ctx->pc + imm32;
+
+    zz_thumb_writer_put_ldr_reg_address(self->output, ZZ_ARM_REG_PC, target_address + 1);
+    return TRUE;
+}
+
+// 0x002 : b.cond.W 0x2;
+// 0x006 : b 0x6
+// 0x008 : ldr pc, [pc, #0]
+// 0x00c : .long 0x0
+// 0x010 : remain code
+
+// PAGE: A8-334
+zbool zz_thumb_relocator_rewrite_B_T3(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                      ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+    zuint32 insn2 = insn_ctx->insn2;
+
+    int S = get_insn_sub(insn_ctx->insn1, 10, 1);
+    int J2 = get_insn_sub(insn_ctx->insn2, 11, 1);
+    int J1 = get_insn_sub(insn_ctx->insn2, 13, 1);
+    int imm6 = get_insn_sub(insn_ctx->insn1, 0, 6);
+    int imm11 = get_insn_sub(insn_ctx->insn2, 0, 11);
+    zuint32 imm32 =
+        imm11 << 1 | imm6 << (1 + 11) | J1 << (1 + 11 + 6) | J2 << (1 + 11 + 6 + 1) | S << (1 + 11 + 6 + 1 + 1);
+    zaddr target_address;
+    target_address = insn_ctx->pc + imm32;
+
+    /* for align , simple solution, maybe the correct solution is get `ldr_reg_address` length and adjust the immediate
+     * of `b_imm`. */
+    if ((zaddr)self->output->pc % 4 == 0) {
+        zz_thumb_writer_put_nop(self->output);
+    }
+    zz_thumb_writer_put_instruction(self->output, insn_ctx->insn1 & 0b1111101111000000);
+    zz_thumb_writer_put_instruction(self->output, (insn_ctx->insn2 & 0b1101000000000000) | 0b1);
+    zz_thumb_writer_put_b_imm(self->output, 0x6);
+    zz_thumb_writer_put_ldr_reg_address(self->output, ZZ_ARM_REG_PC, target_address + 1);
+    return TRUE;
+}
+
+// PAGE: A8-334
+zbool zz_thumb_relocator_rewrite_B_T4(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                      ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+    zuint32 insn2 = insn_ctx->insn2;
+
+    zuint32 S = get_insn_sub(insn_ctx->insn1, 10 + 16, 1);
+    zuint32 J2 = get_insn_sub(insn_ctx->insn2, 11, 1);
+    zuint32 J1 = get_insn_sub(insn_ctx->insn2, 13, 1);
+    zuint32 imm10 = get_insn_sub(insn_ctx->insn1, 0, 10);
+    zuint32 imm11 = get_insn_sub(insn_ctx->insn2, 0, 11);
+    zuint32 I1 = (~(J1 ^ S)) & 0x1;
+    zuint32 I2 = (~(J2 ^ S)) & 0x1;
+    zuint32 imm32 =
+        imm11 << 1 | imm10 << (1 + 11) | I1 << (1 + 11 + 6) | I2 << (1 + 11 + 6 + 1) | S << (1 + 11 + 6 + 1 + 1);
+    zaddr target_address;
+    target_address = insn_ctx->pc + imm32;
+
+    zz_thumb_writer_put_ldr_reg_address(self->output, ZZ_ARM_REG_PC, target_address + 1);
+    return TRUE;
+}
+
+// PAGE: A8-348
+zbool zz_thumb_relocator_rewrite_BLBLX_immediate_T1(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                                    ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+    zuint32 insn2 = insn_ctx->insn2;
+
+    zuint32 S = get_insn_sub(insn_ctx->insn1, 10, 1);
+    zuint32 J2 = get_insn_sub(insn_ctx->insn2, 11, 1);
+    zuint32 J1 = get_insn_sub(insn_ctx->insn2, 13, 1);
+    zuint32 imm10 = get_insn_sub(insn_ctx->insn1, 0, 10);
+    zuint32 imm11 = get_insn_sub(insn_ctx->insn2, 0, 11);
+    zuint32 I1 = (~(J1 ^ S)) & 0x1;
+    zuint32 I2 = (~(J2 ^ S)) & 0x1;
+    zuint32 imm32 =
+        imm11 << 1 | imm10 << (1 + 11) | I1 << (1 + 11 + 6) | I2 << (1 + 11 + 6 + 1) | S << (1 + 11 + 6 + 1 + 1);
+    zaddr target_address;
+
+    // CurrentInstrSet = thumb
+    // targetInstrSet = arm
+    target_address = insn_ctx->pc + imm32;
+
+    ZzLiteralInstruction **literal_insn_ptr = &(self->relocate_literal_insns[self->relocate_literal_insns_size++]);
+    zz_thumb_writer_put_ldr_b_reg_relocate_address(self->output, ZZ_ARM_REG_LR, insn_ctx->pc + 1, literal_insn_ptr);
+    zz_thumb_writer_put_ldr_reg_address(self->output, ZZ_ARM_REG_PC, target_address + 1);
+    return TRUE;
+}
+
+// PAGE: A8-348
+zbool zz_thumb_relocator_rewrite_BLBLX_T2(ZzThumbRelocator *self, const ZzInstruction *insn_ctx,
+                                          ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn1 = insn_ctx->insn1;
+    zuint32 insn2 = insn_ctx->insn2;
+
+    zuint32 S = get_insn_sub(insn_ctx->insn1, 10, 1);
+    zuint32 J2 = get_insn_sub(insn_ctx->insn2, 11, 1);
+    zuint32 J1 = get_insn_sub(insn_ctx->insn2, 13, 1);
+    zuint32 imm10_1 = get_insn_sub(insn_ctx->insn1, 0, 10);
+    zuint32 imm10_2 = get_insn_sub(insn_ctx->insn2, 1, 10);
+    zuint32 I1 = (~(J1 ^ S)) & 0x1;
+    zuint32 I2 = (~(J2 ^ S)) & 0x1;
+    ;
+    zuint32 H = get_insn_sub(insn_ctx->insn2, 0, 1);
+    zuint32 imm32 =
+        imm10_2 << 2 | imm10_1 << (2 + 10) | I1 << (2 + 10 + 6) | I2 << (2 + 10 + 6 + 1) | S << (2 + 10 + 6 + 1 + 1);
+    zaddr target_address;
+
+    // CurrentInstrSet = thumb
+    // targetInstrSet = arm
+    target_address = ALIGN_4(insn_ctx->pc) + imm32;
+
+    ZzLiteralInstruction **literal_insn_ptr = &(self->relocate_literal_insns[self->relocate_literal_insns_size++]);
+    zz_thumb_writer_put_ldr_b_reg_relocate_address(self->output, ZZ_ARM_REG_LR, insn_ctx->pc + 1, literal_insn_ptr);
+    zz_thumb_writer_put_ldr_reg_address(self->output, ZZ_ARM_REG_PC, target_address);
+    return TRUE;
+}
+
+zbool zz_thumb_relocator_write_one(ZzThumbRelocator *self) {
+    const ZzInstruction *insn_ctx;
+    ZzRelocateInstruction *re_insn_ctx;
+    zbool rewritten = FALSE;
+
+    if (self->inpos != self->outpos) {
+        insn_ctx = &self->input_insns[self->outpos];
+        re_insn_ctx = &self->output_insns[self->outpos];
+        self->outpos++;
+    } else
+        return FALSE;
+
+    re_insn_ctx->relocated_offset = (zaddr)self->output->pc - (zaddr)self->output->base;
+
+    switch (GetTHUMBInsnType(insn_ctx->insn1, insn_ctx->insn2)) {
+    case THUMB_INS_CBNZ_CBZ:
+        rewritten = zz_thumb_relocator_rewrite_CBNZ_CBZ(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_ADD_register_T2:
+        rewritten = zz_thumb_relocator_rewrite_ADD_register_T2(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_LDR_literal_T1:
+        rewritten = zz_thumb_relocator_rewrite_LDR_literal_T1(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_LDR_literal_T2:
+        rewritten = zz_thumb_relocator_rewrite_LDR_literal_T2(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_ADR_T1:
+        rewritten = zz_thumb_relocator_rewrite_ADR_T1(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_ADR_T2:
+        rewritten = zz_thumb_relocator_rewrite_ADR_T2(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_ADR_T3:
+        rewritten = zz_thumb_relocator_rewrite_ADR_T3(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_B_T1:
+        rewritten = zz_thumb_relocator_rewrite_B_T1(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_B_T2:
+        rewritten = zz_thumb_relocator_rewrite_B_T2(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_B_T3:
+        rewritten = zz_thumb_relocator_rewrite_B_T3(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_B_T4:
+        rewritten = zz_thumb_relocator_rewrite_B_T4(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_BLBLX_immediate_T1:
+        rewritten = zz_thumb_relocator_rewrite_BLBLX_immediate_T1(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_INS_BLBLX_immediate_T2:
+        rewritten = zz_thumb_relocator_rewrite_BLBLX_T2(self, insn_ctx, re_insn_ctx);
+        break;
+    case THUMB_UNDEF:
+        rewritten = FALSE;
+        break;
+    }
+    if (!rewritten)
+        zz_thumb_writer_put_bytes(self->output, (zbyte *)&insn_ctx->insn, insn_ctx->size);
+
+    re_insn_ctx->relocated_length =
+        (zaddr)self->output->pc - (zaddr)self->output->base - (zaddr)re_insn_ctx->relocated_offset;
+
+    return TRUE;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-thumb.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-thumb.h
new file mode 100644
index 000000000..1baeac0eb
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/relocator-thumb.h
@@ -0,0 +1,57 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_arm_relocator_thumb_h
+#define platforms_arch_arm_relocator_thumb_h
+
+// platforms
+#include "instructions.h"
+#include "reader-thumb.h"
+#include "regs-arm.h"
+#include "writer-thumb.h"
+
+// hookzz
+#include "writer.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef struct _ZzThumbRelocator {
+    zbool try_relocated_again;
+    zsize try_relocated_length;
+    zpointer input_start;
+    zpointer input_cur;
+    zaddr input_pc;
+    ZzInstruction *input_insns;
+    ZzRelocateInstruction *output_insns;
+    ZzLiteralInstruction **relocate_literal_insns;
+    zsize relocate_literal_insns_size;
+    ZzThumbWriter *output;
+    zuint inpos;
+    zuint outpos;
+} ZzThumbRelocator;
+
+void zz_thumb_relocator_init(ZzThumbRelocator *relocator, zpointer input_code, ZzThumbWriter *writer);
+void zz_thumb_relocator_reset(ZzThumbRelocator *self, zpointer input_code, ZzThumbWriter *output);
+zsize zz_thumb_relocator_read_one(ZzThumbRelocator *self, ZzInstruction *instruction);
+zbool zz_thumb_relocator_write_one(ZzThumbRelocator *self);
+void zz_thumb_relocator_relocate_writer(ZzThumbRelocator *relocator, zaddr code_address);
+void zz_thumb_relocator_write_all(ZzThumbRelocator *self);
+void zz_thumb_relocator_try_relocate(zpointer address, zuint min_bytes, zuint *max_bytes);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-arm.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-arm.c
new file mode 100644
index 000000000..d9883eddf
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-arm.c
@@ -0,0 +1,230 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "writer-arm.h"
+
+#include <stdlib.h>
+
+// ATTENTION !!!:
+// 写 writer 部分, 需要参考, `Instrcution Set Encoding` 部分
+// `writer` REF: `ZzInstruction Set Encoding`
+
+ZzArmWriter *zz_arm_writer_new(zpointer data_ptr) {
+    ZzArmWriter *writer = (ZzArmWriter *)malloc(sizeof(ZzArmWriter));
+    memset(writer, 0, sizeof(ZzArmWriter));
+
+    zaddr align_address = (zaddr)data_ptr & ~(zaddr)3;
+    writer->codedata = (zpointer)align_address;
+    writer->base = (zpointer)align_address;
+    writer->pc = align_address;
+    writer->size = 0;
+
+    writer->literal_insn_size = 0;
+    memset(writer->literal_insns, 0, sizeof(ZzLiteralInstruction) * MAX_LITERAL_INSN_SIZE);
+
+    return writer;
+}
+
+void zz_arm_writer_init(ZzArmWriter *self, zpointer data_ptr) { zz_arm_writer_reset(self, data_ptr); }
+
+void zz_arm_writer_reset(ZzArmWriter *self, zpointer data_ptr) {
+
+    zaddr align_address = (zaddr)data_ptr & ~(zaddr)3;
+    self->codedata = (zpointer)align_address;
+    self->base = (zpointer)align_address;
+    self->pc = align_address;
+
+    self->literal_insn_size = 0;
+    memset(self->literal_insns, 0, sizeof(ZzLiteralInstruction) * MAX_LITERAL_INSN_SIZE);
+
+    self->size = 0;
+}
+
+zsize zz_arm_writer_near_jump_range_size() { return ((1 << 23) << 2); }
+
+// ------- relocator -------
+
+ZzLiteralInstruction *zz_arm_writer_put_ldr_b_reg_relocate_address(ZzArmWriter *self, ZzARMReg reg, zaddr address,
+                                                                   ZzLiteralInstruction **literal_insn_ptr) {
+    zz_arm_writer_put_ldr_b_reg_address(self, reg, address);
+    ZzLiteralInstruction *literal_insn = &(self->literal_insns[self->literal_insn_size - 1]);
+    *literal_insn_ptr = literal_insn;
+    return literal_insn;
+}
+
+ZzLiteralInstruction *zz_arm_writer_put_ldr_reg_relocate_address(ZzArmWriter *self, ZzARMReg reg, zaddr address,
+                                                                 ZzLiteralInstruction **literal_insn_ptr) {
+    zz_arm_writer_put_ldr_reg_address(self, reg, address);
+    ZzLiteralInstruction *literal_insn = &(self->literal_insns[self->literal_insn_size - 1]);
+    *literal_insn_ptr = literal_insn;
+    return literal_insn;
+}
+
+// ------- user custom -------
+
+void zz_arm_writer_put_ldr_b_reg_address(ZzArmWriter *self, ZzARMReg reg, zaddr address) {
+    self->literal_insns[self->literal_insn_size].literal_insn_ptr = self->codedata;
+    zz_arm_writer_put_ldr_reg_reg_imm(self, reg, ZZ_ARM_REG_PC, 0);
+    zz_arm_writer_put_b_imm(self, 0x0);
+    self->literal_insns[self->literal_insn_size++].literal_address_ptr = self->codedata;
+    zz_arm_writer_put_bytes(self, (zpointer)&address, sizeof(zpointer));
+}
+
+void zz_arm_writer_put_bx_to_thumb(ZzArmWriter *self) {
+    zz_arm_writer_put_sub_reg_reg_imm(self, ZZ_ARM_REG_SP, ZZ_ARM_REG_SP, 0x8);
+    zz_arm_writer_put_str_reg_reg_imm(self, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x0);
+    zz_arm_writer_put_add_reg_reg_imm(self, ZZ_ARM_REG_R1, ZZ_ARM_REG_PC, 9);
+    zz_arm_writer_put_str_reg_reg_imm(self, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x4);
+    zz_arm_writer_put_ldr_reg_reg_imm_index(self, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 4, 0);
+    zz_arm_writer_put_ldr_reg_reg_imm_index(self, ZZ_ARM_REG_PC, ZZ_ARM_REG_SP, 4, 0);
+}
+// ------- architecture default -------
+void zz_arm_writer_put_bytes(ZzArmWriter *self, zbyte *data, zuint data_size) {
+    memcpy(self->codedata, data, data_size);
+    self->codedata = (zpointer)self->codedata + data_size;
+    self->pc += data_size;
+    self->size += data_size;
+}
+
+void zz_arm_writer_put_instruction(ZzArmWriter *self, zuint32 insn) {
+    *(zuint32 *)(self->codedata) = insn;
+    self->codedata = (zpointer)self->codedata + sizeof(zuint32);
+    self->pc += 4;
+    self->size += 4;
+}
+
+void zz_arm_writer_put_b_imm(ZzArmWriter *self, zuint32 imm) {
+    zz_arm_writer_put_instruction(self, 0xea000000 | ((imm / 4) & 0xffffff));
+}
+
+void zz_arm_writer_put_ldr_reg_reg_imm(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zint32 imm) {
+    ZzArmRegInfo rd, rs;
+
+    zz_arm_register_describe(dst_reg, &rd);
+    zz_arm_register_describe(src_reg, &rs);
+
+    if (rs.meta == ZZ_ARM_REG_PC) {
+        zz_arm_writer_put_ldr_reg_imm_literal(self, dst_reg, imm);
+    } else {
+        zbool P = 1;
+        zbool U = 0;
+        zbool W = 0;
+        if (imm >= 0)
+            U = 1;
+
+        zz_arm_writer_put_ldr_reg_reg_imm_A1(self, dst_reg, src_reg, ABS(imm), P, U, W);
+    }
+}
+
+void zz_arm_writer_put_ldr_reg_reg_imm_index(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zint32 imm,
+                                             zbool index) {
+    ZzArmRegInfo rd, rs;
+
+    zz_arm_register_describe(dst_reg, &rd);
+    zz_arm_register_describe(src_reg, &rs);
+
+    zbool P = index;
+    zbool U = 0;
+    zbool W = 1;
+    if (P == 0)
+        W = 0;
+    if (imm >= 0)
+        U = 1;
+
+    zz_arm_writer_put_ldr_reg_reg_imm_A1(self, dst_reg, src_reg, ABS(imm), P, U, W);
+}
+void zz_arm_writer_put_ldr_reg_reg_imm_A1(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zuint32 imm, zbool P,
+                                          zbool U, zbool W) {
+    ZzArmRegInfo rd, rs;
+
+    zz_arm_register_describe(dst_reg, &rd);
+    zz_arm_register_describe(src_reg, &rs);
+
+    zz_arm_writer_put_instruction(self, 0xe4100000 | rd.index << 12 | rs.index << 16 | P << 24 | U << 23 | W << 21 |
+                                            (imm & ZZ_INT12_MASK));
+}
+void zz_arm_writer_put_ldr_reg_imm_literal(ZzArmWriter *self, ZzARMReg dst_reg, zint32 imm) {
+    ZzArmRegInfo rd;
+
+    zz_arm_register_describe(dst_reg, &rd);
+    zbool U = 0;
+    if (imm >= 0)
+        U = 1;
+    zz_arm_writer_put_instruction(self, 0xe51f0000 | U << 23 | rd.index << 12 | (ABS(imm) & ZZ_INT12_MASK));
+}
+
+void zz_arm_writer_put_str_reg_reg_imm(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zint32 imm) {
+    ZzArmRegInfo rd, rs;
+
+    zz_arm_register_describe(dst_reg, &rd);
+    zz_arm_register_describe(src_reg, &rs);
+
+    zbool P = 1;
+    zbool U = 0;
+    zbool W = 0;
+    if (imm >= 0)
+        U = 1;
+    zz_arm_writer_put_instruction(self, 0xe4000000 | rd.index << 12 | rs.index << 16 | P << 24 | U << 23 | W << 21 |
+                                            (imm & ZZ_INT12_MASK));
+}
+
+void zz_arm_writer_put_ldr_reg_address(ZzArmWriter *self, ZzARMReg reg, zaddr address) {
+    self->literal_insns[self->literal_insn_size].literal_insn_ptr = self->codedata;
+    zz_arm_writer_put_ldr_reg_reg_imm(self, reg, ZZ_ARM_REG_PC, -4);
+    self->literal_insns[self->literal_insn_size++].literal_address_ptr = self->codedata;
+    zz_arm_writer_put_bytes(self, (zpointer)&address, sizeof(zpointer));
+}
+
+void zz_arm_writer_put_add_reg_reg_imm(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zuint32 imm) {
+    ZzArmRegInfo rd, rs;
+
+    zz_arm_register_describe(dst_reg, &rd);
+    zz_arm_register_describe(src_reg, &rs);
+
+    zz_arm_writer_put_instruction(self, 0xe2800000 | rd.index << 12 | rs.index << 16 | (imm & ZZ_INT12_MASK));
+}
+
+void zz_arm_writer_put_sub_reg_reg_imm(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zuint32 imm) {
+    ZzArmRegInfo rd, rs;
+
+    zz_arm_register_describe(dst_reg, &rd);
+    zz_arm_register_describe(src_reg, &rs);
+
+    zz_arm_writer_put_instruction(self, 0xe2400000 | rd.index << 12 | rs.index << 16 | (imm & ZZ_INT12_MASK));
+}
+
+void zz_arm_writer_put_bx_reg(ZzArmWriter *self, ZzARMReg reg) {
+    ZzArmRegInfo rs;
+    zz_arm_register_describe(reg, &rs);
+    zz_arm_writer_put_instruction(self, 0xe12fff10 | rs.index);
+}
+
+void zz_arm_writer_put_nop(ZzArmWriter *self) { zz_arm_writer_put_instruction(self, 0xe320f000); }
+
+void zz_arm_writer_put_push_reg(ZzArmWriter *self, ZzARMReg reg) {
+    ZzArmRegInfo ri;
+    zz_arm_register_describe(reg, &ri);
+    zz_arm_writer_put_instruction(self, 0b11100101001011010000000000000100 | ri.index << 12);
+    return;
+}
+
+void zz_arm_writer_put_pop_reg(ZzArmWriter *self, ZzARMReg reg) {
+    ZzArmRegInfo ri;
+    zz_arm_register_describe(reg, &ri);
+
+    zz_arm_writer_put_instruction(self, 0b11100100100111010000000000000100 | ri.index << 12);
+    return;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-arm.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-arm.h
new file mode 100644
index 000000000..95826608c
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-arm.h
@@ -0,0 +1,71 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_arm_writer_arm_h
+#define platforms_arch_arm_writer_arm_h
+
+#include <string.h>
+
+// platforms
+#include "instructions.h"
+#include "reader-arm.h"
+#include "regs-arm.h"
+#include "writer-arm.h"
+
+// hookzz
+#include "writer.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef ZzWriter ZzArmWriter;
+ZzArmWriter *zz_arm_writer_new(zpointer data_ptr);
+void zz_arm_writer_init(ZzArmWriter *self, zpointer data_ptr);
+void zz_arm_writer_reset(ZzArmWriter *self, zpointer data_ptr);
+zsize zz_arm_writer_near_jump_range_size();
+
+// ------- user custom -------
+
+void zz_arm_writer_put_ldr_b_reg_address(ZzArmWriter *self, ZzARMReg reg, zaddr address);
+void zz_arm_writer_put_bx_to_thumb(ZzArmWriter *self);
+
+// ------- architecture default -------
+
+void zz_arm_writer_put_bytes(ZzArmWriter *self, zbyte *data, zuint data_size);
+void zz_arm_writer_put_instruction(ZzArmWriter *self, zuint32 insn);
+void zz_arm_writer_put_b_imm(ZzArmWriter *self, zuint32 imm);
+void zz_arm_writer_put_bx_reg(ZzArmWriter *self, ZzARMReg reg);
+void zz_arm_writer_put_nop(ZzArmWriter *self);
+void zz_arm_writer_put_ldr_reg_reg_imm(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zint32 imm);
+void zz_arm_writer_put_str_reg_reg_imm(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zint32 imm);
+void zz_arm_writer_put_ldr_reg_imm_literal(ZzArmWriter *self, ZzARMReg dst_reg, zint32 imm);
+void zz_arm_writer_put_ldr_reg_reg_imm_index(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zint32 imm,
+                                             zbool index);
+void zz_arm_writer_put_ldr_reg_reg_imm_A1(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zuint32 imm, zbool P,
+                                          zbool U, zbool W);
+void zz_arm_writer_put_ldr_reg_address(ZzArmWriter *self, ZzARMReg reg, zaddr address);
+void zz_arm_writer_put_add_reg_reg_imm(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zuint32 imm);
+void zz_arm_writer_put_sub_reg_reg_imm(ZzArmWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zuint32 imm);
+void zz_arm_writer_put_push_reg(ZzArmWriter *self, ZzARMReg reg);
+void zz_arm_writer_put_pop_reg(ZzArmWriter *self, ZzARMReg reg);
+ZzLiteralInstruction *zz_arm_writer_put_ldr_b_reg_relocate_address(ZzArmWriter *self, ZzARMReg reg, zaddr address,
+                                                                   ZzLiteralInstruction **literal_insn_ptr);
+ZzLiteralInstruction *zz_arm_writer_put_ldr_reg_relocate_address(ZzArmWriter *self, ZzARMReg reg, zaddr address,
+                                                                 ZzLiteralInstruction **literal_insn_ptr);
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-thumb.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-thumb.c
new file mode 100644
index 000000000..c55db42fe
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-thumb.c
@@ -0,0 +1,501 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "writer-thumb.h"
+
+#include <stdlib.h>
+
+// ATTENTION !!!:
+// 写 writer 部分, 需要参考, `Instrcution Set Encoding` 部分
+// `witer` REF: `ZzInstruction Set Encoding`
+
+ZzThumbWriter *zz_thumb_writer_new(zpointer data_ptr) {
+    ZzThumbWriter *writer = (ZzThumbWriter *)malloc(sizeof(ZzThumbWriter));
+    memset(writer, 0, sizeof(ZzThumbWriter));
+
+    zaddr align_address = (zaddr)data_ptr & ~(zaddr)3;
+    writer->codedata = (zpointer)align_address;
+    writer->base = (zpointer)align_address;
+    writer->pc = align_address;
+    writer->size = 0;
+
+    writer->literal_insn_size = 0;
+    memset(writer->literal_insns, 0, sizeof(ZzLiteralInstruction) * MAX_LITERAL_INSN_SIZE);
+
+    return writer;
+}
+
+void zz_thumb_writer_init(ZzThumbWriter *self, zpointer data_ptr) { zz_thumb_writer_reset(self, data_ptr); }
+
+void zz_thumb_writer_reset(ZzThumbWriter *self, zpointer data_ptr) {
+    zaddr align_address = (zaddr)data_ptr & ~(zaddr)3;
+
+    self->codedata = (zpointer)align_address;
+    self->base = (zpointer)align_address;
+    self->pc = align_address;
+    self->size = 0;
+
+    self->literal_insn_size = 0;
+    memset(self->literal_insns, 0, sizeof(ZzLiteralInstruction) * MAX_LITERAL_INSN_SIZE);
+}
+
+zsize zz_thumb_writer_near_jump_range_size() { return ((1 << 23) << 1); }
+
+// ------- relocator -------
+
+ZzLiteralInstruction *zz_thumb_writer_put_ldr_b_reg_relocate_address(ZzThumbWriter *self, ZzARMReg reg, zaddr address,
+                                                                     ZzLiteralInstruction **literal_insn_ptr) {
+    zz_thumb_writer_put_ldr_b_reg_address(self, reg, address);
+    ZzLiteralInstruction *literal_insn = &(self->literal_insns[self->literal_insn_size - 1]);
+    *literal_insn_ptr = literal_insn;
+    return literal_insn;
+}
+
+ZzLiteralInstruction *zz_thumb_writer_put_ldr_reg_relocate_address(ZzThumbWriter *self, ZzARMReg reg, zaddr address,
+                                                                   ZzLiteralInstruction **literal_insn_ptr) {
+    zz_thumb_writer_put_ldr_reg_address(self, reg, address);
+    ZzLiteralInstruction *literal_insn = &(self->literal_insns[self->literal_insn_size - 1]);
+    *literal_insn_ptr = literal_insn;
+    return literal_insn;
+}
+
+// ------- custom -------
+
+void zz_thumb_writer_put_ldr_b_reg_address(ZzThumbWriter *self, ZzARMReg reg, zaddr address) {
+    ZzArmRegInfo ri;
+    zz_arm_register_describe(reg, &ri);
+    self->literal_insns[self->literal_insn_size].literal_insn_ptr = self->codedata;
+
+    if ((((zaddr)self->pc) % 4)) {
+        if (ri.meta <= ZZ_ARM_REG_R7) {
+            zz_thumb_writer_put_ldr_reg_imm(self, reg, 0x4);
+            zz_thumb_writer_put_nop(self);
+        } else {
+            zz_thumb_writer_put_ldr_reg_imm(self, reg, 0x4);
+        }
+    } else {
+        if (ri.meta <= ZZ_ARM_REG_R7) {
+            zz_thumb_writer_put_ldr_reg_imm(self, reg, 0x0);
+        } else {
+            zz_thumb_writer_put_ldr_reg_imm(self, reg, 0x4);
+            zz_thumb_writer_put_nop(self);
+        }
+    }
+
+    zz_thumb_writer_put_b_imm(self, 0x2);
+    self->literal_insns[self->literal_insn_size++].literal_address_ptr = self->codedata;
+    zz_thumb_writer_put_bytes(self, (zpointer)&address, sizeof(zpointer));
+    return;
+}
+
+void zz_thumb_writer_put_ldr_reg_address(ZzThumbWriter *self, ZzARMReg reg, zaddr address) {
+    ZzArmRegInfo ri;
+    zz_arm_register_describe(reg, &ri);
+
+    self->literal_insns[self->literal_insn_size].literal_insn_ptr = self->codedata;
+
+    if ((((zaddr)self->pc) % 4)) {
+        if (ri.meta <= ZZ_ARM_REG_R7) {
+            zz_thumb_writer_put_ldr_reg_imm(self, reg, 0x0);
+        } else {
+            zz_thumb_writer_put_ldr_reg_imm(self, reg, 0x4);
+            zz_thumb_writer_put_nop(self);
+        }
+    } else {
+        zz_thumb_writer_put_ldr_reg_imm(self, reg, 0x0);
+        if (ri.meta <= ZZ_ARM_REG_R7)
+            zz_thumb_writer_put_nop(self);
+    }
+
+    self->literal_insns[self->literal_insn_size++].literal_address_ptr = self->codedata;
+    zz_thumb_writer_put_bytes(self, (zpointer)&address, sizeof(zpointer));
+    return;
+}
+
+// ------- architecture default -------
+void zz_thumb_writer_put_nop(ZzThumbWriter *self) {
+    zz_thumb_writer_put_instruction(self, 0x46c0);
+    return;
+}
+
+void zz_thumb_writer_put_bytes(ZzThumbWriter *self, zbyte *data, zuint data_size) {
+    memcpy(self->codedata, data, data_size);
+    self->codedata = (zpointer)self->codedata + data_size;
+    self->pc += data_size;
+    self->size += data_size;
+    return;
+}
+
+void zz_thumb_writer_put_instruction(ZzThumbWriter *self, uint16_t insn) {
+    *(uint16_t *)(self->codedata) = insn;
+    self->codedata = (zpointer)self->codedata + sizeof(uint16_t);
+    self->pc += 2;
+    self->size += 2;
+    return;
+}
+
+void zz_thumb_writer_put_b_imm(ZzThumbWriter *self, zuint32 imm) {
+
+    zz_thumb_writer_put_instruction(self, 0xe000 | ((imm / 2) & ZZ_INT11_MASK));
+    return;
+}
+
+void zz_thumb_writer_put_bx_reg(ZzThumbWriter *self, ZzARMReg reg) {
+    ZzArmRegInfo ri;
+
+    zz_arm_register_describe(reg, &ri);
+
+    if ((((zaddr)self->pc) % 4)) {
+        zz_thumb_writer_put_nop(self);
+    }
+
+    zz_thumb_writer_put_instruction(self, 0x4700 | (ri.index << 3));
+    zz_thumb_writer_put_nop(self);
+    return;
+}
+
+void zz_thumb_writer_put_blx_reg(ZzThumbWriter *self, ZzARMReg reg) {
+    ZzArmRegInfo ri;
+
+    zz_arm_register_describe(reg, &ri);
+
+    zz_thumb_writer_put_instruction(self, 0x4780 | (ri.index << 3));
+    return;
+}
+
+// A8.8.18
+void zz_thumb_writer_put_branch_imm(ZzThumbWriter *self, zuint32 imm, zbool link, zbool thumb) {
+    union {
+        zint32 i;
+        zuint32 u;
+    } distance;
+    zuint16 s, j1, j2, imm10, imm11;
+
+    distance.i = (zint32)(imm) / 2;
+
+    s = (distance.u >> 31) & 1;
+    j1 = (~((distance.u >> 22) ^ s)) & 1;
+    j2 = (~((distance.u >> 21) ^ s)) & 1;
+
+    imm10 = (distance.u >> 11) & ZZ_INT10_MASK;
+    imm11 = distance.u & ZZ_INT11_MASK;
+
+    zz_thumb_writer_put_instruction(self, 0xf000 | (s << 10) | imm10);
+    zz_thumb_writer_put_instruction(self, 0x8000 | (link << 14) | (j1 << 13) | (thumb << 12) | (j2 << 11) | imm11);
+    return;
+}
+
+void zz_thumb_writer_put_bl_imm(ZzThumbWriter *self, zuint32 imm) {
+    zz_thumb_writer_put_branch_imm(self, imm, TRUE, TRUE);
+    return;
+}
+
+void zz_thumb_writer_put_blx_imm(ZzThumbWriter *self, zuint32 imm) {
+    zz_thumb_writer_put_branch_imm(self, imm, TRUE, FALSE);
+    return;
+}
+
+void zz_thumb_writer_put_b_imm32(ZzThumbWriter *self, zuint32 imm) {
+    zz_thumb_writer_put_branch_imm(self, imm, FALSE, TRUE);
+    return;
+}
+
+// PAGE: A8-410
+// A8.8.64 LDR (literal)
+void zz_thumb_writer_put_ldr_reg_imm(ZzThumbWriter *self, ZzARMReg reg, zint32 imm) {
+    ZzArmRegInfo ri;
+
+    zz_arm_register_describe(reg, &ri);
+
+    if (ri.meta <= ZZ_ARM_REG_R7 && imm >= 0 && imm < ((1 << 8) << 2)) {
+
+        zz_thumb_writer_put_instruction(self, 0x4800 | (ri.index << 8) | ((imm / 4) & ZZ_INT8_MASK));
+    } else if (imm < (1 << 12)) {
+        zbool add = 0;
+        if (imm >= 0)
+            add = 1;
+        zz_thumb_writer_put_instruction(self, 0xf85f | (add << 7));
+        zz_thumb_writer_put_instruction(self, (ri.index << 12) | ABS(imm));
+    }
+    return;
+}
+
+zbool zz_thumb_writer_put_transfer_reg_reg_offset_T1(ZzThumbWriter *self, ZzThumbMemoryOperation operation,
+                                                     ZzARMReg left_reg, ZzARMReg right_reg, zint32 right_offset) {
+    ZzArmRegInfo lr, rr;
+
+    zz_arm_register_describe(left_reg, &lr);
+    zz_arm_register_describe(right_reg, &rr);
+
+    zuint16 insn;
+
+    if (right_offset < 0)
+        return FALSE;
+
+    if (lr.meta <= ZZ_ARM_REG_R7 && rr.meta <= ZZ_ARM_REG_R7 && right_offset < ((1 << 5) << 2)) {
+        insn = 0x6000 | (right_offset / 4) << 6 | (rr.index << 3) | lr.index;
+        if (operation == ZZ_THUMB_MEMORY_LOAD)
+            insn |= 0x0800;
+        zz_thumb_writer_put_instruction(self, insn);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+zbool zz_thumb_writer_put_transfer_reg_reg_offset_T2(ZzThumbWriter *self, ZzThumbMemoryOperation operation,
+                                                     ZzARMReg left_reg, ZzARMReg right_reg, zint32 right_offset) {
+    ZzArmRegInfo lr, rr;
+
+    zz_arm_register_describe(left_reg, &lr);
+    zz_arm_register_describe(right_reg, &rr);
+
+    zuint16 insn;
+
+    if (right_offset < 0)
+        return FALSE;
+
+    if (rr.meta == ZZ_ARM_REG_SP && lr.meta <= ZZ_ARM_REG_R7 && right_offset < ((1 << 8) << 2)) {
+        insn = 0x9000 | (lr.index << 8) | (right_offset / 4);
+        if (operation == ZZ_THUMB_MEMORY_LOAD)
+            insn |= 0x0800;
+        zz_thumb_writer_put_instruction(self, insn);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+zbool zz_thumb_writer_put_transfer_reg_reg_offset_T3(ZzThumbWriter *self, ZzThumbMemoryOperation operation,
+                                                     ZzARMReg left_reg, ZzARMReg right_reg, zint32 right_offset) {
+    ZzArmRegInfo lr, rr;
+
+    zz_arm_register_describe(left_reg, &lr);
+    zz_arm_register_describe(right_reg, &rr);
+
+    zuint16 insn;
+
+    if (right_offset < 0)
+        return FALSE;
+
+    if (right_offset < (1 << 12)) {
+        if (rr.meta == ZZ_ARM_REG_PC) {
+            zz_thumb_writer_put_ldr_reg_imm(self, left_reg, right_offset);
+        }
+        zz_thumb_writer_put_instruction(self,
+                                        0xf8c0 | ((operation == ZZ_THUMB_MEMORY_LOAD) ? 0x0010 : 0x0000) | rr.index);
+        zz_thumb_writer_put_instruction(self, (lr.index << 12) | right_offset);
+
+        return TRUE;
+    }
+    return FALSE;
+}
+
+zbool zz_thumb_writer_put_transfer_reg_reg_offset_T4(ZzThumbWriter *self, ZzThumbMemoryOperation operation,
+                                                     ZzARMReg left_reg, ZzARMReg right_reg, zint32 right_offset,
+                                                     zbool index, zbool wback) {
+    ZzArmRegInfo lr, rr;
+
+    zz_arm_register_describe(left_reg, &lr);
+    zz_arm_register_describe(right_reg, &rr);
+
+    zuint16 insn;
+
+    if (ABS(right_offset) < (1 << 8)) {
+        if (rr.meta == ZZ_ARM_REG_PC) {
+            zz_thumb_writer_put_ldr_reg_imm(self, left_reg, right_offset);
+        } else {
+            zbool add = 0;
+            if (right_offset > 0)
+                add = 1;
+            zz_thumb_writer_put_instruction(self, 0xf840 | ((operation == ZZ_THUMB_MEMORY_LOAD) ? 0x0010 : 0x0000) |
+                                                      rr.index);
+            zz_thumb_writer_put_instruction(self, 0x0800 | (lr.index << 12) | (index << 10) | (add << 9) |
+                                                      (wback << 8) | (ABS(right_offset)));
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+// PAGE: A8-406
+// PAGE: A8.8.203 STR (immediate, Thumb)
+static void zz_thumb_writer_put_transfer_reg_reg_offset(ZzThumbWriter *self, ZzThumbMemoryOperation operation,
+                                                        ZzARMReg left_reg, ZzARMReg right_reg, zint32 right_offset) {
+    if (zz_thumb_writer_put_transfer_reg_reg_offset_T1(self, operation, left_reg, right_reg, right_offset))
+        return;
+
+    if (zz_thumb_writer_put_transfer_reg_reg_offset_T2(self, operation, left_reg, right_reg, right_offset))
+        return;
+
+    if (zz_thumb_writer_put_transfer_reg_reg_offset_T3(self, operation, left_reg, right_reg, right_offset))
+        return;
+    if (zz_thumb_writer_put_transfer_reg_reg_offset_T4(self, operation, left_reg, right_reg, right_offset, 1, 0))
+        return;
+    return;
+}
+
+void zz_thumb_writer_put_ldr_reg_reg_offset(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg,
+                                            zint32 src_offset) {
+    zz_thumb_writer_put_transfer_reg_reg_offset(self, ZZ_THUMB_MEMORY_LOAD, dst_reg, src_reg, src_offset);
+    return;
+}
+
+void zz_thumb_writer_put_str_reg_reg_offset(ZzThumbWriter *self, ZzARMReg src_reg, ZzARMReg dst_reg,
+                                            zint32 dst_offset) {
+    zz_thumb_writer_put_transfer_reg_reg_offset(self, ZZ_THUMB_MEMORY_STORE, src_reg, dst_reg, dst_offset);
+    return;
+}
+
+void zz_thumb_writer_put_ldr_index_reg_reg_offset(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg,
+                                                  zint32 src_offset, zbool index) {
+    zz_thumb_writer_put_transfer_reg_reg_offset_T4(self, ZZ_THUMB_MEMORY_LOAD, dst_reg, src_reg, src_offset, index, 1);
+    return;
+}
+
+void zz_thumb_writer_put_str_index_reg_reg_offset(ZzThumbWriter *self, ZzARMReg src_reg, ZzARMReg dst_reg,
+                                                  zint32 dst_offset, zbool index) {
+    zz_thumb_writer_put_transfer_reg_reg_offset_T4(self, ZZ_THUMB_MEMORY_STORE, src_reg, dst_reg, dst_offset, index, 1);
+    return;
+}
+
+void zz_thumb_writer_put_str_reg_reg(ZzThumbWriter *self, ZzARMReg src_reg, ZzARMReg dst_reg) {
+    zz_thumb_writer_put_str_reg_reg_offset(self, src_reg, dst_reg, 0);
+    return;
+}
+
+void zz_thumb_writer_put_ldr_reg_reg(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg) {
+    zz_thumb_writer_put_ldr_reg_reg_offset(self, dst_reg, src_reg, 0);
+    return;
+}
+
+void zz_thumb_writer_put_add_reg_imm(ZzThumbWriter *self, ZzARMReg dst_reg, zint32 imm) {
+    ZzArmRegInfo dst;
+    zuint16 sign_mask, insn;
+
+    zz_arm_register_describe(dst_reg, &dst);
+
+    sign_mask = 0x0000;
+    if (dst.meta == ZZ_ARM_REG_SP) {
+
+        if (imm < 0)
+            sign_mask = 0x0080;
+
+        insn = 0xb000 | sign_mask | ABS(imm / 4);
+    } else {
+        if (imm < 0)
+            sign_mask = 0x0800;
+
+        insn = 0x3000 | sign_mask | (dst.index << 8) | ABS(imm);
+    }
+
+    zz_thumb_writer_put_instruction(self, insn);
+    return;
+}
+
+void zz_thumb_writer_put_sub_reg_imm(ZzThumbWriter *self, ZzARMReg dst_reg, zint32 imm) {
+    zz_thumb_writer_put_add_reg_imm(self, dst_reg, -imm);
+    return;
+}
+
+void zz_thumb_writer_put_add_reg_reg_imm(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg left_reg, zint32 right_value) {
+    ZzArmRegInfo dst, left;
+    zuint16 insn;
+
+    zz_arm_register_describe(dst_reg, &dst);
+    zz_arm_register_describe(left_reg, &left);
+
+    if (left.meta == dst.meta) {
+        return zz_thumb_writer_put_add_reg_imm(self, dst_reg, right_value);
+    }
+
+    if (dst.meta <= ZZ_ARM_REG_R7 && left.meta <= ZZ_ARM_REG_R7 && ABS(right_value) < (1 << 3)) {
+        zuint32 sign_mask = 0;
+
+        if (right_value < 0)
+            sign_mask = 1 << 9;
+
+        insn = 0x1c00 | sign_mask | (ABS(right_value) << 6) | (left.index << 3) | dst.index;
+        zz_thumb_writer_put_instruction(self, insn);
+    } else if ((left.meta == ZZ_ARM_REG_SP || left.meta == ZZ_ARM_REG_PC) && dst.meta <= ZZ_ARM_REG_R7 &&
+               right_value > 0 && (right_value % 4 == 0) && right_value < (1 << 8)) {
+        zuint16 base_mask;
+
+        if (left.meta == ZZ_ARM_REG_SP)
+            base_mask = 0x0800;
+        else
+            base_mask = 0x0000;
+
+        insn = 0xa000 | base_mask | (dst.index << 8) | (right_value / 4);
+        zz_thumb_writer_put_instruction(self, insn);
+    } else {
+        zuint16 insn1, insn2;
+        zuint i, imm3, imm8;
+        i = (ABS(right_value) >> (3 + 8)) & 0x1;
+        imm3 = (ABS(right_value) >> 8) & 0b111;
+        imm8 = ABS(right_value) & 0b11111111;
+
+        // A8-708, sub
+        // A8-306 add
+        if (right_value < 0)
+            zz_thumb_writer_put_instruction(self, 0b1111001010100000 | i << 10 | left.index);
+        else
+            zz_thumb_writer_put_instruction(self, 0b1111001000000000 | i << 10 | left.index);
+        zz_thumb_writer_put_instruction(self, 0b0 | imm3 << 12 | dst.index << 8 | imm8);
+    }
+
+    return;
+}
+
+void zz_thumb_writer_put_sub_reg_reg_imm(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg left_reg, zint32 right_value) {
+    zz_thumb_writer_put_add_reg_reg_imm(self, dst_reg, left_reg, -right_value);
+    return;
+}
+
+void zz_thumb_writer_put_push_reg(ZzThumbWriter *self, ZzARMReg reg) {
+    ZzArmRegInfo ri;
+    zz_arm_register_describe(reg, &ri);
+
+    zuint16 M, register_list;
+    M = 0;
+
+    zz_thumb_writer_put_instruction(self, 0b1011010000000000 | M << 8 | 1 << ri.index);
+    return;
+}
+
+void zz_thumb_writer_put_pop_reg(ZzThumbWriter *self, ZzARMReg reg) {
+    ZzArmRegInfo ri;
+    zz_arm_register_describe(reg, &ri);
+
+    zuint16 P, register_list;
+    P = 0;
+
+    zz_thumb_writer_put_instruction(self, 0b1011110000000000 | P << 8 | 1 << ri.index);
+    return;
+}
+
+void zz_thumb_writer_put_add_reg_reg_reg(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg left_reg, ZzARMReg right_reg) {
+    ZzArmRegInfo dst, left, right;
+    zz_arm_register_describe(dst_reg, &dst);
+    zz_arm_register_describe(left_reg, &left);
+    zz_arm_register_describe(right_reg, &right);
+
+    zuint16 Rm_ndx, Rn_ndx, Rd_ndx;
+    Rd_ndx = dst.index;
+    Rm_ndx = right.index;
+    Rn_ndx = left.index;
+
+    zz_thumb_writer_put_instruction(self, 0b0001100000000000 | Rm_ndx << 6 | Rn_ndx << 3 | Rd_ndx);
+    return;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-thumb.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-thumb.h
new file mode 100644
index 000000000..a05f03dec
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm/writer-thumb.h
@@ -0,0 +1,86 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_arm_writer_thumb_h
+#define platforms_arch_arm_writer_thumb_h
+
+#include <string.h>
+
+// platforms
+#include "instructions.h"
+#include "reader-thumb.h"
+#include "regs-arm.h"
+#include "writer-thumb.h"
+
+// hookzz
+#include "writer.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef ZzWriter ZzThumbWriter;
+
+typedef enum _ZzThumbMemoryOperation { ZZ_THUMB_MEMORY_LOAD, ZZ_THUMB_MEMORY_STORE } ZzThumbMemoryOperation;
+
+// ------- user custom -------
+
+void zz_thumb_writer_put_ldr_b_reg_address(ZzThumbWriter *self, ZzARMReg reg, zaddr address);
+
+// ------- architecture default -------
+
+ZzThumbWriter *zz_thumb_writer_new(zpointer data_ptr);
+void zz_thumb_writer_init(ZzThumbWriter *self, zpointer data_ptr);
+void zz_thumb_writer_reset(ZzThumbWriter *self, zpointer data_ptr);
+zsize zz_thumb_writer_near_jump_range_size();
+void zz_thumb_writer_put_nop(ZzThumbWriter *self);
+void zz_thumb_writer_put_bytes(ZzThumbWriter *self, zbyte *data, zuint data_size);
+void zz_thumb_writer_put_instruction(ZzThumbWriter *self, uint16_t insn);
+void zz_thumb_writer_put_b_imm(ZzThumbWriter *self, zuint32 imm);
+void zz_thumb_writer_put_bx_reg(ZzThumbWriter *self, ZzARMReg reg);
+void zz_thumb_writer_put_blx_reg(ZzThumbWriter *self, ZzARMReg reg);
+void zz_thumb_writer_put_branch_imm(ZzThumbWriter *self, zuint32 imm, zbool link, zbool thumb);
+void zz_thumb_writer_put_bl_imm(ZzThumbWriter *self, zuint32 imm);
+void zz_thumb_writer_put_blx_imm(ZzThumbWriter *self, zuint32 imm);
+void zz_thumb_writer_put_b_imm32(ZzThumbWriter *self, zuint32 imm);
+
+void zz_thumb_writer_put_ldr_reg_imm(ZzThumbWriter *self, ZzARMReg reg, zint32 imm);
+void zz_thumb_writer_put_ldr_reg_address(ZzThumbWriter *self, ZzARMReg reg, zaddr address);
+
+static void zz_thumb_writer_put_transfer_reg_reg_offset(ZzThumbWriter *self, ZzThumbMemoryOperation operation,
+                                                        ZzARMReg left_reg, ZzARMReg right_reg, zint32 right_offset);
+void zz_thumb_writer_put_ldr_reg_reg_offset(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg, zint32 src_offset);
+void zz_thumb_writer_put_str_reg_reg_offset(ZzThumbWriter *self, ZzARMReg src_reg, ZzARMReg dst_reg, zint32 dst_offset);
+void zz_thumb_writer_put_str_index_reg_reg_offset(ZzThumbWriter *self, ZzARMReg src_reg, ZzARMReg dst_reg,
+                                                  zint32 dst_offset, zbool index);
+void zz_thumb_writer_put_ldr_index_reg_reg_offset(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg,
+                                                  zint32 src_offset, zbool index);
+void zz_thumb_writer_put_str_reg_reg(ZzThumbWriter *self, ZzARMReg src_reg, ZzARMReg dst_reg);
+void zz_thumb_writer_put_ldr_reg_reg(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg src_reg);
+void zz_thumb_writer_put_add_reg_imm(ZzThumbWriter *self, ZzARMReg dst_reg, zint32 imm);
+void zz_thumb_writer_put_sub_reg_imm(ZzThumbWriter *self, ZzARMReg dst_reg, zint32 imm);
+void zz_thumb_writer_put_add_reg_reg_imm(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg left_reg, zint32 right_value);
+void zz_thumb_writer_put_sub_reg_reg_imm(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg left_reg, zint32 right_value);
+void zz_thumb_writer_put_push_reg(ZzThumbWriter *self, ZzARMReg reg);
+void zz_thumb_writer_put_pop_reg(ZzThumbWriter *self, ZzARMReg reg);
+void zz_thumb_writer_put_add_reg_reg_reg(ZzThumbWriter *self, ZzARMReg dst_reg, ZzARMReg left_reg, ZzARMReg right_reg);
+ZzLiteralInstruction *zz_thumb_writer_put_ldr_reg_relocate_address(ZzThumbWriter *self, ZzARMReg reg, zaddr address,
+                                                                   ZzLiteralInstruction **literal_insn_ptr);
+ZzLiteralInstruction *zz_thumb_writer_put_ldr_b_reg_relocate_address(ZzThumbWriter *self, ZzARMReg reg, zaddr address,
+                                                                     ZzLiteralInstruction **literal_insn_ptr);
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/instructions.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/instructions.c
new file mode 100644
index 000000000..00ec11d1c
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/instructions.c
@@ -0,0 +1,21 @@
+#include "instructions.h"
+#include <string.h>
+
+zuint32 get_insn_sub(zuint32 insn, int start, int length) { return (insn >> start) & ((1 << length) - 1); }
+
+zbool insn_equal(zuint32 insn, char *opstr) {
+    zuint32 mask = 0, value = 0;
+    zsize length = strlen(opstr);
+    int i, j;
+    for (i = length - 1, j = 0; i >= 0 && j < length; i--, j++) {
+        if (opstr[i] == 'x') {
+            mask = mask | (0 << j);
+        } else if (opstr[i] == '0') {
+            mask = mask | (1 << j);
+        } else if (opstr[i] == '1') {
+            value = value | (1 << j);
+            mask = mask | (1 << j);
+        }
+    }
+    return (insn & mask) == value;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/instructions.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/instructions.h
new file mode 100644
index 000000000..d16d377c5
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/instructions.h
@@ -0,0 +1,38 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_arm64_instructions_h
+#define platforms_arch_arm64_instructions_h
+
+#include "hookzz.h"
+
+typedef struct _ZzInstruction {
+    zaddr pc;
+    zaddr address;
+    uint8_t size;
+    zuint32 insn;
+} ZzInstruction;
+
+typedef struct _ZzRelocateInstruction {
+    const ZzInstruction *insn_ctx;
+    zaddr relocated_offset;
+    zsize relocated_length;
+} ZzRelocateInstruction;
+
+zuint32 get_insn_sub(zuint32 insn, int start, int length);
+zbool insn_equal(zuint32 insn, char *opstr);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/reader-arm64.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/reader-arm64.c
new file mode 100644
index 000000000..37c3b4f9a
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/reader-arm64.c
@@ -0,0 +1,61 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "reader-arm64.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+zpointer zz_arm64_reader_read_one_instruction(ZzInstruction *insn_ctx, zpointer address) {
+    insn_ctx->address = (zaddr)address;
+    insn_ctx->size = 4;
+    insn_ctx->pc = (zaddr)address;
+    insn_ctx->insn = *(zuint32 *)address;
+    return (zpointer)insn_ctx->pc;
+}
+
+ARM64InsnType GetARM64InsnType(zuint32 insn) {
+    // PAGE: C6-673
+    if (insn_equal(insn, "01011000xxxxxxxxxxxxxxxxxxxxxxxx")) {
+        return ARM64_INS_LDR_literal;
+    }
+
+    // PAGE: C6-535
+    if (insn_equal(insn, "0xx10000xxxxxxxxxxxxxxxxxxxxxxxx")) {
+        return ARM64_INS_ADR;
+    }
+
+    // PAGE: C6-536
+    if (insn_equal(insn, "1xx10000xxxxxxxxxxxxxxxxxxxxxxxx")) {
+        return ARM64_INS_ADRP;
+    }
+
+    // PAGE: C6-550
+    if (insn_equal(insn, "000101xxxxxxxxxxxxxxxxxxxxxxxxxx")) {
+        return ARM64_INS_B;
+    }
+
+    // PAGE: C6-560
+    if (insn_equal(insn, "100101xxxxxxxxxxxxxxxxxxxxxxxxxx")) {
+        return ARM64_INS_BL;
+    }
+
+    // PAGE: C6-549
+    if (insn_equal(insn, "01010100xxxxxxxxxxxxxxxxxxx0xxxx")) {
+        return ARM64_INS_B_cond;
+    }
+
+    return ARM64_UNDEF;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/reader-arm64.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/reader-arm64.h
new file mode 100644
index 000000000..47a063052
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/reader-arm64.h
@@ -0,0 +1,44 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_arm64_reader_h
+#define platforms_arch_arm64_reader_h
+
+// platforms
+#include "instructions.h"
+
+// hookzz
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef enum _ARM64InsnType {
+    ARM64_INS_LDR_literal,
+    ARM64_INS_ADR,
+    ARM64_INS_ADRP,
+    ARM64_INS_B,
+    ARM64_INS_BL,
+    ARM64_INS_B_cond,
+    ARM64_UNDEF
+} ARM64InsnType;
+
+ARM64InsnType GetARM64InsnType(zuint32 insn);
+zpointer zz_arm64_reader_read_one_instruction(ZzInstruction *insn_ctx, zpointer address);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/regs-arm64.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/regs-arm64.c
new file mode 100644
index 000000000..ee3485280
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/regs-arm64.c
@@ -0,0 +1,44 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "regs-arm64.h"
+
+void zz_arm64_register_describe(ZzARM64Reg reg, ZzArm64RegInfo *ri) {
+    if (reg >= ZZ_ARM64_REG_X0 && reg <= ZZ_ARM64_REG_X28) {
+        ri->is_integer = TRUE;
+        ri->width = 64;
+        ri->meta = ZZ_ARM64_REG_X0 + (reg - ZZ_ARM64_REG_X0);
+    } else if (reg == ZZ_ARM64_REG_X29 || reg == ZZ_ARM64_REG_FP) {
+        ri->is_integer = TRUE;
+        ri->width = 64;
+        ri->meta = ZZ_ARM64_REG_X29;
+    } else if (reg == ZZ_ARM64_REG_X30 || reg == ZZ_ARM64_REG_LR) {
+        ri->is_integer = TRUE;
+        ri->width = 64;
+        ri->meta = ZZ_ARM64_REG_X30;
+    } else if (reg == ZZ_ARM64_REG_SP) {
+        ri->is_integer = TRUE;
+        ri->width = 64;
+        ri->meta = ZZ_ARM64_REG_X31;
+    } else {
+        Serror("zz_arm64_register_describe error.");
+#if defined(DEBUG_MODE)
+        debug_break();
+#endif
+        ri->index = 0;
+    }
+    ri->index = ri->meta - ZZ_ARM64_REG_X0;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/regs-arm64.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/regs-arm64.h
new file mode 100644
index 000000000..2e54247f2
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/regs-arm64.h
@@ -0,0 +1,78 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_arm64_regs_h
+#define platforms_arch_arm64_regs_h
+
+// platforms
+#include "instructions.h"
+
+// hookzz
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef enum _ZzARM64Reg {
+    ZZ_ARM64_REG_X0 = 0,
+    ZZ_ARM64_REG_X1,
+    ZZ_ARM64_REG_X2,
+    ZZ_ARM64_REG_X3,
+    ZZ_ARM64_REG_X4,
+    ZZ_ARM64_REG_X5,
+    ZZ_ARM64_REG_X6,
+    ZZ_ARM64_REG_X7,
+    ZZ_ARM64_REG_X8,
+    ZZ_ARM64_REG_X9,
+    ZZ_ARM64_REG_X10,
+    ZZ_ARM64_REG_X11,
+    ZZ_ARM64_REG_X12,
+    ZZ_ARM64_REG_X13,
+    ZZ_ARM64_REG_X14,
+    ZZ_ARM64_REG_X15,
+    ZZ_ARM64_REG_X16,
+    ZZ_ARM64_REG_X17,
+    ZZ_ARM64_REG_X18,
+    ZZ_ARM64_REG_X19,
+    ZZ_ARM64_REG_X20,
+    ZZ_ARM64_REG_X21,
+    ZZ_ARM64_REG_X22,
+    ZZ_ARM64_REG_X23,
+    ZZ_ARM64_REG_X24,
+    ZZ_ARM64_REG_X25,
+    ZZ_ARM64_REG_X26,
+    ZZ_ARM64_REG_X27,
+    ZZ_ARM64_REG_X28,
+    ZZ_ARM64_REG_X29,
+    ZZ_ARM64_REG_X30,
+    ZZ_ARM64_REG_X31,
+    ZZ_ARM64_REG_FP = ZZ_ARM64_REG_X29,
+    ZZ_ARM64_REG_LR = ZZ_ARM64_REG_X30,
+    ZZ_ARM64_REG_SP = ZZ_ARM64_REG_X31
+} ZzARM64Reg;
+
+typedef struct _ZzArm64RegInfo {
+    zuint index;
+    zuint meta;
+    zuint width;
+    zbool is_integer;
+} ZzArm64RegInfo;
+
+void zz_arm64_register_describe(ZzARM64Reg reg, ZzArm64RegInfo *ri);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/relocator-arm64.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/relocator-arm64.c
new file mode 100644
index 000000000..b64dbd8db
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/relocator-arm64.c
@@ -0,0 +1,306 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "relocator-arm64.h"
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_RELOCATOR_INSTRUCIONS_SIZE 64
+
+void zz_arm64_relocator_init(ZzArm64Relocator *relocator, zpointer input_code, ZzArm64Writer *output) {
+    relocator->inpos = 0;
+    relocator->outpos = 0;
+    relocator->output = output;
+    relocator->input_start = input_code;
+    relocator->input_cur = input_code;
+    relocator->input_pc = (zaddr)input_code;
+    relocator->relocate_literal_insns_size = 0;
+    relocator->try_relocated_length = 0;
+
+    relocator->input_insns = (ZzInstruction *)malloc(MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzInstruction));
+    memset(relocator->input_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzInstruction));
+    relocator->output_insns =
+        (ZzRelocateInstruction *)malloc(MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzRelocateInstruction));
+    memset(relocator->output_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzRelocateInstruction));
+    relocator->relocate_literal_insns =
+        (ZzLiteralInstruction **)malloc(MAX_LITERAL_INSN_SIZE * sizeof(ZzLiteralInstruction *));
+    memset(relocator->relocate_literal_insns, 0, MAX_LITERAL_INSN_SIZE * sizeof(ZzLiteralInstruction *));
+}
+
+void zz_arm64_relocator_reset(ZzArm64Relocator *self, zpointer input_code, ZzArm64Writer *output) {
+    self->input_cur = input_code;
+    self->input_start = input_code;
+    self->input_pc = (zaddr)input_code;
+    self->inpos = 0;
+    self->outpos = 0;
+    self->output = output;
+    self->relocate_literal_insns_size = 0;
+    self->try_relocated_length = 0;
+
+    memset(self->input_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzInstruction));
+    memset(self->output_insns, 0, MAX_RELOCATOR_INSTRUCIONS_SIZE * sizeof(ZzRelocateInstruction));
+    memset(self->relocate_literal_insns, 0, MAX_LITERAL_INSN_SIZE * sizeof(ZzLiteralInstruction *));
+}
+
+zsize zz_arm64_relocator_read_one(ZzArm64Relocator *self, ZzInstruction *instruction) {
+    ZzInstruction *insn_ctx = &self->input_insns[self->inpos];
+    ZzRelocateInstruction *re_insn_ctx = &self->output_insns[self->inpos];
+
+    re_insn_ctx->insn_ctx = insn_ctx;
+    zz_arm64_reader_read_one_instruction(insn_ctx, self->input_cur);
+
+    // switch (0) {}
+
+    self->inpos++;
+
+    if (instruction != NULL)
+        *instruction = *insn_ctx;
+
+    self->input_cur += insn_ctx->size;
+    self->input_pc += insn_ctx->size;
+
+    return self->input_cur - self->input_start;
+}
+
+zaddr zz_arm64_relocator_get_insn_relocated_offset(ZzArm64Relocator *self, zaddr address) {
+    const ZzInstruction *insn_ctx;
+    const ZzRelocateInstruction *re_insn_ctx;
+    int i;
+    for (i = 0; i < self->inpos; i++) {
+        re_insn_ctx = &self->output_insns[i];
+        insn_ctx = re_insn_ctx->insn_ctx;
+        if (insn_ctx->address == address && re_insn_ctx->relocated_offset) {
+            return re_insn_ctx->relocated_offset;
+        }
+    }
+    return 0;
+}
+
+void zz_arm64_relocator_relocate_writer(ZzArm64Relocator *relocator, zaddr code_address) {
+    ZzArm64Writer *arm64_writer;
+    arm64_writer = relocator->output;
+    if (relocator->relocate_literal_insns_size) {
+        int i;
+        zaddr *rebase_ptr;
+        zaddr literal_address, relocated_offset, relocated_address, *literal_address_ptr;
+        for (i = 0; i < relocator->relocate_literal_insns_size; i++) {
+            literal_address_ptr = relocator->relocate_literal_insns[i]->literal_address_ptr;
+            literal_address = *literal_address_ptr;
+            relocated_offset = zz_arm64_relocator_get_insn_relocated_offset(relocator, literal_address);
+            if (relocated_offset) {
+                relocated_address = code_address + relocated_offset;
+                *literal_address_ptr = relocated_address;
+            }
+        }
+    }
+}
+
+void zz_arm64_relocator_write_all(ZzArm64Relocator *self) {
+    zuint count = 0;
+    zuint outpos = self->outpos;
+    ZzArm64Writer arm64_writer = *self->output;
+
+    while (zz_arm64_relocator_write_one(self))
+        count++;
+}
+
+void zz_arm64_relocator_try_relocate(zpointer address, zuint min_bytes, zuint *max_bytes) {
+    int tmp_size = 0;
+    zpointer target_addr;
+    ZzInstruction insn_ctx;
+    zbool early_end = FALSE;
+    target_addr = (zpointer)address;
+
+    do {
+        zz_arm64_reader_read_one_instruction(&insn_ctx, target_addr);
+        switch (GetARM64InsnType(insn_ctx.insn)) {
+        case ARM64_INS_B:
+            early_end = TRUE;
+            break;
+        default:;
+        }
+        tmp_size += insn_ctx.size;
+        target_addr = target_addr + insn_ctx.size;
+    } while (tmp_size < min_bytes);
+
+    if (early_end) {
+        *max_bytes = tmp_size;
+    }
+    return;
+}
+
+// PAGE: C6-673
+static zbool zz_arm64_relocator_rewrite_LDR_literal(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                                    ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    // TODO: check opc == 10, with signed
+    zuint32 imm19 = get_insn_sub(insn, 5, 19);
+    zuint64 offset = imm19 << 2;
+
+    zaddr target_address;
+    target_address = insn_ctx->pc + offset;
+    int Rt_ndx = get_insn_sub(insn, 0, 4);
+
+    zz_arm64_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+    zz_arm64_writer_put_ldr_reg_reg_offset(self->output, Rt_ndx, Rt_ndx, 0);
+
+    return TRUE;
+}
+
+// PAGE: C6-535
+static zbool zz_arm64_relocator_rewrite_ADR(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                            ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 immhi = get_insn_sub(insn, 5, 19);
+    zuint32 immlo = get_insn_sub(insn, 29, 2);
+    zuint64 imm = immhi << 2 | immlo;
+
+    zaddr target_address;
+    target_address = insn_ctx->pc + imm;
+    int Rt_ndx = get_insn_sub(insn, 0, 4);
+
+    zz_arm64_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+
+    return TRUE;
+}
+
+// PAGE: C6-536
+static zbool zz_arm64_relocator_rewrite_ADRP(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                             ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 immhi = get_insn_sub(insn, 5, 19);
+    zuint32 immlo = get_insn_sub(insn, 29, 2);
+    // 12 is PAGE-SIZE
+    zuint64 imm = immhi << 2 << 12 | immlo << 12;
+
+    zaddr target_address;
+    target_address = (insn_ctx->pc & 0xFFFFFFFFFFFFF000) + imm;
+    int Rt_ndx = get_insn_sub(insn, 0, 4);
+
+    zz_arm64_writer_put_ldr_b_reg_address(self->output, Rt_ndx, target_address);
+
+    return TRUE;
+}
+
+// PAGE: C6-550
+static zbool zz_arm64_relocator_rewrite_B(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                          ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 imm26 = get_insn_sub(insn, 0, 26);
+
+    zuint64 offset = imm26 << 2;
+
+    zaddr target_address;
+    target_address = insn_ctx->pc + offset;
+
+    zz_arm64_writer_put_ldr_br_reg_address(self->output, ZZ_ARM64_REG_X17, target_address);
+
+    return TRUE;
+}
+
+// PAGE: C6-560
+static zbool zz_arm64_relocator_rewrite_BL(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                           ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 imm26 = get_insn_sub(insn, 0, 26);
+
+    zuint64 offset = imm26 << 2;
+
+    zaddr target_address;
+    target_address = insn_ctx->pc + offset;
+
+    zz_arm64_writer_put_ldr_blr_b_reg_address(self->output, ZZ_ARM64_REG_X17, target_address);
+    ZzLiteralInstruction **literal_insn_ptr = &(self->relocate_literal_insns[self->relocate_literal_insns_size++]);
+    zz_arm64_writer_put_ldr_br_reg_relocate_address(self->output, ZZ_ARM64_REG_X17, insn_ctx->pc + 4, literal_insn_ptr);
+
+    return TRUE;
+}
+
+// 0x000 : b.cond 0x8;
+
+// 0x004 : b 0x14
+
+// 0x008 : ldr x17, [pc, #4]
+// 0x00c : br x17
+// 0x010 : .long 0x0
+// 0x014 : .long 0x0
+
+// 0x018 : remain code
+
+// PAGE: C6-549
+static zbool zz_arm64_relocator_rewrite_B_cond(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                               ZzRelocateInstruction *re_insn_ctx) {
+    zuint32 insn = insn_ctx->insn;
+    zuint32 imm19 = get_insn_sub(insn, 5, 19);
+
+    zuint64 offset = imm19 << 2;
+
+    zaddr target_address;
+    target_address = insn_ctx->pc + offset;
+
+    zuint32 cond = get_insn_sub(insn, 0, 4);
+
+    zz_arm64_writer_put_b_cond_imm(self->output, cond, 0x8);
+    zz_arm64_writer_put_b_imm(self->output, 0x14);
+    zz_arm64_writer_put_ldr_br_reg_address(self->output, ZZ_ARM64_REG_X17, target_address);
+
+    return TRUE;
+}
+
+zbool zz_arm64_relocator_write_one(ZzArm64Relocator *self) {
+    const ZzInstruction *insn_ctx;
+    ZzRelocateInstruction *re_insn_ctx;
+
+    zbool rewritten = FALSE;
+
+    if (self->inpos != self->outpos) {
+        insn_ctx = &self->input_insns[self->outpos];
+        re_insn_ctx = &self->output_insns[self->outpos];
+        self->outpos++;
+    } else
+        return FALSE;
+
+    re_insn_ctx->relocated_offset = (zaddr)self->output->pc - (zaddr)self->output->base;
+
+    switch (GetARM64InsnType(insn_ctx->insn)) {
+    case ARM64_INS_LDR_literal:
+        rewritten = zz_arm64_relocator_rewrite_LDR_literal(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM64_INS_ADR:
+        rewritten = zz_arm64_relocator_rewrite_ADR(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM64_INS_ADRP:
+        rewritten = zz_arm64_relocator_rewrite_ADRP(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM64_INS_B:
+        rewritten = zz_arm64_relocator_rewrite_B(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM64_INS_BL:
+        rewritten = zz_arm64_relocator_rewrite_BL(self, insn_ctx, re_insn_ctx);
+        break;
+    case ARM64_INS_B_cond:
+        rewritten = zz_arm64_relocator_rewrite_B_cond(self, insn_ctx, re_insn_ctx);
+        break;
+    default:
+        rewritten = FALSE;
+        break;
+    }
+    if (!rewritten)
+        zz_arm64_writer_put_bytes(self->output, (zbyte *)&insn_ctx->insn, insn_ctx->size);
+    re_insn_ctx->relocated_length =
+        (zaddr)self->output->pc - (zaddr)self->output->base - (zaddr)re_insn_ctx->relocated_offset;
+    return TRUE;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/relocator-arm64.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/relocator-arm64.h
new file mode 100644
index 000000000..e0d8824e1
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/relocator-arm64.h
@@ -0,0 +1,69 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_arm64_relocator_h
+#define platforms_arch_arm64_relocator_h
+
+// platforms
+#include "instructions.h"
+#include "reader-arm64.h"
+#include "regs-arm64.h"
+#include "writer-arm64.h"
+
+// hookzz
+#include "writer.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef struct _ZzArm64Relocator {
+    zbool try_relocated_again;
+    zsize try_relocated_length;
+    zpointer input_start;
+    zpointer input_cur;
+    zaddr input_pc;
+    zuint inpos;
+    zuint outpos;
+    ZzInstruction *input_insns;
+    ZzRelocateInstruction *output_insns;
+    ZzArm64Writer *output;
+    ZzLiteralInstruction **relocate_literal_insns;
+    zsize relocate_literal_insns_size;
+} ZzArm64Relocator;
+
+void zz_arm64_relocator_init(ZzArm64Relocator *relocator, zpointer input_code, ZzArm64Writer *writer);
+void zz_arm64_relocator_reset(ZzArm64Relocator *self, zpointer input_code, ZzArm64Writer *output);
+
+zsize zz_arm64_relocator_read_one(ZzArm64Relocator *self, ZzInstruction *instruction);
+zbool zz_arm64_relocator_write_one(ZzArm64Relocator *self);
+void zz_arm64_relocator_write_all(ZzArm64Relocator *self);
+void zz_arm64_relocator_try_relocate(zpointer address, zuint min_bytes, zuint *max_bytes);
+
+/* rewrite */
+static zbool zz_arm64_relocator_rewrite_ldr(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                            ZzRelocateInstruction *re_insn_ctx);
+static zbool zz_arm64_relocator_rewrite_adr(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                            ZzRelocateInstruction *re_insn_ctx);
+static zbool zz_arm64_relocator_rewrite_b(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                          ZzRelocateInstruction *re_insn_ctx);
+static zbool zz_arm64_relocator_rewrite_b_cond(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                               ZzRelocateInstruction *re_insn_ctx);
+static zbool zz_arm64_relocator_rewrite_bl(ZzArm64Relocator *self, const ZzInstruction *insn_ctx,
+                                           ZzRelocateInstruction *re_insn_ctx);
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/writer-arm64.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/writer-arm64.c
new file mode 100644
index 000000000..ae1ec1353
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/writer-arm64.c
@@ -0,0 +1,258 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <string.h>
+
+#include "writer-arm64.h"
+#include <stdlib.h>
+
+// REF:
+// ARM Architecture Reference Manual ARMV8
+// C2.1 Understanding the A64 instruction descriptions
+// C2.1.3 The instruction encoding or encodings
+
+// ATTENTION !!!:
+// 写 writer 部分, 需要参考, `Instrcution Set Encoding` 部分
+// `witer` REF: `ZzInstruction Set Encoding`
+
+ZzArm64Writer *zz_arm64_writer_new(zpointer data_ptr) {
+    ZzArm64Writer *writer = (ZzArm64Writer *)malloc(sizeof(ZzArm64Writer));
+    memset(writer, 0, sizeof(ZzArm64Writer));
+
+    zaddr align_address = (zaddr)data_ptr & ~(zaddr)3;
+    writer->codedata = (zpointer)align_address;
+    writer->base = (zpointer)align_address;
+    writer->pc = align_address;
+    writer->size = 0;
+
+    writer->literal_insn_size = 0;
+    memset(writer->literal_insns, 0, sizeof(ZzLiteralInstruction) * MAX_LITERAL_INSN_SIZE);
+
+    return writer;
+}
+
+void zz_arm64_writer_init(ZzArm64Writer *self, zpointer target_addr) { zz_arm64_writer_reset(self, target_addr); }
+
+void zz_arm64_writer_reset(ZzArm64Writer *self, zpointer data_ptr) {
+    zaddr align_address = (zaddr)data_ptr & ~(zaddr)3;
+
+    self->codedata = (zpointer)align_address;
+    self->base = (zpointer)align_address;
+    self->pc = align_address;
+    self->size = 0;
+
+    self->literal_insn_size = 0;
+    memset(self->literal_insns, 0, sizeof(ZzLiteralInstruction) * MAX_LITERAL_INSN_SIZE);
+}
+
+// ======= relocator =======
+
+ZzLiteralInstruction *zz_arm64_writer_put_ldr_br_reg_relocate_address(ZzWriter *self, ZzARM64Reg reg, zaddr address,
+                                                                      ZzLiteralInstruction **literal_insn_ptr) {
+
+    zz_arm64_writer_put_ldr_br_reg_address(self, reg, address);
+    ZzLiteralInstruction *literal_insn = &(self->literal_insns[self->literal_insn_size - 1]);
+    *literal_insn_ptr = literal_insn;
+    return literal_insn;
+}
+
+// ======= user custom =======
+
+void zz_arm64_writer_put_ldr_br_reg_address(ZzWriter *self, ZzARM64Reg reg, zaddr address) {
+    self->literal_insns[self->literal_insn_size].literal_insn_ptr = self->codedata;
+    zz_arm64_writer_put_ldr_reg_imm(self, reg, (zuint)0x8);
+    zz_arm64_writer_put_br_reg(self, reg);
+    self->literal_insns[self->literal_insn_size++].literal_address_ptr = self->codedata;
+    zz_arm64_writer_put_bytes(self, (zpointer)&address, sizeof(zpointer));
+}
+
+void zz_arm64_writer_put_ldr_blr_b_reg_address(ZzWriter *self, ZzARM64Reg reg, zaddr address) {
+    self->literal_insns[self->literal_insn_size].literal_insn_ptr = self->codedata;
+    zz_arm64_writer_put_ldr_reg_imm(self, reg, (zuint)0xc);
+    zz_arm64_writer_put_blr_reg(self, reg);
+    zz_arm64_writer_put_b_imm(self, 0xc);
+    self->literal_insns[self->literal_insn_size++].literal_address_ptr = self->codedata;
+    zz_arm64_writer_put_bytes(self, (zpointer)&address, sizeof(zpointer));
+}
+
+void zz_arm64_writer_put_ldr_b_reg_address(ZzWriter *self, ZzARM64Reg reg, zaddr address) {
+    self->literal_insns[self->literal_insn_size].literal_insn_ptr = self->codedata;
+    zz_arm64_writer_put_ldr_reg_imm(self, reg, (zuint)0x8);
+    zz_arm64_writer_put_b_imm(self, 0xc);
+    self->literal_insns[self->literal_insn_size++].literal_address_ptr = self->codedata;
+    zz_arm64_writer_put_bytes(self, (zpointer)&address, sizeof(address));
+}
+
+zsize zz_arm64_writer_near_jump_range_size() { return ((1 << 25) << 2); }
+
+void zz_arm64_writer_put_ldr_br_b_reg_address(ZzWriter *self, ZzARM64Reg reg, zaddr address) {
+    self->literal_insns[self->literal_insn_size].literal_insn_ptr = self->codedata;
+    zz_arm64_writer_put_ldr_reg_imm(self, reg, (zuint)0xc);
+    zz_arm64_writer_put_br_reg(self, reg);
+    zz_arm64_writer_put_b_imm(self, 0xc);
+    self->literal_insns[self->literal_insn_size++].literal_address_ptr = self->codedata;
+    zz_arm64_writer_put_bytes(self, (zpointer)&address, sizeof(address));
+}
+
+// ======= default =======
+
+void zz_arm64_writer_put_ldr_reg_imm(ZzWriter *self, ZzARM64Reg reg, zuint32 offset) {
+    ZzArm64RegInfo ri;
+    zz_arm64_register_describe(reg, &ri);
+
+    zuint32 imm19, Rt_ndx;
+
+    imm19 = offset >> 2;
+    Rt_ndx = ri.index;
+
+    zz_arm64_writer_put_instruction(self, 0x58000000 | imm19 << 5 | Rt_ndx);
+    return;
+}
+
+// PAGE: C6-871
+void zz_arm64_writer_put_str_reg_reg_offset(ZzWriter *self, ZzARM64Reg src_reg, ZzARM64Reg dst_reg, zuint64 offset) {
+    ZzArm64RegInfo rs, rd;
+
+    zz_arm64_register_describe(src_reg, &rs);
+    zz_arm64_register_describe(dst_reg, &rd);
+
+    zuint32 size, v = 0, opc = 0, Rn_ndx, Rt_ndx;
+    Rn_ndx = rd.index;
+    Rt_ndx = rs.index;
+
+    if (rs.is_integer) {
+        size = (rs.width == 64) ? 0b11 : 0b10;
+    }
+
+    zuint32 imm12 = offset >> size;
+
+    zz_arm64_writer_put_instruction(self, 0x39000000 | size << 30 | opc << 22 | imm12 << 10 | Rn_ndx << 5 | Rt_ndx);
+    return;
+}
+
+void zz_arm64_writer_put_ldr_reg_reg_offset(ZzWriter *self, ZzARM64Reg dst_reg, ZzARM64Reg src_reg, zuint64 offset) {
+    ZzArm64RegInfo rs, rd;
+
+    zz_arm64_register_describe(src_reg, &rs);
+    zz_arm64_register_describe(dst_reg, &rd);
+
+    zuint32 size, v = 0, opc = 0b01, Rn_ndx, Rt_ndx;
+    Rn_ndx = rs.index;
+    Rt_ndx = rd.index;
+
+    if (rs.is_integer) {
+        size = (rs.width == 64) ? 0b11 : 0b10;
+    }
+
+    zuint32 imm12 = offset >> size;
+
+    zz_arm64_writer_put_instruction(self, 0x39000000 | size << 30 | opc << 22 | imm12 << 10 | Rn_ndx << 5 | Rt_ndx);
+    return;
+}
+
+// C6-562
+void zz_arm64_writer_put_br_reg(ZzWriter *self, ZzARM64Reg reg) {
+    ZzArm64RegInfo ri;
+    zz_arm64_register_describe(reg, &ri);
+
+    zuint32 op = 0, Rn_ndx;
+    Rn_ndx = ri.index;
+    zz_arm64_writer_put_instruction(self, 0xd61f0000 | op << 21 | Rn_ndx << 5);
+    return;
+}
+
+// C6-561
+void zz_arm64_writer_put_blr_reg(ZzWriter *self, ZzARM64Reg reg) {
+    ZzArm64RegInfo ri;
+    zz_arm64_register_describe(reg, &ri);
+
+    zuint32 op = 0b01, Rn_ndx;
+
+    Rn_ndx = ri.index;
+
+    zz_arm64_writer_put_instruction(self, 0xd63f0000 | op << 21 | Rn_ndx << 5);
+    return;
+}
+
+// C6-550
+void zz_arm64_writer_put_b_imm(ZzWriter *self, zuint64 offset) {
+    zuint32 op = 0b0, imm26;
+    imm26 = (offset >> 2) & 0x03ffffff;
+    zz_arm64_writer_put_instruction(self, 0x14000000 | op << 31 | imm26);
+    return;
+}
+
+// TODO: standard form, need fix others
+// PAGE: C6-549
+void zz_arm64_writer_put_b_cond_imm(ZzWriter *self, zuint32 condition, zuint64 imm) {
+    zuint32 imm19, cond;
+    cond = condition;
+    imm19 = (imm >> 2) & 0x7ffff;
+    zz_arm64_writer_put_instruction(self, 0x54000000 | imm19 << 5 | cond);
+    return;
+}
+
+// C6-525
+void zz_arm64_writer_put_add_reg_reg_imm(ZzWriter *self, ZzARM64Reg dst_reg, ZzARM64Reg left_reg, zuint64 imm) {
+    ZzArm64RegInfo rd, rl;
+
+    zz_arm64_register_describe(dst_reg, &rd);
+    zz_arm64_register_describe(left_reg, &rl);
+
+    zuint32 sf = 1, op = 0, S = 0, shift = 0b00, imm12, Rn_ndx, Rd_ndx;
+
+    Rd_ndx = rd.index;
+    Rn_ndx = rl.index;
+    imm12 = imm & 0xFFF;
+
+    zz_arm64_writer_put_instruction(self, 0x11000000 | sf << 31 | op << 30 | S << 29 | shift << 22 | imm12 << 10 |
+                                              Rn_ndx << 5 | Rd_ndx);
+    return;
+}
+
+// C6-930
+void zz_arm64_writer_put_sub_reg_reg_imm(ZzWriter *self, ZzARM64Reg dst_reg, ZzARM64Reg left_reg, zuint64 imm) {
+    ZzArm64RegInfo rd, rl;
+
+    zz_arm64_register_describe(dst_reg, &rd);
+    zz_arm64_register_describe(left_reg, &rl);
+
+    zuint32 sf = 1, op = 1, S = 0, shift = 0b00, imm12, Rn_ndx, Rd_ndx;
+
+    Rd_ndx = rd.index;
+    Rn_ndx = rl.index;
+    imm12 = imm & 0xFFF;
+
+    zz_arm64_writer_put_instruction(self, 0x11000000 | sf << 31 | op << 30 | S << 29 | shift << 22 | imm12 << 10 |
+                                              Rn_ndx << 5 | Rd_ndx);
+    return;
+}
+
+void zz_arm64_writer_put_bytes(ZzWriter *self, zbyte *data, zsize size) {
+    memcpy(self->codedata, data, size);
+    self->codedata = (zpointer)self->codedata + size;
+    self->pc += size;
+    self->size += size;
+    return;
+}
+
+void zz_arm64_writer_put_instruction(ZzWriter *self, zuint32 insn) {
+    *(zuint32 *)(self->codedata) = insn;
+    self->codedata = (zpointer)self->codedata + sizeof(zuint32);
+    self->pc += 4;
+    self->size += 4;
+    return;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/writer-arm64.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/writer-arm64.h
new file mode 100644
index 000000000..2b7370fbd
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-arm64/writer-arm64.h
@@ -0,0 +1,70 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_arm64_writer_h
+#define platforms_arch_arm64_writer_h
+
+// platforms
+#include "instructions.h"
+#include "regs-arm64.h"
+#include "writer-arm64.h"
+
+// hookzz
+#include "writer.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef ZzWriter ZzArm64Writer;
+
+ZzArm64Writer *zz_arm64_writer_new(zpointer data_ptr);
+
+void zz_arm64_writer_reset(ZzArm64Writer *self, zpointer data_ptr);
+
+void zz_arm64_writer_init(ZzArm64Writer *self, zpointer target_addr);
+
+zsize zz_arm64_writer_near_jump_range_size();
+
+// ======= user custom =======
+
+void zz_arm64_writer_put_ldr_br_reg_address(ZzArm64Writer *self, ZzARM64Reg reg, zaddr address);
+void zz_arm64_writer_put_ldr_blr_b_reg_address(ZzArm64Writer *self, ZzARM64Reg reg, zaddr address);
+void zz_arm64_writer_put_ldr_b_reg_address(ZzArm64Writer *self, ZzARM64Reg reg, zaddr address);
+void zz_arm64_writer_put_ldr_br_b_reg_address(ZzArm64Writer *self, ZzARM64Reg reg, zaddr address);
+
+// ======= default =======
+
+void zz_arm64_writer_put_ldr_reg_imm(ZzWriter *self, ZzARM64Reg reg, zuint32 offset);
+void zz_arm64_writer_put_str_reg_reg_offset(ZzWriter *self, ZzARM64Reg src_reg, ZzARM64Reg dst_reg, zuint64 offset);
+void zz_arm64_writer_put_ldr_reg_reg_offset(ZzWriter *self, ZzARM64Reg dst_reg, ZzARM64Reg src_reg, zuint64 offset);
+void zz_arm64_writer_put_br_reg(ZzWriter *self, ZzARM64Reg reg);
+void zz_arm64_writer_put_blr_reg(ZzWriter *self, ZzARM64Reg reg);
+void zz_arm64_writer_put_b_imm(ZzWriter *self, zuint64 offset);
+void zz_arm64_writer_put_b_cond_imm(ZzWriter *self, zuint32 condition, zuint64 imm);
+void zz_arm64_writer_put_add_reg_reg_imm(ZzWriter *self, ZzARM64Reg dst_reg, ZzARM64Reg left_reg, zuint64 imm);
+void zz_arm64_writer_put_sub_reg_reg_imm(ZzWriter *self, ZzARM64Reg dst_reg, ZzARM64Reg left_reg, zuint64 imm);
+void zz_arm64_writer_put_bytes(ZzWriter *self, zbyte *data, zsize size);
+void zz_arm64_writer_put_instruction(ZzWriter *self, zuint32 insn);
+
+// ======= relocator =======
+
+ZzLiteralInstruction *zz_arm64_writer_put_ldr_br_reg_relocate_address(ZzWriter *self, ZzARM64Reg reg, zaddr address,
+                                                                      ZzLiteralInstruction **literal_insn_ptr);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/instructions.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/instructions.c
new file mode 100644
index 000000000..dc09a0d61
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/instructions.c
@@ -0,0 +1,2 @@
+#include "instructions.h"
+#include <string.h>
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/instructions.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/instructions.h
new file mode 100644
index 000000000..adc4675f8
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/instructions.h
@@ -0,0 +1,31 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_x86_instructions_h
+#define platforms_arch_x86_instructions_h
+
+#include "hookzz.h"
+
+typedef struct _ZzInstruction {
+} ZzInstruction;
+
+typedef struct _ZzRelocateInstruction {
+    const ZzInstruction *insn_ctx;
+    zaddr relocated_offset;
+    zsize relocated_length;
+} ZzRelocateInstruction;
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/reader-x86.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/reader-x86.c
new file mode 100644
index 000000000..8185d3ed0
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/reader-x86.c
@@ -0,0 +1,23 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "reader-x86.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+zpointer zz_x86_reader_read_one_instruction(ZzInstruction *insn_ctx, zpointer address) { return NULL; }
+
+X86InsnType GetX86InsnType(zuint32 insn) { return X86_UNDEF; }
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/reader-x86.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/reader-x86.h
new file mode 100644
index 000000000..864610be2
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/reader-x86.h
@@ -0,0 +1,37 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_x86_reader_h
+#define platforms_arch_x86_reader_h
+
+// platforms
+#include "instructions.h"
+
+// hookzz
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef enum _X86InsnType { X86_UNDEF } X86InsnType;
+
+X86InsnType GetX86InsnType(zuint32 insn);
+
+zpointer zz_x86_reader_read_one_instruction(ZzInstruction *insn_ctx, zpointer address);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/regs-x86.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/regs-x86.c
new file mode 100644
index 000000000..91af33c98
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/regs-x86.c
@@ -0,0 +1,21 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "regs-x86.h"
+
+void zz_x86_register_describe(ZzX86Reg reg, ZzX86RegInfo *ri) {
+
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/regs-x86.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/regs-x86.h
new file mode 100644
index 000000000..377ee37a7
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/regs-x86.h
@@ -0,0 +1,39 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_x86_regs_h
+#define platforms_arch_x86_regs_h
+
+// platforms
+#include "instructions.h"
+
+// hookzz
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef enum _ZzX86Reg { X86_REG_UNDEF } ZzX86Reg;
+
+typedef struct _ZzX86RegInfo {
+
+} ZzX86RegInfo;
+
+void zz_x86_register_describe(ZzX86Reg reg, ZzX86RegInfo *ri);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/relocator-x86.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/relocator-x86.c
new file mode 100644
index 000000000..3380a97ae
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/relocator-x86.c
@@ -0,0 +1,48 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "relocator-x86.h"
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_RELOCATOR_INSTRUCIONS_SIZE 64
+
+void zz_x86_relocator_init(ZzX86Relocator *relocator, zpointer input_code, ZzX86Writer *output) {
+}
+
+void zz_x86_relocator_reset(ZzX86Relocator *self, zpointer input_code, ZzX86Writer *output) {
+}
+
+zsize zz_x86_relocator_read_one(ZzX86Relocator *self, ZzInstruction *instruction) {
+    return 0;
+}
+
+zaddr zz_x86_relocator_get_insn_relocated_offset(ZzX86Relocator *self, zaddr address) {
+    return 0;
+}
+
+void zz_x86_relocator_relocate_writer(ZzX86Relocator *relocator, zaddr code_address) {
+}
+
+void zz_x86_relocator_write_all(ZzX86Relocator *self) {
+}
+
+void zz_x86_relocator_try_relocate(zpointer address, zuint min_bytes, zuint *max_bytes) {
+}
+
+zbool zz_x86_relocator_write_one(ZzX86Relocator *self) {
+    return TRUE;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/relocator-x86.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/relocator-x86.h
new file mode 100644
index 000000000..ed6a1efda
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/relocator-x86.h
@@ -0,0 +1,58 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_x86_relocator_h
+#define platforms_arch_x86_relocator_h
+
+// platforms
+#include "instructions.h"
+#include "reader-x86.h"
+#include "regs-x86.h"
+#include "writer-x86.h"
+
+// hookzz
+#include "writer.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef struct _ZzX86Relocator {
+    zbool try_relocated_again;
+    zsize try_relocated_length;
+    zpointer input_start;
+    zpointer input_cur;
+    zaddr input_pc;
+    zuint inpos;
+    zuint outpos;
+    ZzInstruction *input_insns;
+    ZzRelocateInstruction *output_insns;
+    ZzX86Writer *output;
+    ZzLiteralInstruction **relocate_literal_insns;
+    zsize relocate_literal_insns_size;
+} ZzX86Relocator;
+
+void zz_x86_relocator_init(ZzX86Relocator *relocator, zpointer input_code, ZzX86Writer *writer);
+void zz_x86_relocator_reset(ZzX86Relocator *self, zpointer input_code, ZzX86Writer *output);
+
+zsize zz_x86_relocator_read_one(ZzX86Relocator *self, ZzInstruction *instruction);
+zbool zz_x86_relocator_write_one(ZzX86Relocator *self);
+void zz_x86_relocator_write_all(ZzX86Relocator *self);
+void zz_x86_relocator_try_relocate(zpointer address, zuint min_bytes, zuint *max_bytes);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/writer-x86.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/writer-x86.c
new file mode 100644
index 000000000..eb6afe6c7
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/writer-x86.c
@@ -0,0 +1,47 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "writer-x86.h"
+
+ZzX86Writer *zz_x86_writer_new(zpointer data_ptr) {
+    return NULL;
+}
+
+void zz_x86_writer_init(ZzX86Writer *self, zpointer target_addr) { zz_x86_writer_reset(self, target_addr); }
+
+void zz_x86_writer_reset(ZzX86Writer *self, zpointer data_ptr) {
+}
+
+zsize zz_x86_writer_near_jump_range_size() { return 0; }
+
+
+void zz_x86_writer_put_bytes(ZzWriter *self, zbyte *data, zsize size) {
+
+}
+
+void zz_x86_writer_put_instruction(ZzWriter *self, zuint32 insn) {
+
+}
+
+
+// ======= relocator =======
+
+// ======= user custom =======
+
+// ======= default =======
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/writer-x86.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/writer-x86.h
new file mode 100644
index 000000000..602921036
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/arch-x86/writer-x86.h
@@ -0,0 +1,52 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_arch_x86_writer_h
+#define platforms_arch_x86_writer_h
+
+// platforms
+#include "instructions.h"
+#include "regs-x86.h"
+#include "writer-x86.h"
+
+// hookzz
+#include "writer.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef ZzWriter ZzX86Writer;
+
+ZzX86Writer *zz_x86_writer_new(zpointer data_ptr);
+
+void zz_x86_writer_reset(ZzX86Writer *self, zpointer data_ptr);
+
+void zz_x86_writer_init(ZzX86Writer *self, zpointer target_addr);
+
+zsize zz_x86_writer_near_jump_range_size();
+void zz_x86_writer_put_bytes(ZzWriter *self, zbyte *data, zsize size);
+void zz_x86_writer_put_instruction(ZzWriter *self, zuint32 insn);
+
+// ======= user custom =======
+
+// ======= default =======
+
+// ======= relocator =======
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/interceptor-arm.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/interceptor-arm.c
new file mode 100644
index 000000000..c37bb132f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/interceptor-arm.c
@@ -0,0 +1,563 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "interceptor-arm.h"
+#include "zzinfo.h"
+
+#include <stdlib.h>
+
+#define ZZ_THUMB_TINY_REDIRECT_SIZE 4
+#define ZZ_THUMB_FULL_REDIRECT_SIZE 8
+#define ZZ_ARM_TINY_REDIRECT_SIZE 4
+#define ZZ_ARM_FULL_REDIRECT_SIZE 8
+
+ZzInterceptorBackend *ZzBuildInteceptorBackend(ZzAllocator *allocator) {
+    if (!ZzMemoryIsSupportAllocateRXPage()) {
+        return NULL;
+    }
+    ZZSTATUS status;
+    ZzInterceptorBackend *backend = (ZzInterceptorBackend *)malloc(sizeof(ZzInterceptorBackend));
+    memset(backend, 0, sizeof(ZzInterceptorBackend));
+
+    zz_arm_writer_init(&backend->arm_writer, NULL);
+    zz_arm_relocator_init(&backend->arm_relocator, NULL, &backend->arm_writer);
+    zz_thumb_writer_init(&backend->thumb_writer, NULL);
+    zz_thumb_relocator_init(&backend->thumb_relocator, NULL, &backend->thumb_writer);
+
+    backend->allocator = allocator;
+    backend->enter_thunk = NULL;
+    backend->half_thunk = NULL;
+    backend->leave_thunk = NULL;
+
+    status = ZzThunkerBuildThunk(backend);
+
+    if (status == ZZ_FAILED) {
+        ZzInfoLog("%s", "ZzThunkerBuildThunk return ZZ_FAILED\n");
+        return NULL;
+    }
+
+    return backend;
+}
+
+ZzCodeSlice *zz_code_patch_thumb_writer(ZzThumbWriter *thumb_writer, ZzAllocator *allocator, zaddr target_addr,
+                                        zsize range_size) {
+    ZzCodeSlice *code_slice = NULL;
+    if (range_size > 0) {
+        code_slice = ZzNewNearCodeSlice(allocator, target_addr, range_size, thumb_writer->size);
+    } else {
+        code_slice = ZzNewCodeSlice(allocator, thumb_writer->size + 4);
+    }
+    if (!code_slice)
+        return NULL;
+
+    if (!ZzMemoryPatchCode((zaddr)code_slice->data, thumb_writer->base, thumb_writer->size)) {
+
+        free(code_slice);
+        return NULL;
+    }
+    return code_slice;
+}
+
+ZzCodeSlice *zz_code_patch_thumb_relocate_writer(ZzThumbRelocator *thumb_relocator, ZzThumbWriter *thumb_writer,
+                                                 ZzAllocator *allocator, zaddr target_addr, zsize range_size) {
+    ZzCodeSlice *code_slice = NULL;
+    if (range_size > 0) {
+        code_slice = ZzNewNearCodeSlice(allocator, target_addr, range_size, thumb_writer->size);
+    } else {
+        code_slice = ZzNewCodeSlice(allocator, thumb_writer->size + 4);
+    }
+    if (!code_slice)
+        return NULL;
+
+    zz_thumb_relocator_relocate_writer(thumb_relocator, (zaddr)code_slice->data);
+
+    if (!ZzMemoryPatchCode((zaddr)code_slice->data, thumb_writer->base, thumb_writer->size)) {
+
+        free(code_slice);
+        return NULL;
+    }
+    return code_slice;
+}
+
+ZzCodeSlice *zz_code_patch_arm_writer(ZzArmWriter *arm_writer, ZzAllocator *allocator, zaddr target_addr,
+                                      zsize range_size) {
+    ZzCodeSlice *code_slice = NULL;
+    if (range_size > 0) {
+        code_slice = ZzNewNearCodeSlice(allocator, target_addr, range_size, arm_writer->size);
+    } else {
+        code_slice = ZzNewCodeSlice(allocator, arm_writer->size + 4);
+    }
+    if (!code_slice)
+        return NULL;
+
+    if (!ZzMemoryPatchCode((zaddr)code_slice->data, arm_writer->base, arm_writer->size)) {
+        free(code_slice);
+        return NULL;
+    }
+    return code_slice;
+}
+
+ZzCodeSlice *zz_code_patch_arm_relocate_writer(ZzArmRelocator *arm_relocator, ZzArmWriter *arm_writer,
+                                               ZzAllocator *allocator, zaddr target_addr, zsize range_size) {
+    ZzCodeSlice *code_slice = NULL;
+    if (range_size > 0) {
+        code_slice = ZzNewNearCodeSlice(allocator, target_addr, range_size, arm_writer->size);
+    } else {
+        code_slice = ZzNewCodeSlice(allocator, arm_writer->size + 4);
+    }
+    if (!code_slice)
+        return NULL;
+
+    zz_arm_relocator_relocate_writer(arm_relocator, (zaddr)code_slice->data);
+
+    if (!ZzMemoryPatchCode((zaddr)code_slice->data, arm_writer->base, arm_writer->size)) {
+        free(code_slice);
+        return NULL;
+    }
+    return code_slice;
+}
+
+ZZSTATUS ZzPrepareTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbool is_thumb = FALSE;
+    zaddr target_addr = (zaddr)entry->target_ptr;
+    zuint redirect_limit = 0;
+
+    ZzArmHookFunctionEntryBackend *entry_backend;
+    entry_backend = (ZzArmHookFunctionEntryBackend *)malloc(sizeof(ZzArmHookFunctionEntryBackend));
+    memset(entry_backend, 0, sizeof(ZzArmHookFunctionEntryBackend));
+
+    entry->backend = (struct _ZzHookFunctionEntryBackend *)entry_backend;
+
+    is_thumb = INSTRUCTION_IS_THUMB((zaddr)entry->target_ptr);
+    if (is_thumb)
+        target_addr = (zaddr)entry->target_ptr & ~(zaddr)1;
+
+    if (is_thumb) {
+        if (entry->try_near_jump) {
+            entry_backend->redirect_code_size = ZZ_THUMB_TINY_REDIRECT_SIZE;
+        } else {
+            zz_thumb_relocator_try_relocate((zpointer)target_addr, ZZ_THUMB_FULL_REDIRECT_SIZE, &redirect_limit);
+            if (redirect_limit != 0 && redirect_limit > ZZ_THUMB_TINY_REDIRECT_SIZE &&
+                redirect_limit < ZZ_THUMB_FULL_REDIRECT_SIZE) {
+                entry->try_near_jump = TRUE;
+                entry_backend->redirect_code_size = ZZ_THUMB_TINY_REDIRECT_SIZE;
+            } else if (redirect_limit != 0 && redirect_limit < ZZ_THUMB_TINY_REDIRECT_SIZE) {
+                return ZZ_FAILED;
+            } else {
+                entry_backend->redirect_code_size = ZZ_THUMB_FULL_REDIRECT_SIZE;
+                if (target_addr % 4) {
+                    entry_backend->redirect_code_size += 2;
+                }
+            }
+        }
+        self->thumb_relocator.try_relocated_length = entry_backend->redirect_code_size;
+    } else {
+        if (entry->try_near_jump) {
+            entry_backend->redirect_code_size = ZZ_ARM_TINY_REDIRECT_SIZE;
+        } else {
+            zz_arm_relocator_try_relocate((zpointer)target_addr, ZZ_ARM_FULL_REDIRECT_SIZE, &redirect_limit);
+            if (redirect_limit != 0 && redirect_limit > ZZ_ARM_TINY_REDIRECT_SIZE &&
+                redirect_limit < ZZ_ARM_FULL_REDIRECT_SIZE) {
+                entry->try_near_jump = TRUE;
+                entry_backend->redirect_code_size = ZZ_ARM_TINY_REDIRECT_SIZE;
+            } else if (redirect_limit != 0 && redirect_limit < ZZ_ARM_TINY_REDIRECT_SIZE) {
+                return ZZ_FAILED;
+            } else {
+                entry_backend->redirect_code_size = ZZ_ARM_FULL_REDIRECT_SIZE;
+            }
+        }
+        self->arm_relocator.try_relocated_length = entry_backend->redirect_code_size;
+    }
+
+    zz_arm_relocator_init(&self->arm_relocator, (zpointer)target_addr, &self->arm_writer);
+    zz_thumb_relocator_init(&self->thumb_relocator, (zpointer)target_addr, &self->thumb_writer);
+    return ZZ_SUCCESS;
+}
+
+ZZSTATUS ZzBuildEnterTransferTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzArmWriter *arm_writer = NULL;
+    ZzArmWriter *thumb_writer = NULL;
+    ZzCodeSlice *code_slice = NULL;
+    ZzArmHookFunctionEntryBackend *entry_backend = (ZzArmHookFunctionEntryBackend *)entry->backend;
+    ZZSTATUS status = ZZ_SUCCESS;
+    zbool is_thumb = TRUE;
+    zaddr target_addr = (zaddr)entry->target_ptr;
+
+    is_thumb = INSTRUCTION_IS_THUMB((zaddr)entry->target_ptr);
+    if (is_thumb)
+        target_addr = (zaddr)entry->target_ptr & ~(zaddr)1;
+
+    if (is_thumb) {
+        thumb_writer = &self->thumb_writer;
+        zz_thumb_writer_reset(thumb_writer, temp_code_slice_data);
+
+        /* jump to on_enter_trampoline */
+        zz_thumb_writer_put_ldr_reg_address(thumb_writer, ZZ_ARM_REG_PC, (zaddr)entry->on_enter_trampoline);
+
+        /* code patch */
+        code_slice = zz_code_patch_thumb_writer(thumb_writer, self->allocator, target_addr,
+                                                zz_thumb_writer_near_jump_range_size());
+        if (code_slice)
+            entry->on_enter_transfer_trampoline = code_slice->data;
+        else
+            return ZZ_FAILED;
+    } else {
+        arm_writer = &self->arm_writer;
+        zz_arm_writer_reset(arm_writer, temp_code_slice_data);
+
+        /* jump to on_enter_trampoline */
+        zz_arm_writer_put_ldr_reg_address(arm_writer, ZZ_ARM_REG_PC, (zaddr)entry->on_enter_trampoline);
+
+        /* code patch */
+        code_slice =
+            zz_code_patch_arm_writer(arm_writer, self->allocator, target_addr, zz_arm_writer_near_jump_range_size());
+        if (code_slice)
+            entry->on_enter_transfer_trampoline = code_slice->data;
+        else
+            return ZZ_FAILED;
+    }
+
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzBuildEnterTransferTrampoline:");
+        sprintf(buffer + strlen(buffer),
+                "LogInfo: on_enter_transfer_trampoline at %p, length: %ld. and will jump to "
+                "on_enter_trampoline(%p).\n",
+                code_slice->data, code_slice->size, entry->on_enter_trampoline);
+        ZzInfoLog("%s", buffer);
+    }
+
+    free(code_slice);
+    return status;
+}
+
+ZZSTATUS ZzBuildEnterTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzArmWriter *arm_writer = NULL;
+    ZzArmWriter *thumb_writer = NULL;
+    ZzCodeSlice *code_slice = NULL;
+    ZzArmHookFunctionEntryBackend *entry_backend = (ZzArmHookFunctionEntryBackend *)entry->backend;
+    ZZSTATUS status = ZZ_SUCCESS;
+    zbool is_thumb;
+
+    is_thumb = INSTRUCTION_IS_THUMB((zaddr)entry->target_ptr);
+
+    thumb_writer = &self->thumb_writer;
+    zz_thumb_writer_reset(thumb_writer, temp_code_slice_data);
+
+    /* prepare 2 stack space: 1. next_hop 2. entry arg */
+    zz_thumb_writer_put_sub_reg_imm(thumb_writer, ZZ_ARM_REG_SP, 0xc);
+    zz_thumb_writer_put_str_reg_reg_offset(thumb_writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x0); // push r7
+    zz_thumb_writer_put_ldr_b_reg_address(thumb_writer, ZZ_ARM_REG_R1, (zaddr)entry);
+    zz_thumb_writer_put_str_reg_reg_offset(thumb_writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x4);
+    zz_thumb_writer_put_ldr_reg_reg_offset(thumb_writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x0); // pop r7
+    zz_thumb_writer_put_add_reg_imm(thumb_writer, ZZ_ARM_REG_SP, 0x4);
+
+    /* jump to enter thunk */
+    zz_thumb_writer_put_ldr_reg_address(thumb_writer, ZZ_ARM_REG_PC, (zaddr)self->enter_thunk);
+
+    /* code patch */
+    code_slice = zz_code_patch_thumb_writer(thumb_writer, self->allocator, 0, 0);
+    if (code_slice)
+        entry->on_enter_trampoline = code_slice->data + 1;
+    else
+        return ZZ_FAILED;
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzBuildEnterTrampoline:");
+        sprintf(buffer + strlen(buffer),
+                "LogInfo: on_enter_trampoline at %p, length: %ld. hook-entry: %p. and will jump to "
+                "enter_thunk(%p)\n",
+                code_slice->data, code_slice->size, (void *)entry, (void *)self->enter_thunk);
+        ZzInfoLog("%s", buffer);
+    }
+
+    if ((is_thumb && entry_backend->redirect_code_size == ZZ_THUMB_TINY_REDIRECT_SIZE) ||
+        (!is_thumb && entry_backend->redirect_code_size == ZZ_ARM_TINY_REDIRECT_SIZE)) {
+        ZzBuildEnterTransferTrampoline(self, entry);
+    }
+
+    free(code_slice);
+    return status;
+}
+
+ZZSTATUS ZzBuildInvokeTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzCodeSlice *code_slice = NULL;
+    ZzArmHookFunctionEntryBackend *entry_backend = (ZzArmHookFunctionEntryBackend *)entry->backend;
+    ZZSTATUS status = ZZ_SUCCESS;
+    zbool is_thumb = TRUE;
+    zaddr target_addr = (zaddr)entry->target_ptr;
+    zpointer target_end_addr = 0;
+    zpointer restore_target_addr;
+
+    is_thumb = INSTRUCTION_IS_THUMB((zaddr)entry->target_ptr);
+    if (is_thumb)
+        target_addr = (zaddr)entry->target_ptr & ~(zaddr)1;
+
+    if (is_thumb) {
+        target_end_addr = (zpointer)((zaddr)entry->target_end_ptr & ~(zaddr)1);
+    }
+
+    if (is_thumb) {
+        ZzThumbRelocator *thumb_relocator;
+        ZzThumbWriter *thumb_writer;
+        thumb_relocator = &self->thumb_relocator;
+        thumb_writer = &self->thumb_writer;
+
+        zz_thumb_writer_reset(thumb_writer, temp_code_slice_data);
+        zz_thumb_relocator_reset(thumb_relocator, (zpointer)target_addr, thumb_writer);
+        zsize tmp_relocator_insn_size = 0;
+        entry->target_half_ret_addr = 0;
+
+        if (entry->hook_type == HOOK_FUNCTION_TYPE) {
+            do {
+                zz_thumb_relocator_read_one(thumb_relocator, NULL);
+                tmp_relocator_insn_size = thumb_relocator->input_cur - thumb_relocator->input_start;
+            } while (tmp_relocator_insn_size < entry_backend->redirect_code_size);
+            zz_thumb_relocator_write_all(thumb_relocator);
+        } else if (entry->hook_type == HOOK_ADDRESS_TYPE) {
+            do {
+                zz_thumb_relocator_read_one(thumb_relocator, NULL);
+                zz_thumb_relocator_write_one(thumb_relocator);
+                tmp_relocator_insn_size = thumb_relocator->input_cur - thumb_relocator->input_start;
+                if (thumb_relocator->input_cur >= target_end_addr && !entry->target_half_ret_addr) {
+                    /* jump to rest target address */
+                    zz_thumb_writer_put_ldr_reg_address(thumb_writer, ZZ_ARM_REG_PC, (zaddr)entry->on_half_trampoline);
+                    entry->target_half_ret_addr = (zpointer)(thumb_writer->size + 1);
+                }
+            } while (tmp_relocator_insn_size < entry_backend->redirect_code_size ||
+                     thumb_relocator->input_cur < target_end_addr);
+        }
+
+        /* jump to rest target address */
+        restore_target_addr = (zpointer)((zaddr)target_addr + tmp_relocator_insn_size);
+        zz_thumb_writer_put_ldr_reg_address(thumb_writer, ZZ_ARM_REG_PC, (zaddr)(restore_target_addr + 1));
+
+        /* code patch */
+        code_slice = zz_code_patch_thumb_relocate_writer(thumb_relocator, thumb_writer, self->allocator, 0, 0);
+        if (code_slice)
+            entry->on_invoke_trampoline = code_slice->data + 1;
+        else
+            return ZZ_FAILED;
+    } else {
+        ZzArmRelocator *arm_relocator;
+        ZzArmWriter *arm_writer;
+        arm_relocator = &self->arm_relocator;
+        arm_writer = &self->arm_writer;
+
+        zz_arm_writer_reset(arm_writer, temp_code_slice_data);
+        zz_arm_relocator_reset(arm_relocator, (zpointer)target_addr, arm_writer);
+        entry->target_half_ret_addr = 0;
+        zsize tmp_relocator_insn_size = 0;
+
+        if (entry->hook_type == HOOK_FUNCTION_TYPE) {
+            do {
+                zz_arm_relocator_read_one(arm_relocator, NULL);
+                tmp_relocator_insn_size = arm_relocator->input_cur - arm_relocator->input_start;
+            } while (tmp_relocator_insn_size < entry_backend->redirect_code_size);
+            zz_arm_relocator_write_all(arm_relocator);
+        } else if (entry->hook_type == HOOK_ADDRESS_TYPE) {
+            do {
+                zz_arm_relocator_read_one(arm_relocator, NULL);
+                zz_arm_relocator_write_one(arm_relocator);
+                tmp_relocator_insn_size = arm_relocator->input_cur - arm_relocator->input_start;
+                if (arm_relocator->input_cur >= target_end_addr && !entry->target_half_ret_addr) {
+                    /* jump to rest target address */
+                    zz_arm_writer_put_ldr_reg_address(arm_writer, ZZ_ARM_REG_PC, (zaddr)entry->on_half_trampoline);
+                    entry->target_half_ret_addr = (zpointer)arm_writer->size;
+                }
+            } while (tmp_relocator_insn_size < entry_backend->redirect_code_size ||
+                     arm_relocator->input_cur < target_end_addr);
+        }
+
+        /* jump to rest target address */
+        restore_target_addr = (zpointer)((zaddr)target_addr + tmp_relocator_insn_size);
+        zz_arm_writer_put_ldr_reg_address(arm_writer, ZZ_ARM_REG_PC, (zaddr)restore_target_addr);
+
+        /* code patch */
+        code_slice = zz_code_patch_arm_relocate_writer(arm_relocator, arm_writer, self->allocator, 0, 0);
+        if (code_slice)
+            entry->on_invoke_trampoline = code_slice->data;
+        else
+            return ZZ_FAILED;
+    }
+
+    /* update target_half_ret_addr */
+    if (entry->hook_type == HOOK_ADDRESS_TYPE) {
+        entry->target_half_ret_addr += (zaddr)code_slice->data;
+    }
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzBuildInvokeTrampoline:");
+        sprintf(buffer + strlen(buffer),
+                "LogInfo: on_invoke_trampoline at %p, length: %ld. and will jump to rest code(%p).\n", code_slice->data,
+                code_slice->size, restore_target_addr);
+        if (is_thumb) {
+            sprintf(buffer + strlen(buffer),
+                    "ThumbInstructionFix: origin instruction at %p, end at %p, relocator instruction nums %ld\n",
+                    (&self->thumb_relocator)->input_start, (&self->thumb_relocator)->input_cur,
+                    (&self->thumb_relocator)->inpos);
+        } else {
+            sprintf(buffer + strlen(buffer),
+                    "ArmInstructionFix: origin instruction at %p, end at %p, relocator instruction nums %ld\n",
+                    (&self->arm_relocator)->input_start, (&self->arm_relocator)->input_cur,
+                    (&self->arm_relocator)->inpos);
+        }
+
+        char origin_prologue[256] = {0};
+        int t = 0;
+        zpointer p;
+        if (is_thumb) {
+            for (p = (&self->thumb_relocator)->input_start; p < (&self->thumb_relocator)->input_cur; p++, t = t + 5) {
+                sprintf(origin_prologue + t, "0x%.2x ", *(unsigned char *)p);
+            }
+        } else {
+            for (p = (&self->arm_relocator)->input_start; p < (&self->arm_relocator)->input_cur; p++, t = t + 5) {
+                sprintf(origin_prologue + t, "0x%.2x ", *(unsigned char *)p);
+            }
+        }
+        sprintf(buffer + strlen(buffer), "origin_prologue: %s\n", origin_prologue);
+
+        ZzInfoLog("%s", buffer);
+    }
+
+    free(code_slice);
+    return status;
+}
+
+ZZSTATUS ZzBuildHalfTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzArmWriter *arm_writer = NULL;
+    ZzArmWriter *thumb_writer = NULL;
+    ZzCodeSlice *code_slice = NULL;
+    ZZSTATUS status = ZZ_SUCCESS;
+
+    thumb_writer = &self->thumb_writer;
+    zz_thumb_writer_reset(thumb_writer, temp_code_slice_data);
+
+    /* prepare 2 stack space: 1. next_hop 2. entry arg */
+    zz_thumb_writer_put_sub_reg_imm(thumb_writer, ZZ_ARM_REG_SP, 0xc);
+    zz_thumb_writer_put_str_reg_reg_offset(thumb_writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x0); // push r7
+    zz_thumb_writer_put_ldr_b_reg_address(thumb_writer, ZZ_ARM_REG_R1, (zaddr)entry);
+    zz_thumb_writer_put_str_reg_reg_offset(thumb_writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x4);
+    zz_thumb_writer_put_ldr_reg_reg_offset(thumb_writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x0); // pop r7
+    zz_thumb_writer_put_add_reg_imm(thumb_writer, ZZ_ARM_REG_SP, 0x4);
+
+    /* jump to half_thunk */
+    zz_thumb_writer_put_ldr_reg_address(thumb_writer, ZZ_ARM_REG_PC, (zaddr)self->half_thunk);
+
+    /* code patch */
+    code_slice = zz_code_patch_thumb_writer(thumb_writer, self->allocator, 0, 0);
+    if (code_slice)
+        entry->on_half_trampoline = code_slice->data + 1;
+    else
+        return ZZ_FAILED;
+
+    free(code_slice);
+    return status;
+}
+
+ZZSTATUS ZzBuildLeaveTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzCodeSlice *code_slice = NULL;
+    ZZSTATUS status = ZZ_SUCCESS;
+    zbool is_thumb = TRUE;
+    ZzArmWriter *thumb_writer;
+
+    thumb_writer = &self->thumb_writer;
+    zz_thumb_writer_reset(thumb_writer, temp_code_slice_data);
+
+    /* prepare 2 stack space: 1. next_hop 2. entry arg */
+    zz_thumb_writer_put_sub_reg_imm(thumb_writer, ZZ_ARM_REG_SP, 0xc);
+    zz_thumb_writer_put_str_reg_reg_offset(thumb_writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x0); // push r7
+    zz_thumb_writer_put_ldr_b_reg_address(thumb_writer, ZZ_ARM_REG_R1, (zaddr)entry);
+    zz_thumb_writer_put_str_reg_reg_offset(thumb_writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x4);
+    zz_thumb_writer_put_ldr_reg_reg_offset(thumb_writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x0); // pop r7
+    zz_thumb_writer_put_add_reg_imm(thumb_writer, ZZ_ARM_REG_SP, 0x4);
+
+    /* jump to leave_thunk */
+    zz_thumb_writer_put_ldr_reg_address(thumb_writer, ZZ_ARM_REG_PC, (zaddr)self->leave_thunk);
+
+    /* code patch */
+    code_slice = zz_code_patch_thumb_writer(thumb_writer, self->allocator, 0, 0);
+    if (code_slice)
+        entry->on_leave_trampoline = code_slice->data + 1;
+    else
+        return ZZ_FAILED;
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzBuildLeaveTrampoline:");
+        sprintf(buffer + strlen(buffer),
+                "LogInfo: on_leave_trampoline at %p, length: %ld. and will jump to leave_thunk(%p).\n",
+                code_slice->data, code_slice->size, self->leave_thunk);
+        ZzInfoLog("%s", buffer);
+    }
+
+    free(code_slice);
+    return ZZ_DONE;
+}
+
+ZZSTATUS ZzActivateTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzCodeSlice *code_slice = NULL;
+    ZzArmHookFunctionEntryBackend *entry_backend = (ZzArmHookFunctionEntryBackend *)entry->backend;
+    ZZSTATUS status = ZZ_SUCCESS;
+    zbool is_thumb = TRUE;
+    zaddr target_addr = (zaddr)entry->target_ptr;
+
+    is_thumb = INSTRUCTION_IS_THUMB((zaddr)entry->target_ptr);
+    if (is_thumb)
+        target_addr = (zaddr)entry->target_ptr & ~(zaddr)1;
+
+    if (is_thumb) {
+        ZzThumbWriter *thumb_writer;
+        thumb_writer = &self->thumb_writer;
+        zz_thumb_writer_reset(thumb_writer, temp_code_slice_data);
+        thumb_writer->pc = target_addr + 4;
+
+        if (entry_backend->redirect_code_size == ZZ_THUMB_TINY_REDIRECT_SIZE) {
+            zz_thumb_writer_put_b_imm32(thumb_writer,
+                                        (zaddr)entry->on_enter_transfer_trampoline - (zaddr)thumb_writer->pc);
+        } else {
+            zz_thumb_writer_put_ldr_reg_address(thumb_writer, ZZ_ARM_REG_PC, (zaddr)entry->on_enter_trampoline);
+        }
+        if (!ZzMemoryPatchCode((zaddr)target_addr, thumb_writer->base, thumb_writer->size))
+            return ZZ_FAILED;
+    } else {
+        ZzArmWriter *arm_writer;
+        arm_writer = &self->arm_writer;
+        zz_arm_writer_reset(arm_writer, temp_code_slice_data);
+        arm_writer->pc = target_addr + 8;
+
+        if (entry_backend->redirect_code_size == ZZ_ARM_TINY_REDIRECT_SIZE) {
+            zz_arm_writer_put_b_imm(arm_writer, (zaddr)entry->on_enter_transfer_trampoline - (zaddr)arm_writer->pc);
+        } else {
+            zz_arm_writer_put_ldr_reg_address(arm_writer, ZZ_ARM_REG_PC, (zaddr)entry->on_enter_trampoline);
+        }
+        if (!ZzMemoryPatchCode((zaddr)target_addr, arm_writer->base, arm_writer->size))
+            return ZZ_FAILED;
+    }
+
+    return ZZ_DONE_HOOK;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/interceptor-arm.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/interceptor-arm.h
new file mode 100644
index 000000000..5563dfb8d
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/interceptor-arm.h
@@ -0,0 +1,63 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_backend_arm_intercetor_arm
+#define platforms_backend_arm_intercetor_arm
+
+// platforms
+#include "platforms/arch-arm/relocator-arm.h"
+#include "platforms/arch-arm/relocator-thumb.h"
+#include "platforms/arch-arm/writer-arm.h"
+#include "platforms/arch-arm/writer-thumb.h"
+
+// hookzz
+#include "allocator.h"
+#include "interceptor.h"
+#include "thunker.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+// (next_hop + general_regs + sp)
+#define CTX_SAVE_STACK_OFFSET (4 * 14)
+
+typedef struct _ZzInterceptorBackend {
+    ZzAllocator *allocator;
+    ZzArmRelocator arm_relocator;
+    ZzThumbRelocator thumb_relocator;
+
+    ZzArmWriter arm_writer;
+    ZzThumbWriter thumb_writer;
+
+    zpointer enter_thunk;
+    zpointer half_thunk;
+    zpointer leave_thunk;
+} ZzInterceptorBackend;
+
+typedef struct _ZzArmHookFuntionEntryBackend {
+    zbool is_thumb;
+    zuint redirect_code_size;
+} ZzArmHookFunctionEntryBackend;
+
+ZzCodeSlice *zz_code_patch_thumb_writer(ZzThumbWriter *thumb_writer, ZzAllocator *allocator, zaddr target_addr,
+                                        zsize range_size);
+ZzCodeSlice *zz_code_patch_arm_writer(ZzArmWriter *arm_writer, ZzAllocator *allocator, zaddr target_addr,
+                                      zsize range_size);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/interceptor-template-arm.s b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/interceptor-template-arm.s
new file mode 100644
index 000000000..467330c4d
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/interceptor-template-arm.s
@@ -0,0 +1,24 @@
+// .section	__TEXT,__text,regular,pure_instructions
+// .ios_version_min 11, 0
+.align 4
+.globl _ctx_save
+.globl _ctx_restore
+.globl _enter_thunk_template
+.globl _leave_thunk_template
+.globl _on_enter_trampoline_template
+.globl _on_invoke_trampoline_template
+.globl _on_leave_trampoline_template
+
+
+
+_on_enter_trampoline_template:
+	sub sp, #0xc
+	str r1, [sp, #0x0]
+	ldr r1, [pc, #0x0]
+	b #0x2
+	.long 0x0
+	.long 0x0
+	str r1, [sp, #0x4]
+	ldr r1, [sp, #0x0]
+	add sp, #0x4
+	ldr.w pc, [pc, #0x2]
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/thunker-arm.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/thunker-arm.c
new file mode 100644
index 000000000..51529186d
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/thunker-arm.c
@@ -0,0 +1,393 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "thunker-arm.h"
+#include "zzinfo.h"
+
+// 前提: arm 可以直接访问 pc 寄存器, 也就是说无需中间寄存器就可以实现 `abs
+// jump`.
+
+// frida-gum 的做法, 手动恢复 lr, 并将 `next_hop` 写在之前 store `lr` 的位置,
+// 之后利用恢复寄存器跳转
+
+// 还有一个点, 就是 hook-entry 的参数传递, 这就是为啥 frida-gum 中为啥把
+// gum_emit_prolog 分成了两部分, 把 gum_emit_push_cpu_context_high_part 放在 x7
+// 用之前.
+
+// 这个和 arm64 有不同, arm64 中借助了一个中间寄存器 x16 or x17.
+
+// 重点是在: 1. 在 restore 之前完成所有工作 2. 利用 restore 过程修改 pc.
+
+// 当然也有其他做法, 比如最后一个操作是 ldr pc, [sp, #?], 也没啥问题.
+
+// 14 = 5 + 8 + 1
+
+// 按理说应该最先进入 ctx_save, 之后才能保证即使各种操作寄存器不被污染,
+// 几个理想方案 1. 把 ctx_save 归属为 trampoline, 优点: 优先进行寄存器状态保存,
+// 缺点: ctx_save 重复多次 2. 把 ctx_save 归属为 thunk, 统一做寄存器保存,
+// trampline 入口处进行参数保存至栈. 优点: ctx_save 可以作为公用. 缺点:
+// 操作复杂, 耦合略强. 3. 把 ctx_save 进行拆分, 缺点: 模块化设计差, 耦合强
+// (frida-gum采用)
+
+__attribute__((__naked__)) void ctx_save() {
+    __asm__ volatile(".arm\n"
+                     "sub sp, sp, #(14*4)\n"
+
+                     "str lr, [sp, #(13*4)]\n"
+
+                     "str r12, [sp, #(12*4)]\n"
+                     "str r11, [sp, #(11*4)]\n"
+                     "str r10, [sp, #(10*4)]\n"
+                     "str r9, [sp, #(9*4)]\n"
+                     "str r8, [sp, #(8*4)]\n"
+
+                     "str r7, [sp, #(7*4)]\n"
+                     "str r6, [sp, #(6*4)]\n"
+                     "str r5, [sp, #(5*4)]\n"
+                     "str r4, [sp, #(4*4)]\n"
+                     "str r3, [sp, #(3*4)]\n"
+                     "str r2, [sp, #(2*4)]\n"
+                     "str r1, [sp, #(1*4)]\n"
+                     "str r0, [sp, #(0*4)]\n");
+}
+
+__attribute__((__naked__)) void ctx_restore() {
+    __asm__ volatile(".arm\n"
+                     "ldr r0, [sp], #4\n"
+                     "ldr r1, [sp], #4\n"
+                     "ldr r2, [sp], #4\n"
+                     "ldr r3, [sp], #4\n"
+                     "ldr r4, [sp], #4\n"
+                     "ldr r5, [sp], #4\n"
+                     "ldr r6, [sp], #4\n"
+                     "ldr r7, [sp], #4\n"
+
+                     "ldr r8, [sp], #4\n"
+                     "ldr r9, [sp], #4\n"
+                     "ldr r10, [sp], #4\n"
+                     "ldr r11, [sp], #4\n"
+                     "ldr r12, [sp], #4\n"
+
+                     "ldr lr, [sp], #4\n");
+}
+
+// just like pre_call, wow!
+void function_context_begin_invocation(ZzHookFunctionEntry *entry, zpointer next_hop, RegState *rs,
+                                       zpointer caller_ret_addr) {
+
+    Xdebug("target %p call begin-invocation", entry->target_ptr);
+
+    ZzThreadStack *threadstack = ZzGetCurrentThreadStack(entry->thread_local_key);
+    if (!threadstack) {
+        threadstack = ZzNewThreadStack(entry->thread_local_key);
+    }
+    ZzCallStack *callstack = ZzNewCallStack();
+    ZzPushCallStack(threadstack, callstack);
+
+    /* call pre_call */
+    if (entry->pre_call) {
+        PRECALL pre_call;
+        pre_call = entry->pre_call;
+        (*pre_call)(rs, (ThreadStack *)threadstack, (CallStack *)callstack);
+    }
+
+    /* set next hop */
+    if (entry->replace_call) {
+        *(zpointer *)next_hop = entry->replace_call;
+    } else {
+        *(zpointer *)next_hop = entry->on_invoke_trampoline;
+    }
+
+    if (entry->hook_type == HOOK_FUNCTION_TYPE) {
+        callstack->caller_ret_addr = *(zpointer *)caller_ret_addr;
+        *(zpointer *)caller_ret_addr = entry->on_leave_trampoline;
+    }
+}
+
+void function_context_half_invocation(ZzHookFunctionEntry *entry, zpointer next_hop, RegState *rs,
+                                      zpointer caller_ret_addr) {
+    Xdebug("target %p call half-invocation", entry->target_ptr);
+
+    ZzThreadStack *threadstack = ZzGetCurrentThreadStack(entry->thread_local_key);
+    if (!threadstack) {
+#if defined(DEBUG_MODE)
+        debug_break();
+#endif
+    }
+    ZzCallStack *callstack = ZzPopCallStack(threadstack);
+
+    /* call half_call */
+    if (entry->half_call) {
+        HALFCALL half_call;
+        half_call = entry->half_call;
+        (*half_call)(rs, (ThreadStack *)threadstack, (CallStack *)callstack);
+    }
+
+    /* set next hop */
+    *(zpointer *)next_hop = (zpointer)entry->target_half_ret_addr;
+
+    ZzFreeCallStack(callstack);
+}
+
+// just like post_call, wow!
+void function_context_end_invocation(ZzHookFunctionEntry *entry, zpointer next_hop, RegState *rs) {
+    Xdebug("%p call end-invocation", entry->target_ptr);
+
+    ZzThreadStack *threadstack = ZzGetCurrentThreadStack(entry->thread_local_key);
+    if (!threadstack) {
+#if defined(DEBUG_MODE)
+        debug_break();
+#endif
+    }
+    ZzCallStack *callstack = ZzPopCallStack(threadstack);
+
+    /* call post_call */
+    if (entry->post_call) {
+        POSTCALL post_call;
+        post_call = entry->post_call;
+        (*post_call)(rs, (ThreadStack *)threadstack, (CallStack *)callstack);
+    }
+
+    /* set next hop */
+    *(zpointer *)next_hop = callstack->caller_ret_addr;
+
+    ZzFreeCallStack(callstack);
+}
+
+void zz_thumb_thunker_build_enter_thunk(ZzWriter *writer) {
+
+    /* save general registers and sp */
+    zz_thumb_writer_put_bx_reg(writer, ZZ_ARM_REG_PC);
+    zz_arm_writer_put_bytes(writer, THUMB_FUNCTION_ADDRESS((void *)ctx_save), 15 * 4);
+    zz_arm_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_PC, 1);
+    zz_arm_writer_put_bx_reg(writer, ZZ_ARM_REG_R1);
+
+    zz_thumb_writer_put_sub_reg_imm(writer, ZZ_ARM_REG_SP, 0x8);
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, CTX_SAVE_STACK_OFFSET + 0x8 + 0x8);
+    zz_thumb_writer_put_str_reg_reg_offset(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x4);
+
+    /* pass enter func args */
+    /* entry */
+    zz_thumb_writer_put_ldr_reg_reg_offset(writer, ZZ_ARM_REG_R0, ZZ_ARM_REG_SP, CTX_SAVE_STACK_OFFSET + 0x8);
+    /* next hop*/
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, CTX_SAVE_STACK_OFFSET + 0x8 + 0x4);
+    /* RegState */
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R2, ZZ_ARM_REG_SP, 0x4);
+    /* caller ret address */
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R3, ZZ_ARM_REG_SP, 0x8 + 13 * 4);
+
+    /* call function_context_begin_invocation */
+    zz_thumb_writer_put_ldr_b_reg_address(writer, ZZ_ARM_REG_LR, (zaddr)function_context_begin_invocation);
+    zz_thumb_writer_put_blx_reg(writer, ZZ_ARM_REG_LR);
+
+    /* restore general registers and sp */
+    zz_thumb_writer_put_add_reg_imm(writer, ZZ_ARM_REG_SP, 0x8);
+    zz_thumb_writer_put_bx_reg(writer, ZZ_ARM_REG_PC);
+    zz_arm_writer_put_bytes(writer, THUMB_FUNCTION_ADDRESS((void *)ctx_restore), 14 * 4);
+    zz_arm_writer_put_bx_to_thumb(writer);
+
+    /* restore arg space */
+    zz_thumb_writer_put_add_reg_imm(writer, ZZ_ARM_REG_SP, 0x4);
+
+    /* pop and jump to next hop */
+    // use Post-indexed ldr to `pop`
+    zz_thumb_writer_put_ldr_index_reg_reg_offset(writer, ZZ_ARM_REG_PC, ZZ_ARM_REG_SP, 4, 0);
+}
+
+// A4.1.10 BX
+void zz_thumb_thunker_build_half_thunk(ZzWriter *writer) {
+
+    /* save general registers and sp */
+    zz_thumb_writer_put_bx_reg(writer, ZZ_ARM_REG_PC);
+    zz_arm_writer_put_bytes(writer, THUMB_FUNCTION_ADDRESS((void *)ctx_save), 15 * 4);
+    zz_arm_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_PC, 1);
+    zz_arm_writer_put_bx_reg(writer, ZZ_ARM_REG_R1);
+
+    zz_thumb_writer_put_sub_reg_imm(writer, ZZ_ARM_REG_SP, 0x8);
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, CTX_SAVE_STACK_OFFSET + 0x8 + 0x8);
+    zz_thumb_writer_put_str_reg_reg_offset(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x4);
+
+    /* pass enter func args */
+    /* entry */
+    zz_thumb_writer_put_ldr_reg_reg_offset(writer, ZZ_ARM_REG_R0, ZZ_ARM_REG_SP, CTX_SAVE_STACK_OFFSET + 0x8);
+    /* next hop*/
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, CTX_SAVE_STACK_OFFSET + 0x8 + 0x4);
+    /* RegState */
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R2, ZZ_ARM_REG_SP, 0x4);
+    /* caller ret address */
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R3, ZZ_ARM_REG_SP, 0x8 + 13 * 4);
+
+    /* call function_context_half_invocation */
+    zz_thumb_writer_put_ldr_b_reg_address(writer, ZZ_ARM_REG_LR, (zaddr)function_context_half_invocation);
+    zz_thumb_writer_put_blx_reg(writer, ZZ_ARM_REG_LR);
+
+    /* restore general registers and sp */
+    zz_thumb_writer_put_add_reg_imm(writer, ZZ_ARM_REG_SP, 0x8);
+    zz_thumb_writer_put_bx_reg(writer, ZZ_ARM_REG_PC);
+    zz_arm_writer_put_bytes(writer, THUMB_FUNCTION_ADDRESS((void *)ctx_restore), 14 * 4);
+    zz_arm_writer_put_bx_to_thumb(writer);
+
+    /* restore arg space */
+    zz_thumb_writer_put_add_reg_imm(writer, ZZ_ARM_REG_SP, 0x4);
+
+    /* pop and jump to next hop */
+    // use Post-indexed ldr to `pop`
+    zz_thumb_writer_put_ldr_index_reg_reg_offset(writer, ZZ_ARM_REG_PC, ZZ_ARM_REG_SP, 4, 0);
+}
+
+void zz_thumb_thunker_build_leave_thunk(ZzWriter *writer) {
+
+    /* save general registers and sp */
+    zz_thumb_writer_put_bx_reg(writer, ZZ_ARM_REG_PC);
+    zz_arm_writer_put_bytes(writer, THUMB_FUNCTION_ADDRESS((void *)ctx_save), 15 * 4);
+    zz_arm_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_PC, 1);
+    zz_arm_writer_put_bx_reg(writer, ZZ_ARM_REG_R1);
+
+    zz_thumb_writer_put_sub_reg_imm(writer, ZZ_ARM_REG_SP, 0x8);
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, CTX_SAVE_STACK_OFFSET + 0x8 + 0x8);
+    zz_thumb_writer_put_str_reg_reg_offset(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, 0x4);
+
+    /* pass enter func args */
+    /* entry */
+    zz_thumb_writer_put_ldr_reg_reg_offset(writer, ZZ_ARM_REG_R0, ZZ_ARM_REG_SP, CTX_SAVE_STACK_OFFSET + 0x8);
+    /* next hop*/
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R1, ZZ_ARM_REG_SP, CTX_SAVE_STACK_OFFSET + 0x8 + 0x4);
+    /* RegState */
+    zz_thumb_writer_put_add_reg_reg_imm(writer, ZZ_ARM_REG_R2, ZZ_ARM_REG_SP, 0x4);
+
+    /* call function_context_begin_invocation */
+    zz_thumb_writer_put_ldr_b_reg_address(writer, ZZ_ARM_REG_LR, (zaddr)function_context_end_invocation);
+    zz_thumb_writer_put_blx_reg(writer, ZZ_ARM_REG_LR);
+
+    /* restore general registers and sp */
+    zz_thumb_writer_put_add_reg_imm(writer, ZZ_ARM_REG_SP, 0x8);
+    zz_thumb_writer_put_bx_reg(writer, ZZ_ARM_REG_PC);
+    zz_arm_writer_put_bytes(writer, THUMB_FUNCTION_ADDRESS((void *)ctx_restore), 14 * 4);
+    zz_arm_writer_put_bx_to_thumb(writer);
+
+    /* restore arg space */
+    zz_thumb_writer_put_add_reg_imm(writer, ZZ_ARM_REG_SP, 0x4);
+
+    /* pop and jump to next hop */
+    // use Post-indexed ldr to `pop`
+    zz_thumb_writer_put_ldr_index_reg_reg_offset(writer, ZZ_ARM_REG_PC, ZZ_ARM_REG_SP, 4, 0);
+}
+
+ZZSTATUS ZzThunkerBuildThunk(ZzInterceptorBackend *self) {
+    zbyte temp_code_slice_data[512] = {0};
+    ZzThumbWriter *thumb_writer = NULL;
+    ZzCodeSlice *code_slice = NULL;
+    ZZSTATUS status = ZZ_SUCCESS;
+
+    thumb_writer = &self->thumb_writer;
+    zz_thumb_writer_reset(thumb_writer, temp_code_slice_data);
+
+    /* buid enter_thunk */
+    zz_thumb_thunker_build_enter_thunk(thumb_writer);
+
+    /* code patch */
+    code_slice = zz_code_patch_thumb_writer(thumb_writer, self->allocator, 0, 0);
+    if (code_slice)
+        self->enter_thunk = code_slice->data + 1;
+    else
+        return ZZ_FAILED;
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[2048] = {};
+        char thunk_buffer[2048] = {};
+        int t = 0;
+        zpointer p;
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzThunkerBuildThunk:");
+
+        for (p = thumb_writer->base; p < thumb_writer->base + thumb_writer->size; p++, t = t + 5) {
+            sprintf(thunk_buffer + t, "0x%.2x ", *(unsigned char *)p);
+        }
+
+        ZzInfoLog("%s", thunk_buffer);
+        // sprintf(buffer + strlen(buffer), "enter_thunk: %s\n", thunk_buffer);
+
+        sprintf(buffer + strlen(buffer), "LogInfo: enter_thunk at %p, length: %ld.\n", code_slice->data,
+                code_slice->size);
+        ZzInfoLog("%s", buffer);
+    }
+
+    zz_thumb_writer_reset(thumb_writer, temp_code_slice_data);
+
+    /* build leave_thunk */
+    zz_thumb_thunker_build_leave_thunk(thumb_writer);
+
+    /* code patch */
+    code_slice = zz_code_patch_thumb_writer(thumb_writer, self->allocator, 0, 0);
+    if (code_slice)
+        self->leave_thunk = code_slice->data + 1;
+    else
+        return ZZ_FAILED;
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[2048] = {};
+        char thunk_buffer[2048] = {};
+        int t = 0;
+        zpointer p;
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzThunkerBuildThunk:");
+
+        for (p = thumb_writer->base; p < thumb_writer->base + thumb_writer->size; p++, t = t + 5) {
+            sprintf(thunk_buffer + t, "0x%.2x ", *(unsigned char *)p);
+        }
+
+        ZzInfoLog("%s", thunk_buffer);
+        // sprintf(buffer + strlen(buffer), "enter_thunk: %s\n", thunk_buffer);
+
+        sprintf(buffer + strlen(buffer), "LogInfo: leave_thunk at %p, length: %ld.\n", code_slice->data,
+                code_slice->size);
+        ZzInfoLog("%s", buffer);
+    }
+
+    zz_thumb_writer_reset(thumb_writer, temp_code_slice_data);
+
+    /* build half_thunk */
+    zz_thumb_thunker_build_half_thunk(thumb_writer);
+
+    /* code patch */
+    code_slice = zz_code_patch_thumb_writer(thumb_writer, self->allocator, 0, 0);
+    if (code_slice)
+        self->half_thunk = code_slice->data + 1;
+    else
+        return ZZ_FAILED;
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[2048] = {};
+        char thunk_buffer[2048] = {};
+        int t = 0;
+        zpointer p;
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzThunkerBuildThunk:");
+
+        for (p = thumb_writer->base; p < thumb_writer->base + thumb_writer->size; p++, t = t + 5) {
+            sprintf(thunk_buffer + t, "0x%.2x ", *(unsigned char *)p);
+        }
+
+        ZzInfoLog("%s", thunk_buffer);
+        // sprintf(buffer + strlen(buffer), "half_thunk: %s\n", thunk_buffer);
+
+        sprintf(buffer + strlen(buffer), "LogInfo: half_thunk at %p, length: %ld.\n", code_slice->data,
+                code_slice->size);
+        ZzInfoLog("%s", buffer);
+    }
+
+    return status;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/thunker-arm.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/thunker-arm.h
new file mode 100644
index 000000000..352d4ecc3
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm/thunker-arm.h
@@ -0,0 +1,38 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_backend_arm_thunker_arm
+#define platforms_backend_arm_thunker_arm
+
+// platforms
+#include "platforms/arch-arm/relocator-arm.h"
+#include "platforms/arch-arm/relocator-thumb.h"
+#include "platforms/arch-arm/writer-arm.h"
+#include "platforms/arch-arm/writer-thumb.h"
+
+#include "interceptor-arm.h"
+
+// hookzz
+#include "stack.h"
+#include "thunker.h"
+#include "zzdefs.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/interceptor-arm64.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/interceptor-arm64.c
new file mode 100644
index 000000000..3df307a8d
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/interceptor-arm64.c
@@ -0,0 +1,410 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "interceptor-arm64.h"
+#include "zzinfo.h"
+#include <stdlib.h>
+#include <string.h>
+
+#define ZZ_ARM64_TINY_REDIRECT_SIZE 4
+#define ZZ_ARM64_FULL_REDIRECT_SIZE 16
+
+ZzInterceptorBackend *ZzBuildInteceptorBackend(ZzAllocator *allocator) {
+    if (!ZzMemoryIsSupportAllocateRXPage()) {
+        return NULL;
+    }
+    ZZSTATUS status;
+
+    ZzInterceptorBackend *backend = (ZzInterceptorBackend *)malloc(sizeof(ZzInterceptorBackend));
+    memset(backend, 0, sizeof(ZzInterceptorBackend));
+
+    zz_arm64_writer_init(&backend->arm64_writer, NULL);
+    zz_arm64_relocator_init(&backend->arm64_relocator, NULL, &backend->arm64_writer);
+
+    backend->allocator = allocator;
+    backend->enter_thunk = NULL;
+    backend->half_thunk = NULL;
+    backend->leave_thunk = NULL;
+
+    status = ZzThunkerBuildThunk(backend);
+    if (status == ZZ_FAILED) {
+        ZzInfoLog("%s", "ZzThunkerBuildThunk return ZZ_FAILED\n");
+        return NULL;
+    }
+
+    return backend;
+}
+
+ZzCodeSlice *zz_code_patch_arm64_writer(ZzArm64Writer *arm64_writer, ZzAllocator *allocator, zaddr target_addr,
+                                        zsize range_size) {
+    ZzCodeSlice *code_slice = NULL;
+    if (range_size > 0) {
+        code_slice = ZzNewNearCodeSlice(allocator, target_addr, range_size, arm64_writer->size);
+    } else {
+        code_slice = ZzNewCodeSlice(allocator, arm64_writer->size + 4);
+    }
+    if (!code_slice)
+        return NULL;
+
+    if (!ZzMemoryPatchCode((zaddr)code_slice->data, arm64_writer->base, arm64_writer->size)) {
+        free(code_slice);
+        return NULL;
+    }
+    return code_slice;
+}
+
+ZzCodeSlice *zz_code_patch_arm64_relocate_writer(ZzArm64Relocator *relocator, ZzArm64Writer *arm64_writer,
+                                                 ZzAllocator *allocator, zaddr target_addr, zsize range_size) {
+    ZzCodeSlice *code_slice = NULL;
+    if (range_size > 0) {
+        code_slice = ZzNewNearCodeSlice(allocator, target_addr, range_size, arm64_writer->size);
+    } else {
+        code_slice = ZzNewCodeSlice(allocator, arm64_writer->size + 4);
+    }
+    if (!code_slice)
+        return NULL;
+
+    if (!ZzMemoryPatchCode((zaddr)code_slice->data, arm64_writer->base, arm64_writer->size)) {
+        free(code_slice);
+        return NULL;
+    }
+    return code_slice;
+}
+
+ZZSTATUS ZzPrepareTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zaddr target_addr = (zaddr)entry->target_ptr;
+    zuint redirect_limit = 0;
+
+    ZzArm64HookFunctionEntryBackend *entry_backend;
+    entry_backend = (ZzArm64HookFunctionEntryBackend *)malloc(sizeof(ZzArm64HookFunctionEntryBackend));
+    memset(entry_backend, 0, sizeof(ZzArm64HookFunctionEntryBackend));
+
+    entry->backend = (struct _ZzHookFunctionEntryBackend *)entry_backend;
+
+    if (entry->try_near_jump) {
+        entry_backend->redirect_code_size = ZZ_ARM64_TINY_REDIRECT_SIZE;
+    } else {
+        zz_arm64_relocator_try_relocate((zpointer)target_addr, ZZ_ARM64_FULL_REDIRECT_SIZE, &redirect_limit);
+        if (redirect_limit != 0 && redirect_limit > ZZ_ARM64_TINY_REDIRECT_SIZE &&
+            redirect_limit < ZZ_ARM64_FULL_REDIRECT_SIZE) {
+            entry->try_near_jump = TRUE;
+            entry_backend->redirect_code_size = ZZ_ARM64_TINY_REDIRECT_SIZE;
+        } else if (redirect_limit != 0 && redirect_limit < ZZ_ARM64_TINY_REDIRECT_SIZE) {
+            return ZZ_FAILED;
+        } else {
+            entry_backend->redirect_code_size = ZZ_ARM64_FULL_REDIRECT_SIZE;
+        }
+    }
+
+    self->arm64_relocator.try_relocated_length = entry_backend->redirect_code_size;
+    zz_arm64_relocator_init(&self->arm64_relocator, (zpointer)target_addr, &self->arm64_writer);
+    return ZZ_SUCCESS;
+}
+
+ZZSTATUS ZzBuildEnterTransferTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzArm64Writer *arm64_writer = NULL;
+    ZzCodeSlice *code_slice = NULL;
+    ZzArm64HookFunctionEntryBackend *entry_backend = (ZzArm64HookFunctionEntryBackend *)entry->backend;
+    ZZSTATUS status = ZZ_SUCCESS;
+    zaddr target_addr = (zaddr)entry->target_ptr;
+
+    arm64_writer = &self->arm64_writer;
+    zz_arm64_writer_reset(arm64_writer, temp_code_slice_data);
+    zz_arm64_writer_put_ldr_br_reg_address(arm64_writer, ZZ_ARM64_REG_X17, (zaddr)entry->on_enter_trampoline);
+    code_slice =
+        zz_code_patch_arm64_writer(arm64_writer, self->allocator, target_addr, zz_arm64_writer_near_jump_range_size());
+    if (code_slice)
+        entry->on_enter_transfer_trampoline = code_slice->data;
+    else
+        return ZZ_FAILED;
+
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzBuildEnterTransferTrampoline:");
+        sprintf(buffer + strlen(buffer),
+                "LogInfo: on_enter_transfer_trampoline at %p, length: %ld. and will jump to on_enter_trampoline(%p).\n",
+                code_slice->data, code_slice->size, entry->on_enter_trampoline);
+        ZzInfoLog("%s", buffer);
+    }
+
+    free(code_slice);
+    return status;
+}
+ZZSTATUS ZzBuildEnterTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzArm64Writer *arm64_writer = NULL;
+    ZzCodeSlice *code_slice = NULL;
+    ZzArm64HookFunctionEntryBackend *entry_backend = (ZzArm64HookFunctionEntryBackend *)entry->backend;
+    ZZSTATUS status = ZZ_SUCCESS;
+    zaddr target_addr = (zaddr)entry->target_ptr;
+
+    arm64_writer = &self->arm64_writer;
+    zz_arm64_writer_reset(arm64_writer, temp_code_slice_data);
+
+    /* prepare 2 stack space: 1. next_hop 2. entry arg */
+    zz_arm64_writer_put_sub_reg_reg_imm(arm64_writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 0x8);
+    zz_arm64_writer_put_ldr_b_reg_address(arm64_writer, ZZ_ARM64_REG_X17, (zaddr)entry);
+    zz_arm64_writer_put_str_reg_reg_offset(arm64_writer, ZZ_ARM64_REG_X17, ZZ_ARM64_REG_SP, 0x0);
+
+    /* jump to enter thunk */
+    zz_arm64_writer_put_ldr_br_reg_address(arm64_writer, ZZ_ARM64_REG_X17, (zaddr)self->enter_thunk);
+
+    /* code patch */
+    code_slice = zz_code_patch_arm64_writer(arm64_writer, self->allocator, 0, 0);
+    if (code_slice)
+        entry->on_enter_trampoline = code_slice->data;
+    else
+        return ZZ_FAILED;
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzBuildEnterTrampoline:");
+        sprintf(buffer + strlen(buffer),
+                "LogInfo: on_enter_trampoline at %p, length: %ld. hook-entry: %p. and will jump to enter_thunk(%p).\n",
+                code_slice->data, code_slice->size, (void *)entry, (void *)self->enter_thunk);
+        ZzInfoLog("%s", buffer);
+    }
+
+    if (entry_backend->redirect_code_size == ZZ_ARM64_TINY_REDIRECT_SIZE) {
+        ZzBuildEnterTransferTrampoline(self, entry);
+    }
+
+    free(code_slice);
+    return status;
+}
+
+ZZSTATUS ZzBuildInvokeTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzCodeSlice *code_slice = NULL;
+    ZzArm64HookFunctionEntryBackend *entry_backend = (ZzArm64HookFunctionEntryBackend *)entry->backend;
+    ZZSTATUS status = ZZ_SUCCESS;
+    zaddr target_addr = (zaddr)entry->target_ptr;
+    zpointer restore_target_addr;
+
+    ZzArm64Relocator *arm64_relocator;
+    ZzArm64Writer *arm64_writer;
+    arm64_relocator = &self->arm64_relocator;
+    arm64_writer = &self->arm64_writer;
+
+    zz_arm64_writer_reset(arm64_writer, temp_code_slice_data);
+    zz_arm64_relocator_reset(arm64_relocator, (zpointer)target_addr, arm64_writer);
+    zsize tmp_relocator_insn_size = 0;
+    entry->target_half_ret_addr = 0;
+
+    if (entry->hook_type == HOOK_FUNCTION_TYPE) {
+        do {
+            zz_arm64_relocator_read_one(arm64_relocator, NULL);
+            tmp_relocator_insn_size = arm64_relocator->input_cur - arm64_relocator->input_start;
+        } while (tmp_relocator_insn_size < entry_backend->redirect_code_size);
+        zz_arm64_relocator_write_all(arm64_relocator);
+    } else if (entry->hook_type == HOOK_ADDRESS_TYPE) {
+        do {
+            zz_arm64_relocator_read_one(arm64_relocator, NULL);
+            zz_arm64_relocator_write_one(arm64_relocator);
+            tmp_relocator_insn_size = arm64_relocator->input_cur - arm64_relocator->input_start;
+            if (arm64_relocator->input_cur >= entry->target_end_ptr && !entry->target_half_ret_addr) {
+                zz_arm64_writer_put_ldr_br_reg_address(arm64_writer, ZZ_ARM64_REG_X17,
+                                                       (zaddr)entry->on_half_trampoline);
+
+                entry->target_half_ret_addr = (zpointer)arm64_writer->size;
+            }
+        } while (tmp_relocator_insn_size < entry_backend->redirect_code_size ||
+                 arm64_relocator->input_cur < entry->target_end_ptr);
+    }
+
+    /* jump to rest target address */
+    restore_target_addr = (zpointer)((zaddr)target_addr + tmp_relocator_insn_size);
+    zz_arm64_writer_put_ldr_br_reg_address(arm64_writer, ZZ_ARM64_REG_X17, (zaddr)restore_target_addr);
+
+    /* code patch */
+    code_slice = zz_code_patch_arm64_relocate_writer(arm64_relocator, arm64_writer, self->allocator, 0, 0);
+    if (code_slice)
+        entry->on_invoke_trampoline = code_slice->data;
+    else
+        return ZZ_FAILED;
+
+    /* update target_half_ret_addr */
+    if (entry->hook_type == HOOK_ADDRESS_TYPE) {
+        entry->target_half_ret_addr += (zaddr)code_slice->data;
+    }
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {0};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzBuildInvokeTrampoline:");
+        sprintf(buffer + strlen(buffer),
+                "LogInfo: on_invoke_trampoline at %p, length: %ld. and will jump to rest code(%p).\n", code_slice->data,
+                code_slice->size, restore_target_addr);
+        sprintf(buffer + strlen(buffer),
+                "ArmInstructionFix: origin instruction at %p, relocator end at %p, relocator instruction nums %ld\n",
+                (&self->arm64_relocator)->input_start, (&self->arm64_relocator)->input_cur,
+                (&self->arm64_relocator)->inpos);
+
+        char origin_prologue[256] = {0};
+        int t = 0;
+        zpointer p;
+        for (p = (&self->arm64_relocator)->input_start; p < (&self->arm64_relocator)->input_cur; p++, t = t + 5) {
+            sprintf(origin_prologue + t, "0x%.2x ", *(unsigned char *)p);
+        }
+        sprintf(buffer + strlen(buffer), "origin_prologue: %s\n", origin_prologue);
+
+        ZzInfoLog("%s", buffer);
+    }
+
+    free(code_slice);
+    return status;
+}
+
+ZZSTATUS ZzBuildHalfTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzArm64Writer *arm64_writer = NULL;
+    ZzCodeSlice *code_slice = NULL;
+    ZzArm64HookFunctionEntryBackend *entry_backend = (ZzArm64HookFunctionEntryBackend *)entry->backend;
+    ZZSTATUS status = ZZ_SUCCESS;
+    zaddr target_addr = (zaddr)entry->target_ptr;
+
+    arm64_writer = &self->arm64_writer;
+    zz_arm64_writer_reset(arm64_writer, temp_code_slice_data);
+
+    /* prepare 2 stack space: 1. next_hop 2. entry arg */
+    zz_arm64_writer_put_sub_reg_reg_imm(arm64_writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 0x8);
+    zz_arm64_writer_put_ldr_b_reg_address(arm64_writer, ZZ_ARM64_REG_X17, (zaddr)entry);
+    zz_arm64_writer_put_str_reg_reg_offset(arm64_writer, ZZ_ARM64_REG_X17, ZZ_ARM64_REG_SP, 0x0);
+
+    /* jump to half thunk */
+    zz_arm64_writer_put_ldr_br_reg_address(arm64_writer, ZZ_ARM64_REG_X17, (zaddr)self->half_thunk);
+
+    /* code patch */
+    code_slice = zz_code_patch_arm64_writer(arm64_writer, self->allocator, 0, 0);
+    if (code_slice)
+        entry->on_half_trampoline = code_slice->data;
+    else
+        return ZZ_FAILED;
+
+    return status;
+}
+
+ZZSTATUS ZzBuildLeaveTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzCodeSlice *code_slice = NULL;
+    ZzArm64HookFunctionEntryBackend *entry_backend = (ZzArm64HookFunctionEntryBackend *)entry->backend;
+    zaddr target_addr = (zaddr)entry->target_ptr;
+    ZzArm64Writer *arm64_writer = NULL;
+
+    arm64_writer = &self->arm64_writer;
+    zz_arm64_writer_reset(arm64_writer, temp_code_slice_data);
+
+    /* prepare 2 stack space: 1. next_hop 2. entry arg */
+    zz_arm64_writer_put_sub_reg_reg_imm(arm64_writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 0x8);
+    zz_arm64_writer_put_ldr_b_reg_address(arm64_writer, ZZ_ARM64_REG_X17, (zaddr)entry);
+    zz_arm64_writer_put_str_reg_reg_offset(arm64_writer, ZZ_ARM64_REG_X17, ZZ_ARM64_REG_SP, 0x0);
+
+    /* jump to leave thunk */
+    zz_arm64_writer_put_ldr_br_reg_address(arm64_writer, ZZ_ARM64_REG_X17, (zaddr)self->leave_thunk);
+
+    /* code patch */
+    code_slice = zz_code_patch_arm64_writer(arm64_writer, self->allocator, 0, 0);
+    if (code_slice)
+        entry->on_leave_trampoline = code_slice->data;
+    else
+        return ZZ_FAILED;
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzBuildLeaveTrampoline:");
+        sprintf(buffer + strlen(buffer),
+                "LogInfo: on_leave_trampoline at %p, length: %ld. and will jump to leave_thunk(%p).\n",
+                code_slice->data, code_slice->size, self->leave_thunk);
+        ZzInfoLog("%s", buffer);
+    }
+
+    free(code_slice);
+    return ZZ_DONE;
+}
+
+ZZSTATUS ZzActivateTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+    zbyte temp_code_slice_data[256] = {0};
+    ZzCodeSlice *code_slice = NULL;
+    ZzArm64HookFunctionEntryBackend *entry_backend = (ZzArm64HookFunctionEntryBackend *)entry->backend;
+    ZZSTATUS status = ZZ_SUCCESS;
+    zaddr target_addr = (zaddr)entry->target_ptr;
+    ZzArm64Writer *arm64_writer;
+
+    arm64_writer = &self->arm64_writer;
+    zz_arm64_writer_reset(arm64_writer, temp_code_slice_data);
+    arm64_writer->pc = target_addr;
+
+    if (entry_backend->redirect_code_size == ZZ_ARM64_TINY_REDIRECT_SIZE) {
+        zz_arm64_writer_put_b_imm(arm64_writer, (zaddr)entry->on_enter_transfer_trampoline - (zaddr)arm64_writer->pc);
+    } else {
+        zz_arm64_writer_put_ldr_br_reg_address(arm64_writer, ZZ_ARM64_REG_X17, (zaddr)entry->on_enter_trampoline);
+    }
+
+    if (!ZzMemoryPatchCode((zaddr)target_addr, arm64_writer->base, arm64_writer->size))
+        status = ZZ_FAILED;
+
+    return status;
+}
+
+#ifdef TARGET_IS_IOS
+
+#include <mach-o/dyld.h>
+#include <zzdeps/darwin/macho-utils-darwin.h>
+
+typedef struct _ZzInterceptorBackendNoJB {
+    void *enter_thunk; // hardcode
+    void *leave_thunk; // hardcode
+    unsigned long num_of_entry;
+    unsigned long code_seg_offset;
+    unsigned long data_seg_offset;
+} ZzInterceptorBackendNoJB;
+
+typedef struct _ZzHookFunctionEntryNoJB {
+    void *target_fileoff;
+    unsigned long is_near_jump;
+    void *entry_address;
+    void *on_enter_trampoline;  // HookZzData, 99% hardcode
+    void *on_invoke_trampoline; // HookZzData, fixed instructions
+    void *on_leave_trampoline;  // HookZzData, 99% hardcode
+} ZzHookFunctionEntryNoJB;
+
+ZZSTATUS ZzActivateSolidifyTrampoline(ZzHookFunctionEntry *entry, zaddr target_fileoff) {
+    struct mach_header_64 *header = (struct mach_header_64 *)_dyld_get_image_header(0);
+    struct segment_command_64 *text_seg_cmd = zz_macho_get_segment_64_via_name(header, "__TEXT");
+    struct segment_command_64 *data_seg_cmd = zz_macho_get_segment_64_via_name(header, "HookZzData");
+    zaddr aslr_slide = (zaddr)header - text_seg_cmd->vmaddr;
+    ZzInterceptorBackendNoJB *nojb_backend = (ZzInterceptorBackendNoJB *)(aslr_slide + data_seg_cmd->vmaddr);
+    nojb_backend->enter_thunk = (void *)enter_thunk_template;
+    nojb_backend->leave_thunk = (void *)leave_thunk_template;
+
+    ZzHookFunctionEntryNoJB *nojb_entry =
+        (ZzHookFunctionEntryNoJB *)(data_seg_cmd->vmaddr + sizeof(ZzHookFunctionEntryNoJB) + aslr_slide);
+    unsigned long i;
+    for (i = 0; i < nojb_backend->num_of_entry; i++) {
+        nojb_entry = &nojb_entry[i];
+        if ((zaddr)nojb_entry->target_fileoff == target_fileoff) {
+            nojb_entry->entry_address = entry;
+            entry->on_enter_trampoline = (zpointer)(nojb_entry->on_enter_trampoline + aslr_slide);
+            entry->on_invoke_trampoline = nojb_entry->on_invoke_trampoline + aslr_slide;
+            entry->on_leave_trampoline = nojb_entry->on_leave_trampoline + aslr_slide;
+        }
+    }
+    return ZZ_SUCCESS;
+}
+#endif
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/interceptor-arm64.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/interceptor-arm64.h
new file mode 100644
index 000000000..8001aca0b
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/interceptor-arm64.h
@@ -0,0 +1,63 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_backend_arm64_intercetor_arm64
+#define platforms_backend_arm64_intercetor_arm64
+
+// platforms
+#include "platforms/arch-arm64/relocator-arm64.h"
+#include "platforms/arch-arm64/writer-arm64.h"
+
+// hookzz
+#include "allocator.h"
+#include "interceptor.h"
+#include "thunker.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+#define CTX_SAVE_STACK_OFFSET (8 + 30 * 8 + 8 * 16)
+
+typedef struct _ZzInterceptorBackend {
+    ZzAllocator *allocator;
+    ZzArm64Relocator arm64_relocator;
+
+    ZzArm64Writer arm64_writer;
+
+    zpointer enter_thunk;
+    zpointer half_thunk;
+    zpointer leave_thunk;
+} ZzInterceptorBackend;
+
+typedef struct _ZzArm64HookFuntionEntryBackend {
+    zbool is_thumb;
+    zuint redirect_code_size;
+} ZzArm64HookFunctionEntryBackend;
+
+void ctx_save();
+void ctx_restore();
+void enter_thunk_template();
+void leave_thunk_template();
+void on_enter_trampoline_template();
+void on_invoke_trampoline_template();
+void on_leave_trampoline_template();
+
+ZzCodeSlice *zz_code_patch_arm64_writer(ZzArm64Writer *arm64_writer, ZzAllocator *allocator, zaddr target_addr,
+                                        zsize range_size);
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/interceptor-template-arm64.s b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/interceptor-template-arm64.s
new file mode 100644
index 000000000..1b17ce859
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/interceptor-template-arm64.s
@@ -0,0 +1,306 @@
+// .section	__TEXT,__text,regular,pure_instructions
+// .ios_version_min 11, 0
+.align 4
+.globl _ctx_save
+.globl _ctx_restore
+.globl _enter_thunk_template
+.globl _leave_thunk_template
+.globl _on_enter_trampoline_template
+.globl _on_invoke_trampoline_template
+.globl _on_leave_trampoline_template
+
+_ctx_save:
+	// save {q0-q7}
+	sub sp, sp, #(8*16)
+	stp q6, q7, [sp, #(6*16)]
+	stp q4, q5, [sp, #(4*16)]
+	stp q2, q3, [sp, #(2*16)]
+	stp q0, q1, [sp, #(0*16)]
+
+	// save {x1-x30}
+	sub sp, sp, #(30*8)
+	// stp fp, lr, [sp, #(28*8)]
+	stp x29, x30, [sp, #(28*8)]
+	stp x27, x28, [sp, #(26*8)]
+	stp x25, x26, [sp, #(24*8)]
+	stp x23, x24, [sp, #(22*8)]
+	stp x21, x22, [sp, #(20*8)]
+	stp x19, x20, [sp, #(18*8)]
+	stp x17, x18, [sp, #(16*8)]
+	stp x15, x16, [sp, #(14*8)]
+	stp x13, x14, [sp, #(12*8)]
+	stp x11, x12, [sp, #(10*8)]
+	stp x9, x10, [sp, #(8*8)]
+	stp x7, x8, [sp, #(6*8)]
+	stp x5, x6, [sp, #(4*8)]
+	stp x3, x4, [sp, #(2*8)]
+	stp x1, x2, [sp, #(0*8)]
+
+	// C6.1.3
+	// Use of the stack pointer
+	// save x0 (and reserve sp, but this is trick.)
+	sub sp, sp, #(2*8)
+	str x0, [sp, #8]
+
+_ctx_restore:
+	// C6.1.3
+	// Use of the stack pointer
+	// restore x0
+	ldr x0, [sp, #8]
+	add sp, sp, #(2*8)
+
+	// restore {x1-x30}
+	ldp x1, x2, [sp], #16
+	ldp x3, x4, [sp], #16
+	ldp x5, x6, [sp], #16
+	ldp x7, x8, [sp], #16
+	ldp x9, x10, [sp], #16
+	ldp x11, x12, [sp], #16
+	ldp x13, x14, [sp], #16
+	ldp x15, x16, [sp], #16
+	ldp x17, x18, [sp], #16
+	ldp x19, x20, [sp], #16
+	ldp x21, x22, [sp], #16
+	ldp x23, x24, [sp], #16
+	ldp x25, x26, [sp], #16
+	ldp x27, x28, [sp], #16
+	// ldp fp, lr, [sp], #16
+	ldp x29, x30, [sp], #16
+
+	// restore {q0-q7}
+	ldp q0, q1, [sp], #32
+	ldp q2, q3, [sp], #32
+	ldp q4, q5, [sp], #32
+	ldp q6, q7, [sp], #32
+
+_enter_thunk_template:
+	// ----------- ctx_save begin ---------------
+	// save {q0-q7}
+	sub sp, sp, #(8*16)
+	stp q6, q7, [sp, #(6*16)]
+	stp q4, q5, [sp, #(4*16)]
+	stp q2, q3, [sp, #(2*16)]
+	stp q0, q1, [sp, #(0*16)]
+
+	// save {x1-x30}
+	sub sp, sp, #(30*8)
+	// stp fp, lr, [sp, #(28*8)]
+	stp x29, x30, [sp, #(28*8)]
+	stp x27, x28, [sp, #(26*8)]
+	stp x25, x26, [sp, #(24*8)]
+	stp x23, x24, [sp, #(22*8)]
+	stp x21, x22, [sp, #(20*8)]
+	stp x19, x20, [sp, #(18*8)]
+	stp x17, x18, [sp, #(16*8)]
+	stp x15, x16, [sp, #(14*8)]
+	stp x13, x14, [sp, #(12*8)]
+	stp x11, x12, [sp, #(10*8)]
+	stp x9, x10, [sp, #(8*8)]
+	stp x7, x8, [sp, #(6*8)]
+	stp x5, x6, [sp, #(4*8)]
+	stp x3, x4, [sp, #(2*8)]
+	stp x1, x2, [sp, #(0*8)]
+
+	// C6.1.3
+	// Use of the stack pointer
+	// save x0 (and reserve sp, but this is trick.)
+	sub sp, sp, #(2*8)
+	str x0, [sp, #8]
+	// ----------- ctx_save end ---------------
+
+	// use the `ctx_save` left space, to store origin sp
+	add x1, sp, #0x190
+	str x1, [sp, #0]
+
+	// alignment padding
+	sub sp, sp, #0x10
+
+	// prepare args
+	// x0: entry address, x1: next hop, x2: RegState address, x3: caller_ret_addr
+	ldr x0, [sp, #0x190]
+	add x1, sp, #0x198
+	add x2, sp, #0x10
+	add x3, sp, #0x108
+	// call function_context_begin_invocation
+	bl _function_context_begin_invocation
+
+	// restore alignment padding
+	add sp, sp, #0x10
+
+	// ----------- ctx_restore begin ------------
+	// C6.1.3
+	// Use of the stack pointer
+	// restore x0
+	ldr x0, [sp, #8]
+	add sp, sp, #(2*8)
+
+	// restore {x1-x30}
+	ldp x1, x2, [sp], #16
+	ldp x3, x4, [sp], #16
+	ldp x5, x6, [sp], #16
+	ldp x7, x8, [sp], #16
+	ldp x9, x10, [sp], #16
+	ldp x11, x12, [sp], #16
+	ldp x13, x14, [sp], #16
+	ldp x15, x16, [sp], #16
+	ldp x17, x18, [sp], #16
+	ldp x19, x20, [sp], #16
+	ldp x21, x22, [sp], #16
+	ldp x23, x24, [sp], #16
+	ldp x25, x26, [sp], #16
+	ldp x27, x28, [sp], #16
+	// ldp fp, lr, [sp], #16
+	ldp x29, x30, [sp], #16
+
+	// restore {q0-q7}
+	ldp q0, q1, [sp], #32
+	ldp q2, q3, [sp], #32
+	ldp q4, q5, [sp], #32
+	ldp q6, q7, [sp], #32
+	// ----------- ctx_restore end ------------
+
+	// jump to next hop
+	// next hop addess(store at reserve space)
+	ldr x17, [sp, #8]
+	add sp, sp, #0x10
+	br x17
+
+_leave_thunk_template:
+	// ----------- ctx_save begin ---------------
+	// save {q0-q7}
+	sub sp, sp, #(8*16)
+	stp q6, q7, [sp, #(6*16)]
+	stp q4, q5, [sp, #(4*16)]
+	stp q2, q3, [sp, #(2*16)]
+	stp q0, q1, [sp, #(0*16)]
+
+	// save {x1-x30}
+	sub sp, sp, #(30*8)
+	// stp fp, lr, [sp, #(28*8)]
+	stp x29, x30, [sp, #(28*8)]
+	stp x27, x28, [sp, #(26*8)]
+	stp x25, x26, [sp, #(24*8)]
+	stp x23, x24, [sp, #(22*8)]
+	stp x21, x22, [sp, #(20*8)]
+	stp x19, x20, [sp, #(18*8)]
+	stp x17, x18, [sp, #(16*8)]
+	stp x15, x16, [sp, #(14*8)]
+	stp x13, x14, [sp, #(12*8)]
+	stp x11, x12, [sp, #(10*8)]
+	stp x9, x10, [sp, #(8*8)]
+	stp x7, x8, [sp, #(6*8)]
+	stp x5, x6, [sp, #(4*8)]
+	stp x3, x4, [sp, #(2*8)]
+	stp x1, x2, [sp, #(0*8)]
+
+	// C6.1.3
+	// Use of the stack pointer
+	// save x0 (and reserve sp, but this is trick.)
+	sub sp, sp, #(2*8)
+	str x0, [sp, #8]
+	// ----------- ctx_save end ---------------
+
+	// use the `ctx_save` left space, to store origin sp
+	add x1, sp, #0x190
+	str x1, [sp, #0]
+
+	// alignment padding
+	sub sp, sp, #0x10
+
+	// prepare args
+	// x0: entry address, x1: next hop, x2: RegState address
+	ldr x0, [sp, #0x190]
+	add x1, sp, #0x198
+	add x2, sp, #0x10
+	// call function_context_end_invocation
+	bl _function_context_end_invocation
+
+	// restore alignment padding
+	add sp, sp, #0x10
+
+	// ----------- ctx_restore begin ------------
+	// C6.1.3
+	// Use of the stack pointer
+	// restore x0
+	ldr x0, [sp, #8]
+	add sp, sp, #(2*8)
+
+	// restore {x1-x30}
+	ldp x1, x2, [sp], #16
+	ldp x3, x4, [sp], #16
+	ldp x5, x6, [sp], #16
+	ldp x7, x8, [sp], #16
+	ldp x9, x10, [sp], #16
+	ldp x11, x12, [sp], #16
+	ldp x13, x14, [sp], #16
+	ldp x15, x16, [sp], #16
+	ldp x17, x18, [sp], #16
+	ldp x19, x20, [sp], #16
+	ldp x21, x22, [sp], #16
+	ldp x23, x24, [sp], #16
+	ldp x25, x26, [sp], #16
+	ldp x27, x28, [sp], #16
+	// ldp fp, lr, [sp], #16
+	ldp x29, x30, [sp], #16
+
+	// restore {q0-q7}
+	ldp q0, q1, [sp], #32
+	ldp q2, q3, [sp], #32
+	ldp q4, q5, [sp], #32
+	ldp q6, q7, [sp], #32
+	// ----------- ctx_restore end ------------
+
+	// jump to next hop
+	// next hop addess(store at reserve space)
+	ldr x17, [sp, #8]
+	add sp, sp, #0x10
+	br x17
+
+_on_enter_trampoline_template:
+	// store entry address and reserve space for next hop
+	sub sp, sp, 0x10
+	ldr x17, #0x8
+	b #0xc
+	// entry address
+	.long 0x0
+	.long 0x0
+	str x17, [sp]
+	ldr x17, #0x8
+	br x17
+	// enter_thunk address
+	.long 0x0
+	.long 0x0
+
+_on_invoke_trampoline_template:
+	// fix instruction
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	nop
+	ldr x17, #8
+	br x17
+	.long 0x0
+	.long 0x0
+
+_on_leave_trampoline_template:
+	// store entry address and reserve space for next hop
+	sub sp, sp, 0x10
+	ldr x17, #0x8
+	b #0xc
+	// entry address
+	.long 0x0
+	.long 0x0
+	str x17, [sp]
+	ldr x17, #0x8
+	br x17
+	// leave_thunk address
+	.long 0x0
+	.long 0x0
+
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/thunker-arm64.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/thunker-arm64.c
new file mode 100644
index 000000000..a5b3dc648
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/thunker-arm64.c
@@ -0,0 +1,575 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "thunker-arm64.h"
+#include "zzinfo.h"
+#include <string.h>
+
+/*
+    Programmer’s Guide for ARMv8-A:
+        Page: (6-15)
+        Page: (6-16)
+
+    STP X9, X8, [X4]
+        Stores the doubleword in X9 to address X4 and stores the doubleword
+   in X8 to address X4 + 8. LDP X8, X2, [X0, #0x10]! Loads doubleword at
+   address X0 + 0x10 into X8 and the doubleword at address X0 + 0x10 + 8
+   into X2 and add 0x10 to X0. See Figure 6-7.
+ */
+
+// 前提: 不能直接访问 pc, 也就说只有通过寄存器才能实现绝对地址跳
+
+// __attribute__((__naked__)) static void ctx_save() {
+//     __asm__ volatile(
+
+//         /* save {q0-q7} */
+//         "sub sp, sp, #(8*16)\n"
+//         "stp q6, q7, [sp, #(6*16)]\n"
+//         "stp q4, q5, [sp, #(4*16)]\n"
+//         "stp q2, q3, [sp, #(2*16)]\n"
+//         "stp q0, q1, [sp, #(0*16)]\n"
+
+//         /* save {x1-x30} */
+//         "sub sp, sp, #(30*8)\n"
+//         // "stp fp, lr, [sp, #(28*8)]\n"
+//         "stp x29, x30, [sp, #(28*8)]\n"
+//         "stp x27, x28, [sp, #(26*8)]\n"
+//         "stp x25, x26, [sp, #(24*8)]\n"
+//         "stp x23, x24, [sp, #(22*8)]\n"
+//         "stp x21, x22, [sp, #(20*8)]\n"
+//         "stp x19, x20, [sp, #(18*8)]\n"
+//         "stp x17, x18, [sp, #(16*8)]\n"
+//         "stp x15, x16, [sp, #(14*8)]\n"
+//         "stp x13, x14, [sp, #(12*8)]\n"
+//         "stp x11, x12, [sp, #(10*8)]\n"
+//         "stp x9, x10, [sp, #(8*8)]\n"
+//         "stp x7, x8, [sp, #(6*8)]\n"
+//         "stp x5, x6, [sp, #(4*8)]\n"
+//         "stp x3, x4, [sp, #(2*8)]\n"
+//         "stp x1, x2, [sp, #(0*8)]\n"
+
+//         // C6.1.3
+//         // Use of the stack pointer
+//         // save x0 (and reserve sp, but this is trick.)
+//         "sub sp, sp, #(2*8)\n"
+//         "str x0, [sp, #8]\n");
+// }
+
+// __attribute__((__naked__)) static void ctx_restore() {
+//     __asm__ volatile(
+//         // C6.1.3
+//         // Use of the stack pointer
+//         // restore x0
+//         "ldr x0, [sp, #8]\n"
+//         "add sp, sp, #(2*8)\n"
+
+//         /* restore {x1-x30} */
+//         "ldp x1, x2, [sp], #16\n"
+//         "ldp x3, x4, [sp], #16\n"
+//         "ldp x5, x6, [sp], #16\n"
+//         "ldp x7, x8, [sp], #16\n"
+//         "ldp x9, x10, [sp], #16\n"
+//         "ldp x11, x12, [sp], #16\n"
+//         "ldp x13, x14, [sp], #16\n"
+//         "ldp x15, x16, [sp], #16\n"
+//         "ldp x17, x18, [sp], #16\n"
+//         "ldp x19, x20, [sp], #16\n"
+//         "ldp x21, x22, [sp], #16\n"
+//         "ldp x23, x24, [sp], #16\n"
+//         "ldp x25, x26, [sp], #16\n"
+//         "ldp x27, x28, [sp], #16\n"
+//         // "ldp fp, lr, [sp], #16\n"
+//         "ldp x29, x30, [sp], #16\n"
+
+//         /* restore {q0-q7} */
+//         "ldp q0, q1, [sp], #32\n"
+//         "ldp q2, q3, [sp], #32\n"
+//         "ldp q4, q5, [sp], #32\n"
+//         "ldp q6, q7, [sp], #32\n");
+// }
+
+// just like pre_call, wow!
+void function_context_begin_invocation(ZzHookFunctionEntry *entry, zpointer next_hop, RegState *rs,
+                                       zpointer caller_ret_addr) {
+    Xinfo("target %p call begin-invocation", entry->target_ptr);
+
+    ZzThreadStack *stack = ZzGetCurrentThreadStack(entry->thread_local_key);
+    if (!stack) {
+        stack = ZzNewThreadStack(entry->thread_local_key);
+    }
+    ZzCallStack *callstack = ZzNewCallStack();
+    ZzPushCallStack(stack, callstack);
+
+    /* call pre_call */
+    if (entry->pre_call) {
+        PRECALL pre_call;
+        pre_call = entry->pre_call;
+        (*pre_call)(rs, (ThreadStack *)stack, (CallStack *)callstack);
+    }
+
+    /* set next hop */
+    if (entry->replace_call) {
+        *(zpointer *)next_hop = entry->replace_call;
+    } else {
+        *(zpointer *)next_hop = entry->on_invoke_trampoline;
+    }
+
+    if (entry->hook_type == HOOK_FUNCTION_TYPE) {
+        callstack->caller_ret_addr = *(zpointer *)caller_ret_addr;
+        *(zpointer *)caller_ret_addr = entry->on_leave_trampoline;
+    }
+}
+
+// just like post_call, wow!
+void function_context_half_invocation(ZzHookFunctionEntry *entry, zpointer next_hop, RegState *rs,
+                                      zpointer caller_ret_addr) {
+    Xdebug("target %p call half-invocation", entry->target_ptr);
+
+    ZzThreadStack *stack = ZzGetCurrentThreadStack(entry->thread_local_key);
+    if (!stack) {
+#if defined(DEBUG_MODE)
+        debug_break();
+#endif
+    }
+    ZzCallStack *callstack = ZzPopCallStack(stack);
+
+    /* call half_call */
+    if (entry->half_call) {
+        HALFCALL half_call;
+        half_call = entry->half_call;
+        (*half_call)(rs, (ThreadStack *)stack, (CallStack *)callstack);
+    }
+
+    /*  set next hop */
+    *(zpointer *)next_hop = (zpointer)entry->target_half_ret_addr;
+
+    ZzFreeCallStack(callstack);
+}
+
+// just like post_call, wow!
+void function_context_end_invocation(ZzHookFunctionEntry *entry, zpointer next_hop, RegState *rs) {
+    Xdebug("%p call end-invocation", entry->target_ptr);
+
+    ZzThreadStack *stack = ZzGetCurrentThreadStack(entry->thread_local_key);
+    if (!stack) {
+#if defined(DEBUG_MODE)
+        debug_break();
+#endif
+    }
+    ZzCallStack *callstack = ZzPopCallStack(stack);
+
+    /* call post_call */
+    if (entry->post_call) {
+        POSTCALL post_call;
+        post_call = entry->post_call;
+        (*post_call)(rs, (ThreadStack *)stack, (CallStack *)callstack);
+    }
+
+    /* set next hop */
+    *(zpointer *)next_hop = callstack->caller_ret_addr;
+    ZzFreeCallStack(callstack);
+}
+
+// __attribute__((__naked__)) void enter_thunk_template() {
+//     /* register save */
+//     __asm__ volatile(
+
+//         /* save {q0-q7} */
+//         "sub sp, sp, #(8*16)\n"
+//         "stp q6, q7, [sp, #(6*16)]\n"
+//         "stp q4, q5, [sp, #(4*16)]\n"
+//         "stp q2, q3, [sp, #(2*16)]\n"
+//         "stp q0, q1, [sp, #(0*16)]\n"
+
+//         /* save {x1-x30} */
+//         "sub sp, sp, #(30*8)\n"
+//         // "stp fp, lr, [sp, #(28*8)]\n"
+//         "stp x29, x30, [sp, #(28*8)]\n"
+//         "stp x27, x28, [sp, #(26*8)]\n"
+//         "stp x25, x26, [sp, #(24*8)]\n"
+//         "stp x23, x24, [sp, #(22*8)]\n"
+//         "stp x21, x22, [sp, #(20*8)]\n"
+//         "stp x19, x20, [sp, #(18*8)]\n"
+//         "stp x17, x18, [sp, #(16*8)]\n"
+//         "stp x15, x16, [sp, #(14*8)]\n"
+//         "stp x13, x14, [sp, #(12*8)]\n"
+//         "stp x11, x12, [sp, #(10*8)]\n"
+//         "stp x9, x10, [sp, #(8*8)]\n"
+//         "stp x7, x8, [sp, #(6*8)]\n"
+//         "stp x5, x6, [sp, #(4*8)]\n"
+//         "stp x3, x4, [sp, #(2*8)]\n"
+//         "stp x1, x2, [sp, #(0*8)]\n"
+
+//         // C6.1.3
+//         // Use of the stack pointer
+//         // save x0 (and reserve sp, but this is trick.)
+//         "sub sp, sp, #(2*8)\n"
+//         "str x0, [sp, #8]\n");
+
+//     __asm__ volatile(
+//         /* use the `ctx_save` left space, to store origin sp */
+//         "add x1, sp, #0x190\n"
+//         "str x1, [sp, #0]\n"
+//         /* alignment padding */
+//         "sub sp, sp, #0x10\n"
+//         /* prepare args */
+//         /* x0: entry address, x1: next hop, x2: RegState address, x3: caller_ret_addr */
+//         "ldr x0, [sp, #0x190]\n"
+//         "add x1, sp, #0x198\n"
+//         "add x2, sp, #0x10\n"
+//         "add x3, sp, #0x108\n"
+//         "ldr x17, #0xc\n"
+//         "blr x17\n"
+//         "b #0xc\n"
+//         /* function_context_begin_invocation address */
+//         /* TODO: need some trick. */
+//         // ((void (*)(void))(function_context_begin_invocation))();
+//         ".long 0x0\n"
+//         ".long 0x0\n"
+//         /* restore alignment padding */
+//         "add sp, sp, #0x10\n");
+
+//     /* register restore */
+//     __asm__ volatile(
+//         // C6.1.3
+//         // Use of the stack pointer
+//         // restore x0
+//         "ldr x0, [sp, #8]\n"
+//         "add sp, sp, #(2*8)\n"
+
+//         /* restore {x1-x30} */
+//         "ldp x1, x2, [sp], #16\n"
+//         "ldp x3, x4, [sp], #16\n"
+//         "ldp x5, x6, [sp], #16\n"
+//         "ldp x7, x8, [sp], #16\n"
+//         "ldp x9, x10, [sp], #16\n"
+//         "ldp x11, x12, [sp], #16\n"
+//         "ldp x13, x14, [sp], #16\n"
+//         "ldp x15, x16, [sp], #16\n"
+//         "ldp x17, x18, [sp], #16\n"
+//         "ldp x19, x20, [sp], #16\n"
+//         "ldp x21, x22, [sp], #16\n"
+//         "ldp x23, x24, [sp], #16\n"
+//         "ldp x25, x26, [sp], #16\n"
+//         "ldp x27, x28, [sp], #16\n"
+//         // "ldp fp, lr, [sp], #16\n"
+//         "ldp x29, x30, [sp], #16\n"
+
+//         /* restore {q0-q7} */
+//         "ldp q0, q1, [sp], #32\n"
+//         "ldp q2, q3, [sp], #32\n"
+//         "ldp q4, q5, [sp], #32\n"
+//         "ldp q6, q7, [sp], #32\n");
+//     /* register save */
+//     __asm__ volatile(
+//         /* jump to next hop */
+//         /* next hop address(store at reserve space  */
+//         "ldr x17, [sp, #8]\n"
+//         "add sp, sp, #0x10\n"
+//         "br x17");
+// }
+
+void zz_arm64_thunker_build_enter_thunk(ZzWriter *writer) {
+    /* save general registers and sp */
+    zz_arm64_writer_put_bytes(writer, (void *)ctx_save, 23 * 4);
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X1, ZZ_ARM64_REG_SP, 8 + CTX_SAVE_STACK_OFFSET + 2 * 8);
+
+    /* trick: use the `ctx_save` left [sp]*/
+    zz_arm64_writer_put_str_reg_reg_offset(writer, ZZ_ARM64_REG_X1, ZZ_ARM64_REG_SP, 0 * 8);
+
+    /* alignment padding + dummy PC */
+    zz_arm64_writer_put_sub_reg_reg_imm(writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 8);
+
+    /* pass enter func args */
+    /* entry */
+    zz_arm64_writer_put_ldr_reg_reg_offset(writer, ZZ_ARM64_REG_X0, ZZ_ARM64_REG_SP, 2 * 8 + 8 + CTX_SAVE_STACK_OFFSET);
+    /* next hop*/
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X1, ZZ_ARM64_REG_SP,
+                                        2 * 8 + 8 + CTX_SAVE_STACK_OFFSET + 0x8);
+    /* RegState */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X2, ZZ_ARM64_REG_SP, 2 * 8);
+    /* caller ret address */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X3, ZZ_ARM64_REG_SP, 2 * 8 + 2 * 8 + 28 * 8 + 8);
+
+    /* call function_context_begin_invocation */
+    zz_arm64_writer_put_ldr_blr_b_reg_address(writer, ZZ_ARM64_REG_X17, (zaddr)function_context_begin_invocation);
+
+    /* alignment padding + dummy PC */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 8);
+
+    /* restore general registers stack */
+    zz_arm64_writer_put_bytes(writer, (void *)ctx_restore, 21 * 4);
+
+    /* load next hop to x17 */
+    zz_arm64_writer_put_ldr_reg_reg_offset(writer, ZZ_ARM64_REG_X17, ZZ_ARM64_REG_SP, 0x8);
+
+    /* restore next hop and arg stack */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 8);
+
+    /* jump to next hop */
+    zz_arm64_writer_put_br_reg(writer, ZZ_ARM64_REG_X17);
+}
+
+void zz_arm64_thunker_build_half_thunk(ZzWriter *writer) {
+    /* save general registers and sp */
+    zz_arm64_writer_put_bytes(writer, (void *)ctx_save, 23 * 4);
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X1, ZZ_ARM64_REG_SP, 8 + CTX_SAVE_STACK_OFFSET + 2 * 8);
+
+    /* trick: use the `ctx_save` left [sp]*/
+    zz_arm64_writer_put_str_reg_reg_offset(writer, ZZ_ARM64_REG_X1, ZZ_ARM64_REG_SP, 0 * 8);
+
+    /* alignment padding + dummy PC */
+    zz_arm64_writer_put_sub_reg_reg_imm(writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 8);
+
+    /* pass enter func args */
+    /* entry */
+    zz_arm64_writer_put_ldr_reg_reg_offset(writer, ZZ_ARM64_REG_X0, ZZ_ARM64_REG_SP, 2 * 8 + 8 + CTX_SAVE_STACK_OFFSET);
+    /* next hop*/
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X1, ZZ_ARM64_REG_SP,
+                                        2 * 8 + 8 + CTX_SAVE_STACK_OFFSET + 0x8);
+
+    /* RegState */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X2, ZZ_ARM64_REG_SP, 2 * 8);
+    /* caller ret address */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X3, ZZ_ARM64_REG_SP, 2 * 8 + 2 * 8 + 28 * 8 + 8);
+
+    /* call function_context_half_invocation */
+    zz_arm64_writer_put_ldr_blr_b_reg_address(writer, ZZ_ARM64_REG_X17, (zaddr)function_context_half_invocation);
+
+    /* alignment padding + dummy PC */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 8);
+
+    /* restore general registers stack */
+    zz_arm64_writer_put_bytes(writer, (void *)ctx_restore, 21 * 4);
+
+    /* load next hop to x17 */
+    zz_arm64_writer_put_ldr_reg_reg_offset(writer, ZZ_ARM64_REG_X17, ZZ_ARM64_REG_SP, 0x8);
+
+    /* restore next hop and arg stack */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 8);
+
+    /* jump to next hop */
+    zz_arm64_writer_put_br_reg(writer, ZZ_ARM64_REG_X17);
+}
+
+// __attribute__((__naked__)) void leave_thunk_template() {
+//     /* register save */
+//     __asm__ volatile(
+
+//         /* save {q0-q7} */
+//         "sub sp, sp, #(8*16)\n"
+//         "stp q6, q7, [sp, #(6*16)]\n"
+//         "stp q4, q5, [sp, #(4*16)]\n"
+//         "stp q2, q3, [sp, #(2*16)]\n"
+//         "stp q0, q1, [sp, #(0*16)]\n"
+
+//         /* save {x1-x30} */
+//         "sub sp, sp, #(30*8)\n"
+//         // "stp fp, lr, [sp, #(28*8)]\n"
+//         "stp x29, x30, [sp, #(28*8)]\n"
+//         "stp x27, x28, [sp, #(26*8)]\n"
+//         "stp x25, x26, [sp, #(24*8)]\n"
+//         "stp x23, x24, [sp, #(22*8)]\n"
+//         "stp x21, x22, [sp, #(20*8)]\n"
+//         "stp x19, x20, [sp, #(18*8)]\n"
+//         "stp x17, x18, [sp, #(16*8)]\n"
+//         "stp x15, x16, [sp, #(14*8)]\n"
+//         "stp x13, x14, [sp, #(12*8)]\n"
+//         "stp x11, x12, [sp, #(10*8)]\n"
+//         "stp x9, x10, [sp, #(8*8)]\n"
+//         "stp x7, x8, [sp, #(6*8)]\n"
+//         "stp x5, x6, [sp, #(4*8)]\n"
+//         "stp x3, x4, [sp, #(2*8)]\n"
+//         "stp x1, x2, [sp, #(0*8)]\n"
+
+//         // C6.1.3
+//         // Use of the stack pointer
+//         // save x0 (and reserve sp, but this is trick.)
+//         "sub sp, sp, #(2*8)\n"
+//         "str x0, [sp, #8]\n");
+
+//     __asm__ volatile(
+//         /* use the `ctx_save` left space, to store origin sp */
+//         "add x1, sp, #0x190\n"
+//         "str x1, [sp, #0]\n"
+//         /* alignment padding */
+//         "sub sp, sp, #0x10\n"
+//         /* prepare args */
+//         /* x0: entry address, x1: next hop, x2: RegState address, x3: caller_ret_addr */
+//         "ldr x0, [sp, #0x190]\n"
+//         "add x1, sp, #0x198\n"
+//         "add x2, sp, #0x10\n"
+//         "ldr x17, #0xc\n"
+//         "blr x17\n"
+//         "b #0xc\n"
+//         /* function_context_end_invocation address */
+//         ".long 0x0\n"
+//         ".long 0x0\n"
+//         /* restore alignment padding */
+//         "add sp, sp, #0x10\n");
+
+//     /* register restore */
+//     __asm__ volatile(
+//         // C6.1.3
+//         // Use of the stack pointer
+//         // restore x0
+//         "ldr x0, [sp, #8]\n"
+//         "add sp, sp, #(2*8)\n"
+
+//         /* restore {x1-x30} */
+//         "ldp x1, x2, [sp], #16\n"
+//         "ldp x3, x4, [sp], #16\n"
+//         "ldp x5, x6, [sp], #16\n"
+//         "ldp x7, x8, [sp], #16\n"
+//         "ldp x9, x10, [sp], #16\n"
+//         "ldp x11, x12, [sp], #16\n"
+//         "ldp x13, x14, [sp], #16\n"
+//         "ldp x15, x16, [sp], #16\n"
+//         "ldp x17, x18, [sp], #16\n"
+//         "ldp x19, x20, [sp], #16\n"
+//         "ldp x21, x22, [sp], #16\n"
+//         "ldp x23, x24, [sp], #16\n"
+//         "ldp x25, x26, [sp], #16\n"
+//         "ldp x27, x28, [sp], #16\n"
+//         // "ldp fp, lr, [sp], #16\n"
+//         "ldp x29, x30, [sp], #16\n"
+
+//         /* restore {q0-q7} */
+//         "ldp q0, q1, [sp], #32\n"
+//         "ldp q2, q3, [sp], #32\n"
+//         "ldp q4, q5, [sp], #32\n"
+//         "ldp q6, q7, [sp], #32\n");
+//     /* register save */
+//     __asm__ volatile(
+//         /* jump to next hop */
+//         /* next hop address(store at reserve space  */
+//         "ldr x17, [sp, #8]\n"
+//         "add sp, sp, #0x10\n"
+//         "br x17");
+// }
+
+void zz_arm64_thunker_build_leave_thunk(ZzWriter *writer) {
+    /* save general registers and sp */
+    zz_arm64_writer_put_bytes(writer, (void *)ctx_save, 23 * 4);
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X1, ZZ_ARM64_REG_SP, 8 + CTX_SAVE_STACK_OFFSET + 2 * 8);
+
+    /* trick: use the `ctx_save` left [sp]*/
+    zz_arm64_writer_put_str_reg_reg_offset(writer, ZZ_ARM64_REG_X1, ZZ_ARM64_REG_SP, 0 * 8);
+
+    /* alignment padding + dummy PC */
+    zz_arm64_writer_put_sub_reg_reg_imm(writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 8);
+
+    /* pass enter func args */
+    /* entry */
+    zz_arm64_writer_put_ldr_reg_reg_offset(writer, ZZ_ARM64_REG_X0, ZZ_ARM64_REG_SP, 2 * 8 + 8 + CTX_SAVE_STACK_OFFSET);
+    /* next hop*/
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X1, ZZ_ARM64_REG_SP,
+                                        2 * 8 + 8 + CTX_SAVE_STACK_OFFSET + 0x8);
+
+    /* RegState */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_X2, ZZ_ARM64_REG_SP, 2 * 8);
+
+    /* call function_context_end_invocation */
+    zz_arm64_writer_put_ldr_blr_b_reg_address(writer, ZZ_ARM64_REG_X17, (zaddr)function_context_end_invocation);
+
+    /* alignment padding + dummy PC */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 8);
+
+    /* restore general registers stack */
+    zz_arm64_writer_put_bytes(writer, (void *)ctx_restore, 21 * 4);
+
+    /* load next hop to x17 */
+    zz_arm64_writer_put_ldr_reg_reg_offset(writer, ZZ_ARM64_REG_X17, ZZ_ARM64_REG_SP, 0x8);
+
+    /* restore next hop and arg stack */
+    zz_arm64_writer_put_add_reg_reg_imm(writer, ZZ_ARM64_REG_SP, ZZ_ARM64_REG_SP, 2 * 8);
+
+    /* jump to next hop */
+    zz_arm64_writer_put_br_reg(writer, ZZ_ARM64_REG_X17);
+}
+
+ZZSTATUS ZzThunkerBuildThunk(ZzInterceptorBackend *self) {
+    zbyte temp_code_slice_data[512] = {0};
+    ZzArm64Writer *arm64_writer = NULL;
+    ZzCodeSlice *code_slice = NULL;
+    ZZSTATUS status = ZZ_SUCCESS;
+
+    arm64_writer = &self->arm64_writer;
+    zz_arm64_writer_reset(arm64_writer, temp_code_slice_data);
+
+    /* build enter_thunk */
+    zz_arm64_thunker_build_enter_thunk(arm64_writer);
+
+    /* code patch */
+    code_slice = zz_code_patch_arm64_writer(arm64_writer, self->allocator, 0, 0);
+    if (code_slice)
+        self->enter_thunk = (void *)enter_thunk_template;
+    else
+        return ZZ_FAILED;
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzThunkerBuildThunk:");
+        sprintf(buffer + strlen(buffer), "LogInfo: enter_thunk at %p, use enter_thunk_template.\n",
+                (void *)enter_thunk_template);
+        ZzInfoLog("%s", buffer);
+    }
+
+    zz_arm64_writer_reset(arm64_writer, temp_code_slice_data);
+
+    /* build  leave_thunk */
+    zz_arm64_thunker_build_leave_thunk(arm64_writer);
+
+    /* code patch */
+    code_slice = zz_code_patch_arm64_writer(arm64_writer, self->allocator, 0, 0);
+    if (code_slice)
+        self->leave_thunk = code_slice->data;
+    else
+        return ZZ_FAILED;
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzThunkerBuildThunk:");
+        sprintf(buffer + strlen(buffer), "LogInfo: leave_thunk at %p, length: %ld.\n", code_slice->data,
+                code_slice->size);
+        ZzInfoLog("%s", buffer);
+    }
+
+    zz_arm64_writer_reset(arm64_writer, temp_code_slice_data);
+
+    /* build half_thunk */
+    zz_arm64_thunker_build_half_thunk(arm64_writer);
+
+    /* code patch */
+    code_slice = zz_code_patch_arm64_writer(arm64_writer, self->allocator, 0, 0);
+    if (code_slice)
+        self->half_thunk = code_slice->data;
+    else
+        return ZZ_FAILED;
+
+    /* debug log */
+    if (ZzIsEnableDebugMode()) {
+        char buffer[1024] = {};
+        sprintf(buffer + strlen(buffer), "%s\n", "ZzThunkerBuildThunk:");
+        sprintf(buffer + strlen(buffer), "LogInfo: half_thunk at %p, length: %ld.\n", code_slice->data,
+                code_slice->size);
+        ZzInfoLog("%s", buffer);
+    }
+
+    return status;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/thunker-arm64.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/thunker-arm64.h
new file mode 100644
index 000000000..3121a7f9b
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-arm64/thunker-arm64.h
@@ -0,0 +1,35 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_backend_arm64_thunker_arm64
+#define platforms_backend_arm64_thunker_arm64
+
+// platforms
+#include "platforms/arch-arm64/relocator-arm64.h"
+#include "platforms/arch-arm64/writer-arm64.h"
+
+#include "interceptor-arm64.h"
+
+// hookzz
+#include "stack.h"
+#include "thunker.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-darwin/memory-darwin.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-darwin/memory-darwin.c
new file mode 100644
index 000000000..04da1b4b6
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-darwin/memory-darwin.c
@@ -0,0 +1,49 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <mach/mach.h>
+
+#include "memory-darwin.h"
+
+zsize ZzMemoryGetPageSzie() { return zz_posix_vm_get_page_size(); }
+
+zpointer ZzMemoryAllocatePages(zsize n_pages) { return zz_vm_allocate_pages_via_task(mach_task_self(), n_pages); }
+
+zpointer ZzMemoryAllocateNearPages(zaddr address, zsize redirect_range_size, zsize n_pages) {
+    return zz_vm_allocate_near_pages_via_task(mach_task_self(), address, redirect_range_size, n_pages);
+}
+
+zpointer ZzMemoryAllocate(zsize size) { return zz_vm_allocate_via_task(mach_task_self(), size); }
+
+zbool ZzMemoryPatchCode(const zaddr address, const zpointer codedata, zuint codedata_size) {
+    return zz_vm_patch_code_via_task(mach_task_self(), address, codedata, codedata_size);
+}
+
+zbool ZzMemoryProtectAsExecutable(const zaddr address, zsize size) {
+
+    return zz_vm_protect_as_executable_via_task(mach_task_self(), address, size);
+}
+
+zbool ZzMemoryProtectAsWritable(const zaddr address, zsize size) {
+    return zz_vm_protect_as_writable_via_task(mach_task_self(), address, size);
+}
+
+zpointer ZzMemorySearchCodeCave(zaddr address, zsize redirect_range_size, zsize size) {
+    // return zz_vm_search_text_code_cave_via_dylibs(address, redirect_range_size, size);
+    return zz_vm_search_code_cave(address, redirect_range_size, size);
+}
+
+zbool ZzMemoryIsSupportAllocateRXPage() { return zz_vm_can_allocate_rx_page(); }
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-darwin/memory-darwin.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-darwin/memory-darwin.h
new file mode 100644
index 000000000..9c0ea5d62
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-darwin/memory-darwin.h
@@ -0,0 +1,33 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_backend_darwin_memory_h
+#define platforms_backend_darwin_memory_h
+
+// platforms
+
+// hookzz
+#include "hookzz.h"
+#include "memory.h"
+
+// zzdeps
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/darwin/memory-utils-darwin.h"
+#include "zzdeps/posix/memory-utils-posix.h"
+#include "zzdeps/zz.h"
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-linux/memory-linux.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-linux/memory-linux.c
new file mode 100644
index 000000000..83d3d3a8a
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-linux/memory-linux.c
@@ -0,0 +1,47 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "memory-linux.h"
+
+zsize ZzMemoryGetPageSzie() { return zz_posix_vm_get_page_size(); }
+
+zpointer ZzMemoryAllocatePages(zsize n_pages) { return zz_posix_vm_allocate_pages(n_pages); }
+
+zpointer ZzMemoryAllocateNearPages(zaddr address, zsize redirect_range_size, zsize n_pages) {
+    return zz_posix_vm_allocate_near_pages(address, redirect_range_size, n_pages);
+}
+
+zpointer ZzMemoryAllocate(zsize size) { return zz_posix_vm_allocate(size); }
+
+zbool ZzMemoryPatchCode(const zaddr address, const zpointer codedata, zuint codedata_size) {
+    return zz_posix_vm_patch_code(address, codedata, codedata_size);
+}
+
+zbool ZzMemoryProtectAsExecutable(const zaddr address, zsize size) {
+
+    return zz_posix_vm_protect_as_executable(address, size);
+}
+
+zbool ZzMemoryProtectAsWritable(const zaddr address, zsize size) {
+    return zz_posxi_vm_protect_as_writable(address, size);
+}
+
+zpointer ZzMemorySearchCodeCave(zaddr address, zsize redirect_range_size, zsize size) {
+    // return zz_vm_search_text_code_cave_via_dylibs(address, redirect_range_size, size);
+    return zz_linux_vm_search_code_cave(address, redirect_range_size, size);
+}
+
+zbool ZzMemoryIsSupportAllocateRXPage() { return TRUE; }
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-linux/memory-linux.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-linux/memory-linux.h
new file mode 100644
index 000000000..6015e6836
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-linux/memory-linux.h
@@ -0,0 +1,33 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_backend_linux_memory_h
+#define platforms_backend_linux_memory_h
+
+// platforms
+
+// hookzz
+#include "hookzz.h"
+#include "memory.h"
+
+// zzdeps
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/linux/memory-utils-linux.h"
+#include "zzdeps/posix/memory-utils-posix.h"
+#include "zzdeps/zz.h"
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-posix/thread-posix.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-posix/thread-posix.c
new file mode 100644
index 000000000..b779ff2e9
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-posix/thread-posix.c
@@ -0,0 +1,29 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "thread-posix.h"
+
+zpointer ZzThreadNewThreadLocalKeyPtr() { return zz_posix_thread_new_thread_local_key_ptr(); }
+
+zpointer ZzThreadGetCurrentThreadData(zpointer key_ptr) {
+    return zz_posix_thread_get_current_thread_data(key_ptr);
+}
+
+zbool ZzThreadSetCurrentThreadData(zpointer key_ptr, zpointer data) {
+    return zz_posix_thread_set_current_thread_data(key_ptr, data);
+}
+
+long ZzThreadGetCurrentThreadID() { return zz_posix_get_current_thread_id(); }
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-posix/thread-posix.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-posix/thread-posix.h
new file mode 100644
index 000000000..6f21987a5
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-posix/thread-posix.h
@@ -0,0 +1,32 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_backend_posix_thread_h
+#define platforms_backend_posix_thread_h
+
+// platforms
+
+// hookzz
+#include "hookzz.h"
+#include "thread.h"
+
+// zzdeps
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/posix/thread-utils-posix.h"
+#include "zzdeps/zz.h"
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/interceptor-template-x86.s b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/interceptor-template-x86.s
new file mode 100644
index 000000000..b690233d8
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/interceptor-template-x86.s
@@ -0,0 +1,10 @@
+// .section	__TEXT,__text,regular,pure_instructions
+// .ios_version_min 11, 0
+.align 4
+.globl _ctx_save
+.globl _ctx_restore
+.globl _enter_thunk_template
+.globl _leave_thunk_template
+.globl _on_enter_trampoline_template
+.globl _on_invoke_trampoline_template
+.globl _on_leave_trampoline_template
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/interceptor-x86.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/interceptor-x86.c
new file mode 100644
index 000000000..0f1fdc1ed
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/interceptor-x86.c
@@ -0,0 +1,48 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "interceptor-x86.h"
+#include "zzinfo.h"
+#include <stdlib.h>
+#include <string.h>
+
+#define ZZ_X86_TINY_REDIRECT_SIZE 4
+#define ZZ_X86_FULL_REDIRECT_SIZE 16
+
+ZzInterceptorBackend *ZzBuildInteceptorBackend(ZzAllocator *allocator) { return NULL; }
+
+ZzCodeSlice *zz_code_patch_x86_writer(ZzX86Writer *x86_writer, ZzAllocator *allocator, zaddr target_addr,
+                                      zsize range_size) {
+    return NULL;
+}
+ZzCodeSlice *zz_code_patch_x86_relocate_writer(ZzX86Relocator *relocator, ZzX86Writer *x86_writer,
+                                               ZzAllocator *allocator, zaddr target_addr, zsize range_size) {
+    return NULL;
+}
+
+ZZSTATUS ZzPrepareTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) { return ZZ_FAILED; }
+
+ZZSTATUS ZzBuildEnterTransferTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) { return ZZ_FAILED; }
+
+ZZSTATUS ZzBuildEnterTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) { return ZZ_FAILED; }
+
+ZZSTATUS ZzBuildInvokeTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) { return ZZ_FAILED; }
+
+ZZSTATUS ZzBuildHalfTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) { return ZZ_FAILED; }
+
+ZZSTATUS ZzBuildLeaveTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) { return ZZ_FAILED; }
+
+ZZSTATUS ZzActivateTrampoline(ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) { return ZZ_FAILED; }
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/interceptor-x86.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/interceptor-x86.h
new file mode 100644
index 000000000..39848f888
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/interceptor-x86.h
@@ -0,0 +1,54 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_backend_x86_intercetor_x86
+#define platforms_backend_x86_intercetor_x86
+
+// platforms
+#include "platforms/arch-x86/relocator-x86.h"
+#include "platforms/arch-x86/writer-x86.h"
+
+// hookzz
+#include "allocator.h"
+#include "interceptor.h"
+#include "thunker.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+#define CTX_SAVE_STACK_OFFSET (8 + 30 * 8 + 8 * 16)
+
+typedef struct _ZzInterceptorBackend {
+
+} ZzInterceptorBackend;
+
+typedef struct _ZzX86HookFuntionEntryBackend {
+} ZzX86HookFunctionEntryBackend;
+
+void ctx_save();
+void ctx_restore();
+void enter_thunk_template();
+void leave_thunk_template();
+void on_enter_trampoline_template();
+void on_invoke_trampoline_template();
+void on_leave_trampoline_template();
+
+ZzCodeSlice *zz_code_patch_x86_writer(ZzX86Writer *x86_writer, ZzAllocator *allocator, zaddr target_addr,
+                                      zsize range_size);
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/thunker-x86.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/thunker-x86.c
new file mode 100644
index 000000000..59278e206
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/thunker-x86.c
@@ -0,0 +1,115 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "thunker-x86.h"
+#include "zzinfo.h"
+#include <string.h>
+
+
+// just like pre_call, wow!
+void function_context_begin_invocation(ZzHookFunctionEntry *entry, zpointer next_hop, RegState *rs,
+                                       zpointer caller_ret_addr) {
+    Xinfo("target %p call begin-invocation", entry->target_ptr);
+
+    ZzThreadStack *stack = ZzGetCurrentThreadStack(entry->thread_local_key);
+    if (!stack) {
+        stack = ZzNewThreadStack(entry->thread_local_key);
+    }
+    ZzCallStack *callstack = ZzNewCallStack();
+    ZzPushCallStack(stack, callstack);
+
+    /* call pre_call */
+    if (entry->pre_call) {
+        PRECALL pre_call;
+        pre_call = entry->pre_call;
+        (*pre_call)(rs, (ThreadStack *)stack, (CallStack *)callstack);
+    }
+
+    /* set next hop */
+    if (entry->replace_call) {
+        *(zpointer *)next_hop = entry->replace_call;
+    } else {
+        *(zpointer *)next_hop = entry->on_invoke_trampoline;
+    }
+
+    if (entry->hook_type == HOOK_FUNCTION_TYPE) {
+        callstack->caller_ret_addr = *(zpointer *)caller_ret_addr;
+        *(zpointer *)caller_ret_addr = entry->on_leave_trampoline;
+    }
+}
+
+// just like post_call, wow!
+void function_context_half_invocation(ZzHookFunctionEntry *entry, zpointer next_hop, RegState *rs,
+                                      zpointer caller_ret_addr) {
+    Xdebug("target %p call half-invocation", entry->target_ptr);
+
+    ZzThreadStack *stack = ZzGetCurrentThreadStack(entry->thread_local_key);
+    if (!stack) {
+#if defined(DEBUG_MODE)
+        debug_break();
+#endif
+    }
+    ZzCallStack *callstack = ZzPopCallStack(stack);
+
+    /* call half_call */
+    if (entry->half_call) {
+        HALFCALL half_call;
+        half_call = entry->half_call;
+        (*half_call)(rs, (ThreadStack *)stack, (CallStack *)callstack);
+    }
+
+    /*  set next hop */
+    *(zpointer *)next_hop = (zpointer)entry->target_half_ret_addr;
+
+    ZzFreeCallStack(callstack);
+}
+
+// just like post_call, wow!
+void function_context_end_invocation(ZzHookFunctionEntry *entry, zpointer next_hop, RegState *rs) {
+    Xdebug("%p call end-invocation", entry->target_ptr);
+
+    ZzThreadStack *stack = ZzGetCurrentThreadStack(entry->thread_local_key);
+    if (!stack) {
+#if defined(DEBUG_MODE)
+        debug_break();
+#endif
+    }
+    ZzCallStack *callstack = ZzPopCallStack(stack);
+
+    /* call post_call */
+    if (entry->post_call) {
+        POSTCALL post_call;
+        post_call = entry->post_call;
+        (*post_call)(rs, (ThreadStack *)stack, (CallStack *)callstack);
+    }
+
+    /* set next hop */
+    *(zpointer *)next_hop = callstack->caller_ret_addr;
+    ZzFreeCallStack(callstack);
+}
+
+void zz_x86_thunker_build_enter_thunk(ZzWriter *writer) {
+}
+
+void zz_x86_thunker_build_half_thunk(ZzWriter *writer) {
+}
+
+void zz_x86_thunker_build_leave_thunk(ZzWriter *writer) {
+}
+
+ZZSTATUS ZzThunkerBuildThunk(ZzInterceptorBackend *self) {
+    return ZZ_FAILED;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/thunker-x86.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/thunker-x86.h
new file mode 100644
index 000000000..554626c4a
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/backend-x86/thunker-x86.h
@@ -0,0 +1,35 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef platforms_backend_x86_thunker_x86
+#define platforms_backend_x86_thunker_x86
+
+// platforms
+#include "platforms/arch-x86/relocator-x86.h"
+#include "platforms/arch-x86/writer-x86.h"
+
+#include "interceptor-x86.h"
+
+// hookzz
+#include "stack.h"
+#include "thunker.h"
+
+// zzdeps
+#include "hookzz.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/instructions.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/instructions.h
new file mode 100644
index 000000000..4620f19ff
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/instructions.h
@@ -0,0 +1,101 @@
+//    Copyright 2017 jmpews
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+#ifndef instructions_h
+#define instructions_h
+
+#include "../../../include/hookzz.h"
+#include "../../../include/zz.h"
+#include "capstone.h"
+
+// Structs for writing x86/x64 instructions.
+
+// 8-bit relative jump.
+typedef struct _JMP_REL_SHORT {
+    uint8_t opcode; // EB xx: JMP +2+xx
+    uint8_t operand;
+} __attribute__((packed)) JMP_REL_SHORT, *PJMP_REL_SHORT;
+
+// 32-bit direct relative jump/call.
+typedef struct _JMP_REL {
+    uint8_t opcode;  // E9/E8 xxxxxxxx: JMP/CALL +5+xxxxxxxx
+    zuint32 operand; // Relative destination address
+} __attribute__((packed)) JMP_REL, *PJMP_REL, CALL_REL;
+
+// 64-bit indirect absolute jump.
+typedef struct _JMP_ABS {
+    uint8_t opcode0; // FF25 00000000: JMP [+6]
+    uint8_t opcode1;
+    zuint32 dummy;
+    uint64_t address; // Absolute destination address
+} __attribute__((packed)) JMP_ABS, *PJMP_ABS;
+
+// 64-bit indirect absolute call.
+typedef struct _CALL_ABS {
+    uint8_t opcode0; // FF15 00000002: CALL [+6]
+    uint8_t opcode1;
+    zuint32 dummy0;
+    uint8_t dummy1; // EB 08:         JMP +10
+    uint8_t dummy2;
+    uint64_t address; // Absolute destination address
+} __attribute__((packed)) CALL_ABS;
+
+// 32-bit direct relative conditional jumps.
+typedef struct _JCC_REL {
+    uint8_t opcode0; // 0F8* xxxxxxxx: J** +6+xxxxxxxx
+    uint8_t opcode1;
+    zuint32 operand; // Relative destination address
+} __attribute__((packed)) JCC_REL;
+
+/* must understand this, by jmpews */
+// 64bit indirect absolute conditional jumps that x64 lacks.
+typedef struct _JCC_ABS {
+    /*
+        TODO:
+        need prefix ? like `uint8_t prefix;`
+     */
+    uint8_t opcode; // 7* 0E:         J** +16
+    uint8_t dummy0;
+    uint8_t dummy1; // FF25 00000000: JMP [+6]
+    uint8_t dummy2;
+    zuint32 dummy3;
+    uint64_t address; // Absolute destination address
+} __attribute__((packed)) JCC_ABS;
+
+typedef struct _Instruction {
+    zpointer address;
+    cs_insn *ins_cs;
+    uint8_t size;
+    zbyte bytes[16];
+} ZzInstruction;
+
+// not use!!!
+typedef struct _RelocatedInstruction {
+    ZzInstruction old_ins;
+    ZzInstruction new_ins;
+} RelocatedInstruction;
+
+// not use!!!
+typedef struct _RelocatedTrampoline {
+    zpointer old_target;
+    zpointer new_target;
+
+    uint8_t old_size;
+    uint8_t new_size;
+
+    ZzInstruction old_inss[16];
+    ZzInstruction new_inss[16];
+} RelocatedTrampoline;
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/reader.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/reader.c
new file mode 100644
index 000000000..cc52308a0
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/reader.c
@@ -0,0 +1,141 @@
+//    Copyright 2017 jmpews
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+#include "reader.h"
+
+#include <string.h>
+
+static csh handle;
+
+void capstone_init(void)
+{
+    cs_err err = 0;
+
+#if defined(__x86_64__)
+    err = cs_open(CS_ARCH_X86, CS_MODE_64, &handle);
+#elif defined(__arm64__)
+    err = cs_open(CS_ARCH_ARM64, CS_MODE_ARM, &handle);
+#endif
+    if (err)
+    {
+        Xerror("Failed on cs_open() with error returned: %u\n", err);
+        exit(-1);
+    }
+
+    cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);
+}
+
+static cs_insn *zz_arm64_reader_disassemble_at(zpointer address)
+{
+    if (!handle)
+        capstone_init();
+    cs_insn *insn;
+    size_t count;
+    count = cs_disasm(handle, address, 16, address, 0, &insn);
+    return insn;
+}
+
+void relocator_read_one(ZzInstruction *old_ins, ZzInstruction *new_ins)
+{
+
+    // capstone ins
+    cs_insn *ins_cs = zz_arm64_reader_disassemble_at(old_ins->address);
+
+    // capstone ins detail
+    // cs_detail *ins_csd = ins_cs->detail->x86;
+    cs_x86 ins_csd = ins_cs->detail->x86;
+
+    old_ins->ins_cs = ins_cs;
+    old_ins->size = ins_cs->size;
+    uint8_t needFix = 0;
+
+    zpointer copy_ins_start;
+    uint8_t copy_ins_size;
+
+    // https://c9x.me/x86/html/file_module_x86_id_146.html
+
+    /*
+    ATTENTION: why 0x01 ^ cond? because of use `method_1`
+
+    origin:
+        1: je <3>
+        2: push rax;
+        3: push rbx;
+
+    method_1:
+        1: jne <3>
+        2: jmp(abs) <4>
+        3: push rax
+        4: push rbx
+
+    method_2:
+        1: je <3>
+        2: jmp(near) <4>
+        3: jmp(abs) <5>
+        4: push rax
+        5: push rbx
+
+    */
+    if ((ins_csd.opcode[0] & 0xF0) == 0x70 || (ins_csd.opcode[0] & 0xFC) == 0xE0 ||
+        (ins_csd.opcode[1] & 0xF0) == 0x80)
+    {
+        // the imm is calculate by capstone, so the imm is dest;
+        zpointer dest = (zpointer)ins_csd.operands[0].imm;
+        zpointer offset = (zpointer)ins_csd.operands[0].imm - old_ins->address - old_ins->size;
+
+        zpointer new_offset = dest - new_ins->address + sizeof(JMP_ABS);
+
+        if (dest > new_ins->address && dest < (new_ins->address + sizeof(JMP_ABS)))
+        {
+            zpointer internal_jmp_dest = 0;
+            if (internal_jmp_dest < dest)
+            {
+                internal_jmp_dest = dest;
+                Xerror("origin: %p, trampoline: %p is trampoline-internal-jmp !", old_ins->address, new_ins->address);
+                return;
+            }
+        }
+        else
+        {
+            needFix = 1;
+            uint8_t cond = ((ins_csd.opcode[0] != 0x0F ? ins_csd.opcode[0] : ins_csd.opcode[2]) & 0x0F);
+
+            jcc.opcode = 0x71 ^ cond;
+            jcc.address = dest;
+        }
+
+        copy_ins_start = &jcc;
+        copy_ins_size = sizeof(jcc);
+    }
+
+    if (needFix)
+    {
+        new_ins->size = copy_ins_size;
+        memcpy(new_ins->bytes, copy_ins_start, copy_ins_size);
+    }
+    else
+    {
+        /*
+            yes, we can just write to new_ins->address, according to the module of design patterns, we can't do `write` operation at here.
+            memcpy(new_ins->address, old_ins->address, old_ins->size);
+         */
+        new_ins->size = old_ins->size;
+        memcpy(new_ins->bytes, old_ins->address, old_ins->size);
+    }
+    memcpy(old_ins->bytes, old_ins->address, old_ins->size);
+}
+
+void relocator_invoke_trampoline(ZzTrampoline *trampoline, zpointer target, uint8_t *read_size, zpointer read_backup)
+{
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/reader.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/reader.h
new file mode 100644
index 000000000..8c3e33925
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/reader.h
@@ -0,0 +1,28 @@
+//    Copyright 2017 jmpews
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+#ifndef reader_h
+#define reader_h
+
+#include "../../../include/zz.h"
+#include "../../../include/hookzz.h"
+#include "instructions.h"
+
+#include "../../trampoline.h"
+
+void relocator_read_one(ZzInstruction *old_ins, ZzInstruction *new_ins);
+
+void relocator_invoke_trampoline(ZzTrampoline *trampoline, zpointer target, uint8_t *read_size, zpointer read_backup);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/writer.c b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/writer.c
new file mode 100644
index 000000000..d404ab689
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/writer.c
@@ -0,0 +1,32 @@
+/**
+ *    Copyright 2017 jmpews
+ * 
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ * 
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "writer.h"
+#include <string.h>
+
+ZzInstruction *writer_put_jmp(zpointer address)
+{
+
+    JMP_ABS jmp = {
+        0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6]
+        0x0000000000000000ULL   // Absolute destination address
+    };
+    ZzInstruction *ins = (ZzInstruction *)malloc(sizeof(ZzInstruction));
+    jmp.address = address;
+    ins->size = sizeof(jmp);
+    memcpy(ins->bytes, &jmp, sizeof(jmp));
+    return ins;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/writer.h b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/writer.h
new file mode 100644
index 000000000..f252ad113
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/platforms/x86/writer.h
@@ -0,0 +1,26 @@
+//    Copyright 2017 jmpews
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+#ifndef platforms_x86_writer_h
+#define platforms_x86_writer_h
+
+#include "../../../include/zz.h"
+#include "../../../include/hookzz.h"
+#include "instructions.h"
+
+#include "../../trampoline.h"
+
+ZzInstruction *writer_put_jmp(zpointer address);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/relocator.h b/VirtualApp/lib/src/main/jni/HookZz/src/relocator.h
new file mode 100644
index 000000000..85825c46f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/relocator.h
@@ -0,0 +1,27 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef relocator_h
+#define relocator_h
+
+#include "hookzz.h"
+#include "interceptor.h"
+#include "writer.h"
+
+void ZzRelocatorBuildInvokeTrampoline(ZzHookFunctionEntry *entry, ZzWriter *backup_writer,
+                                      ZzWriter *relocate_writer);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/stack.c b/VirtualApp/lib/src/main/jni/HookZz/src/stack.c
new file mode 100644
index 000000000..6329b5e28
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/stack.c
@@ -0,0 +1,135 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "stack.h"
+
+ZzThreadStack *ZzGetCurrentThreadStack(zpointer key_ptr) {
+    ZzThreadStack *stack = (ZzThreadStack *)ZzThreadGetCurrentThreadData(key_ptr);
+    if (!stack)
+        return NULL;
+    return stack;
+}
+
+ZzThreadStack *ZzNewThreadStack(zpointer key_ptr) {
+    ZzThreadStack *stack;
+    stack = (ZzThreadStack *)malloc(sizeof(ZzThreadStack));
+    stack->capacity = 4;
+    ZzCallStack **callstacks = (ZzCallStack **)malloc(sizeof(ZzCallStack *) * (stack->capacity));
+    if (!callstacks)
+        return NULL;
+    stack->callstacks = callstacks;
+    stack->size = 0;
+    stack->key_ptr = key_ptr;
+    stack->thread_id = ZzThreadGetCurrentThreadID();
+    ZzThreadSetCurrentThreadData(key_ptr, (zpointer)stack);
+    return stack;
+}
+
+ZzCallStack *ZzNewCallStack() {
+    ZzCallStack *callstack;
+    callstack = (ZzCallStack *)malloc(sizeof(ZzCallStack));
+
+    callstack->capacity = 4;
+
+    callstack->items = (ZzCallStackItem *)malloc(sizeof(ZzCallStackItem) * callstack->capacity);
+    if (!callstack->items)
+        return NULL;
+
+    callstack->size = 0;
+    return callstack;
+}
+
+void ZzFreeCallStack(ZzCallStack *callstack) {
+    free(callstack->items);
+    free(callstack);
+    callstack = NULL;
+}
+
+ZzCallStack *ZzPopCallStack(ZzThreadStack *stack) {
+    if (stack->size > 0)
+        stack->size--;
+    else
+        return NULL;
+    ZzCallStack *callstack = stack->callstacks[stack->size];
+    return callstack;
+}
+
+zbool ZzPushCallStack(ZzThreadStack *stack, ZzCallStack *callstack) {
+    if (!stack)
+        return FALSE;
+
+    if (stack->size >= stack->capacity) {
+        ZzCallStack **callstacks =
+            (ZzCallStack **)realloc(stack->callstacks, sizeof(ZzCallStack *) * (stack->capacity) * 2);
+        if (!callstacks)
+            return FALSE;
+        stack->callstacks = callstacks;
+        stack->capacity = stack->capacity * 2;
+    }
+
+    callstack->call_id = stack->size;
+    callstack->threadstack = (ThreadStack *)stack;
+
+    stack->callstacks[stack->size++] = callstack;
+    return TRUE;
+}
+
+zpointer ZzGetCallStackData(CallStack *callstack_ptr, char *key) {
+    ZzCallStack *callstack = (ZzCallStack *)callstack_ptr;
+    if (!callstack)
+        return NULL;
+    int i;
+    for (i = 0; i < callstack->size; ++i) {
+        if (!strcmp(callstack->items[i].key, key)) {
+            return callstack->items[i].value;
+        }
+    }
+    return NULL;
+}
+
+ZzCallStackItem *ZzNewCallStackData(ZzCallStack *callstack) {
+    if (!callstack)
+        return NULL;
+    if (callstack->size >= callstack->capacity) {
+        ZzCallStackItem *callstackitems =
+            (ZzCallStackItem *)realloc(callstack->items, sizeof(ZzCallStackItem) * callstack->capacity * 2);
+        if (!callstackitems)
+            return NULL;
+        callstack->items = callstackitems;
+        callstack->capacity = callstack->capacity * 2;
+    }
+    return &(callstack->items[callstack->size++]);
+}
+
+zbool ZzSetCallStackData(CallStack *callstack_ptr, char *key, zpointer value_ptr, zsize value_size) {
+    ZzCallStack *callstack = (ZzCallStack *)callstack_ptr;
+    if (!callstack)
+        return FALSE;
+
+    ZzCallStackItem *item = ZzNewCallStackData(callstack);
+
+    char *key_tmp = (char *)malloc(strlen(key) + 1);
+    strncpy(key_tmp, key, strlen(key) + 1);
+
+    zpointer value_tmp = (zpointer)malloc(value_size);
+    memcpy(value_tmp, value_ptr, value_size);
+    item->key = key_tmp;
+    item->value = value_tmp;
+    return TRUE;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/stack.h b/VirtualApp/lib/src/main/jni/HookZz/src/stack.h
new file mode 100644
index 000000000..0c0f6ed82
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/stack.h
@@ -0,0 +1,61 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef stack_h
+#define stack_h
+
+// platforms
+
+// hookzz
+#include "hookzz.h"
+#include "thread.h"
+
+// zzdeps
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef struct _ZzCallStackItem {
+    char *key;
+    zpointer value;
+} ZzCallStackItem;
+
+typedef struct _ZzCallStack {
+    zsize call_id;
+    ThreadStack *threadstack;
+    zsize size;
+    zsize capacity;
+    zpointer sp;
+    zpointer caller_ret_addr;
+    ZzCallStackItem *items;
+} ZzCallStack;
+
+typedef struct _ZzThreadStack {
+    zsize thread_id;
+    zsize size;
+    zsize capacity;
+    zpointer key_ptr;
+    ZzCallStack **callstacks;
+} ZzThreadStack;
+
+ZzThreadStack *ZzNewThreadStack(zpointer key_ptr);
+ZzCallStack *ZzNewCallStack();
+ZzThreadStack *ZzGetCurrentThreadStack(zpointer key_ptr);
+zbool ZzPushCallStack(ZzThreadStack *stack, ZzCallStack *callstack);
+ZzCallStack *ZzPopCallStack(ZzThreadStack *stack);
+void ZzFreeCallStack(ZzCallStack *callstack);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/thread.h b/VirtualApp/lib/src/main/jni/HookZz/src/thread.h
new file mode 100644
index 000000000..bfbed00e9
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/thread.h
@@ -0,0 +1,31 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef thread_h
+#define thread_h
+
+// hookzz
+#include "hookzz.h"
+
+zpointer ZzThreadNewThreadLocalKeyPtr();
+
+zpointer ZzThreadGetCurrentThreadData(zpointer key_ptr);
+
+zbool ZzThreadSetCurrentThreadData(zpointer key_ptr, zpointer data);
+
+long ZzThreadGetCurrentThreadID();
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/thunker.h b/VirtualApp/lib/src/main/jni/HookZz/src/thunker.h
new file mode 100644
index 000000000..3934f114b
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/thunker.h
@@ -0,0 +1,26 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef thunker_h
+#define thunker_h
+
+#include "hookzz.h"
+#include "interceptor.h"
+
+struct _ZzInterceptorBackend;
+ZZSTATUS ZzThunkerBuildThunk(struct _ZzInterceptorBackend *backend);
+
+#endif
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/trampoline.c b/VirtualApp/lib/src/main/jni/HookZz/src/trampoline.c
new file mode 100644
index 000000000..141ff8476
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/trampoline.c
@@ -0,0 +1,35 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include "trampoline.h"
+
+ZZSTATUS ZzBuildTrampoline(struct _ZzInterceptorBackend *self, ZzHookFunctionEntry *entry) {
+
+    ZzPrepareTrampoline(self, entry);
+    ZzBuildEnterTrampoline(self, entry);
+
+    if (entry->hook_type == HOOK_ADDRESS_TYPE) {
+        ZzBuildHalfTrampoline(self, entry);
+        ZzBuildInvokeTrampoline(self, entry);
+    } else {
+        ZzBuildInvokeTrampoline(self, entry);
+        ZzBuildLeaveTrampoline(self, entry);
+    }
+
+    return ZZ_DONE;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/trampoline.h b/VirtualApp/lib/src/main/jni/HookZz/src/trampoline.h
new file mode 100644
index 000000000..362bc1c65
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/trampoline.h
@@ -0,0 +1,65 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef trampoline_h
+#define trampoline_h
+
+// platforms
+
+// hookzz
+#include "hookzz.h"
+#include "interceptor.h"
+
+// zzdeps
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+/*
+    prepare_trampline:
+        1. 判断跳板类型
+        2. 初始化
+
+    enter_trampoline:
+        1. 跳转到 `enter_thunk`
+
+    invoke_trampoline:
+        1. 之前保存的指令(涉及到指令修复)
+        2. 跳转到剩余的指令
+
+    leave_trampoline
+        1. 跳转到 `leave_thunk`
+ */
+
+typedef struct _ZzTrampoline {
+    ZzCodeSlice *code_slice;
+} ZzTrampoline;
+
+ZZSTATUS ZzPrepareTrampoline(struct _ZzInterceptorBackend *self, ZzHookFunctionEntry *entry);
+ZZSTATUS ZzBuildTrampoline(struct _ZzInterceptorBackend *self, ZzHookFunctionEntry *entry);
+ZZSTATUS ZzActivateTrampoline(struct _ZzInterceptorBackend *self, ZzHookFunctionEntry *entry);
+struct _ZzInterceptorBackend *ZzBuildInteceptorBackend(ZzAllocator *allocator);
+
+ZZSTATUS ZzBuildEnterTrampoline(struct _ZzInterceptorBackend *self, ZzHookFunctionEntry *entry);
+ZZSTATUS ZzBuildHalfTrampoline(struct _ZzInterceptorBackend *self, ZzHookFunctionEntry *entry);
+ZZSTATUS ZzBuildInvokeTrampoline(struct _ZzInterceptorBackend *self, ZzHookFunctionEntry *entry);
+ZZSTATUS ZzBuildLeaveTrampoline(struct _ZzInterceptorBackend *self, ZzHookFunctionEntry *entry);
+
+#ifdef TARGET_IS_IOS
+ZZSTATUS ZzActivateSolidifyTrampoline(ZzHookFunctionEntry *entry, zaddr target_fileoff);
+#endif
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/writer.h b/VirtualApp/lib/src/main/jni/HookZz/src/writer.h
new file mode 100644
index 000000000..a1036f62c
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/writer.h
@@ -0,0 +1,40 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef writer_h
+#define writer_h
+
+#include "hookzz.h"
+
+#define MAX_LITERAL_INSN_SIZE 128
+
+typedef struct _ZzLiteralInstruction {
+    zpointer literal_insn_ptr;
+    zaddr *literal_address_ptr;
+} ZzLiteralInstruction;
+
+typedef struct _ZzWriter {
+    zpointer codedata;
+    zpointer base;
+    zaddr pc;
+    zsize size;
+
+    ZzLiteralInstruction literal_insns[MAX_LITERAL_INSN_SIZE];
+    zsize literal_insn_size;
+
+} ZzWriter;
+
+#endif
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdefs.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdefs.h
new file mode 100644
index 000000000..9630b0f89
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdefs.h
@@ -0,0 +1,43 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <stdint.h> // for: uint64_t
+/*
+    -ARM64
+    http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf (7.2.1
+   Floating-point) (4.6.1 Floating-point register organization in AArch64) use struct and union to
+   describe diagram in the above link, nice!
+
+    -X86
+    https://en.wikipedia.org/wiki/X86_calling_conventions
+*/
+
+#define ZZ_INT5_MASK 0x0000001f
+#define ZZ_INT8_MASK 0x000000ff
+#define ZZ_INT10_MASK 0x000003ff
+#define ZZ_INT11_MASK 0x000007ff
+#define ZZ_INT12_MASK 0x00000fff
+#define ZZ_INT14_MASK 0x00003fff
+#define ZZ_INT16_MASK 0x0000ffff
+#define ZZ_INT18_MASK 0x0003ffff
+#define ZZ_INT19_MASK 0x0007ffff
+#define ZZ_INT24_MASK 0x00ffffff
+#define ZZ_INT26_MASK 0x03ffffff
+#define ZZ_INT28_MASK 0x0fffffff
+
+#define THUMB_FUNCTION_ADDRESS(target_addr) (void *)((unsigned long)target_addr & ~(unsigned long)1)
+#define INSTRUCTION_IS_THUMB(insn_addr) ((insn_addr & 0x1) == 0x1)
+#define ALIGN_4(target_addr) ((unsigned long)target_addr & ~(unsigned long)3)
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/.gitignore b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/.gitignore
new file mode 100644
index 000000000..683b548bf
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/.gitignore
@@ -0,0 +1,58 @@
+.DS_Store
+.idea/
+.vscode/
+cmake-build-debug/
+CMakeLists.txt
+
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/README.md b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/README.md
new file mode 100644
index 000000000..4e9bc13f5
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/README.md
@@ -0,0 +1,12 @@
+## zzdeps
+
+common deps, C/C++ is ok and include darwin & common.
+
+#### common deps
+
+os independent.
+
+#### darwin deps
+
+about darwin.
+
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/LEB128.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/LEB128.h
new file mode 100644
index 000000000..6767f0b08
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/LEB128.h
@@ -0,0 +1,41 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+/*
+ *  refs: https://github.com/aquynh/capstone/blob/master/LEB128.h
+ *  refs: http://llvm.org/docs/doxygen/html/LEB128_8h_source.html
+ *  refs: ld64-274.2/src/other/dyldinfo.cpp:1760
+ */
+
+#if !defined(_MSC_VER) || !defined(_KERNEL_MODE)
+
+#include <stdint.h>
+
+#endif
+
+/// Utility function to decode a ULEB128 value.
+static inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n) {
+    const uint8_t *orig_p = p;
+    uint64_t Value = 0;
+    unsigned Shift = 0;
+    do {
+        Value += (*p & 0x7f) << Shift;
+        Shift += 7;
+    } while (*p++ >= 128);
+    if (n)
+        *n = (unsigned)(p - orig_p);
+    return Value;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/debugbreak.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/debugbreak.h
new file mode 100644
index 000000000..98825efc1
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/debugbreak.h
@@ -0,0 +1,155 @@
+/* Copyright (c) 2011-2015, Scott Tsai
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DEBUG_BREAK_H
+#define DEBUG_BREAK_H
+
+#ifdef _MSC_VER
+
+#define debug_break __debugbreak
+
+#else
+
+#include <signal.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum {
+    /* gcc optimizers consider code after __builtin_trap() dead.
+     * Making __builtin_trap() unsuitable for breaking into the debugger */
+    DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP = 0,
+};
+
+#if defined(__i386__) || defined(__x86_64__)
+enum {
+    HAVE_TRAP_INSTRUCTION = 1,
+};
+__attribute__((gnu_inline, always_inline)) __inline__ static void trap_instruction(void) {
+    __asm__ volatile("int $0x03");
+}
+#elif defined(__thumb__) && defined(__APPLE__)
+enum {
+    HAVE_TRAP_INSTRUCTION = 1,
+};
+__attribute__((gnu_inline, always_inline)) static void __inline__ trap_instruction(void) {
+    __builtin_trap();
+}
+#elif defined(__thumb__)
+enum {
+    HAVE_TRAP_INSTRUCTION = 1,
+};
+/* FIXME: handle __THUMB_INTERWORK__ */
+__attribute__((gnu_inline, always_inline)) __inline__ static void trap_instruction(void) {
+/* See 'arm-linux-tdep.c' in GDB source.
+ * Both instruction sequences below work. */
+#if 1
+    /* 'eabi_linux_thumb_le_breakpoint' */
+    __asm__ volatile(".inst 0xde01");
+#else
+    /* 'eabi_linux_thumb2_le_breakpoint' */
+    __asm__ volatile(".inst.w 0xf7f0a000");
+#endif
+
+    /* Known problem:
+     * After a breakpoint hit, can't stepi, step, or continue in GDB.
+     * 'step' stuck on the same instruction.
+     *
+     * Workaround: a new GDB command,
+     * 'debugbreak-step' is defined in debugbreak-gdb.py
+     * that does:
+     * (gdb) set $instruction_len = 2
+     * (gdb) tbreak *($pc + $instruction_len)
+     * (gdb) jump   *($pc + $instruction_len)
+     */
+}
+#elif defined(__arm__) && !defined(__thumb__) && defined(__APPLE__)
+enum {
+    HAVE_TRAP_INSTRUCTION = 1,
+};
+__attribute__((gnu_inline, always_inline)) static void __inline__ trap_instruction(void) {
+    __builtin_trap();
+}
+#elif defined(__arm__) && !defined(__thumb__)
+enum {
+    HAVE_TRAP_INSTRUCTION = 1,
+};
+__attribute__((gnu_inline, always_inline)) __inline__ static void trap_instruction(void) {
+    /* See 'arm-linux-tdep.c' in GDB source,
+     * 'eabi_linux_arm_le_breakpoint' */
+    __asm__ volatile(".inst 0xe7f001f0");
+    /* Has same known problem and workaround
+     * as Thumb mode */
+}
+#elif defined(__aarch64__) && defined(__APPLE__)
+enum {
+    HAVE_TRAP_INSTRUCTION = 1,
+};
+__attribute__((gnu_inline, always_inline)) static void __inline__ trap_instruction(void) {
+    __builtin_trap();
+}
+#elif defined(__aarch64__)
+enum {
+    HAVE_TRAP_INSTRUCTION = 1,
+};
+__attribute__((gnu_inline, always_inline)) __inline__ static void trap_instruction(void) {
+    /* See 'aarch64-tdep.c' in GDB source,
+     * 'aarch64_default_breakpoint' */
+    __asm__ volatile(".inst 0xd4200000");
+}
+#else
+enum {
+    HAVE_TRAP_INSTRUCTION = 0,
+};
+#endif
+
+__attribute__((gnu_inline, always_inline)) __inline__ static void debug_break(void) {
+    if (HAVE_TRAP_INSTRUCTION) {
+        trap_instruction();
+    } else if (DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP) {
+        /* raises SIGILL on Linux x86{,-64}, to continue in gdb:
+         * (gdb) handle SIGILL stop nopass
+         * */
+        __builtin_trap();
+    } else {
+#ifdef _WIN32
+        /* SIGTRAP available only on POSIX-compliant operating systems
+         * use builtin trap instead */
+        __builtin_trap();
+#else
+        raise(SIGTRAP);
+#endif
+    }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#endif
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/memory-utils-common.c b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/memory-utils-common.c
new file mode 100644
index 000000000..9bbf458ea
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/memory-utils-common.c
@@ -0,0 +1,72 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "memory-utils-common.h"
+
+char *zz_vm_read_string(const zpointer address) {
+    const char *start_addr = (const char *)address;
+    unsigned int string_limit = 1024;
+    unsigned int i;
+    char *result;
+
+    for (i = 0; i < string_limit; i++) {
+        if (*(start_addr + i) == '\0')
+            break;
+    }
+    if (i == string_limit)
+        return NULL;
+    else {
+        result = (char *)malloc(i + 1);
+        memcpy(result, (const zpointer)start_addr, i + 1);
+        return result;
+    }
+}
+
+zpointer zz_vm_search_data(const zpointer start_addr, zpointer end_addr, zbyte *data,
+                           zsize data_len) {
+    zpointer curr_addr;
+    if (start_addr <= 0)
+        Xerror("search address start_addr(%p) < 0", (zpointer)start_addr);
+    if (start_addr > end_addr)
+        Xerror("search start_add(%p) < end_addr(%p)", (zpointer)start_addr, (zpointer)end_addr);
+
+    curr_addr = start_addr;
+
+    while (end_addr > curr_addr) {
+        if (!memcmp(curr_addr, data, data_len)) {
+            return curr_addr;
+        }
+        curr_addr += data_len;
+    }
+    return 0;
+}
+
+zaddr zz_vm_align_floor(zaddr address, zsize range_size) {
+    zaddr result;
+    result = address & ~(range_size - 1);
+    return result;
+}
+
+zaddr zz_vm_align_ceil(zaddr address, zsize range_size) {
+    zaddr result;
+    result = (address + range_size - 1) & ~(range_size - 1);
+    return result;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/memory-utils-common.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/memory-utils-common.h
new file mode 100644
index 000000000..3029c31d1
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/common/memory-utils-common.h
@@ -0,0 +1,35 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef zzdeps_common_memory_utils_common_h
+#define zzdeps_common_memory_utils_common_h
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../zz.h"
+
+char *zz_vm_read_string(const zpointer address);
+
+zpointer zz_vm_search_data(const zpointer start_addr, const zpointer end_addr, zbyte *data,
+                           zsize data_len);
+
+zaddr zz_vm_align_floor(zaddr address, zsize range_size);
+
+zaddr zz_vm_align_ceil(zaddr address, zsize range_size);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/mach_vm.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/mach_vm.h
new file mode 100644
index 000000000..febec706b
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/mach_vm.h
@@ -0,0 +1,1057 @@
+#ifndef    _mach_vm_user_
+#define    _mach_vm_user_
+
+/* Module mach_vm */
+
+#include <string.h>
+#include <mach/ndr.h>
+#include <mach/boolean.h>
+#include <mach/kern_return.h>
+#include <mach/notify.h>
+#include <mach/mach_types.h>
+#include <mach/message.h>
+#include <mach/mig_errors.h>
+#include <mach/port.h>
+
+/* BEGIN MIG_STRNCPY_ZEROFILL CODE */
+
+#if defined(__has_include)
+#if __has_include(<mach/mig_strncpy_zerofill_support.h>)
+#ifndef USING_MIG_STRNCPY_ZEROFILL
+#define USING_MIG_STRNCPY_ZEROFILL
+#endif
+#ifndef __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__
+#define __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__
+#ifdef __cplusplus
+extern "C" {
+#endif
+extern int mig_strncpy_zerofill(char *dest, const char *src, int len) __attribute__((weak_import));
+#ifdef __cplusplus
+}
+#endif
+#endif /* __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ */
+#endif /* __has_include(<mach/mig_strncpy_zerofill_support.h>) */
+#endif /* __has_include */
+
+/* END MIG_STRNCPY_ZEROFILL CODE */
+
+
+#ifdef AUTOTEST
+#ifndef FUNCTION_PTR_T
+#define FUNCTION_PTR_T
+typedef void (*function_ptr_t)(mach_port_t, char *, mach_msg_type_number_t);
+typedef struct {
+        char            *name;
+        function_ptr_t  function;
+} function_table_entry;
+typedef function_table_entry   *function_table_t;
+#endif /* FUNCTION_PTR_T */
+#endif /* AUTOTEST */
+
+#ifndef    mach_vm_MSG_COUNT
+#define    mach_vm_MSG_COUNT    20
+#endif    /* mach_vm_MSG_COUNT */
+
+#include <mach/std_types.h>
+#include <mach/mig.h>
+#include <mach/mig.h>
+#include <mach/mach_types.h>
+#include <mach_debug/mach_debug_types.h>
+
+#ifdef __BeforeMigUserHeader
+__BeforeMigUserHeader
+#endif /* __BeforeMigUserHeader */
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+
+/* Routine mach_vm_allocate */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_allocate
+        (
+                vm_map_t target,
+                mach_vm_address_t *address,
+                mach_vm_size_t size,
+                int flags
+        );
+
+/* Routine mach_vm_deallocate */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_deallocate
+        (
+                vm_map_t target,
+                mach_vm_address_t address,
+                mach_vm_size_t size
+        );
+
+/* Routine mach_vm_protect */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_protect
+        (
+                vm_map_t target_task,
+                mach_vm_address_t address,
+                mach_vm_size_t size,
+                boolean_t set_maximum,
+                vm_prot_t new_protection
+        );
+
+/* Routine mach_vm_inherit */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_inherit
+        (
+                vm_map_t target_task,
+                mach_vm_address_t address,
+                mach_vm_size_t size,
+                vm_inherit_t new_inheritance
+        );
+
+/* Routine mach_vm_read */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_read
+        (
+                vm_map_t target_task,
+                mach_vm_address_t address,
+                mach_vm_size_t size,
+                vm_offset_t *data,
+                mach_msg_type_number_t *dataCnt
+        );
+
+/* Routine mach_vm_read_list */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_read_list
+        (
+                vm_map_t target_task,
+                mach_vm_read_entry_t data_list,
+                natural_t count
+        );
+
+/* Routine mach_vm_write */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_write
+        (
+                vm_map_t target_task,
+                mach_vm_address_t address,
+                vm_offset_t data,
+                mach_msg_type_number_t dataCnt
+        );
+
+/* Routine mach_vm_copy */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_copy
+        (
+                vm_map_t target_task,
+                mach_vm_address_t source_address,
+                mach_vm_size_t size,
+                mach_vm_address_t dest_address
+        );
+
+/* Routine mach_vm_read_overwrite */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_read_overwrite
+        (
+                vm_map_t target_task,
+                mach_vm_address_t address,
+                mach_vm_size_t size,
+                mach_vm_address_t data,
+                mach_vm_size_t *outsize
+        );
+
+/* Routine mach_vm_msync */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_msync
+        (
+                vm_map_t target_task,
+                mach_vm_address_t address,
+                mach_vm_size_t size,
+                vm_sync_t sync_flags
+        );
+
+/* Routine mach_vm_behavior_set */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_behavior_set
+        (
+                vm_map_t target_task,
+                mach_vm_address_t address,
+                mach_vm_size_t size,
+                vm_behavior_t new_behavior
+        );
+
+/* Routine mach_vm_map */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_map
+        (
+                vm_map_t target_task,
+                mach_vm_address_t *address,
+                mach_vm_size_t size,
+                mach_vm_offset_t mask,
+                int flags,
+                mem_entry_name_port_t object,
+                memory_object_offset_t offset,
+                boolean_t copy,
+                vm_prot_t cur_protection,
+                vm_prot_t max_protection,
+                vm_inherit_t inheritance
+        );
+
+/* Routine mach_vm_machine_attribute */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_machine_attribute
+        (
+                vm_map_t target_task,
+                mach_vm_address_t address,
+                mach_vm_size_t size,
+                vm_machine_attribute_t attribute,
+                vm_machine_attribute_val_t *value
+        );
+
+/* Routine mach_vm_remap */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_remap
+        (
+                vm_map_t target_task,
+                mach_vm_address_t *target_address,
+                mach_vm_size_t size,
+                mach_vm_offset_t mask,
+                int flags,
+                vm_map_t src_task,
+                mach_vm_address_t src_address,
+                boolean_t copy,
+                vm_prot_t *cur_protection,
+                vm_prot_t *max_protection,
+                vm_inherit_t inheritance
+        );
+
+/* Routine mach_vm_page_query */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_page_query
+        (
+                vm_map_t target_map,
+                mach_vm_offset_t offset,
+                integer_t *disposition,
+                integer_t *ref_count
+        );
+
+/* Routine mach_vm_region_recurse */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_region_recurse
+        (
+                vm_map_t target_task,
+                mach_vm_address_t *address,
+                mach_vm_size_t *size,
+                natural_t *nesting_depth,
+                vm_region_recurse_info_t info,
+                mach_msg_type_number_t *infoCnt
+        );
+
+/* Routine mach_vm_region */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_region
+        (
+                vm_map_t target_task,
+                mach_vm_address_t *address,
+                mach_vm_size_t *size,
+                vm_region_flavor_t flavor,
+                vm_region_info_t info,
+                mach_msg_type_number_t *infoCnt,
+                mach_port_t *object_name
+        );
+
+/* Routine _mach_make_memory_entry */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t _mach_make_memory_entry
+        (
+                vm_map_t target_task,
+                memory_object_size_t *size,
+                memory_object_offset_t offset,
+                vm_prot_t permission,
+                mem_entry_name_port_t *object_handle,
+                mem_entry_name_port_t parent_handle
+        );
+
+/* Routine mach_vm_purgable_control */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_purgable_control
+        (
+                vm_map_t target_task,
+                mach_vm_address_t address,
+                vm_purgable_t control,
+                int *state
+        );
+
+/* Routine mach_vm_page_info */
+#ifdef    mig_external
+mig_external
+#else
+extern
+#endif    /* mig_external */
+kern_return_t mach_vm_page_info
+        (
+                vm_map_t target_task,
+                mach_vm_address_t address,
+                vm_page_info_flavor_t flavor,
+                vm_page_info_t info,
+                mach_msg_type_number_t *infoCnt
+        );
+
+__END_DECLS
+
+/********************** Caution **************************/
+/* The following data types should be used to calculate  */
+/* maximum message sizes only. The actual message may be */
+/* smaller, and the position of the arguments within the */
+/* message layout may vary from what is presented here.  */
+/* For example, if any of the arguments are variable-    */
+/* sized, and less than the maximum is sent, the data    */
+/* will be packed tight in the actual message to reduce  */
+/* the presence of holes.                                */
+/********************** Caution **************************/
+
+/* typedefs for all requests */
+
+#ifndef __Request__mach_vm_subsystem__defined
+#define __Request__mach_vm_subsystem__defined
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+    int flags;
+} __Request__mach_vm_allocate_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+} __Request__mach_vm_deallocate_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+    boolean_t set_maximum;
+    vm_prot_t new_protection;
+} __Request__mach_vm_protect_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+    vm_inherit_t new_inheritance;
+} __Request__mach_vm_inherit_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+} __Request__mach_vm_read_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_read_entry_t data_list;
+    natural_t count;
+} __Request__mach_vm_read_list_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    /* start of the kernel processed data */
+    mach_msg_body_t msgh_body;
+    mach_msg_ool_descriptor_t data;
+    /* end of the kernel processed data */
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_msg_type_number_t dataCnt;
+} __Request__mach_vm_write_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t source_address;
+    mach_vm_size_t size;
+    mach_vm_address_t dest_address;
+} __Request__mach_vm_copy_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+    mach_vm_address_t data;
+} __Request__mach_vm_read_overwrite_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+    vm_sync_t sync_flags;
+} __Request__mach_vm_msync_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+    vm_behavior_t new_behavior;
+} __Request__mach_vm_behavior_set_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    /* start of the kernel processed data */
+    mach_msg_body_t msgh_body;
+    mach_msg_port_descriptor_t object;
+    /* end of the kernel processed data */
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+    mach_vm_offset_t mask;
+    int flags;
+    memory_object_offset_t offset;
+    boolean_t copy;
+    vm_prot_t cur_protection;
+    vm_prot_t max_protection;
+    vm_inherit_t inheritance;
+} __Request__mach_vm_map_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+    vm_machine_attribute_t attribute;
+    vm_machine_attribute_val_t value;
+} __Request__mach_vm_machine_attribute_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    /* start of the kernel processed data */
+    mach_msg_body_t msgh_body;
+    mach_msg_port_descriptor_t src_task;
+    /* end of the kernel processed data */
+    NDR_record_t NDR;
+    mach_vm_address_t target_address;
+    mach_vm_size_t size;
+    mach_vm_offset_t mask;
+    int flags;
+    mach_vm_address_t src_address;
+    boolean_t copy;
+    vm_inherit_t inheritance;
+} __Request__mach_vm_remap_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_offset_t offset;
+} __Request__mach_vm_page_query_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    natural_t nesting_depth;
+    mach_msg_type_number_t infoCnt;
+} __Request__mach_vm_region_recurse_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    vm_region_flavor_t flavor;
+    mach_msg_type_number_t infoCnt;
+} __Request__mach_vm_region_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    /* start of the kernel processed data */
+    mach_msg_body_t msgh_body;
+    mach_msg_port_descriptor_t parent_handle;
+    /* end of the kernel processed data */
+    NDR_record_t NDR;
+    memory_object_size_t size;
+    memory_object_offset_t offset;
+    vm_prot_t permission;
+} __Request___mach_make_memory_entry_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    vm_purgable_t control;
+    int state;
+} __Request__mach_vm_purgable_control_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    vm_page_info_flavor_t flavor;
+    mach_msg_type_number_t infoCnt;
+} __Request__mach_vm_page_info_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+#endif /* !__Request__mach_vm_subsystem__defined */
+
+/* union of all requests */
+
+#ifndef __RequestUnion__mach_vm_subsystem__defined
+#define __RequestUnion__mach_vm_subsystem__defined
+union __RequestUnion__mach_vm_subsystem {
+    __Request__mach_vm_allocate_t Request_mach_vm_allocate;
+    __Request__mach_vm_deallocate_t Request_mach_vm_deallocate;
+    __Request__mach_vm_protect_t Request_mach_vm_protect;
+    __Request__mach_vm_inherit_t Request_mach_vm_inherit;
+    __Request__mach_vm_read_t Request_mach_vm_read;
+    __Request__mach_vm_read_list_t Request_mach_vm_read_list;
+    __Request__mach_vm_write_t Request_mach_vm_write;
+    __Request__mach_vm_copy_t Request_mach_vm_copy;
+    __Request__mach_vm_read_overwrite_t Request_mach_vm_read_overwrite;
+    __Request__mach_vm_msync_t Request_mach_vm_msync;
+    __Request__mach_vm_behavior_set_t Request_mach_vm_behavior_set;
+    __Request__mach_vm_map_t Request_mach_vm_map;
+    __Request__mach_vm_machine_attribute_t Request_mach_vm_machine_attribute;
+    __Request__mach_vm_remap_t Request_mach_vm_remap;
+    __Request__mach_vm_page_query_t Request_mach_vm_page_query;
+    __Request__mach_vm_region_recurse_t Request_mach_vm_region_recurse;
+    __Request__mach_vm_region_t Request_mach_vm_region;
+    __Request___mach_make_memory_entry_t Request__mach_make_memory_entry;
+    __Request__mach_vm_purgable_control_t Request_mach_vm_purgable_control;
+    __Request__mach_vm_page_info_t Request_mach_vm_page_info;
+};
+#endif /* !__RequestUnion__mach_vm_subsystem__defined */
+/* typedefs for all replies */
+
+#ifndef __Reply__mach_vm_subsystem__defined
+#define __Reply__mach_vm_subsystem__defined
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+    mach_vm_address_t address;
+} __Reply__mach_vm_allocate_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+} __Reply__mach_vm_deallocate_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+} __Reply__mach_vm_protect_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+} __Reply__mach_vm_inherit_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    /* start of the kernel processed data */
+    mach_msg_body_t msgh_body;
+    mach_msg_ool_descriptor_t data;
+    /* end of the kernel processed data */
+    NDR_record_t NDR;
+    mach_msg_type_number_t dataCnt;
+} __Reply__mach_vm_read_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+    mach_vm_read_entry_t data_list;
+} __Reply__mach_vm_read_list_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+} __Reply__mach_vm_write_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+} __Reply__mach_vm_copy_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+    mach_vm_size_t outsize;
+} __Reply__mach_vm_read_overwrite_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+} __Reply__mach_vm_msync_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+} __Reply__mach_vm_behavior_set_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+    mach_vm_address_t address;
+} __Reply__mach_vm_map_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+    vm_machine_attribute_val_t value;
+} __Reply__mach_vm_machine_attribute_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+    mach_vm_address_t target_address;
+    vm_prot_t cur_protection;
+    vm_prot_t max_protection;
+} __Reply__mach_vm_remap_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+    integer_t disposition;
+    integer_t ref_count;
+} __Reply__mach_vm_page_query_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+    natural_t nesting_depth;
+    mach_msg_type_number_t infoCnt;
+    int info[19];
+} __Reply__mach_vm_region_recurse_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    /* start of the kernel processed data */
+    mach_msg_body_t msgh_body;
+    mach_msg_port_descriptor_t object_name;
+    /* end of the kernel processed data */
+    NDR_record_t NDR;
+    mach_vm_address_t address;
+    mach_vm_size_t size;
+    mach_msg_type_number_t infoCnt;
+    int info[10];
+} __Reply__mach_vm_region_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    /* start of the kernel processed data */
+    mach_msg_body_t msgh_body;
+    mach_msg_port_descriptor_t object_handle;
+    /* end of the kernel processed data */
+    NDR_record_t NDR;
+    memory_object_size_t size;
+} __Reply___mach_make_memory_entry_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+    int state;
+} __Reply__mach_vm_purgable_control_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+
+#ifdef  __MigPackStructs
+#pragma pack(4)
+#endif
+typedef struct {
+    mach_msg_header_t Head;
+    NDR_record_t NDR;
+    kern_return_t RetCode;
+    mach_msg_type_number_t infoCnt;
+    int info[32];
+} __Reply__mach_vm_page_info_t __attribute__((unused));
+#ifdef  __MigPackStructs
+#pragma pack()
+#endif
+#endif /* !__Reply__mach_vm_subsystem__defined */
+
+/* union of all replies */
+
+#ifndef __ReplyUnion__mach_vm_subsystem__defined
+#define __ReplyUnion__mach_vm_subsystem__defined
+union __ReplyUnion__mach_vm_subsystem {
+    __Reply__mach_vm_allocate_t Reply_mach_vm_allocate;
+    __Reply__mach_vm_deallocate_t Reply_mach_vm_deallocate;
+    __Reply__mach_vm_protect_t Reply_mach_vm_protect;
+    __Reply__mach_vm_inherit_t Reply_mach_vm_inherit;
+    __Reply__mach_vm_read_t Reply_mach_vm_read;
+    __Reply__mach_vm_read_list_t Reply_mach_vm_read_list;
+    __Reply__mach_vm_write_t Reply_mach_vm_write;
+    __Reply__mach_vm_copy_t Reply_mach_vm_copy;
+    __Reply__mach_vm_read_overwrite_t Reply_mach_vm_read_overwrite;
+    __Reply__mach_vm_msync_t Reply_mach_vm_msync;
+    __Reply__mach_vm_behavior_set_t Reply_mach_vm_behavior_set;
+    __Reply__mach_vm_map_t Reply_mach_vm_map;
+    __Reply__mach_vm_machine_attribute_t Reply_mach_vm_machine_attribute;
+    __Reply__mach_vm_remap_t Reply_mach_vm_remap;
+    __Reply__mach_vm_page_query_t Reply_mach_vm_page_query;
+    __Reply__mach_vm_region_recurse_t Reply_mach_vm_region_recurse;
+    __Reply__mach_vm_region_t Reply_mach_vm_region;
+    __Reply___mach_make_memory_entry_t Reply__mach_make_memory_entry;
+    __Reply__mach_vm_purgable_control_t Reply_mach_vm_purgable_control;
+    __Reply__mach_vm_page_info_t Reply_mach_vm_page_info;
+};
+#endif /* !__RequestUnion__mach_vm_subsystem__defined */
+
+#ifndef subsystem_to_name_map_mach_vm
+#define subsystem_to_name_map_mach_vm \
+    { "mach_vm_allocate", 4800 },\
+    { "mach_vm_deallocate", 4801 },\
+    { "mach_vm_protect", 4802 },\
+    { "mach_vm_inherit", 4803 },\
+    { "mach_vm_read", 4804 },\
+    { "mach_vm_read_list", 4805 },\
+    { "mach_vm_write", 4806 },\
+    { "mach_vm_copy", 4807 },\
+    { "mach_vm_read_overwrite", 4808 },\
+    { "mach_vm_msync", 4809 },\
+    { "mach_vm_behavior_set", 4810 },\
+    { "mach_vm_map", 4811 },\
+    { "mach_vm_machine_attribute", 4812 },\
+    { "mach_vm_remap", 4813 },\
+    { "mach_vm_page_query", 4814 },\
+    { "mach_vm_region_recurse", 4815 },\
+    { "mach_vm_region", 4816 },\
+    { "_mach_make_memory_entry", 4817 },\
+    { "mach_vm_purgable_control", 4818 },\
+    { "mach_vm_page_info", 4819 }
+#endif
+
+#ifdef __AfterMigUserHeader
+__AfterMigUserHeader
+#endif /* __AfterMigUserHeader */
+
+#endif     /* _mach_vm_user_ */
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/macho-utils-darwin.c b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/macho-utils-darwin.c
new file mode 100644
index 000000000..1ab97a980
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/macho-utils-darwin.c
@@ -0,0 +1,181 @@
+
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <mach-o/dyld.h>
+#include <mach-o/dyld_images.h>
+#include <mach-o/nlist.h>
+#include <mach/task_info.h>
+
+#include "../common/debugbreak.h"
+#include "../darwin/memory-utils-darwin.h"
+#include "macho-utils-darwin.h"
+
+#if 0
+#ifdef __LP64__
+#define mach_hdr struct mach_header_64
+#define sgmt_cmd struct segment_command_64
+#define sect_cmd struct section_64
+#define nlist_ struct nlist_64
+#define LC_SGMT LC_SEGMENT_64
+#define MH_MAGIC_ MH_MAGIC_64
+#else
+#define mach_hdr struct mach_header
+#define sgmt_cmd struct segment_command
+#define sect_cmd struct section
+#define nlist_ struct nlist
+#define LC_SGMT LC_SEGMENT
+#define MH_MAGIC_ MH_MAGIC
+#endif
+#define load_cmd struct load_command
+#endif
+
+// get dyld load address by task_info, TASK_DYLD_INFO
+zpointer zz_macho_get_dyld_load_address_via_task(task_t task) {
+    // http://stackoverflow.com/questions/4309117/determining-programmatically-what-modules-are-loaded-in-another-process-os-x
+    kern_return_t kr;
+    task_flavor_t flavor = TASK_DYLD_INFO;
+    task_dyld_info_data_t infoData;
+    mach_msg_type_number_t task_info_outCnt = TASK_DYLD_INFO_COUNT;
+    kr = task_info(task, flavor, (task_info_t)&infoData, &task_info_outCnt);
+    if (kr != KERN_SUCCESS) {
+        KR_ERROR(kr);
+        return 0;
+    }
+    struct dyld_all_image_infos *allImageInfos = (struct dyld_all_image_infos *)infoData.all_image_info_addr;
+    allImageInfos = (struct dyld_all_image_infos *)malloc(sizeof(struct dyld_all_image_infos));
+    if (zz_vm_read_data_via_task(task, infoData.all_image_info_addr, allImageInfos,
+                                 sizeof(struct dyld_all_image_infos))) {
+        return (zpointer)(allImageInfos->dyldImageLoadAddress);
+    } else {
+        return NULL;
+    }
+}
+
+task_t zz_darwin_get_task_via_pid(int pid) {
+    task_t t;
+    kern_return_t kr = task_for_pid(mach_task_self(), pid, &t);
+    if (kr != KERN_SUCCESS) {
+        KR_ERROR(kr);
+        return 0;
+    }
+    return t;
+}
+
+struct segment_command_64 *zz_macho_get_segment_64_via_name(struct mach_header_64 *header, char *segment_name) {
+    struct load_command *load_cmd;
+    struct segment_command_64 *seg_cmd_64;
+    struct section_64 *sect_64;
+
+    load_cmd = (zpointer)header + sizeof(struct mach_header_64);
+    zsize i;
+    for (i = 0; i < header->ncmds; i++, load_cmd = (zpointer)load_cmd + load_cmd->cmdsize) {
+        if (load_cmd->cmd == LC_SEGMENT_64) {
+            seg_cmd_64 = (struct segment_command_64 *)load_cmd;
+            if (!strcmp(seg_cmd_64->segname, segment_name)) {
+                return seg_cmd_64;
+            }
+        }
+    }
+    return NULL;
+}
+
+struct section_64 *zz_macho_get_section_64_via_name(struct mach_header_64 *header, char *sect_name) {
+    struct load_command *load_cmd;
+    struct segment_command_64 *seg_cmd_64;
+    struct section_64 *sect_64;
+
+    load_cmd = (zpointer)header + sizeof(struct mach_header_64);
+    zsize i;
+    zsize j;
+    for (i = 0; i < header->ncmds; i++, load_cmd = (zpointer)load_cmd + load_cmd->cmdsize) {
+        if (load_cmd->cmd == LC_SEGMENT_64) {
+            seg_cmd_64 = (struct segment_command_64 *)load_cmd;
+            sect_64 = (struct section_64 *)((zpointer)seg_cmd_64 + sizeof(struct segment_command_64));
+            for (j = 0; j < seg_cmd_64->nsects; j++, sect_64 = (zpointer)sect_64 + sizeof(struct section_64)) {
+                if (!strcmp(sect_64->sectname, sect_name)) {
+                    return sect_64;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+struct load_command *zz_macho_get_load_command_via_cmd(struct mach_header_64 *header, zuint32 cmd) {
+    struct load_command *load_cmd;
+    struct segment_command_64 *seg_cmd_64;
+    struct section_64 *sect_64;
+    zsize i;
+
+    load_cmd = (zpointer)header + sizeof(struct mach_header_64);
+    for (i = 0; i < header->ncmds; i++, load_cmd = (zpointer)load_cmd + load_cmd->cmdsize) {
+        if (load_cmd->cmd == cmd) {
+            return load_cmd;
+        }
+    }
+    return NULL;
+}
+
+zpointer zz_macho_get_symbol_via_name(struct mach_header_64 *header, const char *name) {
+
+    struct segment_command_64 *seg_cmd_64 =
+        zz_macho_get_segment_64_via_name((struct mach_header_64 *)header, (char *)"__TEXT");
+    struct segment_command_64 *seg_cmd_64_linkedit =
+        zz_macho_get_segment_64_via_name((struct mach_header_64 *)header, (char *)"__LINKEDIT");
+    zsize slide = (zaddr)header - (zaddr)seg_cmd_64->vmaddr;
+    zsize linkEditBase = seg_cmd_64_linkedit->vmaddr - seg_cmd_64_linkedit->fileoff + slide;
+    struct symtab_command *symtab = (struct symtab_command *)zz_macho_get_load_command_via_cmd(header, LC_SYMTAB);
+
+    char *sym_str_table = (char *)linkEditBase + symtab->stroff;
+    struct nlist_64 *sym_table = (struct nlist_64 *)(linkEditBase + symtab->symoff);
+
+    int i;
+    for (i = 0; i < symtab->nsyms; i++) {
+        if (sym_table[i].n_value && !strcmp(name, &sym_str_table[sym_table[i].n_un.n_strx])) {
+            return (void *)(uint64_t)(sym_table[i].n_value + slide);
+        }
+    }
+    return 0;
+}
+
+zpointer zz_macho_get_section_64_address_via_name(struct mach_header_64 *header, char *sect_name) {
+    struct load_command *load_cmd;
+    struct segment_command_64 *seg_cmd_64;
+    struct section_64 *sect_64;
+    zsize slide, linkEditBase;
+    zsize i, j;
+
+    load_cmd = (zpointer)header + sizeof(struct mach_header_64);
+    for (i = 0; i < header->ncmds; i++, load_cmd = (zpointer)load_cmd + load_cmd->cmdsize) {
+        if (load_cmd->cmd == LC_SEGMENT_64) {
+            seg_cmd_64 = (struct segment_command_64 *)load_cmd;
+            if ((seg_cmd_64->fileoff == 0) && (seg_cmd_64->filesize != 0)) {
+                slide = (uintptr_t)header - seg_cmd_64->vmaddr;
+            }
+            if (strcmp(seg_cmd_64->segname, "__LINKEDIT") == 0) {
+                linkEditBase = seg_cmd_64->vmaddr - seg_cmd_64->fileoff + slide;
+            }
+            sect_64 = (struct section_64 *)((zpointer)seg_cmd_64 + sizeof(struct segment_command_64));
+            for (j = 0; j < seg_cmd_64->nsects; j++, sect_64 = (zpointer)sect_64 + sizeof(struct section_64)) {
+                if (!strcmp(sect_64->sectname, sect_name)) {
+                    return (zpointer)(sect_64->addr + slide);
+                }
+            }
+        }
+    }
+    return NULL;
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/macho-utils-darwin.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/macho-utils-darwin.h
new file mode 100644
index 000000000..c41928823
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/macho-utils-darwin.h
@@ -0,0 +1,44 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef zzdeps_darwin_macho_utils_h
+#define zzdeps_darwin_macho_utils_h
+
+#include <err.h>
+#include <mach/task.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <mach-o/dyld.h>
+
+#include "../zz.h"
+
+zpointer zz_macho_get_dyld_load_address_via_task(task_t task);
+
+task_t zz_darwin_get_task_via_pid(int pid);
+
+struct section_64 *zz_macho_get_section_64_via_name(struct mach_header_64 *header, char *sect_name);
+
+struct segment_command_64 *zz_macho_get_segment_64_via_name(struct mach_header_64 *header,
+                                                            char *segment_name);
+
+zpointer zz_macho_get_section_64_address_via_name(struct mach_header_64 *header, char *sect_name);
+
+zpointer zz_macho_get_symbol_via_name(struct mach_header_64 *header, const char *name);
+
+struct load_command *zz_macho_get_load_command_via_cmd(struct mach_header_64 *header, zuint32 cmd);
+
+#endif
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/memory-utils-darwin.c b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/memory-utils-darwin.c
new file mode 100644
index 000000000..4ac4cbbba
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/memory-utils-darwin.c
@@ -0,0 +1,521 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <errno.h>
+#include <sys/mman.h>
+
+#include <mach-o/dyld.h>
+#include <mach/mach.h>
+
+// for : getpagesize,
+#include <unistd.h>
+
+// for : vm_read_overwrite
+#include <mach/vm_map.h>
+
+#include "../common/debugbreak.h"
+#include "../common/memory-utils-common.h"
+#include "../memory-utils.h"
+#include "../posix/memory-utils-posix.h"
+
+#include "macho-utils-darwin.h"
+#include "memory-utils-darwin.h"
+
+// --- about read function ---
+
+zbool zz_vm_read_data_via_task(task_t task, const zaddr address, zpointer buffer, zsize length) {
+    vm_size_t dataCnt;
+    dataCnt = 0;
+    if (address <= 0) {
+        Xerror("read address %p< 0", (zpointer)address);
+        return FALSE;
+    }
+    if (length <= 0) {
+        Xerror("read length %p <0", (zpointer)address);
+        return FALSE;
+    }
+    dataCnt = length;
+    kern_return_t kr = vm_read_overwrite(task, address, length, (zaddr)buffer, (vm_size_t *)&dataCnt);
+
+    if (kr != KERN_SUCCESS) {
+        // KR_ERROR_AT(kr, address);
+        return FALSE;
+    }
+    if (length != dataCnt) {
+        warnx("rt_read size return not match!");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+char *zz_vm_read_string_via_task(task_t task, const zaddr address) {
+    char end_c = '\0';
+    zaddr end_addr;
+    char *result = NULL;
+
+    // string upper limit 0x1000
+    end_addr = zz_vm_search_data_via_task(task, address, address + 0x1000, (zbyte *)&end_c, 1);
+    if (!end_addr) {
+        return NULL;
+    }
+    result = (char *)malloc(end_addr - address + 1);
+    if (result && zz_vm_read_data_via_task(task, address, result, end_addr - address + 1)) {
+        return result;
+    }
+    return NULL;
+}
+
+// --- end ---
+
+zaddr zz_vm_search_data_via_task(task_t task, const zaddr start_addr, const zaddr end_addr, zbyte *data,
+                                 zsize data_len) {
+    zaddr curr_addr;
+    zbyte *temp_buf;
+    if (start_addr <= 0)
+        Xerror("search address start_addr(%p) < 0", (zpointer)start_addr);
+    if (start_addr > end_addr)
+        Xerror("search start_add(%p) < end_addr(%p)", (zpointer)start_addr, (zpointer)end_addr);
+
+    curr_addr = (zaddr)start_addr;
+    temp_buf = (zbyte *)malloc(data_len);
+
+    while (end_addr > curr_addr) {
+        if (zz_vm_read_data_via_task(task, curr_addr, temp_buf, data_len))
+            if (!memcmp(temp_buf, data, data_len)) {
+                return curr_addr;
+            }
+        curr_addr += data_len;
+    }
+    return 0;
+}
+
+zbool zz_vm_check_address_valid_via_task(task_t task, const zaddr address) {
+    if (address <= 0)
+        return FALSE;
+#define CHECK_LEN 1
+    char n_read_bytes[1];
+    zuint len;
+    kern_return_t kr = vm_read_overwrite(task, address, CHECK_LEN, (zaddr)&n_read_bytes, (vm_size_t *)&len);
+
+    if (kr != KERN_SUCCESS || len != CHECK_LEN)
+        KR_ERROR_AT(kr, address);
+    return FALSE;
+    return TRUE;
+}
+
+zbool zz_vm_get_page_info_via_task(task_t task, const zaddr address, vm_prot_t *prot_p, vm_inherit_t *inherit_p) {
+
+    vm_address_t region = (zaddr)address;
+    vm_size_t region_len = 0;
+    struct vm_region_submap_short_info_64 info;
+    mach_msg_type_number_t info_count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
+    natural_t max_depth = 99999;
+    kern_return_t kr =
+        vm_region_recurse_64(task, &region, &region_len, &max_depth, (vm_region_recurse_info_t)&info, &info_count);
+    if (kr != KERN_SUCCESS) {
+        KR_ERROR_AT(kr, address);
+        return FALSE;
+    }
+    *prot_p = info.protection & (PROT_READ | PROT_WRITE | PROT_EXEC);
+    *inherit_p = info.inheritance;
+    return TRUE;
+}
+
+zbool zz_vm_protect_via_task(task_t task, const zaddr address, zsize size, vm_prot_t page_prot) {
+    kern_return_t kr;
+
+    zsize page_size;
+    zaddr aligned_addr;
+    zsize aligned_size;
+
+    page_size = zz_posix_vm_get_page_size();
+    aligned_addr = (zaddr)address & ~(page_size - 1);
+    aligned_size = (1 + ((address + size - 1 - aligned_addr) / page_size)) * page_size;
+
+    kr = mach_vm_protect(task, (vm_address_t)aligned_addr, aligned_size, FALSE, page_prot);
+    if (kr != KERN_SUCCESS) {
+        KR_ERROR_AT(kr, address);
+        Xerror("kr = %d, at (%p) error!", kr, (zpointer)address);
+        return FALSE;
+    }
+    return TRUE;
+}
+
+zbool zz_vm_protect_as_executable_via_task(task_t task, const zaddr address, zsize size) {
+    return zz_vm_protect_via_task(task, address, size, (VM_PROT_READ | VM_PROT_EXECUTE));
+}
+
+zbool zz_vm_protect_as_writable_via_task(task_t task, const zaddr address, zsize size) {
+    if (!zz_vm_protect_via_task(task, address, size, (VM_PROT_ALL | VM_PROT_COPY))) {
+        return zz_vm_protect_via_task(task, address, size, (VM_PROT_DEFAULT | VM_PROT_COPY));
+    }
+    return FALSE;
+}
+
+zpointer zz_vm_allocate_pages_via_task(task_t task, zsize n_pages) {
+    mach_vm_address_t result;
+    kern_return_t kr;
+    zsize page_size;
+    page_size = zz_posix_vm_get_page_size();
+
+    if (n_pages <= 0) {
+        n_pages = 1;
+    }
+
+    kr = mach_vm_allocate(task, &result, page_size * n_pages, VM_FLAGS_ANYWHERE);
+
+    if (kr != KERN_SUCCESS) {
+        KR_ERROR(kr);
+        return NULL;
+    }
+
+    if (!zz_vm_protect_via_task(task, (zaddr)result, page_size * n_pages, (VM_PROT_DEFAULT | VM_PROT_COPY)))
+        return NULL;
+
+    return (zpointer)result;
+}
+
+bool zz_vm_can_allocate_rx_page() {
+    vm_prot_t prot;
+    vm_inherit_t inherit;
+    kern_return_t kr;
+    mach_port_t task_self = mach_task_self();
+    mach_vm_address_t result;
+
+    unsigned long temp_page_addr = (unsigned long)zz_vm_allocate_pages_via_task(mach_task_self(), 1);
+    zz_vm_protect_as_executable_via_task(mach_task_self(), temp_page_addr, zz_posix_vm_get_page_size());
+    if (!zz_vm_get_page_info_via_task(task_self, temp_page_addr, &prot, &inherit)) {
+        return FALSE;
+    }
+    kr = mach_vm_deallocate(task_self, temp_page_addr, zz_posix_vm_get_page_size());
+    if (kr != KERN_SUCCESS) {
+        KR_ERROR(kr);
+        return FALSE;
+    }
+    if (prot & VM_PROT_EXECUTE) {
+        return TRUE;
+    }
+    return FALSE;
+}
+
+zpointer zz_vm_allocate_via_task(task_t task, zsize size) {
+    zsize page_size;
+    zpointer result;
+    zsize n_pages;
+
+    page_size = zz_posix_vm_get_page_size();
+    n_pages = ((size + page_size - 1) & ~(page_size - 1)) / page_size;
+
+    result = zz_vm_allocate_pages_via_task(task, n_pages);
+    return (zpointer)result;
+}
+
+zpointer zz_vm_allocate_near_pages_via_task(task_t task, zaddr address, zsize range_size, zsize n_pages) {
+    mach_vm_address_t aligned_addr;
+    kern_return_t kr;
+    mach_vm_address_t tmp_addr;
+    zsize page_size;
+    page_size = zz_posix_vm_get_page_size();
+
+    if (n_pages <= 0) {
+        n_pages = 1;
+    }
+    aligned_addr = (zaddr)address & ~(page_size - 1);
+
+    vm_address_t target_start_addr = zz_vm_align_floor(address - range_size, page_size);
+    vm_address_t target_end_addr = zz_vm_align_floor(address + range_size, page_size);
+
+    for (tmp_addr = target_start_addr; tmp_addr < target_end_addr; tmp_addr += page_size) {
+        kr = mach_vm_allocate(task, &tmp_addr, page_size * n_pages, VM_FLAGS_FIXED);
+        if (kr == KERN_SUCCESS) {
+            return (zpointer)tmp_addr;
+        }
+    }
+    return NULL;
+}
+
+zpointer zz_vm_search_text_code_cave_via_task(task_t task, zaddr address, zsize range_size, zsize *size_ptr) {
+    char zeroArray[128];
+    char readZeroArray[128];
+    mach_vm_address_t aligned_addr, tmp_addr, target_search_start, target_search_end;
+    kern_return_t kr;
+    zsize page_size;
+
+    memset(zeroArray, 0, 128);
+
+    page_size = zz_posix_vm_get_page_size();
+    aligned_addr = (zaddr)address & ~(page_size - 1);
+    target_search_start = aligned_addr - range_size;
+    target_search_end = aligned_addr + range_size;
+
+    Xdebug("searching for %p cave...", (zpointer)address);
+    // TODO: check the memory region attributes
+    for (tmp_addr = target_search_start; tmp_addr < target_search_end; tmp_addr += 0x1000) {
+        if (zz_vm_read_data_via_task(task, tmp_addr, readZeroArray, 128)) {
+            if (!memcmp(readZeroArray, zeroArray, 128)) {
+                *size_ptr = 0x1000;
+                Xdebug("found a cave at %p, size %d", (zpointer)tmp_addr, 0x1000);
+                return (void *)tmp_addr;
+            }
+        }
+    }
+    return NULL;
+}
+
+MemoryLayout *zz_vm_get_memory_layout_via_task(task_t task) {
+    mach_msg_type_number_t count;
+    struct vm_region_submap_info_64 info;
+    zuint32 nesting_depth;
+
+    kern_return_t kr = KERN_SUCCESS;
+    vm_address_t address_tmp = 0;
+    vm_size_t size_tmp = 0;
+
+    MemoryLayout *mlayout = (MemoryLayout *)malloc(sizeof(MemoryLayout));
+    memset(mlayout, 0, sizeof(MemoryLayout));
+
+    while (1) {
+        mach_msg_type_number_t count;
+        struct vm_region_submap_info_64 info;
+        zuint32 nesting_depth;
+
+        count = VM_REGION_SUBMAP_INFO_COUNT_64;
+        kr = vm_region_recurse_64(task, &address_tmp, &size_tmp, &nesting_depth, (vm_region_info_64_t)&info, &count);
+        if (kr == KERN_INVALID_ADDRESS) {
+            break;
+        } else if (kr) {
+            mach_error("vm_region:", kr);
+            break; /* last region done */
+        }
+
+        if (info.is_submap) {
+            nesting_depth++;
+        } else {
+            address_tmp += size_tmp;
+
+            mlayout->mem[mlayout->size].start = (zpointer)(address_tmp - size_tmp);
+            mlayout->mem[mlayout->size].end = (zpointer)address_tmp;
+            mlayout->mem[mlayout->size++].flags = ((info.protection & PROT_READ) ? (1 << 0) : 0) |
+                                                  ((info.protection & PROT_WRITE) ? (1 << 1) : 0) |
+                                                  ((info.protection & PROT_EXEC) ? (1 << 2) : 0);
+        }
+    }
+    return mlayout;
+}
+
+// https://github.com/kpwn/935csbypass/blob/master/cs_bypass.m
+zpointer zz_vm_search_code_cave(zaddr address, zsize range_size, zsize size) {
+    char zeroArray[128];
+    char readZeroArray[128];
+    zaddr aligned_addr, tmp_addr, search_start, search_end, search_start_limit, search_end_limit;
+    zsize page_size;
+
+    zpointer result_ptr;
+    memset(zeroArray, 0, 128);
+
+    search_start_limit = address - range_size;
+    search_end_limit = address + range_size;
+
+    MemoryLayout *mlayout = zz_vm_get_memory_layout_via_task(mach_task_self());
+
+    int i;
+    for (i = 0; i < mlayout->size; i++) {
+        if (mlayout->mem[i].flags == (1 << 0 | 1 << 2)) {
+            search_start = (zaddr)mlayout->mem[i].start;
+            search_end = (zaddr)mlayout->mem[i].end;
+
+            if (search_start < search_start_limit) {
+
+                if (search_end > search_start_limit && search_end < search_end_limit) {
+                    search_start = search_start_limit;
+                } else if (search_end > search_end_limit) {
+                    search_start = search_start_limit;
+                    search_end = search_end_limit;
+                } else {
+                    continue;
+                }
+            } else if (search_start >= search_start_limit && search_start <= search_end_limit) {
+                if (search_end > search_start_limit && search_end < search_end_limit) {
+                } else if (search_end > search_end_limit) {
+                    search_end = search_end_limit;
+                } else {
+                    continue;
+                }
+            } else {
+                continue;
+            }
+
+            result_ptr = zz_vm_search_data((zpointer)search_start, (zpointer)search_end, (zbyte *)zeroArray, size);
+            if (result_ptr) {
+                free(mlayout);
+                return result_ptr;
+            }
+        }
+    }
+    free(mlayout);
+    return NULL;
+}
+
+// TODO: vm_region_recurse_64 is better ?
+zpointer zz_vm_search_text_code_cave_via_dylibs(zaddr address, zsize range_size, zsize size) {
+    char zeroArray[128];
+    char readZeroArray[128];
+    zaddr aligned_addr, tmp_addr, search_start, search_end, search_start_limit, search_end_limit;
+    zsize page_size;
+
+    zpointer result_ptr;
+
+    memset(zeroArray, 0, 128);
+
+    page_size = zz_posix_vm_get_page_size();
+    search_start_limit = address - range_size;
+    search_end_limit = address + range_size;
+
+    zsize n_dylibs = _dyld_image_count();
+    for (size_t i = 0; i < n_dylibs; i++) {
+        struct mach_header_64 *header = (struct mach_header_64 *)_dyld_get_image_header(i);
+        struct segment_command_64 *seg_cmd_64 = zz_macho_get_segment_64_via_name(header, "__TEXT");
+
+        // ATTENTION: as the __TEXT segment region is 'r-x', diffrent from
+        // others, so it's page align.
+        search_start = (zaddr)header;
+        // no need align again.
+        search_start = zz_vm_align_floor(search_start, page_size);
+        search_end = (zaddr)header + seg_cmd_64->vmsize;
+        // no need align again.
+        search_end = zz_vm_align_ceil(search_end, page_size);
+
+        if (search_start < search_start_limit) {
+
+            if (search_end > search_start_limit && search_end < search_end_limit) {
+                search_start = search_start_limit;
+            } else if (search_end > search_end_limit) {
+                search_start = search_start_limit;
+                search_end = search_end_limit;
+            } else {
+                continue;
+            }
+        } else if (search_start >= search_start_limit && search_start <= search_end_limit) {
+            if (search_end > search_start_limit && search_end < search_end_limit) {
+            } else if (search_end > search_end_limit) {
+                search_end = search_end_limit;
+            } else {
+                continue;
+            }
+        } else {
+            continue;
+        }
+
+        result_ptr = zz_vm_search_data((zpointer)search_start, (zpointer)search_end, (zbyte *)zeroArray, size);
+        if (result_ptr) {
+            return result_ptr;
+        }
+    }
+    return NULL;
+}
+
+/*
+  ref:
+  substitute/lib/darwin/execmem.c:execmem_foreign_write_with_pc_patch
+  frida-gum-master/gum/gummemory.c:gum_memory_patch_code
+
+  frida-gum-master/gum/backend-darwin/gummemory-darwin.c:gum_alloc_n_pages
+
+  mach mmap use __vm_allocate and __vm_map
+  https://github.com/bminor/glibc/blob/master/sysdeps/mach/hurd/mmap.c
+  https://github.com/bminor/glibc/blob/master/sysdeps/mach/munmap.c
+
+  http://shakthimaan.com/downloads/hurd/A.Programmers.Guide.to.the.Mach.System.Calls.pdf
+*/
+
+zbool zz_vm_patch_code_via_task(task_t task, const zaddr address, const zpointer codedata, zuint codedata_size) {
+    zsize page_size;
+    zaddr start_page_addr, end_page_addr;
+    zsize page_offset, range_size;
+
+    page_size = zz_posix_vm_get_page_size();
+    /*
+      https://www.gnu.org/software/hurd/gnumach-doc/Memory-Attributes.html
+     */
+    start_page_addr = (address) & ~(page_size - 1);
+    end_page_addr = ((address + codedata_size - 1)) & ~(page_size - 1);
+    page_offset = address - start_page_addr;
+    range_size = (end_page_addr + page_size) - start_page_addr;
+
+    vm_prot_t prot;
+    vm_inherit_t inherit;
+    kern_return_t kr;
+    mach_port_t task_self = mach_task_self();
+
+    if (!zz_vm_get_page_info_via_task(task_self, (const zaddr)start_page_addr, &prot, &inherit)) {
+        return FALSE;
+    }
+
+    /*
+      another method, pelease read `REF`;
+
+     */
+    // zpointer code_mmap = mmap(NULL, range_size, PROT_READ | PROT_WRITE,
+    //                           MAP_ANON | MAP_SHARED, -1, 0);
+    // if (code_mmap == MAP_FAILED) {
+    //   return;
+    // }
+
+    zpointer code_mmap = zz_vm_allocate_via_task(task_self, range_size);
+
+    kr = vm_copy(task_self, start_page_addr, range_size, (vm_address_t)code_mmap);
+
+    if (kr != KERN_SUCCESS) {
+        KR_ERROR_AT(kr, start_page_addr);
+        return FALSE;
+    }
+    memcpy(code_mmap + page_offset, codedata, codedata_size);
+
+    /* SAME: mprotect(code_mmap, range_size, prot); */
+    if (!zz_vm_protect_via_task(task_self, (zaddr)code_mmap, range_size, prot))
+        return FALSE;
+
+    // TODO: need check `memory region` again.
+    /*
+        TODO:
+        // if only this, `memory region` is `r-x`
+        vm_protect((vm_map_t)mach_task_self(), 0x00000001816b2030, 16, FALSE,
+       0x13);
+        // and with this, `memory region` is `rwx`
+        *(char *)0x00000001816b01a8 = 'a';
+     */
+
+    mach_vm_address_t target = (zaddr)start_page_addr;
+    vm_prot_t c, m;
+    kr = mach_vm_remap(task_self, &target, range_size, 0, VM_FLAGS_OVERWRITE, task_self, (mach_vm_address_t)code_mmap,
+                       /*copy*/ TRUE, &c, &m, inherit);
+
+    if (kr != KERN_SUCCESS) {
+        KR_ERROR(kr);
+        return FALSE;
+    }
+    /*
+      read `REF`
+     */
+    // munmap(code_mmap, range_size);
+    kr = mach_vm_deallocate(task_self, (zaddr)code_mmap, range_size);
+    if (kr != KERN_SUCCESS) {
+        KR_ERROR(kr);
+        return FALSE;
+    }
+    return TRUE;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/memory-utils-darwin.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/memory-utils-darwin.h
new file mode 100644
index 000000000..7c928a1f2
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/darwin/memory-utils-darwin.h
@@ -0,0 +1,82 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef zzdeps_darwin_memory_utils_darwin_h
+#define zzdeps_darwin_memory_utils_darwin_h
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <mach/mach_error.h>
+#include <mach/task.h>
+
+#if defined(__arm64__) || defined(__arm__)
+
+#include "mach_vm.h"
+
+#else
+#include <mach/mach_vm.h>
+#endif
+
+#include "../posix/memory-utils-posix.h"
+#include "../zz.h"
+
+#define KR_ERROR(kr)                                                                                                   \
+    {                                                                                                                  \
+        Xerror("kr = %d, reason: %s!", kr, mach_error_string(kr));                                                     \
+        debug_break();                                                                                                 \
+    }
+#define KR_ERROR_AT(kr, address)                                                                                       \
+    {                                                                                                                  \
+        Xerror("kr = %d, at %p, reason: %s!", kr, (zpointer)address, mach_error_string(kr));                           \
+        debug_break();                                                                                                 \
+    }
+
+zbool zz_vm_read_data_via_task(task_t task, const zaddr address, zpointer buffer, zsize length);
+
+char *zz_vm_read_string_via_task(task_t task, const zaddr address);
+
+zaddr zz_vm_search_data_via_task(task_t task, const zaddr start_addr, const zaddr end_addr, zbyte *data,
+                                 zsize data_len);
+
+zbool zz_vm_check_address_valid_via_task(task_t task, const zaddr address);
+
+zbool zz_vm_can_allocate_rx_page();
+
+zbool zz_vm_protect_via_task(task_t task, const zaddr address, zsize size, vm_prot_t page_prot);
+
+zbool zz_vm_protect_as_executable_via_task(task_t task, const zaddr address, zsize size);
+
+zbool zz_vm_protect_as_writable_via_task(task_t task, const zaddr address, zsize size);
+
+zbool zz_vm_get_page_info_via_task(task_t task, const zaddr address, vm_prot_t *prot_p, vm_inherit_t *inherit_p);
+
+zpointer zz_vm_allocate_pages_via_task(task_t task, zsize n_pages);
+
+zpointer zz_vm_allocate_near_pages_via_task(task_t task, zaddr address, zsize range_size, zsize n_pages);
+
+zpointer zz_vm_allocate_via_task(task_t task, zsize size);
+
+zpointer zz_vm_search_text_code_cave_via_task(task_t task, zaddr address, zsize range_size, zsize *size_ptr);
+
+zpointer zz_vm_search_text_code_cave_via_dylibs(zaddr address, zsize range_size, zsize size);
+
+zpointer zz_vm_search_code_cave(zaddr address, zsize range_size, zsize size);
+
+zbool zz_vm_patch_code_via_task(task_t task, const zaddr address, const zpointer codedata, zuint codedata_size);
+
+#endif
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/linux/memory-utils-linux.c b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/linux/memory-utils-linux.c
new file mode 100644
index 000000000..6ad056a23
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/linux/memory-utils-linux.c
@@ -0,0 +1,119 @@
+#include "memory-utils-linux.h"
+#include "../common/memory-utils-common.h"
+#include "../memory-utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+MemoryLayout *zz_linux_vm_get_memory_layout_via_pid(pid_t pid) {
+    char filename[64];
+    char buf[256];
+    FILE *fp;
+    MemoryLayout *mlayout;
+
+    mlayout = (MemoryLayout *)malloc(sizeof(MemoryLayout));
+    memset(mlayout, 0, sizeof(MemoryLayout));
+
+    // given pid, open /proc/pid/maps; or not, open current maps.
+    if (pid > 0) {
+        sprintf(filename, "/proc/%d/maps", pid);
+    } else {
+        sprintf(filename, "/proc/self/maps");
+    }
+
+    fp = fopen(filename, "r");
+    if (fp < 0) {
+        return NULL;
+    }
+
+    while (fgets(buf, sizeof(buf), fp) != NULL) {
+        zaddr start, end;
+        unsigned dev, sdev;
+        unsigned long inode;
+        unsigned long long offset;
+        char prot[5];
+        char path[64];
+        int len;
+
+        /* format in /proc/pid/maps is constructed as below in fs/proc/task_mmu.c
+        167	seq_printf(m,
+        168			   "%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu ",
+        169			   vma->vm_start,
+        170			   vma->vm_end,
+        171			   flags & VM_READ ? 'r' : '-',
+        172			   flags & VM_WRITE ? 'w' : '-',
+        173			   flags & VM_EXEC ? 'x' : '-',
+        174			   flags & VM_MAYSHARE ? flags & VM_SHARED ? 'S' : 's' : 'p',
+        175			   pgoff,
+        176			   MAJOR(dev), MINOR(dev), ino);
+        177
+        178		if (file) {
+        179			seq_pad(m, ' ');
+        180			seq_file_path(m, file, "");
+        181		} else if (mm && is_stack(priv, vma)) {
+        182			seq_pad(m, ' ');
+        183			seq_printf(m, "[stack]");
+        184		}
+         */
+        if (sscanf(buf, "%lx-%lx %s %llx %x:%x %lu %s", &start, &end, prot, &offset, &dev, &sdev, &inode, path) != 8)
+            continue;
+        mlayout->mem[mlayout->size].start = (zpointer)start;
+        mlayout->mem[mlayout->size].end = (zpointer)end;
+        mlayout->mem[mlayout->size++].flags =
+            (prot[0] == 'r' ? (1 << 0) : 0) | (prot[1] == 'w' ? (1 << 1) : 0) | (prot[2] == 'x' ? (1 << 2) : 0);
+    }
+    return mlayout;
+}
+
+zpointer zz_linux_vm_search_code_cave(zaddr address, zsize range_size, zsize size) {
+    char zeroArray[128];
+    char readZeroArray[128];
+    zaddr aligned_addr, tmp_addr, search_start, search_end, search_start_limit, search_end_limit;
+    zsize page_size;
+
+    zpointer result_ptr;
+    memset(zeroArray, 0, 128);
+
+    search_start_limit = address - range_size;
+    search_end_limit = address + range_size;
+
+    MemoryLayout *mlayout = zz_linux_vm_get_memory_layout_via_pid(-1);
+
+    int i;
+    for (i = 0; i < mlayout->size; i++) {
+        if (mlayout->mem[i].flags == (1 << 0 | 1 << 2)) {
+            search_start = (zaddr)mlayout->mem[i].start;
+            search_end = (zaddr)mlayout->mem[i].end;
+
+            if (search_start < search_start_limit) {
+
+                if (search_end > search_start_limit && search_end < search_end_limit) {
+                    search_start = search_start_limit;
+                } else if (search_end > search_end_limit) {
+                    search_start = search_start_limit;
+                    search_end = search_end_limit;
+                } else {
+                    continue;
+                }
+            } else if (search_start >= search_start_limit && search_start <= search_end_limit) {
+                if (search_end > search_start_limit && search_end < search_end_limit) {
+                } else if (search_end > search_end_limit) {
+                    search_end = search_end_limit;
+                } else {
+                    continue;
+                }
+            } else {
+                continue;
+            }
+
+            result_ptr = zz_vm_search_data((zpointer)search_start, (zpointer)search_end, (zbyte *)zeroArray, size);
+            if (result_ptr) {
+                free(mlayout);
+                return result_ptr;
+            }
+        }
+    }
+    free(mlayout);
+    return NULL;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/linux/memory-utils-linux.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/linux/memory-utils-linux.h
new file mode 100644
index 000000000..4a5ba143f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/linux/memory-utils-linux.h
@@ -0,0 +1,12 @@
+#ifndef zzdeps_linux_memory_utils_linux_h
+#define zzdeps_linux_memory_utils_linux_h
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../zz.h"
+
+zpointer zz_linux_vm_search_code_cave(zaddr address, zsize range_size, zsize size);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/memory-utils.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/memory-utils.h
new file mode 100644
index 000000000..ee020b0c1
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/memory-utils.h
@@ -0,0 +1,15 @@
+#ifndef zzdeps_memory_utils_h
+#define zzdeps_memory_utils_h
+
+#include "zz.h"
+
+typedef struct _MemoryLayout {
+    int size;
+    struct {
+        int flags;
+        zpointer start;
+        zpointer end;
+    } mem[4096];
+} MemoryLayout;
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/memory-utils-posix.c b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/memory-utils-posix.c
new file mode 100644
index 000000000..08be30f67
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/memory-utils-posix.c
@@ -0,0 +1,246 @@
+
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "memory-utils-posix.h"
+// http://renatocunha.com/blog/2015/12/msync-pointer-validity/
+zbool zz_posix_vm_check_address_valid_via_msync(const zpointer p) {
+    int ret = 0;
+    zsize page_size;
+    zpointer base;
+    /* get the page size */
+    page_size = zz_posix_vm_get_page_size();
+    /* find the address of the page that contains p */
+    base = (void *)((((size_t)p) / page_size) * page_size);
+    /* call msync, if it returns non-zero, return FALSE */
+    ret = msync(base, page_size, MS_ASYNC) != -1;
+    return ret ? ret : errno != ENOMEM;
+}
+
+// ATTENTION !!!
+// lldb is still catch EXC_BAD_ACCESS, without lldb is ok.
+// https://www.cocoawithlove.com/2010/10/testing-if-arbitrary-pointer-is-valid.html
+// https://stackoverflow.com/questions/26829119/how-to-make-lldb-ignore-exc-bad-access-exception
+// ---check start---
+#include <setjmp.h>
+#include <signal.h>
+
+static sigjmp_buf sigjmp_env;
+
+void PointerReadFailedHandler(int signum) { siglongjmp(sigjmp_env, 1); }
+
+zbool zz_posix_vm_check_address_valid_via_signal(zpointer p) {
+    // Set up SIGSEGV and SIGBUS handlers
+    struct sigaction new_segv_action, old_segv_action;
+    struct sigaction new_bus_action, old_bus_action;
+    new_segv_action.sa_handler = PointerReadFailedHandler;
+    new_bus_action.sa_handler = PointerReadFailedHandler;
+    sigemptyset(&new_segv_action.sa_mask);
+    sigemptyset(&new_bus_action.sa_mask);
+    new_segv_action.sa_flags = 0;
+    new_bus_action.sa_flags = 0;
+    sigaction(SIGSEGV, &new_segv_action, &old_segv_action);
+    sigaction(SIGBUS, &new_bus_action, &old_bus_action);
+
+    // The signal handler will return us to here if a signal is raised
+    if (sigsetjmp(sigjmp_env, 1)) {
+        sigaction(SIGSEGV, &old_segv_action, NULL);
+        sigaction(SIGBUS, &old_bus_action, NULL);
+        return FALSE;
+    }
+    // ATTENTION !!! this function is conflict with LLDB, reason is below.
+    // lldb is still catch EXC_BAD_ACCESS, without lldb is ok.
+    // or you can use `zz_check_address_valid_via_mem` replace
+    // https://stackoverflow.com/questions/26829119/how-to-make-lldb-ignore-exc-bad-access-exception
+    char x = *(char *)p;
+    return TRUE;
+}
+
+zsize zz_posix_vm_get_page_size() { return getpagesize(); }
+
+// int mprotect(void *addr, size_t len, int prot);
+zbool zz_posix_vm_protect(const zaddr address, zsize size, int page_prot) {
+    int r;
+
+    zsize page_size;
+    zaddr aligned_addr;
+    zsize aligned_size;
+
+    page_size = zz_posix_vm_get_page_size();
+    aligned_addr = (zaddr)address & ~(page_size - 1);
+    aligned_size = (1 + ((address + size - 1 - aligned_addr) / page_size)) * page_size;
+
+    r = mprotect((zpointer)aligned_addr, aligned_size, page_prot);
+    if (r == -1) {
+        Xerror("r = %d, at (%p) error!", r, (zpointer)address);
+        return FALSE;
+    }
+    return TRUE;
+}
+
+zbool zz_posix_vm_protect_as_executable(const zaddr address, zsize size) {
+    return zz_posix_vm_protect(address, size, (PROT_READ | PROT_EXEC | PROT_WRITE));
+}
+
+zbool zz_posxi_vm_protect_as_writable(const zaddr address, zsize size) {
+    if (!zz_posix_vm_protect(address, size, (PROT_READ | PROT_EXEC | PROT_WRITE)))
+        return FALSE;
+    return TRUE;
+}
+
+//  void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
+zpointer zz_posix_vm_allocate_pages(zsize n_pages) {
+    zpointer page_mmap;
+    int kr;
+    zsize page_size;
+    page_size = zz_posix_vm_get_page_size();
+
+    if (n_pages <= 0) {
+        n_pages = 1;
+    }
+
+    page_mmap = mmap(0, page_size * n_pages, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+
+    if (page_mmap == MAP_FAILED) {
+        perror("mmap");
+        return NULL;
+    }
+
+    if (!zz_posix_vm_protect((zaddr)page_mmap, page_size * n_pages, (PROT_WRITE | PROT_READ)))
+        return NULL;
+    return (zpointer)page_mmap;
+}
+
+zpointer zz_posix_vm_allocate(zsize size) {
+    zsize page_size;
+    zpointer result;
+    zsize n_pages;
+
+    page_size = zz_posix_vm_get_page_size();
+    n_pages = ((size + page_size - 1) & ~(page_size - 1)) / page_size;
+
+    result = zz_posix_vm_allocate_pages(n_pages);
+    return (zpointer)result;
+}
+
+zpointer zz_posix_vm_allocate_near_pages(zaddr address, zsize range_size, zsize n_pages) {
+    zaddr aligned_addr;
+    zpointer page_mmap;
+    zaddr t;
+    zsize page_size;
+    page_size = zz_posix_vm_get_page_size();
+
+    if (n_pages <= 0) {
+        n_pages = 1;
+    }
+    aligned_addr = (zaddr)address & ~(page_size - 1);
+
+    zaddr target_start_addr = aligned_addr - range_size;
+    zaddr target_end_addr = aligned_addr + range_size;
+
+    for (t = target_start_addr; t < target_end_addr; t += page_size) {
+        page_mmap = mmap((zpointer)t, page_size * n_pages, PROT_WRITE | PROT_READ,
+                         MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
+        if (page_mmap != MAP_FAILED) {
+            return (zpointer)page_mmap;
+        }
+    }
+    return NULL;
+}
+
+zpointer zz_posix_vm_search_text_code_cave(zaddr address, zsize range_size, zsize size) {
+    char zeroArray[128];
+    char readZeroArray[128];
+    zaddr aligned_addr, tmp_addr, target_search_start, target_search_end;
+    zsize page_size;
+
+    memset(zeroArray, 0, 128);
+
+    page_size = zz_posix_vm_get_page_size();
+    aligned_addr = (zaddr)address & ~(page_size - 1);
+    target_search_start = aligned_addr - range_size;
+    target_search_end = aligned_addr + range_size;
+
+    Xdebug("searching for %p cave, use 0x1000 interval.", (zpointer)address);
+    for (tmp_addr = target_search_start; tmp_addr < target_search_end; tmp_addr += 0x1000) {
+        if (zz_posix_vm_check_address_valid_via_signal((zpointer)tmp_addr))
+            if (memcpy(readZeroArray, (zpointer)tmp_addr, 128)) {
+                if (!memcmp(readZeroArray, zeroArray, 128)) {
+                    return (void *)tmp_addr;
+                }
+            }
+    }
+    return NULL;
+}
+
+/*
+  ref:
+  substitute/lib/darwin/execmem.c:execmem_foreign_write_with_pc_patch
+  frida-gum-master/gum/gummemory.c:gum_memory_patch_code
+
+  frida-gum-master/gum/backend-darwin/gummemory-darwin.c:gum_alloc_n_pages
+
+  mach mmap use __vm_allocate and __vm_map
+  https://github.com/bminor/glibc/blob/master/sysdeps/mach/hurd/mmap.c
+  https://github.com/bminor/glibc/blob/master/sysdeps/mach/munmap.c
+
+  http://shakthimaan.com/downloads/hurd/A.Programmers.Guide.to.the.Mach.System.Calls.pdf
+*/
+
+zbool zz_posix_vm_patch_code(const zaddr address, const zpointer codedata, zuint codedata_size) {
+    zsize page_size;
+    zaddr start_page_addr, end_page_addr;
+    zsize page_offset, range_size;
+
+    page_size = zz_posix_vm_get_page_size();
+    /*
+      https://www.gnu.org/software/hurd/gnumach-doc/Memory-Attributes.html
+     */
+    start_page_addr = (address) & ~(page_size - 1);
+    end_page_addr = ((address + codedata_size - 1)) & ~(page_size - 1);
+    page_offset = address - start_page_addr;
+    range_size = (end_page_addr + page_size) - start_page_addr;
+
+    //  another method, pelease read `REF`;
+
+    // zpointer code_mmap = mmap(NULL, range_size, PROT_READ | PROT_WRITE,
+    //                           MAP_ANON | MAP_SHARED, -1, 0);
+    // if (code_mmap == MAP_FAILED) {
+    //   return;
+    // }
+
+    zpointer code_mmap = zz_posix_vm_allocate(range_size);
+
+    memcpy(code_mmap, (void *)start_page_addr, range_size);
+
+    memcpy(code_mmap + page_offset, codedata, codedata_size);
+
+    /* SAME: mprotect(code_mmap, range_size, prot); */
+    // if (!zz_posix_vm_protect((zaddr)code_mmap, range_size, PROT_READ | PROT_EXEC))
+    //     return FALSE;
+
+    zaddr target = (zaddr)start_page_addr;
+    zz_posxi_vm_protect_as_writable(start_page_addr, range_size);
+    memcpy((zpointer)start_page_addr, (zpointer)code_mmap, range_size);
+    zz_posix_vm_protect_as_executable(start_page_addr, range_size);
+    munmap(code_mmap, range_size);
+    return TRUE;
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/memory-utils-posix.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/memory-utils-posix.h
new file mode 100644
index 000000000..38ad130db
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/memory-utils-posix.h
@@ -0,0 +1,50 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef zzdeps_posix_memory_utils_posix_h
+#define zzdeps_posix_memory_utils_posix_h
+
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../common/memory-utils-common.h"
+#include "../zz.h"
+
+zsize zz_posix_vm_get_page_size();
+
+zbool zz_posix_vm_check_address_valid_via_msync(const zpointer p);
+
+zbool zz_posix_vm_check_address_valid_via_signal(zpointer p);
+
+zbool zz_posix_vm_protect(const zaddr address, zsize size, int page_prot);
+
+zbool zz_posix_vm_protect_as_executable(const zaddr address, zsize size);
+
+zbool zz_posxi_vm_protect_as_writable(const zaddr address, zsize size);
+
+zpointer zz_posix_vm_allocate_pages(zsize n_pages);
+
+zpointer zz_posix_vm_allocate(zsize size);
+
+zpointer zz_posix_vm_allocate_near_pages(zaddr address, zsize range_size, zsize n_pages);
+
+zpointer zz_posix_vm_search_text_code_cave(zaddr address, zsize range_size, zsize size);
+
+zbool zz_posix_vm_patch_code(const zaddr address, const zpointer codedata, zuint codedata_size);
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/thread-utils-posix.c b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/thread-utils-posix.c
new file mode 100644
index 000000000..151b22d62
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/thread-utils-posix.c
@@ -0,0 +1,90 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "thread-utils-posix.h"
+#include <pthread.h>
+
+ThreadLocalKeyList *g_thread_local_key_list = 0;
+
+ThreadLocalKeyList *zz_posix_thread_new_thread_local_key_list() {
+    ThreadLocalKeyList *keylist_tmp = (ThreadLocalKeyList *)malloc(sizeof(ThreadLocalKeyList));
+    keylist_tmp->capacity = 4;
+    keylist_tmp->keys = (ThreadLocalKey **)malloc(sizeof(ThreadLocalKey *) * keylist_tmp->capacity);
+    if (!keylist_tmp->keys) {
+        return NULL;
+    }
+    keylist_tmp->size = 0;
+    return keylist_tmp;
+}
+
+zbool zz_posix_thread_add_thread_local_key(ThreadLocalKeyList *keylist, ThreadLocalKey *key) {
+    if (!keylist)
+        return FALSE;
+
+    if (keylist->size >= keylist->capacity) {
+        ThreadLocalKey **keys_tmp =
+            (ThreadLocalKey **)realloc(keylist->keys, sizeof(ThreadLocalKey *) * keylist->capacity * 2);
+        if (!keys_tmp)
+            return FALSE;
+        keylist->keys = keys_tmp;
+        keylist->capacity = keylist->capacity * 2;
+    }
+    keylist->keys[keylist->size++] = key;
+    return TRUE;
+}
+
+void zz_posix_thread_initialize_thread_local_key_list() {
+    if (!g_thread_local_key_list) {
+        g_thread_local_key_list = zz_posix_thread_new_thread_local_key_list();
+    }
+}
+
+zpointer zz_posix_thread_new_thread_local_key_ptr() {
+    if (!g_thread_local_key_list) {
+        zz_posix_thread_initialize_thread_local_key_list();
+    }
+    ThreadLocalKey *key = (ThreadLocalKey *)malloc(sizeof(ThreadLocalKey));
+    zz_posix_thread_add_thread_local_key(g_thread_local_key_list, key);
+
+    pthread_key_create(&(key->key), NULL);
+    return (zpointer)key;
+}
+
+zpointer zz_posix_thread_get_current_thread_data(zpointer key_ptr) {
+    ThreadLocalKeyList *g_keys = g_thread_local_key_list;
+    zsize i;
+
+    if (!key_ptr)
+        return NULL;
+    for (i = 0; i < g_keys->size; i++) {
+        if (g_keys->keys[i] == key_ptr)
+            return (zpointer)pthread_getspecific(g_keys->keys[i]->key);
+    }
+    return NULL;
+}
+
+zbool zz_posix_thread_set_current_thread_data(zpointer key_ptr, zpointer data) {
+    ThreadLocalKeyList *g_keys = g_thread_local_key_list;
+    zsize i;
+
+    for (i = 0; i < g_keys->size; i++) {
+        if (g_keys->keys[i] == key_ptr)
+            return pthread_setspecific(g_keys->keys[i]->key, data);
+    }
+    return FALSE;
+}
+
+long zz_posix_get_current_thread_id() { return (long)pthread_self(); }
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/thread-utils-posix.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/thread-utils-posix.h
new file mode 100644
index 000000000..bd916ed56
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/posix/thread-utils-posix.h
@@ -0,0 +1,49 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef zzdeps_posix_thread_utils_h
+#define zzdeps_posix_thread_utils_h
+
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+
+#include "../zz.h"
+
+typedef struct _ThreadLocalKey {
+    pthread_key_t key;
+} ThreadLocalKey;
+
+typedef struct _ThreadLocalKeyList {
+    zsize size;
+    zsize capacity;
+    ThreadLocalKey **keys;
+} ThreadLocalKeyList;
+
+void zz_posix_thread_initialize_thread_local_key_list();
+
+zpointer zz_posix_thread_new_thread_local_key_ptr();
+
+zpointer zz_posix_thread_get_current_thread_data(zpointer key_ptr);
+
+zbool zz_posix_thread_set_current_thread_data(zpointer key_ptr, zpointer data);
+
+long zz_posix_get_current_thread_id();
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/zz.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/zz.h
new file mode 100644
index 000000000..69fa53eff
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzdeps/zz.h
@@ -0,0 +1,132 @@
+#ifndef zz_h
+#define zz_h
+
+// Created by jmpews on 2017/5/3.
+//
+#define PROGRAM_NAME "zz"
+#define PROGRAM_VER "1.0.0"
+#define PROGRAM_AUTHOR "jmpews@gmail.com"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+// --- custom type ---
+
+// 1. zpointer and zaddr is different
+
+#define DEBUG_MODE 0
+
+#ifndef zz_type
+#define zz_type
+
+typedef void *zpointer;
+typedef unsigned long zsize;
+typedef unsigned long zaddr;
+
+typedef uint64_t zuint64;
+typedef uint32_t zuint32;
+typedef uint16_t zuint16;
+typedef uint8_t zuint8;
+
+typedef int32_t zint32;
+typedef int16_t zint16;
+typedef int8_t zint8;
+
+typedef unsigned long zuint;
+typedef long zint;
+typedef unsigned char zbyte;
+typedef bool zbool;
+
+#endif
+
+#if defined(FALSE)
+#else
+#define FALSE 0
+#define TRUE 1
+#endif
+
+// --- log configuration ---
+
+#define GLOBAL_DEBUG 0
+#define GLOBAL_INFO 1
+#define SYSLOG 0
+#define COLOR_LOG 0
+
+#if (COLOR_LOG)
+#define RED "\x1B[31m"
+#define GRN "\x1B[32m"
+#define YEL "\x1B[33m"
+#define BLU "\x1B[34m"
+#define MAG "\x1B[35m"
+#define CYN "\x1B[36m"
+#define WHT "\x1B[37m"
+#define RESET "\x1B[0m"
+#else
+#define RED ""
+#define GRN ""
+#define YEL ""
+#define BLU ""
+#define MAG ""
+#define CYN ""
+#define WHT ""
+#define RESET ""
+#endif
+
+#include <stdio.h>
+
+// Important!!!
+// STDERR before STDOUT, because sync
+
+#if (SYSLOG)
+#include <sys/syslog.h>
+#define Xinfo(fmt, ...)                                                                                                \
+    do {                                                                                                               \
+        if (GLOBAL_INFO)                                                                                               \
+            syslog(LOG_WARNING, RESET fmt, __VA_ARGS__);                                                               \
+    } while (0)
+#define Sinfo(MSG) Xinfo("%s", MSG)
+#define Xdebug(fmt, ...)                                                                                               \
+    do {                                                                                                               \
+        if (GLOBAL_DEBUG)                                                                                              \
+            syslog(LOG_WARNING, RESET fmt, __VA_ARGS__);                                                               \
+    } while (0)
+#define Sdebug(MSG) Xdebug("%s", MSG)
+#define Xerror(fmt, ...)                                                                                               \
+    do {                                                                                                               \
+        syslog(LOG_DEBUG,                                                                                              \
+               RED "[!] "                                                                                              \
+                   "%s:%d:%s(): " fmt RESET "\n",                                                                      \
+               __FILE__, __LINE__, __func__, __VA_ARGS__);                                                             \
+    } while (0)
+
+#define Serror(MSG) Xerror("%s", MSG)
+#else
+#define Xinfo(fmt, ...)                                                                                                \
+    do {                                                                                                               \
+        if (GLOBAL_INFO)                                                                                               \
+            fprintf(stdout, RESET fmt "\n", __VA_ARGS__);                                                              \
+    } while (0)
+#define Sinfo(MSG) Xinfo("%s", MSG)
+
+#define Xdebug(fmt, ...)                                                                                               \
+    do {                                                                                                               \
+        if (GLOBAL_DEBUG)                                                                                              \
+            fprintf(stdout, RESET fmt "\n", __VA_ARGS__);                                                              \
+    } while (0)
+#define Sdebug(MSG) Xdebug("%s", MSG)
+#define Xerror(fmt, ...)                                                                                               \
+    do {                                                                                                               \
+        fprintf(stderr,                                                                                                \
+                RED "[!] "                                                                                             \
+                    "%s:%d:%s(): " fmt RESET "\n",                                                                     \
+                __FILE__, __LINE__, __func__, __VA_ARGS__);                                                            \
+    } while (0)
+
+#define Serror(MSG) Xerror("%s", MSG)
+#endif
+
+// --- common macro ---
+#undef ABS
+#define ABS(a) (((a) < 0) ? -(a) : (a))
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzinfo.c b/VirtualApp/lib/src/main/jni/HookZz/src/zzinfo.c
new file mode 100644
index 000000000..338a8dd8f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzinfo.c
@@ -0,0 +1,25 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "zzinfo.h"
+
+ZzInfo g_zz;
+
+void ZzEnableDebugMode() { g_zz.g_enable_debug_flag = TRUE; }
+
+zbool ZzIsEnableDebugMode() { return g_zz.g_enable_debug_flag; }
+
+ZzInfo *ZzInfoObtain(void) { return &g_zz; }
diff --git a/VirtualApp/lib/src/main/jni/HookZz/src/zzinfo.h b/VirtualApp/lib/src/main/jni/HookZz/src/zzinfo.h
new file mode 100644
index 000000000..db9d37f17
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/src/zzinfo.h
@@ -0,0 +1,44 @@
+//    Copyright 2017 jmpews
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//        http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+#ifndef zzinfo_h
+#define zzinfo_h
+
+// platforms
+
+// hookzz
+#include "hookzz.h"
+
+// zzdeps
+#include "zzdefs.h"
+#include "zzdeps/common/debugbreak.h"
+#include "zzdeps/zz.h"
+
+typedef struct _ZzInfo {
+    zbool g_enable_debug_flag;
+} ZzInfo;
+
+ZzInfo *ZzInfoObtain(void);
+zbool ZzIsEnableDebugMode();
+
+#if defined(__ANDROID__)
+#include <android/log.h>
+#define ZzInfoLog(fmt, ...)                                                                                            \
+    { __android_log_print(ANDROID_LOG_INFO, "zzinfo", fmt, __VA_ARGS__); }
+#else
+#define ZzInfoLog(fmt, ...)                                                                                            \
+    { Xinfo(fmt, __VA_ARGS__); }
+#endif
+
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/makefile b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/makefile
new file mode 100644
index 000000000..ecb631886
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/makefile
@@ -0,0 +1,57 @@
+NO_COLOR=\x1b[0m
+OK_COLOR=\x1b[32;01m
+ERROR_COLOR=\x1b[31;01m
+WARN_COLOR=\x1b[33;01m
+
+HOOKZZ_INCLUDE_DIR := $(abspath ../../include)
+HOOKZZ_LIB_DIR := $(abspath ../../build/android-armv7)
+
+CFLAGS ?= -O0 -g -std=c99 -pie -fPIE
+
+HOST ?= $(shell uname -s)
+HOST_ARCH ?= $(shell uname -m)
+
+ARCH := arm
+
+ifeq ($(ARCH), arm)
+	ZZ_ARCH := armv7
+	ZZ_API_LEVEL := android-19
+	ZZ_CROSS_PREFIX := arm-linux-androideabi-
+else ifeq ($(ARCH), arm64)
+	ZZ_ARCH := arm64
+	ZZ_API_LEVEL := android-21
+	ZZ_CROSS_PREFIX := aarch64-linux-android-
+endif
+
+HOST_DIR := $(shell echo $(HOST) | tr A-Z a-z)-$(HOST_ARCH)
+ZZ_NDK_HOME := $(shell dirname `which ndk-build`)
+ZZ_SDK_ROOT := $(ZZ_NDK_HOME)/platforms/$(ZZ_API_LEVEL)/arch-$(ARCH)
+ZZ_GCC_BIN := $(ZZ_NDK_HOME)/toolchains/$(ZZ_CROSS_PREFIX)4.9/prebuilt/$(HOST_DIR)/bin/$(ZZ_CROSS_PREFIX)gcc
+ZZ_GXX_BIN := $(ZZ_NDK_HOME)/toolchains/$(ZZ_CROSS_PREFIX)4.9/prebuilt/$(HOST_DIR)/bin/$(ZZ_CROSS_PREFIX)g++
+ZZ_AR_BIN := $(ZZ_NDK_HOME)/toolchains/$(ZZ_CROSS_PREFIX)4.9/prebuilt/$(HOST_DIR)/bin/$(ZZ_CROSS_PREFIX)ar
+ZZ_RANLIB_BIN := $(ZZ_NDK_HOME)/toolchains/$(ZZ_CROSS_PREFIX)4.9/prebuilt/$(HOST_DIR)/bin/$(ZZ_CROSS_PREFIX)ranlib
+
+ZZ_GCC_SOURCE := $(ZZ_GCC_BIN) --sysroot=$(ZZ_SDK_ROOT)
+ZZ_GXX_SOURCE := $(ZZ_GXX_BIN) --sysroot=$(ZZ_SDK_ROOT)
+ZZ_GCC_TEST := $(ZZ_GCC_BIN) --sysroot=$(ZZ_SDK_ROOT)
+ZZ_GXX_TEST := $(ZZ_GXX_BIN) --sysroot=$(ZZ_SDK_ROOT)
+
+test: 
+	@$(ZZ_GCC_TEST) $(CFLAGS) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_open_arm.c -o test_hook_open_arm.o
+	@$(ZZ_GCC_TEST) $(CFLAGS) -I$(HOOKZZ_INCLUDE_DIR) test_hook_open_arm.o  -L$(HOOKZZ_LIB_DIR) -lhookzz.static -o $(HOOKZZ_LIB_DIR)/test_hook_open_arm
+	@echo "$(OK_COLOR)build [test_hook_open_arm.dylib] success for armv7-ios! $(NO_COLOR)"
+
+	@$(ZZ_GCC_TEST) $(CFLAGS) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_address_thumb.c -o test_hook_address_thumb.o
+	@$(ZZ_GCC_TEST) $(CFLAGS) -I$(HOOKZZ_INCLUDE_DIR) test_hook_address_thumb.o  -L$(HOOKZZ_LIB_DIR) -lhookzz.static -o $(HOOKZZ_LIB_DIR)/test_hook_address_thumb
+	@echo "$(OK_COLOR)build [test_hook_address_thumb.dylib] success for armv7-ios! $(NO_COLOR)"
+
+	@$(ZZ_GCC_TEST) $(CFLAGS) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_printf.c -o test_hook_printf.o
+	@$(ZZ_GCC_TEST) $(CFLAGS) -I$(HOOKZZ_INCLUDE_DIR) test_hook_printf.o  -L$(HOOKZZ_LIB_DIR) -lhookzz.static -o $(HOOKZZ_LIB_DIR)/test_hook_printf
+	@echo "$(OK_COLOR)build [test_hook_printf.dylib] success for armv7-ios! $(NO_COLOR)"
+
+	@echo "$(OK_COLOR)build [test] success for armv7-android-hookzz! $(NO_COLOR)"
+
+clean:
+	@rm -rf $(shell find ./ -name "*\.o" | xargs echo)
+	@rm -rf $(shell find $(HOOKZZ_LIB_DIR) -name "test_*" | xargs echo)
+	@echo "$(OK_COLOR)clean all *.o success!$(NO_COLOR)"
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/test_hook_address_thumb.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/test_hook_address_thumb.c
new file mode 100644
index 000000000..3452945c7
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/test_hook_address_thumb.c
@@ -0,0 +1,80 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#include <stdio.h>
+#include <unistd.h>
+
+__attribute__((__naked__)) static void hack_this_function() {
+#ifdef __arm__
+    __asm__ volatile(".code 32\n"
+                     "mov r0, #0\n"
+                     "mov r12, #20\n"
+                     "svc #0x80\n"
+                     "nop\n"
+                     "nop\n"
+                     "nop\n"
+                     "nop\n"
+                     "nop\n"
+                     "nop");
+#endif
+}
+
+__attribute__((__naked__)) static void sorry_to_exit() {
+#ifdef __arm__
+    __asm__ volatile(".code 32\n"
+                     "mov r0, #0\n"
+                     "mov r12, #1\n"
+                     "svc #0x80");
+#endif
+}
+
+void getpid_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    unsigned long request = *(unsigned long *)(&rs->general.regs.r12);
+    printf("request(r12) is: %ld\n", request);
+    printf("r0 is: %ld\n", (long)rs->general.regs.r0);
+}
+
+void getpid_half_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    pid_t r0 = (pid_t)(rs->general.regs.r0);
+    printf("getpid() return at r0 is: %d\n", r0);
+}
+
+__attribute__((constructor)) void test_hook_address() {
+    void *hack_this_function_ptr = (void *)hack_this_function;
+    // hook address with only `pre_call`
+    // ZzBuildHookAddress(hack_this_function_ptr + 8, hack_this_function_ptr + 12, (void
+    // *)getpid_pre_call, NULL);
+
+    // hook address with only `half_call`
+    // ZzBuildHookAddress(hack_this_function_ptr + 8, hack_this_function_ptr + 12, NULL, (void
+    // *)getpid_half_call);
+
+    // hook address with both `half_call` and `pre_call`
+    ZzEnableDebugMode();
+    ZzHookAddress(hack_this_function_ptr + 8, hack_this_function_ptr + 8 + 4, getpid_pre_call, getpid_half_call);
+
+    void *sorry_to_exit_ptr = (void *)sorry_to_exit;
+    unsigned long nop_bytes = 0xE1A00000;
+    ZzRuntimeCodePatch((unsigned long)sorry_to_exit_ptr + 8, (zpointer)&nop_bytes, 4);
+
+    hack_this_function();
+    sorry_to_exit();
+
+    printf("hack success -.0\n");
+}
+
+int main(int args, char **argv) {}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/test_hook_open_arm.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/test_hook_open_arm.c
new file mode 100644
index 000000000..5a5e1d0a9
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/test_hook_open_arm.c
@@ -0,0 +1,46 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+void open_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    zpointer t = (void *)0x1234;
+    // STACK_SET(callstack ,"key_x", t, void *);
+    // STACK_SET(callstack ,"key_y", t, zpointer);
+    // NSLog(@"hookzz OC-Method: -[UIViewController %s]",
+    // (zpointer)(rs->general.regs.x1));
+}
+
+void open_post_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    // zpointer x = STACK_GET(callstack, "key_x", void *);
+    // zpointer y = STACK_GET(callstack, "key_y", zpointer);
+    // NSLog(@"function over, and get 'key_x' is: %p", x);
+    // NSLog(@"function over, and get 'key_y' is: %p", y);
+}
+
+__attribute__((constructor)) void test_hook_printf() {
+    void *open_ptr = (void *)open;
+
+    ZzEnableDebugMode();
+    ZzHookPrePost((void *)open_ptr, open_pre_call, open_post_call);
+
+    open("/home/zz", O_RDONLY);
+}
+
+int main(int args, char **argv) {}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/test_hook_printf.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/test_hook_printf.c
new file mode 100644
index 000000000..70d7fdf66
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-android/test_hook_printf.c
@@ -0,0 +1,64 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+int (*orig_printf)(const char *format, ...);
+int fake_printf(const char *format, ...) {
+    puts("call printf");
+
+    char *stack[16];
+    va_list args;
+    va_start(args, format);
+    // *(void **)&args for android
+    memcpy(stack, *(void **)&args, sizeof(char *) * 16);
+    va_end(args);
+
+    // how to hook variadic function? fake a original copy stack.
+    // [move to
+    // detail-1](http://jmpews.github.io/2017/08/29/pwn/%E7%9F%AD%E5%87%BD%E6%95%B0%E5%92%8C%E4%B8%8D%E5%AE%9A%E5%8F%82%E6%95%B0%E7%9A%84hook/)
+    // [move to detail-2](https://github.com/jmpews/HookZzModules/tree/master/AntiDebugBypass)
+    int x = orig_printf(format, stack[0], stack[1], stack[2], stack[3], stack[4], stack[5], stack[6], stack[7],
+                        stack[8], stack[9], stack[10], stack[11], stack[12], stack[13], stack[14], stack[15]);
+    return x;
+}
+
+void printf_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    puts((char *)rs->general.regs.r0);
+    STACK_SET(callstack, "format", rs->general.regs.r0, char *);
+    puts("printf-pre-call");
+}
+
+void printf_post_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    if (STACK_CHECK_KEY(callstack, "format")) {
+        char *format = STACK_GET(callstack, "format", char *);
+        puts(format);
+    }
+    puts("printf-post-call");
+}
+
+__attribute__((constructor)) void test_hook_printf() {
+    void *printf_ptr = (void *)printf;
+
+    ZzEnableDebugMode();
+    ZzHook((void *)printf_ptr, (void *)fake_printf, (void **)&orig_printf, printf_pre_call, printf_post_call, FALSE);
+    printf("HookZzzzzzz, %d, %p, %d, %d, %d, %d, %d, %d, %d\n", 1, (void *)2, 3, (char)4, (char)5, (char)6, 7, 8, 9);
+}
+
+int main(int args, char **argv) {}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-insn-fix/makefile b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-insn-fix/makefile
new file mode 100644
index 000000000..a4a8203f0
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-insn-fix/makefile
@@ -0,0 +1,19 @@
+NO_COLOR=\x1b[0m
+OK_COLOR=\x1b[32;01m
+ERROR_COLOR=\x1b[31;01m
+WARN_COLOR=\x1b[33;01m
+
+
+HOOKZZ_INCLUDE_DIR := -I$(abspath ../../include) -I$(abspath ../../src)
+HOOKZZ_LIB_DIR := -L$(abspath ../../build/ios-armv7)
+
+ZZ_GCC_TEST := $(shell xcrun --sdk iphoneos --find clang) -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -arch armv7 -O0 -g
+
+# -undefined dynamic_lookup
+test: 
+	@$(ZZ_GCC_TEST) $(HOOKZZ_INCLUDE_DIR) -c test_insn_fix.c -o test_insn_fix.o
+	@$(ZZ_GCC_TEST) -dynamiclib $(HOOKZZ_LIB_DIR) -lhookzz.static test_insn_fix.o -o test_insn_fix.dylib
+	@echo "$(OK_COLOR)build [test_insn_fix.dylib] success for armv7! $(NO_COLOR)"
+clean:
+	rm -rf test_insn_fix.o
+	rm -rf test_insn_fix.dylib
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-insn-fix/test_insn_fix.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-insn-fix/test_insn_fix.c
new file mode 100644
index 000000000..26069eb60
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-insn-fix/test_insn_fix.c
@@ -0,0 +1,106 @@
+#include "hookzz.h"
+#include <stdio.h>
+#include <unistd.h>
+
+static void thumb_insn_need_fix() {
+    __asm__ volatile(".code 16\n"
+
+                     "add r0, pc\n"
+
+                     "ldr r0, [pc, #8]\n"
+                     "ldr.W r0, [pc, #8]\n"
+
+                     "adr r0, #8\n"
+                     "adr.W r0, #8\n"
+                     "adr.W r0, #-8\n"
+
+                     "beq #8\n"
+                     "b #8\n"
+                     "beq.W #8\n"
+                     "b.W #8\n"
+
+                     "bl #8\n"
+                     "blx #8\n"
+                     "nop");
+}
+
+#include "platforms/backend-arm64/interceptor-arm64.h"
+#include <stdlib.h>
+
+#if 1
+__attribute__((constructor)) void test_insn_fix_thumb() {
+
+    ZzInterceptorBackend *backend = (ZzInterceptorBackend *)malloc(sizeof(ZzInterceptorBackend));
+    zbyte temp_code_slice_data[256] = {0};
+
+    zz_arm_writer_init(&backend->arm_writer, NULL);
+    zz_arm_relocator_init(&backend->arm_relocator, NULL, &backend->arm_writer);
+    zz_thumb_writer_init(&backend->thumb_writer, NULL);
+    zz_thumb_relocator_init(&backend->thumb_relocator, NULL, &backend->thumb_writer);
+
+    ZzThumbRelocator *thumb_relocator;
+    ZzThumbWriter *thumb_writer;
+    thumb_relocator = &backend->thumb_relocator;
+    thumb_writer = &backend->thumb_writer;
+
+    zz_thumb_writer_reset(thumb_writer, temp_code_slice_data);
+
+    zz_thumb_relocator_reset(thumb_relocator, (zpointer)((zaddr)thumb_insn_need_fix & ~(zaddr)1), thumb_writer);
+    zsize tmp_relocator_insn_size = 0;
+
+    do {
+        zz_thumb_relocator_read_one(thumb_relocator, NULL);
+        zz_thumb_relocator_write_one(thumb_relocator);
+        tmp_relocator_insn_size = thumb_relocator->input_cur - thumb_relocator->input_start;
+    } while (tmp_relocator_insn_size < 36);
+}
+#endif
+
+#if 0
+__attribute__((__naked__)) void arm_insn_need_fix() {
+    __asm__ volatile(".arm\n"
+                     "add r0, pc, r0\n"
+
+                     "ldr r0, [pc, #8]\n"
+
+                     "adr r0, #8\n"
+                     "adr r0, #-8\n"
+
+                     "beq #8\n"
+                     "b #8\n"
+
+                     "bl #8\n"
+                     "blx #8\n"
+                     "nop");
+}
+
+#include "platforms/backend-arm/interceptor-arm.h"
+#include <stdlib.h>
+
+__attribute__((constructor)) void test_insn_fix_arm() {
+
+    ZzInterceptorBackend *backend = (ZzInterceptorBackend *)malloc(sizeof(ZzInterceptorBackend));
+    zbyte temp_code_slice_data[256] = {0};
+
+    zz_arm_writer_init(&backend->arm_writer, NULL);
+    zz_arm_relocator_init(&backend->arm_relocator, NULL, &backend->arm_writer);
+    zz_thumb_writer_init(&backend->thumb_writer, NULL);
+    zz_thumb_relocator_init(&backend->thumb_relocator, NULL, &backend->thumb_writer);
+
+    ZzArmRelocator *arm_relocator;
+    ZzArmWriter *arm_writer;
+    arm_relocator = &backend->arm_relocator;
+    arm_writer = &backend->arm_writer;
+
+    zz_arm_writer_reset(arm_writer, temp_code_slice_data);
+
+    zz_arm_relocator_reset(arm_relocator, (zpointer)((zaddr)arm_insn_need_fix & ~(zaddr)1), arm_writer);
+    zsize tmp_relocator_insn_size = 0;
+
+    do {
+        zz_arm_relocator_read_one(arm_relocator, NULL);
+        zz_arm_relocator_write_one(arm_relocator);
+        tmp_relocator_insn_size = arm_relocator->input_cur - arm_relocator->input_start;
+    } while (tmp_relocator_insn_size < 36);
+}
+#endif
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/makefile b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/makefile
new file mode 100644
index 000000000..ed1ac5305
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/makefile
@@ -0,0 +1,41 @@
+NO_COLOR=\x1b[0m
+OK_COLOR=\x1b[32;01m
+ERROR_COLOR=\x1b[31;01m
+WARN_COLOR=\x1b[33;01m
+
+
+HOOKZZ_INCLUDE_DIR := $(abspath ../../include)
+HOOKZZ_LIB_DIR := $(abspath ../../build/ios-armv7)
+
+ZZ_GCC_TEST := $(shell xcrun --sdk iphoneos --find clang) -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -arch armv7 -O0 -g
+
+# -undefined dynamic_lookup
+test: 
+	@$(ZZ_GCC_TEST) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_oc_thumb.m -o test_hook_oc_thumb.o
+	@$(ZZ_GCC_TEST) -dynamiclib  -Wl,-U,_func -framework Foundation -L$(HOOKZZ_LIB_DIR) -lhookzz.static test_hook_oc_thumb.o -arch armv7 -o $(HOOKZZ_LIB_DIR)/test_hook_oc_thumb.dylib
+	@echo "$(OK_COLOR)build [test_hook_oc_thumb.dylib] success for armv7-ios! $(NO_COLOR)"
+
+	@$(ZZ_GCC_TEST) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_open_arm.c -o test_hook_open_arm.o
+	@$(ZZ_GCC_TEST) -dynamiclib -Wl,-U,_func -framework Foundation -L$(HOOKZZ_LIB_DIR) -lhookzz.static test_hook_open_arm.o -o $(HOOKZZ_LIB_DIR)/test_hook_open_arm.dylib
+	@echo "$(OK_COLOR)build [test_hook_open_arm.dylib] success for armv7-ios! $(NO_COLOR)"
+
+	@$(ZZ_GCC_TEST) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_address_thumb.c -o test_hook_address_thumb.o
+	@$(ZZ_GCC_TEST) -dynamiclib -Wl,-U,_func -framework Foundation -L$(HOOKZZ_LIB_DIR) -lhookzz.static test_hook_address_thumb.o -o $(HOOKZZ_LIB_DIR)/test_hook_address_thumb.dylib
+	@echo "$(OK_COLOR)build [test_hook_address_thumb.dylib] success for armv7-ios! $(NO_COLOR)"
+
+	@$(ZZ_GCC_TEST) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_printf.c -o test_hook_printf.o
+	@$(ZZ_GCC_TEST) -dynamiclib -Wl,-U,_func -framework Foundation -L$(HOOKZZ_LIB_DIR) -lhookzz.static test_hook_printf.o -o $(HOOKZZ_LIB_DIR)/test_hook_printf.dylib
+	@echo "$(OK_COLOR)build [test_hook_printf.dylib] success for armv7-ios! $(NO_COLOR)"
+
+
+	@$(ZZ_GCC_TEST) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_freeaddr.c -o test_hook_freeaddr.o
+	@$(ZZ_GCC_TEST) -dynamiclib -Wl,-U,_func -framework Foundation -L$(HOOKZZ_LIB_DIR) -lhookzz.static test_hook_freeaddr.o -o $(HOOKZZ_LIB_DIR)/test_hook_freeaddr.dylib
+	@echo "$(OK_COLOR)build [test_hook_freeaddr.dylib] success for armv7-ios! $(NO_COLOR)"
+
+
+	@echo "$(OK_COLOR)build [test] success for armv7-ios-hookzz! $(NO_COLOR)"
+
+clean:
+	@rm -rf $(shell find ./ -name "*\.o" | xargs echo)
+	@rm -rf $(shell find $(HOOKZZ_LIB_DIR) -name "test_*" | xargs echo)
+	@echo "$(OK_COLOR)clean all *.o success!$(NO_COLOR)"
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_address_thumb.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_address_thumb.c
new file mode 100644
index 000000000..790e33768
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_address_thumb.c
@@ -0,0 +1,79 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#include <stdio.h>
+#include <unistd.h>
+
+static void hack_this_function() {
+#ifdef __arm__
+    __asm__ volatile(".code 16\n"
+                     "mov r0, #0\n"
+                     "mov r12, #20\n"
+                     "svc #0x80\n"
+                     "nop\n"
+                     "nop\n"
+                     "nop\n"
+                     "nop\n"
+                     "nop\n"
+                     "nop");
+#endif
+}
+
+static void sorry_to_exit() {
+#ifdef __arm__
+    __asm__ volatile(".code 16\n"
+                     "mov r0, #0\n"
+                     "mov r12, #1\n"
+                     "svc #0x80");
+#endif
+}
+
+void getpid_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    unsigned long request = *(unsigned long *)(&rs->general.regs.r12);
+    printf("request(r12) is: %ld\n", request);
+    printf("r0 is: %ld\n", (long)rs->general.regs.r0);
+}
+
+void getpid_half_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    pid_t r0 = (pid_t)(rs->general.regs.r0);
+    printf("getpid() return at r0 is: %d\n", r0);
+}
+
+__attribute__((constructor)) void test_hook_address() {
+    void *hack_this_function_ptr = (void *)hack_this_function;
+    // hook address with only `pre_call`
+    // ZzBuildHookAddress(hack_this_function_ptr + 8, hack_this_function_ptr + 12, (void
+    // *)getpid_pre_call, NULL);
+
+    // hook address with only `half_call`
+    // ZzBuildHookAddress(hack_this_function_ptr + 8, hack_this_function_ptr + 12, NULL, (void
+    // *)getpid_half_call);
+
+    // hook address with both `half_call` and `pre_call`
+    ZzBuildHookAddress(hack_this_function_ptr + 8, hack_this_function_ptr + 10, getpid_pre_call, getpid_half_call,
+                       FALSE);
+    ZzEnableHook((void *)hack_this_function_ptr + 8);
+
+    void *sorry_to_exit_ptr = (void *)sorry_to_exit;
+    unsigned long nop_bytes = 0x46c0;
+    ZzRuntimeCodePatch((unsigned long)sorry_to_exit_ptr + 8, (zpointer)&nop_bytes, 2);
+
+    hack_this_function();
+    sorry_to_exit();
+
+    printf("hack success -.0\n");
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_freeaddr.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_freeaddr.c
new file mode 100644
index 000000000..e9a16ea1a
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_freeaddr.c
@@ -0,0 +1,70 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+void (*orig_freeaddrinfo)(struct addrinfo *ai);
+void fake_freeaddrinfo(struct addrinfo *ai) { orig_freeaddrinfo(ai); }
+
+__attribute__((constructor)) void test_hook_freeaddrinfo() {
+    ZzEnableDebugMode();
+    ZzHook((void *)freeaddrinfo, (void *)fake_freeaddrinfo, &orig_freeaddrinfo, NULL, NULL, FALSE);
+
+    int sockfd;
+    struct addrinfo hints, *servinfo, *p;
+    int rv;
+
+    memset(&hints, 0, sizeof hints);
+    hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_PASSIVE; // use my IP address
+
+    if ((rv = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
+        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+        exit(1);
+    }
+
+    // loop through all the results and bind to the first we can
+    for (p = servinfo; p != NULL; p = p->ai_next) {
+        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
+            perror("socket");
+            continue;
+        }
+
+        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
+            close(sockfd);
+            perror("bind");
+            continue;
+        }
+
+        break; // if we get here, we must have connected successfully
+    }
+
+    if (p == NULL) {
+        // looped off the end of the list with no successful bind
+        fprintf(stderr, "failed to bind socket\n");
+        exit(2);
+    }
+
+    freeaddrinfo(servinfo); // all done with this structure
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_oc_thumb.m b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_oc_thumb.m
new file mode 100644
index 000000000..7710fcb0a
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_oc_thumb.m
@@ -0,0 +1,82 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#import <Foundation/Foundation.h>
+#import <dlfcn.h>
+#import <mach-o/dyld.h>
+#import <objc/runtime.h>
+
+@interface HookZz : NSObject
+
+@end
+
+@implementation HookZz
+
++ (void)load {
+    [self zzMethodSwizzlingHook];
+}
+
+void objcMethod_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    zpointer t = (void *)0x1234;
+    // STACK_SET(callstack ,"key_x", t, void *);
+    // STACK_SET(callstack ,"key_y", t, zpointer);
+    // NSLog(@"hookzz OC-Method: -[UIViewController %s]",
+    // (zpointer)(rs->general.regs.x1));
+}
+
+void objcMethod_post_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    // zpointer x = STACK_GET(callstack, "key_x", void *);
+    // zpointer y = STACK_GET(callstack, "key_y", zpointer);
+    // NSLog(@"function over, and get 'key_x' is: %p", x);
+    // NSLog(@"function over, and get 'key_y' is: %p", y);
+}
+
++ (void)zzMethodSwizzlingHook {
+    Class hookClass = objc_getClass("UIViewController");
+    SEL oriSEL = @selector(viewWillAppear:);
+    Method oriMethod = class_getInstanceMethod(hookClass, oriSEL);
+    IMP oriImp = method_getImplementation(oriMethod);
+
+    ZzEnableDebugMode();
+    ZzHookPrePost((void *)oriImp, objcMethod_pre_call, objcMethod_post_call);
+}
+
+@end
+
+/*
+(lldb) disass -n "-[UIViewController viewWillAppear:]" -c 3
+UIKit`-[UIViewController viewWillAppear:]:
+    0x18881c10c <+0>: adrp   x8, 126868
+    0x18881c110 <+4>: ldrsw  x8, [x8, #0x280]
+    0x18881c114 <+8>: ldr    x9, [x0, x8]
+
+(lldb) c
+Process 41637 resuming
+(lldb) c
+Process 41637 resuming
+(lldb) c
+Process 41637 resuming
+2017-08-30 02:01:58.954875+0800 T007[41637:10198806] hookzz OC-Method:
+-[UIViewController viewWillAppear:] 2017-08-30 02:01:58.956558+0800
+T007[41637:10198806] function over, and get 'key_x' is: 0x1234 2017-08-30
+02:01:58.956654+0800 T007[41637:10198806] function over, and get 'key_y' is:
+0x1234 (lldb) disass -n "-[UIViewController viewWillAppear:]" -c 3
+UIKit`-[UIViewController viewWillAppear:]:
+    0x18881c10c <+0>: b      0x1810b0b4c
+    0x18881c110 <+4>: ldrsw  x8, [x8, #0x280]
+    0x18881c114 <+8>: ldr    x9, [x0, x8]
+*/
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_open_arm.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_open_arm.c
new file mode 100644
index 000000000..5e23e1531
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_open_arm.c
@@ -0,0 +1,37 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+void open_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    char *path = (char *)rs->general.regs.r0;
+    printf("open file: %s\n", path);
+}
+
+void open_post_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {}
+
+__attribute__((constructor)) void test_hook_printf() {
+    void *open_ptr = (void *)open;
+
+    ZzEnableDebugMode();
+    // ZzHookPrePost((void *)open_ptr, open_pre_call, open_post_call);
+    ZzHook((void *)open_ptr, NULL, NULL, open_pre_call, open_post_call, TRUE);
+
+    open("/home/zz", O_RDONLY);
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_printf.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_printf.c
new file mode 100644
index 000000000..e494b9e96
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm-ios/test_hook_printf.c
@@ -0,0 +1,62 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+int (*orig_printf)(const char *restrict format, ...);
+int fake_printf(const char *restrict format, ...) {
+    puts("call printf");
+
+    char *stack[16];
+    va_list args;
+    va_start(args, format);
+    // *(void **)&args for android
+    memcpy(stack, *(void **)&args, sizeof(char *) * 16);
+    va_end(args);
+
+    // how to hook variadic function? fake a original copy stack.
+    // [move to
+    // detail-1](http://jmpews.github.io/2017/08/29/pwn/%E7%9F%AD%E5%87%BD%E6%95%B0%E5%92%8C%E4%B8%8D%E5%AE%9A%E5%8F%82%E6%95%B0%E7%9A%84hook/)
+    // [move to detail-2](https://github.com/jmpews/HookZzModules/tree/master/AntiDebugBypass)
+    int x = orig_printf(format, stack[0], stack[1], stack[2], stack[3], stack[4], stack[5], stack[6], stack[7],
+                        stack[8], stack[9], stack[10], stack[11], stack[12], stack[13], stack[14], stack[15]);
+    return x;
+}
+
+void printf_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    puts((char *)rs->general.regs.r0);
+    STACK_SET(callstack, "format", rs->general.regs.r0, char *);
+    puts("printf-pre-call");
+}
+
+void printf_post_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    if (STACK_CHECK_KEY(callstack, "format")) {
+        char *format = STACK_GET(callstack, "format", char *);
+        puts(format);
+    }
+    puts("printf-post-call");
+}
+
+__attribute__((constructor)) void test_hook_printf() {
+    void *printf_ptr = (void *)printf;
+
+    ZzEnableDebugMode();
+    ZzHook((void *)printf_ptr, (void *)fake_printf, (void **)&orig_printf, printf_pre_call, printf_post_call, TRUE);
+    printf("HookZzzzzzz, %d, %p, %d, %d, %d, %d, %d, %d, %d\n", 1, (void *)2, 3, (char)4, (char)5, (char)6, 7, 8, 9);
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-insn-fix/makefile b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-insn-fix/makefile
new file mode 100644
index 000000000..5974da54e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-insn-fix/makefile
@@ -0,0 +1,19 @@
+NO_COLOR=\x1b[0m
+OK_COLOR=\x1b[32;01m
+ERROR_COLOR=\x1b[31;01m
+WARN_COLOR=\x1b[33;01m
+
+
+HOOKZZ_INCLUDE_DIR := -I$(abspath ../../include) -I$(abspath ../../src)
+HOOKZZ_LIB_DIR := -L$(abspath ../../build/ios-arm64)
+
+ZZ_GCC_TEST := $(shell xcrun --sdk iphoneos --find clang) -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -arch arm64 -O0 -g
+
+# -undefined dynamic_lookup
+test: 
+	@$(ZZ_GCC_TEST) $(HOOKZZ_INCLUDE_DIR) -c test_insn_fix.c -o test_insn_fix.o
+	@$(ZZ_GCC_TEST) -dynamiclib $(HOOKZZ_LIB_DIR) -lhookzz.static test_insn_fix.o -o test_insn_fix.dylib
+	@echo "$(OK_COLOR)build [test_insn_fix.dylib] success for arm64! $(NO_COLOR)"
+clean:
+	rm -rf test_insn_fix.o
+	rm -rf test_insn_fix.dylib
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-insn-fix/test_insn_fix.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-insn-fix/test_insn_fix.c
new file mode 100644
index 000000000..e2e793d9e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-insn-fix/test_insn_fix.c
@@ -0,0 +1,40 @@
+#include "hookzz.h"
+#include <stdio.h>
+#include <unistd.h>
+
+static void arm64_insn_need_fix() {
+    __asm__ volatile("bl #40\n"
+                     "nop");
+}
+
+#include "platforms/backend-arm64/interceptor-arm64.h"
+#include <stdlib.h>
+
+#if 1
+__attribute__((constructor)) void test_insn_fix_arm64() {
+
+    ZzInterceptorBackend *backend = (ZzInterceptorBackend *)malloc(sizeof(ZzInterceptorBackend));
+    zbyte temp_code_slice_data[256] = {0};
+
+    zz_arm64_writer_init(&backend->arm64_writer, NULL);
+    zz_arm64_relocator_init(&backend->arm64_relocator, NULL, &backend->arm64_writer);
+    zz_arm64_writer_init(&backend->arm64_writer, NULL);
+    zz_arm64_relocator_init(&backend->arm64_relocator, NULL, &backend->arm64_writer);
+
+    ZzArm64Relocator *arm64_relocator;
+    ZzArm64Writer *arm64_writer;
+    arm64_relocator = &backend->arm64_relocator;
+    arm64_writer = &backend->arm64_writer;
+
+    zz_arm64_writer_reset(arm64_writer, temp_code_slice_data);
+
+    zz_arm64_relocator_reset(arm64_relocator, (zpointer)((zaddr)arm64_insn_need_fix & ~(zaddr)1), arm64_writer);
+    zsize tmp_relocator_insn_size = 0;
+
+    do {
+        zz_arm64_relocator_read_one(arm64_relocator, NULL);
+        zz_arm64_relocator_write_one(arm64_relocator);
+        tmp_relocator_insn_size = arm64_relocator->input_cur - arm64_relocator->input_start;
+    } while (tmp_relocator_insn_size < 36);
+}
+#endif
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/makefile b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/makefile
new file mode 100644
index 000000000..3447f8127
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/makefile
@@ -0,0 +1,31 @@
+NO_COLOR=\x1b[0m
+OK_COLOR=\x1b[32;01m
+ERROR_COLOR=\x1b[31;01m
+WARN_COLOR=\x1b[33;01m
+
+
+HOOKZZ_INCLUDE_DIR := $(abspath ../../include)
+HOOKZZ_LIB_DIR := $(abspath ../../build/ios-arm64)
+
+ZZ_GCC_TEST := $(shell xcrun --sdk iphoneos --find clang) -isysroot $(shell xcrun --sdk iphoneos --show-sdk-path) -arch arm64 -O0 -g
+
+# -undefined dynamic_lookup
+test: 
+	@$(ZZ_GCC_TEST) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_oc.m -o test_hook_oc.o
+	@$(ZZ_GCC_TEST) -dynamiclib  -Wl,-U,_func -framework Foundation -L$(HOOKZZ_LIB_DIR) -lhookzz.static test_hook_oc.o -o $(HOOKZZ_LIB_DIR)/test_hook_oc.dylib
+	@echo "$(OK_COLOR)build [test_hook_oc.dylib] success for arm64-ios! $(NO_COLOR)"
+
+	@$(ZZ_GCC_TEST) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_address.c -o test_hook_address.o
+	@$(ZZ_GCC_TEST) -dynamiclib -Wl,-U,_func -framework Foundation -L$(HOOKZZ_LIB_DIR) -lhookzz.static test_hook_address.o -o $(HOOKZZ_LIB_DIR)/test_hook_address.dylib
+	@echo "$(OK_COLOR)build [test_hook_address.dylib] success for arm64-ios! $(NO_COLOR)"
+
+	@$(ZZ_GCC_TEST) -I$(HOOKZZ_INCLUDE_DIR) -c test_hook_printf.c -o test_hook_printf.o
+	@$(ZZ_GCC_TEST) -dynamiclib -Wl,-U,_func -framework Foundation -L$(HOOKZZ_LIB_DIR) -lhookzz.static test_hook_printf.o -o $(HOOKZZ_LIB_DIR)/test_hook_printf.dylib
+	@echo "$(OK_COLOR)build [test_hook_printf.dylib] success for arm64-ios! $(NO_COLOR)"
+
+	@echo "$(OK_COLOR)build [test] success for arm64-ios-hookzz! $(NO_COLOR)"
+
+clean:
+	@rm -rf $(shell find ./ -name "*\.o" | xargs echo)
+	@rm -rf $(shell find $(HOOKZZ_LIB_DIR) -name "test_*" | xargs echo)
+	@echo "$(OK_COLOR)clean all *.o success!$(NO_COLOR)"
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/test_hook_address.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/test_hook_address.c
new file mode 100644
index 000000000..d1ef161a6
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/test_hook_address.c
@@ -0,0 +1,111 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#include <stdio.h>
+#include <unistd.h>
+
+static void hack_this_function() {
+#ifdef __arm64__
+    __asm__("mov X0, #0\n"
+            "mov w16, #20\n"
+            "svc #0x80\n"
+            "nop\n"
+            "nop\n"
+            "nop\n"
+            "nop");
+#endif
+}
+
+static void sorry_to_exit() {
+#ifdef __arm64__
+    __asm__("mov X0, #0\n"
+            "mov w16, #1\n"
+            "svc #0x80");
+#endif
+}
+
+void getpid_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    unsigned long request = *(unsigned long *)(&rs->general.regs.x16);
+    printf("request(x16) is: %ld\n", request);
+    printf("x0 is: %ld\n", (long)rs->general.regs.x0);
+}
+
+void getpid_half_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    pid_t x0 = (pid_t)(rs->general.regs.x0);
+    printf("getpid() return at x0 is: %d\n", x0);
+}
+
+__attribute__((constructor)) void test_hook_address() {
+    void *hack_this_function_ptr = (void *)hack_this_function;
+    // hook address with only `pre_call`
+    // ZzBuildHookAddress(hack_this_function_ptr + 8, hack_this_function_ptr + 12, (void
+    // *)getpid_pre_call, NULL);
+
+    // hook address with only `half_call`
+    // ZzBuildHookAddress(hack_this_function_ptr + 8, hack_this_function_ptr + 12, NULL, (void
+    // *)getpid_half_call);
+
+    // hook address with both `half_call` and `pre_call`
+    ZzBuildHookAddress(hack_this_function_ptr + 8, hack_this_function_ptr + 12, getpid_pre_call, getpid_half_call,
+                       TRUE);
+    ZzEnableHook((void *)hack_this_function_ptr + 8);
+
+    void *sorry_to_exit_ptr = (void *)sorry_to_exit;
+    unsigned long nop_bytes = 0xD503201F;
+    ZzRuntimeCodePatch((unsigned long)sorry_to_exit_ptr + 8, (zpointer)&nop_bytes, 4);
+
+    hack_this_function();
+    sorry_to_exit();
+
+    printf("hack success -.0\n");
+}
+
+/*
+(lldb) disass -n hack_this_function
+test_hook_address.dylib`hack_this_function:
+    0x1000b0280 <+0>:  mov    x0, #0x0
+    0x1000b0284 <+4>:  mov    w16, #0x14
+    0x1000b0288 <+8>:  svc    #0x80
+    0x1000b028c <+12>: ret
+
+(lldb) disass -n sorry_to_exit
+test_hook_address.dylib`sorry_to_exit:
+    0x1000b0290 <+0>:  mov    x0, #0x0
+    0x1000b0294 <+4>:  mov    w16, #0x1
+    0x1000b0298 <+8>:  svc    #0x80
+    0x1000b029c <+12>: ret
+
+(lldb) c
+Process 41414 resuming
+request(x16) is: 20
+x0 is: 0
+getpid() return at x0 is: 41414
+hack success -.0
+(lldb) disass -n hack_this_function
+test_hook_address.dylib`hack_this_function:
+    0x1000b0280 <+0>:  mov    x0, #0x0
+    0x1000b0284 <+4>:  mov    w16, #0x14
+    0x1000b0288 <+8>:  b      0x1001202cc
+    0x1000b028c <+12>: ret
+
+(lldb) disass -n sorry_to_exit
+test_hook_address.dylib`sorry_to_exit:
+    0x1000b0290 <+0>:  mov    x0, #0x0
+    0x1000b0294 <+4>:  mov    w16, #0x1
+    0x1000b0298 <+8>:  nop
+    0x1000b029c <+12>: ret
+*/
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/test_hook_oc.m b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/test_hook_oc.m
new file mode 100644
index 000000000..9159a57f1
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/test_hook_oc.m
@@ -0,0 +1,56 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#import <Foundation/Foundation.h>
+#import <dlfcn.h>
+#import <mach-o/dyld.h>
+#import <objc/runtime.h>
+
+@interface HookZz : NSObject
+
+@end
+
+@implementation HookZz
+
++ (void)load {
+    [self zzMethodSwizzlingHook];
+}
+
+void objcMethod_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    zpointer t = 0x1234;
+    STACK_SET(callstack, "key_x", t, void *);
+    STACK_SET(callstack, "key_y", t, zpointer);
+    NSLog(@"hookzz OC-Method: -[UIViewController %s]", (zpointer)(rs->general.regs.x1));
+}
+
+void objcMethod_post_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    zpointer x = STACK_GET(callstack, "key_x", void *);
+    zpointer y = STACK_GET(callstack, "key_y", zpointer);
+    NSLog(@"function over, and get 'key_x' is: %p", x);
+    NSLog(@"function over, and get 'key_y' is: %p", y);
+}
+
++ (void)zzMethodSwizzlingHook {
+    Class hookClass = objc_getClass("UIViewController");
+    SEL oriSEL = @selector(viewWillAppear:);
+    Method oriMethod = class_getInstanceMethod(hookClass, oriSEL);
+    IMP oriImp = method_getImplementation(oriMethod);
+
+    ZzHookPrePost((void *)oriImp, objcMethod_pre_call, objcMethod_post_call);
+}
+
+@end
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/test_hook_printf.c b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/test_hook_printf.c
new file mode 100644
index 000000000..f7111f8f3
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tests/arm64-ios/test_hook_printf.c
@@ -0,0 +1,64 @@
+/**
+ *    Copyright 2017 jmpews
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "hookzz.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+int (*orig_printf)(const char *restrict format, ...);
+int fake_printf(const char *restrict format, ...) {
+    puts("call printf");
+
+    char *stack[16];
+    va_list args;
+    va_start(args, format);
+    // *(void **)&args for android
+    memcpy(stack, *(void **)&args, sizeof(char *) * 16);
+    va_end(args);
+
+    // how to hook variadic function? fake a original copy stack.
+    // [move to
+    // detail-1](http://jmpews.github.io/2017/08/29/pwn/%E7%9F%AD%E5%87%BD%E6%95%B0%E5%92%8C%E4%B8%8D%E5%AE%9A%E5%8F%82%E6%95%B0%E7%9A%84hook/)
+    // [move to detail-2](https://github.com/jmpews/HookZzModules/tree/master/AntiDebugBypass)
+    int x = orig_printf(format, stack[0], stack[1], stack[2], stack[3], stack[4], stack[5], stack[6], stack[7],
+                        stack[8], stack[9], stack[10], stack[11], stack[12], stack[13], stack[14], stack[15]);
+    return x;
+}
+
+void printf_pre_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    puts((char *)rs->general.regs.x0);
+    STACK_SET(callstack, "format", rs->general.regs.x0, char *);
+    puts("printf-pre-call");
+}
+
+void printf_post_call(RegState *rs, ThreadStack *threadstack, CallStack *callstack) {
+    if (STACK_CHECK_KEY(callstack, "format")) {
+        char *format = STACK_GET(callstack, "format", char *);
+        puts(format);
+    }
+    puts("printf-post-call");
+}
+
+__attribute__((constructor)) void test_hook_printf() {
+    void *printf_ptr = (void *)printf;
+
+    ZzEnableDebugMode();
+    ZzHook((void *)printf_ptr, (void *)fake_printf, (void **)&orig_printf, printf_pre_call, printf_post_call, TRUE);
+    printf("HookZzzzzzz, %d, %p, %d, %d, %d, %d, %d, %d, %d\n", 1, (void *)2, 3, (char)4, (char)5, (char)6, 7, 8, 9);
+}
+
+// int main(int args, char **argv) {}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tools/ZzSolidifyHook/solidifyhook b/VirtualApp/lib/src/main/jni/HookZz/tools/ZzSolidifyHook/solidifyhook
new file mode 100755
index 000000000..f7bc7eb0e
Binary files /dev/null and b/VirtualApp/lib/src/main/jni/HookZz/tools/ZzSolidifyHook/solidifyhook differ
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tools/ZzSolidifyHook/solidifyhook.cpp b/VirtualApp/lib/src/main/jni/HookZz/tools/ZzSolidifyHook/solidifyhook.cpp
new file mode 100644
index 000000000..623ed169b
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tools/ZzSolidifyHook/solidifyhook.cpp
@@ -0,0 +1,639 @@
+/*
+ clang++ -L/Users/jmpews/Desktop/SpiderZz/project/HookZz/tools/deps/MachoParser/build \
+ -lmachoparser -o solidifyhook solidifyhook.cpp
+ */
+
+#include <iostream>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <mach-o/loader.h>
+
+#ifndef zz_h
+#define zz_h
+
+// Created by jmpews on 2017/5/3.
+//
+#define PROGRAM_NAME "zz"
+#define PROGRAM_VER "1.0.0"
+#define PROGRAM_AUTHOR "jmpews@gmail.com"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+// --- custom type ---
+
+// 1. zpointer and zaddr is different
+
+#define DEBUG_MODE 0
+
+#ifndef zz_type
+#define zz_type
+
+typedef void *zpointer;
+typedef unsigned long zsize;
+typedef unsigned long zaddr;
+typedef uint32_t zuint32;
+typedef uint16_t zuint16;
+typedef uint8_t zuint8;
+typedef int32_t zint32;
+typedef int16_t zint16;
+typedef int8_t zint8;
+typedef unsigned long zuint;
+typedef long zint;
+typedef unsigned char zbyte;
+typedef bool zbool;
+
+#endif
+
+#if defined(FALSE)
+#else
+#define FALSE 0
+#define TRUE 1
+#endif
+
+// --- log configuration ---
+
+#define GLOBAL_DEBUG 0
+#define GLOBAL_INFO 1
+#define SYSLOG 0
+#define COLOR_LOG 0
+
+#if (COLOR_LOG)
+#define RED "\x1B[31m"
+#define GRN "\x1B[32m"
+#define YEL "\x1B[33m"
+#define BLU "\x1B[34m"
+#define MAG "\x1B[35m"
+#define CYN "\x1B[36m"
+#define WHT "\x1B[37m"
+#define RESET "\x1B[0m"
+#else
+#define RED ""
+#define GRN ""
+#define YEL ""
+#define BLU ""
+#define MAG ""
+#define CYN ""
+#define WHT ""
+#define RESET ""
+#endif
+
+#include <stdio.h>
+
+// Important!!!
+// STDERR before STDOUT, because sync
+
+#if (SYSLOG)
+#include <sys/syslog.h>
+#define Xinfo(fmt, ...)                                                                                                \
+    do {                                                                                                               \
+        if (GLOBAL_INFO)                                                                                               \
+            syslog(LOG_WARNING, RESET fmt, __VA_ARGS__);                                                               \
+    } while (0)
+#define Sinfo(MSG) Xinfo("%s", MSG)
+#define Xdebug(fmt, ...)                                                                                               \
+    do {                                                                                                               \
+        if (GLOBAL_DEBUG)                                                                                              \
+            syslog(LOG_WARNING, RESET fmt, __VA_ARGS__);                                                               \
+    } while (0)
+#define Sdebug(MSG) Xdebug("%s", MSG)
+#define Xerror(fmt, ...)                                                                                               \
+    do {                                                                                                               \
+        syslog(LOG_DEBUG,                                                                                              \
+               RED "[!] "                                                                                              \
+                   "%s:%d:%s(): " fmt RESET "\n",                                                                      \
+               __FILE__, __LINE__, __func__, __VA_ARGS__);                                                             \
+    } while (0)
+
+#define Serror(MSG) Xerror("%s", MSG)
+#else
+#define Xinfo(fmt, ...)                                                                                                \
+    do {                                                                                                               \
+        if (GLOBAL_INFO)                                                                                               \
+            fprintf(stdout, RESET fmt "\n", __VA_ARGS__);                                                              \
+    } while (0)
+#define Sinfo(MSG) Xinfo("%s", MSG)
+
+#define Xdebug(fmt, ...)                                                                                               \
+    do {                                                                                                               \
+        if (GLOBAL_DEBUG)                                                                                              \
+            fprintf(stdout, RESET fmt "\n", __VA_ARGS__);                                                              \
+    } while (0)
+#define Sdebug(MSG) Xdebug("%s", MSG)
+#define Xerror(fmt, ...)                                                                                               \
+    do {                                                                                                               \
+        fprintf(stderr,                                                                                                \
+                RED "[!] "                                                                                             \
+                    "%s:%d:%s(): " fmt RESET "\n",                                                                     \
+                __FILE__, __LINE__, __func__, __VA_ARGS__);                                                            \
+    } while (0)
+
+#define Serror(MSG) Xerror("%s", MSG)
+#endif
+
+// --- common macro ---
+#undef ABS
+#define ABS(a) (((a) < 0) ? -(a) : (a))
+
+#endif
+//#include "../deps/MachoParser/include/MachoFD.h"
+#include "MachoFD.h"
+
+void zz_debug() {
+#ifdef DEBUGMODE
+#ifdef ZZDEPS
+    debug_break();
+#else
+    perror(NULL);
+#endif
+#endif
+}
+
+using namespace std;
+
+unsigned long zz_file_get_size(const char *target_path) {
+    unsigned long filesize = -1;
+    struct stat statbuff;
+    if (stat(target_path, &statbuff) < 0) {
+        return filesize;
+    } else {
+        filesize = statbuff.st_size;
+    }
+    return filesize;
+}
+
+bool zz_file_is_exist(const char *target_path) {
+    if ((access(target_path, F_OK)) != -1) {
+        return true;
+    }
+    return FALSE;
+}
+
+bool zz_file_remove(const char *target_path) {
+    if (zz_file_is_exist(target_path)) {
+        if (!remove(target_path)) {
+            return TRUE;
+        }
+    }
+    zz_debug();
+    return FALSE;
+}
+
+void zz_write_file_to_file(const char *src_path, const char *dst_path, unsigned long src_offset,
+                           unsigned long dst_offset, unsigned long size) {
+    FILE *src_fd;
+    FILE *dst_fd;
+    src_fd = fopen(src_path, "rb");
+    fseek(src_fd, src_offset, SEEK_SET);
+
+    if (!zz_file_is_exist(dst_path))
+        dst_fd = fopen(dst_path, "wb");
+    else
+        dst_fd = fopen(dst_path, "rb+");
+
+    fseek(dst_fd, dst_offset, SEEK_SET);
+
+    unsigned int WRITE_BLOCK_SIZE = 1024;
+    unsigned char tmp_block[1024];
+
+    for (int i = 0; i < size / WRITE_BLOCK_SIZE; i++) {
+        fread(tmp_block, WRITE_BLOCK_SIZE, 1, src_fd);
+        fwrite(tmp_block, WRITE_BLOCK_SIZE, 1, dst_fd);
+    }
+
+    if (size % WRITE_BLOCK_SIZE) {
+        fread(tmp_block, size % WRITE_BLOCK_SIZE, 1, src_fd);
+        fwrite(tmp_block, size % WRITE_BLOCK_SIZE, 1, dst_fd);
+    }
+
+    fclose(src_fd);
+    fclose(dst_fd);
+}
+
+void zz_copy_file_to_file(const char *src_path, const char *dst_path) {
+    unsigned long file_size;
+    file_size = zz_file_get_size(src_path);
+    zz_write_file_to_file(src_path, dst_path, 0, 0, file_size);
+}
+
+void zz_file_write(const char *dst_path, unsigned long offset, void *content, unsigned long size) {
+    FILE *dst_fd = fopen(dst_path, "rb+");
+    fseek(dst_fd, offset, SEEK_SET);
+    fwrite(content, size, 1, dst_fd);
+    fclose(dst_fd);
+}
+
+void zz_file_read(const char *dst_path, unsigned long offset, void *content, unsigned long size) {
+    FILE *dst_fd = fopen(dst_path, "rb");
+    fseek(dst_fd, offset, SEEK_SET);
+    fread(content, size, 1, dst_fd);
+    fclose(dst_fd);
+}
+
+void zz_write_append_to_file(const char *dst_path, void *content, unsigned long size) {
+    FILE *dst_fd = fopen(dst_path, "ab+");
+    fwrite(content, size, 1, dst_fd);
+    fclose(dst_fd);
+}
+unsigned long get_linkedit_loadcmd_offset(MachoFD *machofd) {
+    for (const auto &loadcmd : machofd->loadcommands.load_command_infos) {
+        /* iterate dump section */
+        if (loadcmd.load_cmd->cmd == LC_SEGMENT_64) {
+            if (!strcmp(((struct segment_command_64 *)loadcmd.cmd_info)->segname, "__LINKEDIT"))
+                return loadcmd.fileoff;
+        }
+    }
+    return 0;
+}
+
+void macho_fix_load_command(const char *target_path) {
+    MachoFD *machofd = new MachoFD(target_path);
+    if (machofd->isFat) {
+        printf("use lipo to thin it.");
+    }
+    machofd->parse_macho();
+
+    for (const auto &loadcmd : machofd->loadcommands.load_command_infos) {
+        if (loadcmd.load_cmd->cmd == LC_DYLD_INFO_ONLY) {
+            struct dyld_info_command *tmp = (struct dyld_info_command *)loadcmd.cmd_info;
+            struct dyld_info_command new_tmp = *tmp;
+            new_tmp.rebase_off += 0x8000;
+            new_tmp.bind_off += 0x8000;
+            if (new_tmp.weak_bind_off)
+                new_tmp.weak_bind_off += 0x8000;
+            if (new_tmp.lazy_bind_off)
+                new_tmp.lazy_bind_off += 0x8000;
+            if (new_tmp.export_off)
+                new_tmp.export_off += 0x8000;
+            zz_file_write(target_path, loadcmd.fileoff, &new_tmp, sizeof(new_tmp));
+            Sinfo("[*] fix LC_DYLD_INFO_ONLY done");
+        }
+        if (loadcmd.load_cmd->cmd == LC_SYMTAB) {
+            struct symtab_command *tmp = (struct symtab_command *)loadcmd.cmd_info;
+            struct symtab_command new_tmp = *tmp;
+            if (new_tmp.symoff)
+                new_tmp.symoff += 0x8000;
+            if (new_tmp.stroff)
+                new_tmp.stroff += 0x8000;
+            zz_file_write(target_path, loadcmd.fileoff, &new_tmp, sizeof(new_tmp));
+            Sinfo("[*] fix LC_SYMTAB done");
+        }
+        if (loadcmd.load_cmd->cmd == LC_DYSYMTAB) {
+            struct dysymtab_command *tmp = (struct dysymtab_command *)loadcmd.cmd_info;
+            struct dysymtab_command new_tmp = *tmp;
+            if (new_tmp.tocoff)
+                new_tmp.tocoff += 0x8000;
+            if (new_tmp.modtaboff)
+                new_tmp.modtaboff += 0x8000;
+            if (new_tmp.extrefsymoff)
+                new_tmp.extrefsymoff += 0x8000;
+            if (new_tmp.indirectsymoff)
+                new_tmp.indirectsymoff += 0x8000;
+            if (new_tmp.extreloff)
+                new_tmp.extreloff += 0x8000;
+            if (new_tmp.locreloff)
+                new_tmp.locreloff += 0x8000;
+            zz_file_write(target_path, loadcmd.fileoff, &new_tmp, sizeof(new_tmp));
+            Sinfo("[*] fix LC_DYSYMTAB done");
+        }
+        if (loadcmd.load_cmd->cmd == LC_FUNCTION_STARTS || loadcmd.load_cmd->cmd == LC_DATA_IN_CODE) {
+            struct linkedit_data_command *tmp = (struct linkedit_data_command *)loadcmd.cmd_info;
+            struct linkedit_data_command new_tmp = *tmp;
+            if (new_tmp.dataoff)
+                new_tmp.dataoff += 0x8000;
+            zz_file_write(target_path, loadcmd.fileoff, &new_tmp, sizeof(new_tmp));
+
+            Sinfo("[*] fix LC_FUNCTION_STARTS/LC_DATA_IN_CODE done");
+        }
+    }
+}
+
+void zz_file_move_offset_to_offset(const char *target_path, unsigned long src_offset, unsigned long dst_offset,
+                                   unsigned long size) {
+    FILE *target_fd;
+    target_fd = fopen(target_path, "rb+");
+    unsigned char *data = (unsigned char *)malloc(size);
+    fseek(target_fd, src_offset, SEEK_SET);
+    fread(data, size, 1, target_fd);
+    fseek(target_fd, dst_offset, SEEK_SET);
+    fwrite(data, size, 1, target_fd);
+    free(data);
+    fclose(target_fd);
+}
+
+void macho_insert_segment(string target_path, string new_target_path) {
+
+    char *rx_segment_name = (char *)"HookZzCode";
+    char *rw_segment_name = (char *)"HookZzData";
+    MachoFD *machofd = new MachoFD(target_path.c_str());
+
+    Sinfo("[*] start insert rw- and r-x segments");
+
+    if (machofd->isFat) {
+        printf("use lipo to thin it.");
+    }
+    machofd->parse_macho();
+    Xinfo("[*] parse origin file %s", target_path.c_str());
+
+    const segment_command_64_info_t *seg_linkedit = machofd->get_seg_by_name("__LINKEDIT");
+
+    struct mach_header_64 new_target_header;
+    struct segment_command_64 new_target_rx_segment;
+    struct segment_command_64 new_target_rw_segment;
+    struct segment_command_64 new_target_linkedit_segment;
+
+    memcpy(&new_target_header, machofd->header.header64, sizeof(struct mach_header_64));
+    memcpy(&new_target_rx_segment, seg_linkedit->seg_cmd_64, sizeof(struct segment_command_64));
+    memcpy(&new_target_rw_segment, seg_linkedit->seg_cmd_64, sizeof(struct segment_command_64));
+    memcpy(&new_target_linkedit_segment, seg_linkedit->seg_cmd_64, sizeof(struct segment_command_64));
+
+    // add new rx segment
+    memcpy(new_target_rx_segment.segname, rx_segment_name, strlen(rx_segment_name));
+    new_target_rx_segment.vmsize = 0x4000;
+    new_target_rx_segment.filesize = 0x4000;
+    new_target_rx_segment.vmaddr = new_target_rx_segment.vmaddr;
+    new_target_rx_segment.fileoff = new_target_rx_segment.fileoff;
+    new_target_rx_segment.maxprot = 5;
+    new_target_rx_segment.initprot = 5;
+
+    // add new rw segment
+    memcpy(new_target_rw_segment.segname, rw_segment_name, strlen(rw_segment_name));
+    new_target_rw_segment.vmsize = 0x4000;
+    new_target_rw_segment.filesize = 0x4000;
+    new_target_rw_segment.vmaddr = new_target_rw_segment.vmaddr + 0x4000;
+    new_target_rw_segment.fileoff = new_target_rw_segment.fileoff + 0x4000;
+    new_target_rw_segment.maxprot = 3;
+    new_target_rw_segment.initprot = 3;
+
+    // fix linkedit segment
+    new_target_linkedit_segment.vmaddr = new_target_linkedit_segment.vmaddr + 0x4000 + 0x4000;
+    new_target_linkedit_segment.fileoff = new_target_linkedit_segment.fileoff + 0x4000 + 0x4000;
+
+    // fix header
+    new_target_header.ncmds += 2;
+    new_target_header.sizeofcmds += (new_target_rx_segment.cmdsize + new_target_rw_segment.cmdsize);
+
+    //    zz_copy_file_to_file(target_path.c_str(), new_target_path.c_str());
+    zz_write_file_to_file(target_path.c_str(), new_target_path.c_str(), 0, 0, seg_linkedit->seg_cmd_64->fileoff);
+
+    unsigned long orig_linkedit_offset = get_linkedit_loadcmd_offset(machofd);
+    unsigned long move_size =
+        machofd->header.header64->sizeofcmds + sizeof(struct mach_header_64) - orig_linkedit_offset;
+    unsigned long new_linkedit_offset =
+        orig_linkedit_offset + (new_target_rx_segment.cmdsize + new_target_rw_segment.cmdsize);
+    zz_file_move_offset_to_offset(new_target_path.c_str(), orig_linkedit_offset, new_linkedit_offset, move_size);
+
+    zz_file_write(new_target_path.c_str(), 0, &new_target_header, sizeof(new_target_header));
+    Sinfo("[*] fix macho header");
+    zz_file_write(new_target_path.c_str(), orig_linkedit_offset, &new_target_rx_segment,
+                  sizeof(struct segment_command_64));
+    Sinfo("[*] add \'HookZzCode(r-x)\' Segment");
+    zz_file_write(new_target_path.c_str(), orig_linkedit_offset + new_target_rx_segment.cmdsize, &new_target_rw_segment,
+                  sizeof(struct segment_command_64));
+    Sinfo("[*] add \'HookZzData(rw-)\' Segment");
+    zz_file_write(new_target_path.c_str(),
+                  orig_linkedit_offset + new_target_rx_segment.cmdsize + new_target_rw_segment.cmdsize,
+                  &new_target_linkedit_segment, sizeof(struct segment_command_64));
+    Sinfo("[*] fix \'__LINKEDIT\' Segment");
+
+    char segment_blank[0x4000] = {0};
+    zz_write_append_to_file(new_target_path.c_str(), &segment_blank, 0x4000);
+    Xinfo("[*] reserve %p size space to HookZzCode Segment", (zpointer)0x4000);
+    zz_write_append_to_file(new_target_path.c_str(), &segment_blank, 0x4000);
+    Xinfo("[*] reserve %p size space to HookZzData Segment", (zpointer)0x4000);
+    zz_write_file_to_file(target_path.c_str(), new_target_path.c_str(), seg_linkedit->seg_cmd_64->fileoff,
+                          new_target_linkedit_segment.fileoff,
+                          zz_file_get_size(target_path.c_str()) - seg_linkedit->seg_cmd_64->fileoff);
+
+    /* fix load command */
+    macho_fix_load_command(new_target_path.c_str());
+}
+
+typedef struct _ZzInterceptorBackendNoJB {
+    void *enter_thunk; // hardcode
+    void *leave_thunk; // hardcode
+    void *function_context_begin_invocation;
+    void *function_context_end_invocation;
+    unsigned long num_of_entry;
+    unsigned long code_seg_offset;
+    unsigned long data_seg_offset;
+} ZzInterceptorBackendNoJB;
+
+typedef struct _ZzHookFunctionEntryNoJB {
+    void *target_fileoff;
+    unsigned long is_near_jump;
+    void *entry_address;
+    void *on_enter_trampoline;  // HookZzData, 99% hardcode
+    void *on_invoke_trampoline; // HookZzData, fixed instructions
+    void *on_leave_trampoline;  // HookZzData, 99% hardcode
+} ZzHookFunctionEntryNoJB;
+
+//------------------------------------------------------------
+//-----------       Created with 010 Editor        -----------
+//------         www.sweetscape.com/010editor/          ------
+//
+// File    : /Users/jmpews/Desktop/SpiderZz/project/HookZz/tools/ZzSolidifyHook/solidifytrampoline.dylib
+// Address : 32456 (0x7EC8)
+// Size    : 84 (0x54)
+//------------------------------------------------------------
+unsigned char on_enter_trampoline_template[84] = {
+    0xFF, 0x43, 0x00, 0xD1, 0xF0, 0x03, 0x1F, 0xF8, 0x10, 0x00, 0x00, 0x10, 0x51, 0x00, 0x00, 0x58, 0x03,
+    0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x02, 0x10, 0x8B, 0xF0, 0x07,
+    0x41, 0xF8, 0x31, 0x02, 0x40, 0xF9, 0xF1, 0x03, 0x00, 0xF9, 0xF0, 0x03, 0x1F, 0xF8, 0x10, 0x00, 0x00,
+    0x10, 0x51, 0x00, 0x00, 0x58, 0x03, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x31, 0x02, 0x10, 0x8B, 0xF0, 0x07, 0x41, 0xF8, 0x31, 0x02, 0x40, 0xF9, 0x20, 0x02, 0x1F, 0xD6};
+#define ON_ENTER_TRAMPOLINE_SIZE 84
+
+//------------------------------------------------------------
+//-----------       Created with 010 Editor        -----------
+//------         www.sweetscape.com/010editor/          ------
+//
+// File    : /Users/jmpews/Desktop/SpiderZz/project/HookZz/tools/ZzSolidifyHook/solidifytrampoline.dylib
+// Address : 32540 (0x7F1C)
+// Size    : 72 (0x48)
+//------------------------------------------------------------
+unsigned char on_invoke_trampoline_template[72] = {
+    0x1F, 0x20, 0x03, 0xD5, 0x1F, 0x20, 0x03, 0xD5, 0x1F, 0x20, 0x03, 0xD5, 0x1F, 0x20, 0x03, 0xD5, 0x1F, 0x20,
+    0x03, 0xD5, 0x1F, 0x20, 0x03, 0xD5, 0x1F, 0x20, 0x03, 0xD5, 0x1F, 0x20, 0x03, 0xD5, 0xF0, 0x03, 0x1F, 0xF8,
+    0x10, 0x00, 0x00, 0x10, 0x51, 0x00, 0x00, 0x58, 0x03, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x31, 0x02, 0x10, 0x8B, 0xF0, 0x07, 0x41, 0xF8, 0x31, 0x02, 0x40, 0xF9, 0x20, 0x02, 0x1F, 0xD6};
+#define ON_INVOKE_TRAMPOLINE_SIZE 72
+
+//------------------------------------------------------------
+//-----------       Created with 010 Editor        -----------
+//------         www.sweetscape.com/010editor/          ------
+//
+// File    : /Users/jmpews/Desktop/SpiderZz/project/HookZz/tools/ZzSolidifyHook/solidifytrampoline.dylib
+// Address : 32612 (0x7F64)
+// Size    : 84 (0x54)
+//------------------------------------------------------------
+
+unsigned char on_leave_trampoline_template[84] = {
+    0xFF, 0x43, 0x00, 0xD1, 0xF0, 0x03, 0x1F, 0xF8, 0x10, 0x00, 0x00, 0x10, 0x51, 0x00, 0x00, 0x58, 0x03,
+    0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x02, 0x10, 0x8B, 0xF0, 0x07,
+    0x41, 0xF8, 0x31, 0x02, 0x40, 0xF9, 0xF1, 0x03, 0x00, 0xF9, 0xF0, 0x03, 0x1F, 0xF8, 0x10, 0x00, 0x00,
+    0x10, 0x51, 0x00, 0x00, 0x58, 0x03, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x31, 0x02, 0x10, 0x8B, 0xF0, 0x07, 0x41, 0xF8, 0x31, 0x02, 0x40, 0xF9, 0x20, 0x02, 0x1F, 0xD6};
+
+#define ON_LEAVE_TRAMPOLINE_SIZE 84
+
+void ZzSolidifyBuildEnterTrampoline(ZzInterceptorBackendNoJB *nojb_backend, ZzHookFunctionEntryNoJB *nojb_entry,
+                                    MachoFD *machofd) {
+
+    const char *target_path = machofd->getPath();
+    const segment_command_64_info_t *code_seg_info = machofd->get_seg_by_name((char *)"HookZzCode");
+    const segment_command_64_info_t *data_seg_info = machofd->get_seg_by_name((char *)"HookZzData");
+
+    unsigned char on_enter_trampoline_tp[256] = {0};
+    zsize ENTER_TRAMPOLINE_SIZE = 21 * 4;
+
+    Sinfo("[*] solidify build on_enter_trampoline");
+    memcpy(on_enter_trampoline_tp, (void *)on_enter_trampoline_template, ENTER_TRAMPOLINE_SIZE);
+
+    zaddr data_vmaddr = (zaddr)data_seg_info->vmaddr + sizeof(ZzInterceptorBackendNoJB) +
+                        nojb_backend->num_of_entry * sizeof(ZzHookFunctionEntryNoJB) + 2 * sizeof(void *);
+    zaddr codepatch_vmaddr = code_seg_info->vmaddr + +2 * 4;
+    zaddr offset = data_vmaddr - codepatch_vmaddr;
+    *(unsigned long *)&on_enter_trampoline_tp[5 * 4] = (unsigned long)offset;
+
+    data_vmaddr = data_seg_info->vmaddr + 0x8 * 0;
+    codepatch_vmaddr = code_seg_info->vmaddr + (zaddr)nojb_backend->code_seg_offset + 15 * 4;
+    offset = data_vmaddr - codepatch_vmaddr;
+    *(unsigned long *)&on_enter_trampoline_tp[15 * 4] = (unsigned long)offset;
+
+    zz_file_write(target_path, code_seg_info->fileoff + nojb_backend->code_seg_offset, on_enter_trampoline_tp,
+                  ENTER_TRAMPOLINE_SIZE);
+    nojb_entry->on_enter_trampoline = (zpointer)(code_seg_info->vmaddr + nojb_backend->code_seg_offset);
+    nojb_backend->code_seg_offset += ENTER_TRAMPOLINE_SIZE;
+}
+
+void ZzSolidifyBuildInvokeTrampoline(ZzInterceptorBackendNoJB *nojb_backend, ZzHookFunctionEntryNoJB *nojb_entry,
+                                     MachoFD *machofd) {
+    const char *target_path = machofd->getPath();
+    const segment_command_64_info_t *code_seg_info = machofd->get_seg_by_name((char *)"HookZzCode");
+    const segment_command_64_info_t *data_seg_info = machofd->get_seg_by_name((char *)"HookZzData");
+    zpointer target_fileoff = nojb_entry->target_fileoff;
+    unsigned char on_invoke_trampoline_tp[256] = {0};
+    zsize INVOKE_TRAMPOLINE_SIZE = 18 * 4;
+
+    uint32_t insn;
+    Sinfo("[*] solidify build on_invoke_trampoline");
+    memcpy(on_invoke_trampoline_tp, (void *)on_invoke_trampoline_template, INVOKE_TRAMPOLINE_SIZE);
+
+    zz_file_read(target_path, (zaddr)target_fileoff, &insn, 4);
+    memcpy(on_invoke_trampoline_tp, &insn, 4);
+
+    // from on_invoke_trampoline jump to origin rest code
+    // TODO:
+    zaddr offset = (zaddr)target_fileoff + 4 - (zaddr)nojb_entry->on_enter_trampoline - 4;
+    insn = 0x14000000;
+    insn = insn | ((offset / 4) & 0x03ffffff);
+    *(uint32_t *)&on_invoke_trampoline_tp[4] = insn;
+
+    nojb_entry->is_near_jump = TRUE;
+
+    zz_file_write(target_path, code_seg_info->fileoff + nojb_backend->code_seg_offset, on_invoke_trampoline_tp,
+                  INVOKE_TRAMPOLINE_SIZE);
+    nojb_entry->on_invoke_trampoline = (zpointer)(code_seg_info->vmaddr + nojb_backend->code_seg_offset);
+    nojb_backend->code_seg_offset += INVOKE_TRAMPOLINE_SIZE;
+}
+
+void ZzSolidifyBuildLeaveTrampoline(ZzInterceptorBackendNoJB *nojb_backend, ZzHookFunctionEntryNoJB *nojb_entry,
+                                    MachoFD *machofd) {
+
+    const char *target_path = machofd->getPath();
+    const segment_command_64_info_t *code_seg_info = machofd->get_seg_by_name((char *)"HookZzCode");
+    const segment_command_64_info_t *data_seg_info = machofd->get_seg_by_name((char *)"HookZzData");
+
+    unsigned char on_leave_trampoline_tp[256] = {0};
+    zsize LEAVE_TRAMPOLINE_SIZE = 21 * 4;
+
+    Sinfo("[*] solidify build on_leave_trampoline");
+    memcpy(on_leave_trampoline_tp, (void *)on_leave_trampoline_template, LEAVE_TRAMPOLINE_SIZE);
+
+    zaddr data_vmaddr = (zaddr)data_seg_info->vmaddr + sizeof(ZzInterceptorBackendNoJB) +
+                        nojb_backend->num_of_entry * sizeof(ZzHookFunctionEntryNoJB) + 2 * sizeof(void *);
+    zaddr codepatch_vmaddr = code_seg_info->vmaddr + +2 * 4;
+    zaddr offset = data_vmaddr - codepatch_vmaddr;
+    *(unsigned long *)&on_leave_trampoline_tp[5 * 4] = (unsigned long)offset;
+
+    data_vmaddr = data_seg_info->vmaddr + 0x8 * 1;
+    codepatch_vmaddr = code_seg_info->vmaddr + (zaddr)nojb_backend->code_seg_offset + 12 * 4;
+    offset = data_vmaddr - codepatch_vmaddr;
+    *(unsigned long *)&on_leave_trampoline_tp[15 * 4] = (unsigned long)offset;
+
+    zz_file_write(target_path, code_seg_info->fileoff + nojb_backend->code_seg_offset, on_leave_trampoline_tp,
+                  LEAVE_TRAMPOLINE_SIZE);
+    nojb_entry->on_leave_trampoline = (zpointer)(code_seg_info->vmaddr + nojb_backend->code_seg_offset);
+    nojb_backend->code_seg_offset += LEAVE_TRAMPOLINE_SIZE;
+}
+
+void ZzSolidifyHookInitilize(ZzInterceptorBackendNoJB *nojb_backend, MachoFD *machofd) {
+    const char *target_path = machofd->getPath();
+    const segment_command_64_info_t *code_seg_info = machofd->get_seg_by_name((char *)"HookZzCode");
+    const segment_command_64_info_t *data_seg_info = machofd->get_seg_by_name((char *)"HookZzData");
+
+    Sinfo("[*] solidify hook initilize");
+    zz_file_read(target_path, data_seg_info->fileoff, nojb_backend, sizeof(ZzInterceptorBackendNoJB));
+    nojb_backend->code_seg_offset = 0;
+}
+
+void ZzSolidifyHookActivate(ZzHookFunctionEntryNoJB *nojb_entry, MachoFD *machofd) {
+    zpointer target_fileoff = nojb_entry->target_fileoff;
+    const char *target_path = machofd->getPath();
+
+    // try near jump
+    if (nojb_entry->is_near_jump) {
+        zaddr near_jump_offset = (zaddr)nojb_entry->on_enter_trampoline - (zaddr)target_fileoff;
+        uint32_t insn = 0x14000000;
+        insn = insn | ((near_jump_offset / 4) & 0x03ffffff);
+        zz_file_write(target_path, (zaddr)target_fileoff, &insn, 4);
+    }
+}
+
+void ZzSolidifyHook(zpointer target_fileoff, MachoFD *machofd) {
+    const char *target_path = machofd->getPath();
+    const segment_command_64_info_t *code_seg_info = machofd->get_seg_by_name((char *)"HookZzCode");
+    const segment_command_64_info_t *data_seg_info = machofd->get_seg_by_name((char *)"HookZzData");
+
+    ZzInterceptorBackendNoJB nojb_backend;
+    ZzHookFunctionEntryNoJB nojb_entry;
+    ZzSolidifyHookInitilize(&nojb_backend, machofd);
+
+    Xinfo("[*] start solidify hook at %p", target_fileoff);
+
+    nojb_entry.target_fileoff = target_fileoff;
+    ZzSolidifyBuildEnterTrampoline(&nojb_backend, &nojb_entry, machofd);
+    ZzSolidifyBuildInvokeTrampoline(&nojb_backend, &nojb_entry, machofd);
+    ZzSolidifyBuildLeaveTrampoline(&nojb_backend, &nojb_entry, machofd);
+
+    /* solidify new hook entry to file */
+    zz_file_write(target_path, data_seg_info->fileoff + sizeof(ZzHookFunctionEntryNoJB) * nojb_backend.num_of_entry,
+                  &nojb_entry, sizeof(ZzHookFunctionEntryNoJB));
+
+    /* solidify update interceptor backend to file */
+    nojb_backend.num_of_entry += 1;
+    zz_file_write(target_path, data_seg_info->fileoff, &nojb_backend, sizeof(ZzInterceptorBackendNoJB));
+
+    ZzSolidifyHookActivate(&nojb_entry, machofd);
+}
+
+int main(int args, const char **argv) {
+    string target_file_path = "/Users/jmpews/Desktop/test/test.dylib";
+    string new_target_file_path = target_file_path;
+    new_target_file_path.insert(new_target_file_path.rfind('.'), ".hook");
+
+    zz_file_remove(new_target_file_path.c_str());
+
+    macho_insert_segment(target_file_path, new_target_file_path);
+
+    MachoFD *machofd = new MachoFD(new_target_file_path.c_str());
+    if (machofd->isFat) {
+        printf("use lipo to thin it.");
+    }
+    machofd->parse_macho();
+
+    ZzSolidifyHook((zpointer)0x7f0c, machofd);
+}
diff --git a/VirtualApp/lib/src/main/jni/HookZz/tools/ZzSolidifyHook/solidifytrampoline.c b/VirtualApp/lib/src/main/jni/HookZz/tools/ZzSolidifyHook/solidifytrampoline.c
new file mode 100644
index 000000000..235771aea
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/HookZz/tools/ZzSolidifyHook/solidifytrampoline.c
@@ -0,0 +1,108 @@
+/*
+`xcrun --sdk iphoneos --find clang` \
+-fPIC -shared -dynamiclib \
+-arch arm64 \
+-isysroot `xcrun --sdk iphoneos --show-sdk-path` \
+solidifytrampoline.c \
+-o  solidifytrampoline.dylib
+*/
+
+__attribute__((__naked__)) void on_enter_trampoline_template() {
+    __asm__ volatile(
+        /* store entry address and reserve space for next hop */
+        "sub sp, sp, 0x10\n"
+
+        /* push x16 */
+        "str x16,[sp, #-0x10]\n"
+        "adr x16, #0\n"
+        "ldr x17, #0x8\n"
+        "b #0xc\n"
+        /* entry address'address offset!!! */
+        ".long 0x0\n"
+        ".long 0x0\n"
+        "add x17, x17, x16\n"
+        /* pop x16 */
+        "ldr x16, [sp], #0x10\n"
+        "ldr x17, [x17]\n"
+
+        "str x17, [sp]\n"
+
+        /* push x16 */
+        "str x16,[sp, #-0x10]\n"
+        "adr x16, #0\n"
+        "ldr x17, #0x8\n"
+        "b #0xc\n"
+        /* enter_thunk address'address offset!!! */
+        ".long 0x0\n"
+        ".long 0x0\n"
+        "add x17, x17, x16\n"
+        /* pop x16 */
+        "ldr x16, [sp], #0x10\n"
+        "ldr x17, [x17]\n"
+
+        "br x17");
+}
+
+__attribute__((__naked__)) void on_inovke_trampoline_template() {
+    __asm__ volatile(
+        /* fixed instruction */
+        "nop\n"
+        "nop\n"
+        "nop\n"
+        "nop\n"
+        "nop\n"
+        "nop\n"
+        "nop\n"
+        "nop\n"
+
+        /* push x16 */
+        "str x16,[sp, #-0x10]\n"
+        "adr x16, #0\n"
+        "ldr x17, #0x8\n"
+        "b #0xc\n"
+        /* rest of orgin function address'address offset !!! */
+        ".long 0x0\n"
+        ".long 0x0\n"
+        "add x17, x17, x16\n"
+        /* pop x16 */
+        "ldr x16, [sp], #0x10\n"
+        "ldr x17, [x17]\n"
+
+        "br x17");
+}
+
+__attribute__((__naked__)) void on_leave_trampoline_template() {
+    __asm__ volatile(
+        /* store entry address and reserve space for next hop */
+        "sub sp, sp, 0x10\n"
+
+        /* push x16 */
+        "str x16,[sp, #-0x10]\n"
+        "adr x16, #0\n"
+        "ldr x17, #0x8\n"
+        "b #0xc\n"
+        /* entry address'address offset!!! */
+        ".long 0x0\n"
+        ".long 0x0\n"
+        "add x17, x17, x16\n"
+        /* pop x16 */
+        "ldr x16, [sp], #0x10\n"
+        "ldr x17, [x17]\n"
+
+        "str x17, [sp]\n"
+
+        /* push x16 */
+        "str x16,[sp, #-0x10]\n"
+        "adr x16, #0\n"
+        "ldr x17, #0x8\n"
+        "b #0xc\n"
+        /* leave_thunk address'address offset!!! */
+        ".long 0x0\n"
+        ".long 0x0\n"
+        "add x17, x17, x16\n"
+        /* pop x16 */
+        "ldr x16, [sp], #0x10\n"
+        "ldr x17, [x17]\n"
+
+        "br x17");
+}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/Jni/Helper.h b/VirtualApp/lib/src/main/jni/Jni/Helper.h
new file mode 100644
index 000000000..398fd7b91
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Jni/Helper.h
@@ -0,0 +1,31 @@
+//
+// VirtualApp Native Project
+//
+
+#ifndef NDK_LOG_H
+#define NDK_LOG_H
+
+#include <fb/include/fb/fbjni.h>
+
+#define NATIVE_METHOD(func_ptr, func_name, signature) { func_name, signature, reinterpret_cast<void*>(func_ptr) }
+
+class ScopeUtfString {
+public:
+    ScopeUtfString(jstring j_str) : _j_str(j_str),
+                                    _c_str(facebook::jni::Environment::current()->GetStringUTFChars(j_str, NULL)) {
+    }
+
+    const char *c_str() {
+        return _c_str;
+    }
+
+    ~ScopeUtfString() {
+        facebook::jni::Environment::current()->ReleaseStringUTFChars(_j_str, _c_str);
+    }
+
+private:
+    jstring _j_str;
+    const char *_c_str;
+};
+
+#endif //NDK_LOG_H
diff --git a/VirtualApp/lib/src/main/jni/Jni/VAJni.cpp b/VirtualApp/lib/src/main/jni/Jni/VAJni.cpp
new file mode 100644
index 000000000..aa8273572
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Jni/VAJni.cpp
@@ -0,0 +1,220 @@
+#include <elf.h>//
+// VirtualApp Native Project
+//
+#include <Foundation/IOUniformer.h>
+#include <fb/include/fb/Build.h>
+#include <fb/include/fb/ALog.h>
+#include <fb/include/fb/fbjni.h>
+#include <ctime>
+#include "VAJni.h"
+
+using namespace facebook::jni;
+
+static void jni_nativeLaunchEngine(alias_ref<jclass> clazz, JArrayClass<jobject> javaMethods,
+                                   jstring packageName,
+                                   jboolean isArt, jint apiLevel, jint cameraMethodType) {
+    hookAndroidVM(javaMethods, packageName, isArt, apiLevel, cameraMethodType);
+}
+
+static void jni_disableJit(alias_ref<jclass> clazz, jint apiLevel) {
+    disableJit(apiLevel);
+}
+
+static void jni_nativeEnableIORedirect(alias_ref<jclass>, jstring selfSoPath, jint apiLevel,
+                                       jint preview_api_level) {
+    ScopeUtfString so_path(selfSoPath);
+    IOUniformer::startUniformer(so_path.c_str(), apiLevel, preview_api_level);
+}
+
+static void jni_nativeIOWhitelist(alias_ref<jclass> jclazz, jstring _path) {
+    ScopeUtfString path(_path);
+    IOUniformer::whitelist(path.c_str());
+}
+
+static void jni_nativeIOForbid(alias_ref<jclass> jclazz, jstring _path) {
+    ScopeUtfString path(_path);
+    IOUniformer::forbid(path.c_str());
+}
+
+
+static void jni_nativeIORedirect(alias_ref<jclass> jclazz, jstring origPath, jstring newPath) {
+    ScopeUtfString orig_path(origPath);
+    ScopeUtfString new_path(newPath);
+    IOUniformer::redirect(orig_path.c_str(), new_path.c_str());
+
+}
+
+static jstring jni_nativeGetRedirectedPath(alias_ref<jclass> jclazz, jstring origPath) {
+    ScopeUtfString orig_path(origPath);
+    const char *redirected_path = IOUniformer::query(orig_path.c_str());
+    if (redirected_path != NULL) {
+        return Environment::current()->NewStringUTF(redirected_path);
+    }
+    return NULL;
+}
+
+static jstring jni_nativeReverseRedirectedPath(alias_ref<jclass> jclazz, jstring redirectedPath) {
+    ScopeUtfString redirected_path(redirectedPath);
+    const char *orig_path = IOUniformer::reverse(redirected_path.c_str());
+    return Environment::current()->NewStringUTF(orig_path);
+}
+
+
+alias_ref<jclass> nativeEngineClass;
+
+const char hexcode[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
+                        'F'};
+// 君子坦荡荡,小人常戚戚
+// 这个类没有别的用途,就是用来防止别人直接通过我编译好的APK进行修改操作
+// 如果你是源码编译,删掉即可
+// The gentleman is frank and the villains are often sad
+// This class has no other purpose. It is used to prevent others from directly modifying the compiled APK.
+// If you are compiling source code, delete it
+
+void verifySignature(JNIEnv *env) {
+    jclass context_class = env->FindClass("android/content/Context");
+
+    jclass xpp_class = env->FindClass("io/virtualapp/XApp");
+    jmethodID get_app = env->GetStaticMethodID(xpp_class, "getApp", "()Lio/virtualapp/XApp;");
+    jobject context_object = env->CallStaticObjectMethod(xpp_class, get_app);
+    env->DeleteLocalRef(xpp_class);
+
+    jmethodID methodId = env->GetMethodID(context_class, "getPackageManager",
+                                          "()Landroid/content/pm/PackageManager;");
+    jobject package_manager = env->CallObjectMethod(context_object, methodId);
+    if (package_manager == NULL) {
+        ALOGD("package_manager is NULL!!!");
+        return;
+    }
+
+    methodId = env->GetMethodID(context_class, "getPackageName", "()Ljava/lang/String;");
+    jstring package_name = (jstring) env->CallObjectMethod(context_object, methodId);
+    if (package_name == NULL) {
+        ALOGD("package_name is NULL!!!");
+        return;
+    }
+    env->DeleteLocalRef(context_class);
+
+    //获取PackageInfo对象
+    jclass pack_manager_class = env->GetObjectClass(package_manager);
+    methodId = env->GetMethodID(pack_manager_class, "getPackageInfo",
+                                "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
+    env->DeleteLocalRef(pack_manager_class);
+    jobject package_info = env->CallObjectMethod(package_manager, methodId, package_name, 0x40);
+    if (package_info == NULL) {
+        ALOGD("getPackageInfo() is NULL!!!");
+        return;
+    }
+    env->DeleteLocalRef(package_manager);
+
+    //获取签名信息
+    jclass package_info_class = env->GetObjectClass(package_info);
+    jfieldID fieldId = env->GetFieldID(package_info_class, "signatures",
+                                       "[Landroid/content/pm/Signature;");
+    env->DeleteLocalRef(package_info_class);
+    jobjectArray signature_object_array = (jobjectArray) env->GetObjectField(package_info, fieldId);
+    if (signature_object_array == NULL) {
+        ALOGD("signature is NULL!!!");
+        return;
+    }
+    jobject signature_object = env->GetObjectArrayElement(signature_object_array, 0);
+    env->DeleteLocalRef(package_info);
+
+    //签名信息转换成sha1值
+    jclass signature_class = env->GetObjectClass(signature_object);
+    methodId = env->GetMethodID(signature_class, "toByteArray", "()[B");
+    env->DeleteLocalRef(signature_class);
+    jbyteArray signature_byte = (jbyteArray) env->CallObjectMethod(signature_object, methodId);
+    jclass byte_array_input_class = env->FindClass("java/io/ByteArrayInputStream");
+    methodId = env->GetMethodID(byte_array_input_class, "<init>", "([B)V");
+    jobject byte_array_input = env->NewObject(byte_array_input_class, methodId, signature_byte);
+    jclass certificate_factory_class = env->FindClass("java/security/cert/CertificateFactory");
+    methodId = env->GetStaticMethodID(certificate_factory_class, "getInstance",
+                                      "(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
+    jstring x_509_jstring = env->NewStringUTF("X.509");
+    jobject cert_factory = env->CallStaticObjectMethod(certificate_factory_class, methodId,
+                                                       x_509_jstring);
+    methodId = env->GetMethodID(certificate_factory_class, "generateCertificate",
+                                ("(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"));
+    jobject x509_cert = env->CallObjectMethod(cert_factory, methodId, byte_array_input);
+    env->DeleteLocalRef(certificate_factory_class);
+    jclass x509_cert_class = env->GetObjectClass(x509_cert);
+    methodId = env->GetMethodID(x509_cert_class, "getEncoded", "()[B");
+    jbyteArray cert_byte = (jbyteArray) env->CallObjectMethod(x509_cert, methodId);
+    env->DeleteLocalRef(x509_cert_class);
+    jclass message_digest_class = env->FindClass("java/security/MessageDigest");
+    methodId = env->GetStaticMethodID(message_digest_class, "getInstance",
+                                      "(Ljava/lang/String;)Ljava/security/MessageDigest;");
+    jstring sha1_jstring = env->NewStringUTF("SHA1");
+    jobject sha1_digest = env->CallStaticObjectMethod(message_digest_class, methodId, sha1_jstring);
+    methodId = env->GetMethodID(message_digest_class, "digest", "([B)[B");
+    jbyteArray sha1_byte = (jbyteArray) env->CallObjectMethod(sha1_digest, methodId, cert_byte);
+    env->DeleteLocalRef(message_digest_class);
+
+    //转换成char
+    jsize array_size = env->GetArrayLength(sha1_byte);
+    jbyte *sha1 = env->GetByteArrayElements(sha1_byte, NULL);
+    char *hex_sha = new char[array_size * 2 + 1];
+    for (int i = 0; i < array_size; ++i) {
+        hex_sha[2 * i] = hexcode[((unsigned char) sha1[i]) / 16];
+        hex_sha[2 * i + 1] = hexcode[((unsigned char) sha1[i]) % 16];
+    }
+    hex_sha[array_size * 2] = '\0';
+
+    if (strcmp("739A2395C962B54285D9D2B9F418F23BAA175EDD", hex_sha) != 0) {
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+        jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
+        srand((unsigned) time(0));
+        int random = rand() % 4;
+        // ALOGE("random: %d", random);
+        if (random <= 0) {
+            // set VirtualCore to null to cause a random crash.
+            jclass virtual_core_class = env->FindClass("com/lody/virtual/client/core/VirtualCore");
+            jfieldID gCore = env->GetStaticFieldID(virtual_core_class, "gCore", "Lcom/lody/virtual/client/core/VirtualCore;");
+            env->SetStaticObjectField(virtual_core_class, gCore, NULL);
+            env->DeleteLocalRef(virtual_core_class);
+
+            env->ThrowNew(newExcCls, "");
+        }
+        env->DeleteLocalRef(newExcCls);
+    }
+}
+
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
+    JNIEnv* env;
+    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
+        return -1;
+    }
+
+    verifySignature(env);
+
+    return initialize(vm, [] {
+        nativeEngineClass = findClassStatic("com/lody/virtual/client/NativeEngine");
+        nativeEngineClass->registerNatives({
+                        makeNativeMethod("nativeEnableIORedirect",
+                                         jni_nativeEnableIORedirect),
+                        makeNativeMethod("nativeIOWhitelist",
+                                         jni_nativeIOWhitelist),
+                        makeNativeMethod("nativeIOForbid",
+                                         jni_nativeIOForbid),
+                        makeNativeMethod("nativeIORedirect",
+                                         jni_nativeIORedirect),
+                        makeNativeMethod("nativeGetRedirectedPath",
+                                         jni_nativeGetRedirectedPath),
+                        makeNativeMethod("nativeReverseRedirectedPath",
+                                         jni_nativeReverseRedirectedPath),
+                        makeNativeMethod("nativeLaunchEngine",
+                                         jni_nativeLaunchEngine),
+                        makeNativeMethod("disableJit", jni_disableJit)
+                }
+        );
+    });
+}
+
+extern "C" __attribute__((constructor)) void _init(void) {
+    IOUniformer::init_env_before_all();
+}
+
+
diff --git a/VirtualApp/lib/src/main/jni/Core.h b/VirtualApp/lib/src/main/jni/Jni/VAJni.h
similarity index 88%
rename from VirtualApp/lib/src/main/jni/Core.h
rename to VirtualApp/lib/src/main/jni/Jni/VAJni.h
index d507815c5..b20a3575e 100644
--- a/VirtualApp/lib/src/main/jni/Core.h
+++ b/VirtualApp/lib/src/main/jni/Jni/VAJni.h
@@ -13,10 +13,10 @@
 #include "Foundation/VMPatch.h"
 #include "Foundation/IOUniformer.h"
 
-__BEGIN_DECLS
+extern alias_ref<jclass> nativeEngineClass;
+
 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved);
 JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved);
-__END_DECLS
 
 
 #endif //NDK_CORE_H
diff --git a/VirtualApp/lib/src/main/jni/MSHook/ARM.cpp b/VirtualApp/lib/src/main/jni/MSHook/ARM.cpp
deleted file mode 100644
index 41ae1f263..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/ARM.cpp
+++ /dev/null
@@ -1,137 +0,0 @@
-#include "ARM.h"
-#include "PosixMemory.h"
-
-void ARM::SubstrateHookFunctionARM(SubstrateProcessRef process, void *symbol, void *replace, void **result) {
-    if (symbol == NULL)
-        return;
-
-    uint32_t *area(reinterpret_cast<uint32_t *>(symbol));
-    uint32_t *arm(area);
-
-    const size_t used(8);
-
-    uint32_t backup[used / sizeof(uint32_t)] = {arm[0], arm[1]};
-
-    if (MSDebug) {
-        char name[16];
-        sprintf(name, "%p", area);
-        MSLogHexEx(area, used + sizeof(uint32_t), 4, name);
-    }
-
-    if (result != NULL) {
-
-    if (backup[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8)) {
-        *result = reinterpret_cast<void *>(backup[1]);
-        return;
-    }
-
-    size_t length(used);
-    for (unsigned offset(0); offset != used / sizeof(uint32_t); ++offset)
-        if (A$pcrel$r(backup[offset])) {
-            if ((backup[offset] & 0x02000000) == 0 || (backup[offset] & 0x0000f000 >> 12) != (backup[offset] & 0x0000000f))
-                length += 2 * sizeof(uint32_t);
-            else
-                length += 4 * sizeof(uint32_t);
-        }
-
-    length += 2 * sizeof(uint32_t);
-
-    uint32_t *buffer(reinterpret_cast<uint32_t *>(mmap(
-        NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0
-    )));
-
-    if (buffer == MAP_FAILED) {
-        MSLog(MSLogLevelError, "MS:Error:mmap() = %d", errno);
-        *result = NULL;
-        return;
-    }
-
-    if (false) fail: {
-        munmap(buffer, length);
-        *result = NULL;
-        return;
-    }
-
-    size_t start(0), end(length / sizeof(uint32_t));
-    uint32_t *trailer(reinterpret_cast<uint32_t *>(buffer + end));
-    for (unsigned offset(0); offset != used / sizeof(uint32_t); ++offset)
-        if (A$pcrel$r(backup[offset])) {
-            union {
-                uint32_t value;
-
-                struct {
-                    uint32_t rm : 4;
-                    uint32_t : 1;
-                    uint32_t shift : 2;
-                    uint32_t shiftamount : 5;
-                    uint32_t rd : 4;
-                    uint32_t rn : 4;
-                    uint32_t l : 1;
-                    uint32_t w : 1;
-                    uint32_t b : 1;
-                    uint32_t u : 1;
-                    uint32_t p : 1;
-                    uint32_t mode : 1;
-                    uint32_t type : 2;
-                    uint32_t cond : 4;
-                };
-            } bits = {backup[offset+0]}, copy(bits);
-
-            bool guard;
-            if (bits.mode == 0 || bits.rd != bits.rm) {
-                copy.rn = bits.rd;
-                guard = false;
-            } else {
-                copy.rn = bits.rm != A$r0 ? A$r0 : A$r1;
-                guard = true;
-            }
-
-            if (guard)
-                buffer[start++] = A$stmdb_sp$_$rs$((1 << copy.rn));
-
-            buffer[start+0] = A$ldr_rd_$rn_im$(copy.rn, A$pc, (end-1 - (start+0)) * 4 - 8);
-            buffer[start+1] = copy.value;
-
-            start += 2;
-
-            if (guard)
-                buffer[start++] = A$ldmia_sp$_$rs$((1 << copy.rn));
-
-            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 8;
-            end -= 1;
-        } else
-            buffer[start++] = backup[offset];
-
-    buffer[start+0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
-    buffer[start+1] = reinterpret_cast<uint32_t>(area + used / sizeof(uint32_t));
-
-    if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) {
-        MSLog(MSLogLevelError, "MS:Error:mprotect():%d", errno);
-        goto fail;
-    }
-
-    *result = buffer;
-
-    if (MSDebug) {
-        char name[16];
-        sprintf(name, "%p", *result);
-        MSLogHexEx(buffer, length, 4, name);
-    }
-
-    }
-
-    {
-        SubstrateHookMemory code(process, symbol, used);
-
-        arm[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
-        arm[1] = reinterpret_cast<uint32_t>(replace);
-    }
-
-    if (MSDebug) {
-        char name[16];
-        sprintf(name, "%p", area);
-        MSLogHexEx(area, used + sizeof(uint32_t), 4, name);
-    }
-}
-
-
diff --git a/VirtualApp/lib/src/main/jni/MSHook/CydiaSubstrate.h b/VirtualApp/lib/src/main/jni/MSHook/CydiaSubstrate.h
deleted file mode 100644
index 4340399bf..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/CydiaSubstrate.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef CYDIASUBSTRATE_H_
-#define CYDIASUBSTRATE_H_
-
-#include <dlfcn.h>
-#include <stdlib.h>
-
-#define _finline \
-    inline __attribute__((__always_inline__))
-#define _disused \
-    __attribute__((__unused__))
-#define _extern \
-    extern "C" __attribute__((__visibility__("default")))
-
-#include "SubstrateStruct.h"
-#endif /* CYDIASUBSTRATE_H_ */
diff --git a/VirtualApp/lib/src/main/jni/MSHook/Hooker.cpp b/VirtualApp/lib/src/main/jni/MSHook/Hooker.cpp
deleted file mode 100644
index 26689a6a0..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/Hooker.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#include <unistd.h>
-#include "Hooker.h"
-#include "util.h"
-#include "ARM.h"
-#include "Thumb.h"
-#include "x86.h"
-
-void Cydia::MSHookFunction(void *symbol, void *replace, void **result) {
-
-    SubstrateProcessRef process = NULL;
-    if (MSDebug){
-        MSLog(MSLogLevelNotice, "SubstrateHookFunction(process:%p, symbol:%p, replace:%p, result:%p)", process, symbol, replace, result);
-    }
-#if defined(__arm__) || defined(__thumb__)
-    if ((reinterpret_cast<uintptr_t>(symbol) & 0x1) == 0){
-        return ARM::SubstrateHookFunctionARM(process, symbol, replace, result);
-    }else{
-        return Thumb::SubstrateHookFunctionThumb(process, reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(symbol) & ~0x1), replace, result);
-    }
-#endif
-
-
-#if defined(__i386__) || defined(__x86_64__)
-    return x86::SubstrateHookFunctionx86(process, symbol, replace, result);
-#endif
-}
-
-void Cydia::MSHookFunction(const char *soname, const char *symbol, void *replace_func,
-                           void **old_func) {
-    void *addr = NULL;
-    if (find_name(getpid(), symbol, soname, (unsigned long *)&addr) < 0) {
-        MSLog(MSLogLevelError, "Not found %s in %s.", symbol, soname);
-        return;
-    }
-    Cydia::MSHookFunction(addr, replace_func, old_func);
-}
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/MSHook/Hooker.h b/VirtualApp/lib/src/main/jni/MSHook/Hooker.h
deleted file mode 100644
index bb9f44a6c..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/Hooker.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef HOOKER_H_
-#define HOOKER_H_
-
-#include <sys/mman.h>
-#include <errno.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "Debug.h"
-#include "Log.h"
-#include "PosixMemory.h"
-#include "CydiaSubstrate.h"
-
-namespace Cydia{
-
-	void MSHookFunction(const char *soname, const char *symbol, void *replace_func, void **old_func);
-	void MSHookFunction(void *symbol, void *replace, void **result);
-}
-#endif /* HOOKER_H_ */
\ No newline at end of file
diff --git a/VirtualApp/lib/src/main/jni/MSHook/Log.h b/VirtualApp/lib/src/main/jni/MSHook/Log.h
deleted file mode 100644
index 9b56a8031..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/Log.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/* Cydia Substrate - Powerful Code Insertion Platform
- * Copyright (C) 2008-2011  Jay Freeman (saurik)
-*/
-
-/* GNU Lesser General Public License, Version 3 {{{ */
-/*
- * Substrate is free software: you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or (at your
- * option) any later version.
- *
- * Substrate is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
-**/
-/* }}} */
-
-#ifndef SUBSTRATE_LOG_HPP
-#define SUBSTRATE_LOG_HPP
-
-#include <android/log.h>
-
-#define MSLogLevelNotice ANDROID_LOG_INFO
-#define MSLogLevelWarning ANDROID_LOG_WARN
-#define MSLogLevelError ANDROID_LOG_ERROR
-#define LOG_TAG "zzz"
-#define MS_DEBUG 1
-//#define MS_EXE_PRINTF 0
-#ifndef MS_LOG_TAG
-	#define MS_LOG_TAG "VA-Native"
-#endif
-
-#if MS_DEBUG
-	#ifdef MS_EXE_PRINTF
-		#define MS_LOGD(fmt,...) printf("[%12s] " fmt "\n", __FUNCTION__,##__VA_ARGS__)
-		#define MS_LOGI(fmt,...) printf("[%12s] " fmt "\n", __FUNCTION__,##__VA_ARGS__)
-		#define MS_LOGV(fmt,...) printf("[%12s] " fmt "\n", __FUNCTION__,##__VA_ARGS__)
-		#define MS_LOGW(fmt,...) printf("[%12s] " fmt "\n", __FUNCTION__,##__VA_ARGS__)
-		#define MS_LOGE(fmt,...) printf("[%12s] " fmt "\n", __FUNCTION__,##__VA_ARGS__)
-		#define MS_LOGF(fmt,...) printf("[%12s] " fmt "\n", __FUNCTION__,##__VA_ARGS__)
-
-	#else
-		#define MS_LOGD(fmt,...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "[%s]" fmt, __FUNCTION__,##__VA_ARGS__)
-		#define MS_LOGI(fmt,...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "[%s]" fmt, __FUNCTION__,##__VA_ARGS__)
-		#define MS_LOGV(fmt,...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "[%s]" fmt, __FUNCTION__,##__VA_ARGS__)
-		#define MS_LOGW(fmt,...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "[%s]" fmt, __FUNCTION__,##__VA_ARGS__)
-		#define MS_LOGE(fmt,...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "[%s]" fmt, __FUNCTION__,##__VA_ARGS__)
-		#define MS_LOGF(fmt,...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, "[%s]" fmt, __FUNCTION__,##__VA_ARGS__)
-	#endif
-#else
-	#define MS_LOGD(...) while(0){}
-	#define MS_LOGI(...) while(0){}
-	#define MS_LOGV(...) while(0){}
-	#define MS_LOGW(...) while(0){}
-	#define MS_LOGE(...) while(0){}
-	#define MS_LOGW(...) while(0){}
-#endif
-
-#define MSLog(level, fmt,...) do { \
-	printf("[%12s] " fmt "\n", __FUNCTION__,##__VA_ARGS__); \
-    __android_log_print(level, MS_LOG_TAG, "[%s]" fmt, __FUNCTION__,##__VA_ARGS__); \
-} while (false)
-#endif//SUBSTRATE_LOG_HPP
diff --git a/VirtualApp/lib/src/main/jni/MSHook/MSHook.cpp b/VirtualApp/lib/src/main/jni/MSHook/MSHook.cpp
deleted file mode 100644
index ed591de80..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/MSHook.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#include <stdio.h>
-#include <unistd.h>
-#include "util.h"
-
-#include "Hooker.h"
-#include "MSHook.h"
-
-
-int inlineHook(const char *soname, const char *symbol, void *replace_func,
-               void **old_func) {
-    int ret = -1;
-    void *addr = NULL;
-    if (findSymbol(symbol, soname, (unsigned long *) &addr) < 0) {
-        return -1;
-    }
-    Cydia::MSHookFunction(addr, replace_func, old_func);
-    ret = 0;
-    return ret;
-}
-
-int findSymbol(const char *name, const char *libn,
-              unsigned long *addr) {
-    return find_name(getpid(), name, libn, addr);
-}
-
-int inlineHookDirect(unsigned int addr, void *replace_func, void **old_func) {
-    if (addr == 0) {
-        return -1;
-    }
-    Cydia::MSHookFunction((void *) addr, replace_func, old_func);
-    return 0;
-}
diff --git a/VirtualApp/lib/src/main/jni/MSHook/MSHook.h b/VirtualApp/lib/src/main/jni/MSHook/MSHook.h
deleted file mode 100644
index 4a19db7b5..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/MSHook.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef LIBHOOK_H_
-#define LIBHOOK_H_
-
-#define HOOK_FAILED -1
-#define HOOK_SUCCESS 0
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-int findSymbol(const char *name, const char *libn,
-               unsigned long *addr);
-int inlineHook(const char *soname, const char *symbol, void *replace_func, void **old_func);
-int inlineHookDirect(unsigned int addr, void *replace_func, void **old_func);
-
-#ifdef __cplusplus
-}
-#endif
-#endif /* LIBHOOK_HOOK2_H_ */
diff --git a/VirtualApp/lib/src/main/jni/MSHook/PosixMemory.cpp b/VirtualApp/lib/src/main/jni/MSHook/PosixMemory.cpp
deleted file mode 100644
index 1b53a841a..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/PosixMemory.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/* Cydia Substrate - Powerful Code Insertion Platform
- * Copyright (C) 2008-2011  Jay Freeman (saurik)
- */
-
-/* GNU Lesser General Public License, Version 3 {{{ */
-/*
- * Substrate is free software: you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or (at your
- * option) any later version.
- *
- * Substrate is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
- **/
-/* }}} */
-
-#include "CydiaSubstrate.h"
-#include "PosixMemory.h"
-#include "Log.h"
-
-#include <sys/mman.h>
-#include <errno.h>
-#include <stdio.h>
-#include <unistd.h>
-
-extern "C" SubstrateMemoryRef SubstrateMemoryCreate(
-		SubstrateAllocatorRef allocator, SubstrateProcessRef process,
-		void *data, size_t size) {
-	if (allocator != NULL) {
-		MSLog(MSLogLevelError, "MS:Error:allocator != NULL");
-		return NULL;
-	}
-
-	if (size == 0)
-		return NULL;
-
-	int page(PAGE_SIZE/*getpagesize()*/);
-
-	uintptr_t base(reinterpret_cast<uintptr_t>(data) / page * page);
-	size_t width(
-			((reinterpret_cast<uintptr_t>(data) + size - 1) / page + 1) * page
-					- base);
-	void *address(reinterpret_cast<void *>(base));
-
-	if (mprotect(address, width, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) {
-		MSLog(MSLogLevelError, "MS:Error:mprotect() = %d", errno);
-		return NULL;
-	}
-
-	return new SubstrateMemory(address, width);
-}
-
-extern "C" void SubstrateMemoryRelease(SubstrateMemoryRef memory) {
-	if (mprotect(memory->address_, memory->width_,
-			PROT_READ | PROT_WRITE | PROT_EXEC) == -1)
-		MSLog(MSLogLevelError, "MS:Error:mprotect() = %d", errno);
-
-	__clear_cache(reinterpret_cast<char *>(memory->address_),
-			reinterpret_cast<char *>(memory->address_) + memory->width_);
-
-	delete memory;
-}
diff --git a/VirtualApp/lib/src/main/jni/MSHook/PosixMemory.h b/VirtualApp/lib/src/main/jni/MSHook/PosixMemory.h
deleted file mode 100644
index 2cfdbe8d7..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/PosixMemory.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * PosixMemory.h
- *
- *  Created on: 2016��2��22��
- *      Author: peng
- */
-
-#ifndef POSIXMEMORY_H_
-#define POSIXMEMORY_H_
-
-#include "CydiaSubstrate.h"
-
-
-extern "C" SubstrateMemoryRef SubstrateMemoryCreate(SubstrateAllocatorRef allocator, SubstrateProcessRef process, void *data, size_t size);
-extern "C" void SubstrateMemoryRelease(SubstrateMemoryRef memory);
-extern "C" void __clear_cache(void *beg, void *end);
-
-struct SubstrateHookMemory {
-	SubstrateMemoryRef handle_;
-
-	SubstrateHookMemory(SubstrateProcessRef process, void *data, size_t size) :	handle_(SubstrateMemoryCreate(NULL, NULL, data, size)) {}
-
-	~SubstrateHookMemory() {
-		if (handle_ != NULL)
-			SubstrateMemoryRelease(handle_);
-	}
-};
-
-#endif /* POSIXMEMORY_H_ */
diff --git a/VirtualApp/lib/src/main/jni/MSHook/SubstrateStruct.h b/VirtualApp/lib/src/main/jni/MSHook/SubstrateStruct.h
deleted file mode 100644
index 66677cd49..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/SubstrateStruct.h
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * SubstrateMacro.h
- *
- *  Created on: 2016��2��22��
- *      Author: peng
- */
-
-#ifndef SUBSTRATEMACRO_H_
-#define SUBSTRATEMACRO_H_
-
-#include <stdlib.h>
-typedef struct __SubstrateProcess *SubstrateProcessRef;
-typedef void *SubstrateAllocatorRef;
-typedef struct SubstrateMemory {
-    void *address_;
-    size_t width_;
-	SubstrateMemory(void *address, size_t width):address_(address), width_(width) {}
-}*SubstrateMemoryRef;
-
-#endif /* SUBSTRATEMACRO_H_ */
diff --git a/VirtualApp/lib/src/main/jni/MSHook/Thumb.cpp b/VirtualApp/lib/src/main/jni/MSHook/Thumb.cpp
deleted file mode 100644
index 5380dd919..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/Thumb.cpp
+++ /dev/null
@@ -1,407 +0,0 @@
-#include "ARM.h"
-#include "Thumb.h"
-
-static size_t Thumb::MSGetInstructionWidth(void *start) {
-	if ((reinterpret_cast<uintptr_t>(start) & 0x1) == 0)
-		return MSGetInstructionWidthARM(start);
-	else
-		return MSGetInstructionWidthThumb(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(start) & ~0x1));
-}
-
-void Thumb::SubstrateHookFunctionThumb(SubstrateProcessRef process, void *symbol, void *replace, void **result){
-    if (symbol == NULL)
-        return;
-
-    uint16_t *area(reinterpret_cast<uint16_t *>(symbol));
-
-    unsigned align((reinterpret_cast<uintptr_t>(area) & 0x2) == 0 ? 0 : 1);
-    uint16_t *thumb(area + align);
-
-    uint32_t *arm(reinterpret_cast<uint32_t *>(thumb + 2));
-    uint16_t *trail(reinterpret_cast<uint16_t *>(arm + 2));
-
-    if (
-        (align == 0 || area[0] == T$nop) &&
-        thumb[0] == T$bx(A$pc) &&
-        thumb[1] == T$nop &&
-        arm[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8)
-    ) {
-        if (result != NULL)
-            *result = reinterpret_cast<void *>(arm[1]);
-
-        SubstrateHookMemory code(process, arm + 1, sizeof(uint32_t) * 1);
-
-        arm[1] = reinterpret_cast<uint32_t>(replace);
-
-        return;
-    }
-
-    size_t required((trail - area) * sizeof(uint16_t));
-
-    size_t used(0);
-    while (used < required)
-        used += MSGetInstructionWidthThumb(reinterpret_cast<uint8_t *>(area) + used);
-    used = (used + sizeof(uint16_t) - 1) / sizeof(uint16_t) * sizeof(uint16_t);
-
-    size_t blank((used - required) / sizeof(uint16_t));
-
-    uint16_t backup[used / sizeof(uint16_t)];
-    memcpy(backup, area, used);
-
-    if (MSDebug) {
-        char name[16];
-        sprintf(name, "%p", area);
-        MSLogHexEx(area, used + sizeof(uint16_t), 2, name);
-    }
-
-    if (result != NULL) {
-
-    size_t length(used);
-    for (unsigned offset(0); offset != used / sizeof(uint16_t); ++offset)
-        if (T$pcrel$ldr(backup[offset]))
-            length += 3 * sizeof(uint16_t);
-        else if (T$pcrel$b(backup[offset]))
-            length += 6 * sizeof(uint16_t);
-        else if (T2$pcrel$b(backup + offset)) {
-            length += 5 * sizeof(uint16_t);
-            ++offset;
-        } else if (T$pcrel$bl(backup + offset)) {
-            length += 5 * sizeof(uint16_t);
-            ++offset;
-        } else if (T$pcrel$cbz(backup[offset])) {
-            length += 16 * sizeof(uint16_t);
-        } else if (T$pcrel$ldrw(backup[offset])) {
-            length += 4 * sizeof(uint16_t);
-            ++offset;
-        } else if (T$pcrel$add(backup[offset]))
-            length += 6 * sizeof(uint16_t);
-        else if (T$32bit$i(backup[offset]))
-            ++offset;
-
-    unsigned pad((length & 0x2) == 0 ? 0 : 1);
-    length += (pad + 2) * sizeof(uint16_t) + 2 * sizeof(uint32_t);
-
-    uint16_t *buffer(reinterpret_cast<uint16_t *>(mmap(
-        NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0
-    )));
-
-    if (buffer == MAP_FAILED) {
-        MSLog(MSLogLevelError, "MS:Error:mmap() = %d", errno);
-        *result = NULL;
-        return;
-    }
-
-    if (false) fail: {
-        munmap(buffer, length);
-        *result = NULL;
-        return;
-    }
-
-    size_t start(pad), end(length / sizeof(uint16_t));
-    uint32_t *trailer(reinterpret_cast<uint32_t *>(buffer + end));
-    for (unsigned offset(0); offset != used / sizeof(uint16_t); ++offset) {
-        if (T$pcrel$ldr(backup[offset])) {
-            union {
-                uint16_t value;
-
-                struct {
-                    uint16_t immediate : 8;
-                    uint16_t rd : 3;
-                    uint16_t : 5;
-                };
-            } bits = {backup[offset+0]};
-
-            buffer[start+0] = T$ldr_rd_$pc_im_4$(bits.rd, T$Label(start+0, end-2) / 4);
-            buffer[start+1] = T$ldr_rd_$rn_im_4$(bits.rd, bits.rd, 0);
-
-            // XXX: this code "works", but is "wrong": the mechanism is more complex than this
-            *--trailer = ((reinterpret_cast<uint32_t>(area + offset) + 4) & ~0x2) + bits.immediate * 4;
-
-            start += 2;
-            end -= 2;
-        } else if (T$pcrel$b(backup[offset])) {
-            union {
-                uint16_t value;
-
-                struct {
-                    uint16_t imm8 : 8;
-                    uint16_t cond : 4;
-                    uint16_t /*1101*/ : 4;
-                };
-            } bits = {backup[offset+0]};
-
-            intptr_t jump(bits.imm8 << 1);
-            jump |= 1;
-            jump <<= 23;
-            jump >>= 23;
-
-            buffer[start+0] = T$b$_$im(bits.cond, (end-6 - (start+0)) * 2 - 4);
-
-            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 4 + jump;
-            *--trailer = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
-            *--trailer = T$nop << 16 | T$bx(A$pc);
-
-            start += 1;
-            end -= 6;
-        } else if (T2$pcrel$b(backup + offset)) {
-            union {
-                uint16_t value;
-
-                struct {
-                    uint16_t imm6 : 6;
-                    uint16_t cond : 4;
-                    uint16_t s : 1;
-                    uint16_t : 5;
-                };
-            } bits = {backup[offset+0]};
-
-            union {
-                uint16_t value;
-
-                struct {
-                    uint16_t imm11 : 11;
-                    uint16_t j2 : 1;
-                    uint16_t a : 1;
-                    uint16_t j1 : 1;
-                    uint16_t : 2;
-                };
-            } exts = {backup[offset+1]};
-
-            intptr_t jump(1);
-            jump |= exts.imm11 << 1;
-            jump |= bits.imm6 << 12;
-
-            if (exts.a) {
-                jump |= bits.s << 24;
-                jump |= (~(bits.s ^ exts.j1) & 0x1) << 23;
-                jump |= (~(bits.s ^ exts.j2) & 0x1) << 22;
-                jump |= bits.cond << 18;
-                jump <<= 7;
-                jump >>= 7;
-            } else {
-                jump |= bits.s << 20;
-                jump |= exts.j2 << 19;
-                jump |= exts.j1 << 18;
-                jump <<= 11;
-                jump >>= 11;
-            }
-
-            buffer[start+0] = T$b$_$im(exts.a ? A$al : bits.cond, (end-6 - (start+0)) * 2 - 4);
-
-            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 4 + jump;
-            *--trailer = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
-            *--trailer = T$nop << 16 | T$bx(A$pc);
-
-            ++offset;
-            start += 1;
-            end -= 6;
-        } else if (T$pcrel$bl(backup + offset)) {
-            union {
-                uint16_t value;
-
-                struct {
-                    uint16_t immediate : 10;
-                    uint16_t s : 1;
-                    uint16_t : 5;
-                };
-            } bits = {backup[offset+0]};
-
-            union {
-                uint16_t value;
-
-                struct {
-                    uint16_t immediate : 11;
-                    uint16_t j2 : 1;
-                    uint16_t x : 1;
-                    uint16_t j1 : 1;
-                    uint16_t : 2;
-                };
-            } exts = {backup[offset+1]};
-
-            int32_t jump(0);
-            jump |= bits.s << 24;
-            jump |= (~(bits.s ^ exts.j1) & 0x1) << 23;
-            jump |= (~(bits.s ^ exts.j2) & 0x1) << 22;
-            jump |= bits.immediate << 12;
-            jump |= exts.immediate << 1;
-            jump |= exts.x;
-            jump <<= 7;
-            jump >>= 7;
-
-            buffer[start+0] = T$push_r(1 << A$r7);
-            buffer[start+1] = T$ldr_rd_$pc_im_4$(A$r7, ((end-2 - (start+1)) * 2 - 4 + 2) / 4);
-            buffer[start+2] = T$mov_rd_rm(A$lr, A$r7);
-            buffer[start+3] = T$pop_r(1 << A$r7);
-            buffer[start+4] = T$blx(A$lr);
-
-            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 4 + jump;
-
-            ++offset;
-            start += 5;
-            end -= 2;
-        } else if (T$pcrel$cbz(backup[offset])) {
-            union {
-                uint16_t value;
-
-                struct {
-                    uint16_t rn : 3;
-                    uint16_t immediate : 5;
-                    uint16_t : 1;
-                    uint16_t i : 1;
-                    uint16_t : 1;
-                    uint16_t op : 1;
-                    uint16_t : 4;
-                };
-            } bits = {backup[offset+0]};
-
-            intptr_t jump(1);
-            jump |= bits.i << 6;
-            jump |= bits.immediate << 1;
-
-            //jump <<= 24;
-            //jump >>= 24;
-
-            unsigned rn(bits.rn);
-            unsigned rt(rn == A$r7 ? A$r6 : A$r7);
-
-            buffer[start+0] = T$push_r(1 << rt);
-            buffer[start+1] = T1$mrs_rd_apsr(rt);
-            buffer[start+2] = T2$mrs_rd_apsr(rt);
-            buffer[start+3] = T$cbz$_rn_$im(bits.op, rn, (end-10 - (start+3)) * 2 - 4);
-            buffer[start+4] = T1$msr_apsr_nzcvqg_rn(rt);
-            buffer[start+5] = T2$msr_apsr_nzcvqg_rn(rt);
-            buffer[start+6] = T$pop_r(1 << rt);
-
-            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 4 + jump;
-            *--trailer = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
-            *--trailer = T$nop << 16 | T$bx(A$pc);
-            *--trailer = T$nop << 16 | T$pop_r(1 << rt);
-            *--trailer = T$msr_apsr_nzcvqg_rn(rt);
-
-#if 0
-            if ((start & 0x1) == 0)
-                buffer[start++] = T$nop;
-            buffer[start++] = T$bx(A$pc);
-            buffer[start++] = T$nop;
-
-            uint32_t *arm(reinterpret_cast<uint32_t *>(buffer + start));
-            arm[0] = A$add(A$lr, A$pc, 1);
-            arm[1] = A$ldr_rd_$rn_im$(A$pc, A$pc, (trailer - arm) * sizeof(uint32_t) - 8);
-#endif
-
-            start += 7;
-            end -= 10;
-        } else if (T$pcrel$ldrw(backup[offset])) {
-            union {
-                uint16_t value;
-
-                struct {
-                    uint16_t : 7;
-                    uint16_t u : 1;
-                    uint16_t : 8;
-                };
-            } bits = {backup[offset+0]};
-
-            union {
-                uint16_t value;
-
-                struct {
-                    uint16_t immediate : 12;
-                    uint16_t rt : 4;
-                };
-            } exts = {backup[offset+1]};
-
-            buffer[start+0] = T1$ldr_rt_$rn_im$(exts.rt, A$pc, T$Label(start+0, end-2));
-            buffer[start+1] = T2$ldr_rt_$rn_im$(exts.rt, A$pc, T$Label(start+0, end-2));
-
-            buffer[start+2] = T1$ldr_rt_$rn_im$(exts.rt, exts.rt, 0);
-            buffer[start+3] = T2$ldr_rt_$rn_im$(exts.rt, exts.rt, 0);
-
-            // XXX: this code "works", but is "wrong": the mechanism is more complex than this
-            *--trailer = ((reinterpret_cast<uint32_t>(area + offset) + 4) & ~0x2) + (bits.u == 0 ? -exts.immediate : exts.immediate);
-
-            ++offset;
-            start += 4;
-            end -= 2;
-        } else if (T$pcrel$add(backup[offset])) {
-            union {
-                uint16_t value;
-
-                struct {
-                    uint16_t rd : 3;
-                    uint16_t rm : 3;
-                    uint16_t h2 : 1;
-                    uint16_t h1 : 1;
-                    uint16_t : 8;
-                };
-            } bits = {backup[offset+0]};
-
-            if (bits.h1) {
-                MSLog(MSLogLevelError, "MS:Error:pcrel(%u):add (rd > r7)", offset);
-                goto fail;
-            }
-
-            unsigned rt(bits.rd == A$r7 ? A$r6 : A$r7);
-
-            buffer[start+0] = T$push_r(1 << rt);
-            buffer[start+1] = T$mov_rd_rm(rt, (bits.h1 << 3) | bits.rd);
-            buffer[start+2] = T$ldr_rd_$pc_im_4$(bits.rd, T$Label(start+2, end-2) / 4);
-            buffer[start+3] = T$add_rd_rm((bits.h1 << 3) | bits.rd, rt);
-            buffer[start+4] = T$pop_r(1 << rt);
-            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 4;
-
-            start += 5;
-            end -= 2;
-        } else if (T$32bit$i(backup[offset])) {
-            buffer[start++] = backup[offset];
-            buffer[start++] = backup[++offset];
-        } else {
-            buffer[start++] = backup[offset];
-        }
-    }
-
-    buffer[start++] = T$bx(A$pc);
-    buffer[start++] = T$nop;
-
-    uint32_t *transfer = reinterpret_cast<uint32_t *>(buffer + start);
-    transfer[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
-    transfer[1] = reinterpret_cast<uint32_t>(area + used / sizeof(uint16_t)) + 1;
-
-    if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) {
-        MSLog(MSLogLevelError, "MS:Error:mprotect():%d", errno);
-        return;
-    }
-
-    *result = reinterpret_cast<uint8_t *>(buffer + pad) + 1;
-
-    if (MSDebug) {
-        char name[16];
-        sprintf(name, "%p", *result);
-        MSLogHexEx(buffer, length, 2, name);
-    }
-
-    }
-
-    {
-        SubstrateHookMemory code(process, area, used);
-
-        if (align != 0)
-            area[0] = T$nop;
-
-        thumb[0] = T$bx(A$pc);
-        thumb[1] = T$nop;
-
-        arm[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
-        arm[1] = reinterpret_cast<uint32_t>(replace);
-
-        for (unsigned offset(0); offset != blank; ++offset)
-            trail[offset] = T$nop;
-    }
-
-    if (MSDebug) {
-        char name[16];
-        sprintf(name, "%p", area);
-        MSLogHexEx(area, used + sizeof(uint16_t), 2, name);
-    }
-}
-
diff --git a/VirtualApp/lib/src/main/jni/MSHook/Thumb.h b/VirtualApp/lib/src/main/jni/MSHook/Thumb.h
deleted file mode 100644
index faf3aa462..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/Thumb.h
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Thumb.h
- *
- *  Created on: 2016��2��22��
- *      Author: peng
- */
-
-#ifndef THUMB_H_
-#define THUMB_H_
-
-#include "Debug.h"
-#include "Log.h"
-#include "PosixMemory.h"
-#include <stdlib.h>
-#include <errno.h>
-#include <sys/mman.h>
-
-#define T$Label(l, r) \
-	(((r) - (l)) * 2 - 4 + ((l) % 2 == 0 ? 0 : 2))
-#define T$pop_$r0$ 0xbc01 // pop {r0}
-#define T$b(im) /* b im */ \
-	(0xde00 | (im & 0xff))
-#define T$blx(rm) /* blx rm */ \
-	(0x4780 | (rm << 3))
-#define T$bx(rm) /* bx rm */ \
-	(0x4700 | (rm << 3))
-#define T$nop /* nop */ \
-	(0x46c0)
-#define T$add_rd_rm(rd, rm) /* add rd, rm */ \
-	(0x4400 | (((rd) & 0x8) >> 3 << 7) | (((rm) & 0x8) >> 3 << 6) | (((rm) & 0x7) << 3) | ((rd) & 0x7))
-#define T$push_r(r) /* push r... */ \
-	(0xb400 | (((r) & (1 << A$lr)) >> A$lr << 8) | ((r) & 0xff))
-#define T$pop_r(r) /* pop r... */ \
-	(0xbc00 | (((r) & (1 << A$pc)) >> A$pc << 8) | ((r) & 0xff))
-#define T$mov_rd_rm(rd, rm) /* mov rd, rm */ \
-	(0x4600 | (((rd) & 0x8) >> 3 << 7) | (((rm) & 0x8) >> 3 << 6) | (((rm) & 0x7) << 3) | ((rd) & 0x7))
-#define T$ldr_rd_$rn_im_4$(rd, rn, im) /* ldr rd, [rn, #im * 4] */ \
-	(0x6800 | (((im) & 0x1f) << 6) | ((rn) << 3) | (rd))
-#define T$ldr_rd_$pc_im_4$(rd, im) /* ldr rd, [PC, #im * 4] */ \
-	(0x4800 | ((rd) << 8) | ((im) & 0xff))
-#define T$cmp_rn_$im(rn, im) /* cmp rn, #im */ \
-	(0x2000 | ((rn) << 8) | ((im) & 0xff))
-#define T$it$_cd(cd, ms) /* it<ms>, cd */ \
-	(0xbf00 | ((cd) << 4) | (ms))
-#define T$cbz$_rn_$im(op,rn,im) /* cb<op>z rn, #im */ \
-	(0xb100 | ((op) << 11) | (((im) & 0x40) >> 6 << 9) | (((im) & 0x3e) >> 1 << 3) | (rn))
-#define T$b$_$im(cond,im) /* b<cond> #im */ \
-	(cond == A$al ? 0xe000 | (((im) >> 1) & 0x7ff) : 0xd000 | ((cond) << 8) | (((im) >> 1) & 0xff))
-#define T1$ldr_rt_$rn_im$(rt, rn, im) /* ldr rt, [rn, #im] */ \
-	(0xf850 | ((im < 0 ? 0 : 1) << 7) | (rn))
-#define T2$ldr_rt_$rn_im$(rt, rn, im) /* ldr rt, [rn, #im] */ \
-	(((rt) << 12) | abs(im))
-#define T1$mrs_rd_apsr(rd) /* mrs rd, apsr */ \
-	(0xf3ef)
-#define T2$mrs_rd_apsr(rd) /* mrs rd, apsr */ \
-	(0x8000 | ((rd) << 8))
-#define T1$msr_apsr_nzcvqg_rn(rn) /* msr apsr, rn */ \
-	(0xf380 | (rn))
-#define T2$msr_apsr_nzcvqg_rn(rn) /* msr apsr, rn */ \
-	(0x8c00)
-#define T$msr_apsr_nzcvqg_rn(rn) /* msr apsr, rn */ \
-	(T2$msr_apsr_nzcvqg_rn(rn) << 16 | T1$msr_apsr_nzcvqg_rn(rn))
-#define A$ldr_rd_$rn_im$(rd, rn, im) /* ldr rd, [rn, #im] */ \
-    (0xe5100000 | ((im) < 0 ? 0 : 1 << 23) | ((rn) << 16) | ((rd) << 12) | abs(im))
-
-static inline bool T$32bit$i(uint16_t ic) {
-	return ((ic & 0xe000) == 0xe000 && (ic & 0x1800) != 0x0000);
-}
-
-static inline bool T$pcrel$cbz(uint16_t ic) {
-	return (ic & 0xf500) == 0xb100;
-}
-
-static inline bool T$pcrel$b(uint16_t ic) {
-	return (ic & 0xf000) == 0xd000 && (ic & 0x0e00) != 0x0e00;
-}
-
-static inline bool T2$pcrel$b(uint16_t *ic) {
-	return (ic[0] & 0xf800) == 0xf000 && ((ic[1] & 0xd000) == 0x9000 || ((ic[1] & 0xd000) == 0x8000 && (ic[0] & 0x0380) != 0x0380));
-}
-
-static inline bool T$pcrel$bl(uint16_t *ic) {
-	return (ic[0] & 0xf800) == 0xf000 && ((ic[1] & 0xd000) == 0xd000 || (ic[1] & 0xd001) == 0xc000);
-}
-
-static inline bool T$pcrel$ldr(uint16_t ic) {
-	return (ic & 0xf800) == 0x4800;
-}
-
-static inline bool T$pcrel$add(uint16_t ic) {
-	return (ic & 0xff78) == 0x4478;
-}
-
-static inline bool T$pcrel$ldrw(uint16_t ic) {
-	return (ic & 0xff7f) == 0xf85f;
-}
-
-static size_t MSGetInstructionWidthThumb(void *start) {
-	uint16_t *thumb(reinterpret_cast<uint16_t *>(start));
-	return T$32bit$i(thumb[0]) ? 4 : 2;
-}
-
-static size_t MSGetInstructionWidthARM(void *start) {
-	return 4;
-}
-
-namespace Thumb{
-	static size_t MSGetInstructionWidth(void *start);
-	extern "C" void SubstrateHookFunctionThumb(SubstrateProcessRef process, void *symbol, void *replace, void **result);
-}
-#endif /* THUMB_H_ */
diff --git a/VirtualApp/lib/src/main/jni/MSHook/util.cpp b/VirtualApp/lib/src/main/jni/MSHook/util.cpp
deleted file mode 100644
index e63421cee..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/util.cpp
+++ /dev/null
@@ -1,426 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <elf.h>
-#include <sys/mman.h>
-#include <Helper.h>
-#include "Log.h"
-
-/* memory map for libraries */
-#define MAX_NAME_LEN 256
-#define MEMORY_ONLY  "[memory]"
-struct mm {
-	char name[MAX_NAME_LEN];
-	unsigned long start, end;
-};
-
-typedef struct symtab *symtab_t;
-struct symlist {
-	Elf32_Sym *sym; /* symbols */
-	char *str; /* symbol strings */
-	unsigned num; /* number of symbols */
-};
-struct symtab {
-	struct symlist *st; /* "static" symbols */
-	struct symlist *dyn; /* dynamic symbols */
-};
-
-static void* xmalloc(size_t size) {
-	void *p;
-	p = malloc(size);
-	if (!p) {
-		printf("Out of memory\n");
-		exit(1);
-	}
-	return p;
-}
-
-static int my_pread(int fd, void *buf, size_t count, off_t offset) {
-	lseek(fd, offset, SEEK_SET);
-	return read(fd, buf, count);
-}
-
-static struct symlist* get_syms(int fd, Elf32_Shdr *symh, Elf32_Shdr *strh) {
-	struct symlist *sl, *ret;
-	int rv;
-
-	ret = NULL;
-	sl = (struct symlist *) xmalloc(sizeof(struct symlist));
-	sl->str = NULL;
-	sl->sym = NULL;
-
-	/* sanity */
-	if (symh->sh_size % sizeof(Elf32_Sym)) {
-		//printf("elf_error\n");
-		goto out;
-	}
-
-	/* symbol table */
-	sl->num = symh->sh_size / sizeof(Elf32_Sym);
-	sl->sym = (Elf32_Sym *) xmalloc(symh->sh_size);
-	rv = my_pread(fd, sl->sym, symh->sh_size, symh->sh_offset);
-	if (0 > rv) {
-		//perror("read");
-		goto out;
-	}
-	if (rv != symh->sh_size) {
-		//printf("elf error\n");
-		goto out;
-	}
-
-	/* string table */
-	sl->str = (char *) xmalloc(strh->sh_size);
-	rv = my_pread(fd, sl->str, strh->sh_size, strh->sh_offset);
-	if (0 > rv) {
-		//perror("read");
-		goto out;
-	}
-	if (rv != strh->sh_size) {
-		//printf("elf error");
-		goto out;
-	}
-
-	ret = sl;
-	out: return ret;
-}
-
-static int do_load(int fd, symtab_t symtab) {
-	int rv;
-	size_t size;
-	Elf32_Ehdr ehdr;
-	Elf32_Shdr *shdr = NULL, *p;
-	Elf32_Shdr *dynsymh, *dynstrh;
-	Elf32_Shdr *symh, *strh;
-	char *shstrtab = NULL;
-	int i;
-	int ret = -1;
-
-	/* elf header */
-	rv = read(fd, &ehdr, sizeof(ehdr));
-	if (0 > rv) {
-		LOGD("read\n");
-		goto out;
-	}
-	if (rv != sizeof(ehdr)) {
-		LOGD("elf error 1\n");
-		goto out;
-	}
-	if (strncmp((const char *) ELFMAG, (const char *) ehdr.e_ident, SELFMAG)) { /* sanity */
-		LOGD("not an elf\n");
-		goto out;
-	}
-	if (sizeof(Elf32_Shdr) != ehdr.e_shentsize) { /* sanity */
-		LOGD("elf error 2\n");
-		goto out;
-	}
-
-	/* section header table */
-	size = ehdr.e_shentsize * ehdr.e_shnum;
-	shdr = (Elf32_Shdr *) xmalloc(size);
-	rv = my_pread(fd, shdr, size, ehdr.e_shoff);
-	if (0 > rv) {
-		LOGD("read\n");
-		goto out;
-	}
-	if (rv != size) {
-		LOGD("elf error 3 %d %d\n", rv, size);
-		goto out;
-	}
-
-	/* section header string table */
-	size = shdr[ehdr.e_shstrndx].sh_size;
-	shstrtab = (char *) xmalloc(size);
-	rv = my_pread(fd, shstrtab, size, shdr[ehdr.e_shstrndx].sh_offset);
-	if (0 > rv) {
-		LOGD("read\n");
-		goto out;
-	}
-	if (rv != size) {
-		LOGD("elf error 4 %d %d\n", rv, size);
-		goto out;
-	}
-
-	/* symbol table headers */
-	symh = dynsymh = NULL;
-	strh = dynstrh = NULL;
-	for (i = 0, p = shdr; i < ehdr.e_shnum; i++, p++)
-		if (SHT_SYMTAB == p->sh_type) {
-			if (symh) {
-				LOGD("too many symbol tables\n");
-				goto out;
-			}
-			symh = p;
-		} else if (SHT_DYNSYM == p->sh_type) {
-			if (dynsymh) {
-				LOGD("too many symbol tables\n");
-				goto out;
-			}
-			dynsymh = p;
-		} else if (SHT_STRTAB == p->sh_type
-				&& !strncmp(shstrtab + p->sh_name, ".strtab", 7)) {
-			if (strh) {
-				LOGD("too many string tables\n");
-				goto out;
-			}
-			strh = p;
-		} else if (SHT_STRTAB == p->sh_type
-				&& !strncmp(shstrtab + p->sh_name, ".dynstr", 7)) {
-			if (dynstrh) {
-				LOGD("too many string tables\n");
-				goto out;
-			}
-			dynstrh = p;
-		}
-	/* sanity checks */
-	if ((!dynsymh && dynstrh) || (dynsymh && !dynstrh)) {
-		LOGD("bad dynamic symbol table\n");
-		goto out;
-	}
-	if ((!symh && strh) || (symh && !strh)) {
-		LOGD("bad symbol table\n");
-		goto out;
-	}
-	if (!dynsymh && !symh) {
-		LOGD("no symbol table\n");
-		goto out;
-	}
-
-	/* symbol tables */
-	if (dynsymh)
-		symtab->dyn = get_syms(fd, dynsymh, dynstrh);
-	if (symh)
-		symtab->st = get_syms(fd, symh, strh);
-	ret = 0;
-	out: free(shstrtab);
-	free(shdr);
-	return ret;
-}
-
-static symtab_t load_symtab(char *filename) {
-	int fd;
-	symtab_t symtab;
-
-	symtab = (symtab_t) xmalloc(sizeof(*symtab));
-	memset(symtab, 0, sizeof(*symtab));
-
-	fd = open(filename, O_RDONLY);
-	if (0 > fd) {
-		LOGE("%s open\n", __func__);
-		return NULL;
-	}
-	if (0 > do_load(fd, symtab)) {
-		LOGE("Error ELF parsing %s\n", filename);
-		free(symtab);
-		symtab = NULL;
-	}
-	close(fd);
-	return symtab;
-}
-
-static int load_memmap(pid_t pid, struct mm *mm, int *nmmp) {
-	size_t buf_size = 0x40000;
-	char *p_buf = (char *) malloc(buf_size); // increase this if needed for larger "maps"
-	char name[MAX_NAME_LEN] = { 0 };
-	char *p;
-	unsigned long start, end;
-	struct mm *m;
-	int nmm = 0;
-	int fd, rv;
-	int i;
-
-	sprintf(p_buf, "/proc/%d/maps", pid);
-	fd = open(p_buf, O_RDONLY);
-	if (0 > fd) {
-		LOGE("Can't open %s for reading\n", p_buf);
-		free(p_buf);
-		return -1;
-	}
-
-	/* Zero to ensure data is null terminated */
-	memset(p_buf, 0, buf_size);
-
-	p = p_buf;
-	while (1) {
-		rv = read(fd, p, buf_size - (p - p_buf));
-		if (0 > rv) {
-			LOGE("%s read", __FUNCTION__);
-			free(p_buf);
-			return -1;
-		}
-		if (0 == rv)
-			break;
-		p += rv;
-		if (p - p_buf >= buf_size) {
-			LOGE("Too many memory mapping\n");
-			free(p_buf);
-			return -1;
-		}
-	}
-	close(fd);
-
-	p = strtok(p_buf, "\n");
-	m = mm;
-	while (p) {
-		/* parse current map line */
-		rv = sscanf(p, "%08lx-%08lx %*s %*s %*s %*s %s\n", &start, &end, name);
-
-		p = strtok(NULL, "\n");
-
-		if (rv == 2) {
-			m = &mm[nmm++];
-			m->start = start;
-			m->end = end;
-			memcpy(m->name, MEMORY_ONLY, sizeof(MEMORY_ONLY));
-			continue;
-		}
-
-		/* search backward for other mapping with same name */
-		for (i = nmm - 1; i >= 0; i--) {
-			m = &mm[i];
-			if (!strcmp(m->name, name))
-				break;
-		}
-
-		if (i >= 0) {
-			if (start < m->start)
-				m->start = start;
-			if (end > m->end)
-				m->end = end;
-		} else {
-			/* new entry */
-			m = &mm[nmm++];
-			m->start = start;
-			m->end = end;
-			memcpy(m->name, name, strlen(name));
-		}
-	}
-
-	*nmmp = nmm;
-	free(p_buf);
-	return 0;
-}
-
-/* Find libc in MM, storing no more than LEN-1 chars of
- its name in NAME and set START to its starting
- address.  If libc cannot be found return -1 and
- leave NAME and START untouched.  Otherwise return 0
- and null-terminated NAME. */
-static int find_libname(const char *libn, char *name, int len, unsigned long *start,
-		struct mm *mm, int nmm) {
-	int i;
-	struct mm *m;
-	char *p;
-	for (i = 0, m = mm; i < nmm; i++, m++) {
-		if (!strcmp(m->name, MEMORY_ONLY))
-			continue;
-		p = strrchr(m->name, '/');
-		if (!p)
-			continue;
-		p++;
-		if (strncmp(libn, p, strlen(libn)))
-			continue;
-		p += strlen(libn);
-
-		/* here comes our crude test -> 'libc.so' or 'libc-[0-9]' */
-		if (!strncmp("so", p, 2) || 1) // || (p[0] == '-' && isdigit(p[1])))
-			break;
-	}
-	if (i >= nmm)
-		/* not found */
-		return -1;
-
-	*start = m->start;
-	strncpy(name, m->name, len);
-	if (strlen(m->name) >= len)
-		name[len - 1] = '\0';
-
-	mprotect((void*) m->start, m->end - m->start,
-			PROT_READ | PROT_WRITE | PROT_EXEC);
-	return 0;
-}
-
-static int lookup2(struct symlist *sl, unsigned char type, char *name,
-		unsigned long *val) {
-	Elf32_Sym *p;
-	int len;
-	int i;
-
-	len = strlen(name);
-	for (i = 0, p = sl->sym; i < sl->num; i++, p++) {
-		//LOGD("name: %s %x\n", sl->str+p->st_name, p->st_value)
-		if (!strncmp(sl->str + p->st_name, name, len)
-				&& *(sl->str + p->st_name + len) == 0
-				&& ELF32_ST_TYPE(p->st_info) == type) {
-			//if (p->st_value != 0) {
-			*val = p->st_value;
-			return 0;
-			//}
-		}
-	}
-	return -1;
-}
-
-static int lookup_sym(symtab_t s, unsigned char type, char *name,
-		unsigned long *val) {
-	if (s->dyn && !lookup2(s->dyn, type, name, val))
-		return 0;
-	if (s->st && !lookup2(s->st, type, name, val))
-		return 0;
-	return -1;
-}
-
-static int lookup_func_sym(symtab_t s, char *name, unsigned long *val) {
-	return lookup_sym(s, STT_FUNC, name, val);
-}
-
-int find_name(pid_t pid, const char *name, const char *libn,
-		unsigned long *addr) {
-	struct mm mm[1000] = { 0 };
-	unsigned long libcaddr;
-	int nmm;
-	char libc[1024] = { 0 };
-	symtab_t s;
-
-	if (0 > load_memmap(pid, mm, &nmm)) {
-		LOGD("cannot read memory map\n");
-		return -1;
-	}
-	if (0
-			> find_libname((char *) libn, (char *) libc, sizeof(libc),
-					&libcaddr, mm, nmm)) {
-		LOGD("cannot find lib: %s\n", libn);
-		return -1;
-	}
-	//LOGD("lib: >%s<\n", libc)
-	s = load_symtab(libc);
-	if (!s) {
-		LOGD("cannot read symbol table\n");
-		return -1;
-	}
-	if (0 > lookup_func_sym(s, (char *) name, addr)) {
-		LOGD("cannot find function: %s\n", name);
-		return -1;
-	}
-	*addr += libcaddr;
-	return 0;
-}
-
-int find_libbase(pid_t pid, const char *libn, unsigned long *addr) {
-	struct mm mm[1000] = { 0 };
-	unsigned long libcaddr;
-	int nmm;
-	char libc[1024] = { 0 };
-	symtab_t s;
-
-	if (0 > load_memmap(pid, mm, &nmm)) {
-		LOGD("cannot read memory map\n");
-		return -1;
-	}
-	if (0 > find_libname(libn, libc, sizeof(libc), &libcaddr, mm, nmm)) {
-		LOGD("cannot find lib\n");
-		return -1;
-	}
-	*addr = libcaddr;
-	return 0;
-}
-
diff --git a/VirtualApp/lib/src/main/jni/MSHook/x86.cpp b/VirtualApp/lib/src/main/jni/MSHook/x86.cpp
deleted file mode 100644
index 24c6e6bea..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/x86.cpp
+++ /dev/null
@@ -1,220 +0,0 @@
-#include "x86.h"
-#include "x86_64.h"
-
-static size_t MSGetInstructionWidthIntel(void *start) {
-    hde64s decode;
-    return hde64_disasm(start, &decode);
-}
-
-void x86::SubstrateHookFunctionx86(SubstrateProcessRef process, void *symbol, void *replace, void **result){
-    if (MSDebug)
-        MSLog(MSLogLevelNotice, "SubstrateHookFunctionx86(process:%p, symbol:%p, replace:%p, result:%p)", process, symbol, replace, result);
-    if (symbol == NULL)
-        return;
-
-    uintptr_t source(reinterpret_cast<uintptr_t>(symbol));
-    uintptr_t target(reinterpret_cast<uintptr_t>(replace));
-
-    uint8_t *area(reinterpret_cast<uint8_t *>(symbol));
-
-    size_t required(MSSizeOfJump(target, source));
-
-    if (MSDebug) {
-        char name[16];
-        sprintf(name, "%p", area);
-        MSLogHex(area, 32, name);
-    }
-
-    size_t used(0);
-    while (used < required) {
-        size_t width(MSGetInstructionWidthIntel(area + used));
-        if (width == 0) {
-            MSLog(MSLogLevelError, "MS:Error:MSGetInstructionWidthIntel(%p) == 0", area + used);
-            return;
-        }
-
-        used += width;
-    }
-
-    size_t blank(used - required);
-
-    if (MSDebug) {
-        char name[16];
-        sprintf(name, "%p", area);
-        MSLogHex(area, used + sizeof(uint16_t), name);
-    }
-
-    uint8_t backup[used];
-    memcpy(backup, area, used);
-
-    if (result != NULL) {
-
-    if (backup[0] == 0xe9) {
-        *result = reinterpret_cast<void *>(source + 5 + *reinterpret_cast<uint32_t *>(backup + 1));
-        return;
-    }
-
-    if (!ia32 && backup[0] == 0xff && backup[1] == 0x25) {
-        *result = *reinterpret_cast<void **>(source + 6 + *reinterpret_cast<uint32_t *>(backup + 2));
-        return;
-    }
-
-    size_t length(used + MSSizeOfJump(source + used));
-
-    for (size_t offset(0), width; offset != used; offset += width) {
-        hde64s decode;
-        hde64_disasm(backup + offset, &decode);
-        width = decode.len;
-        //_assert(width != 0 && offset + width <= used);
-
-#ifdef __LP64__
-        if ((decode.modrm & 0xc7) == 0x05) {
-            if (decode.opcode == 0x8b) {
-                void *destiny(area + offset + width + int32_t(decode.disp.disp32));
-                uint8_t reg(decode.rex_r << 3 | decode.modrm_reg);
-                length -= decode.len;
-                length += MSSizeOfPushPointer(destiny);
-                length += MSSizeOfPop(reg);
-                length += MSSizeOfMove64();
-            } else {
-                MSLog(MSLogLevelError, "MS:Error: Unknown RIP-Relative (%.2x %.2x)", decode.opcode, decode.opcode2);
-                continue;
-            }
-        } else
-#endif
-
-        if (backup[offset] == 0xe8) {
-            int32_t relative(*reinterpret_cast<int32_t *>(backup + offset + 1));
-            void *destiny(area + offset + decode.len + relative);
-
-            if (relative == 0) {
-                length -= decode.len;
-                length += MSSizeOfPushPointer(destiny);
-            } else {
-                length += MSSizeOfSkip();
-                length += MSSizeOfJump(destiny);
-            }
-        } else if (backup[offset] == 0xeb) {
-            length -= decode.len;
-            length += MSSizeOfJump(area + offset + decode.len + *reinterpret_cast<int8_t *>(backup + offset + 1));
-        } else if (backup[offset] == 0xe9) {
-            length -= decode.len;
-            length += MSSizeOfJump(area + offset + decode.len + *reinterpret_cast<int32_t *>(backup + offset + 1));
-        } else if (
-            backup[offset] == 0xe3 ||
-            (backup[offset] & 0xf0) == 0x70
-            // XXX: opcode2 & 0xf0 is 0x80?
-        ) {
-            length += decode.len;
-            length += MSSizeOfJump(area + offset + decode.len + *reinterpret_cast<int8_t *>(backup + offset + 1));
-        }
-    }
-
-    uint8_t *buffer(reinterpret_cast<uint8_t *>(mmap(
-        NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0
-    )));
-
-    if (buffer == MAP_FAILED) {
-        MSLog(MSLogLevelError, "MS:Error:mmap() = %d", errno);
-        *result = NULL;
-        return;
-    }
-
-    if (false) fail: {
-        munmap(buffer, length);
-        *result = NULL;
-        return;
-    }
-
-    {
-        uint8_t *current(buffer);
-
-        for (size_t offset(0), width; offset != used; offset += width) {
-            hde64s decode;
-            hde64_disasm(backup + offset, &decode);
-            width = decode.len;
-            //_assert(width != 0 && offset + width <= used);
-
-#ifdef __LP64__
-            if ((decode.modrm & 0xc7) == 0x05) {
-                if (decode.opcode == 0x8b) {
-                    void *destiny(area + offset + width + int32_t(decode.disp.disp32));
-                    uint8_t reg(decode.rex_r << 3 | decode.modrm_reg);
-                    MSPushPointer(current, destiny);
-                    MSWritePop(current, reg);
-                    MSWriteMove64(current, reg, reg);
-                } else {
-                    MSLog(MSLogLevelError, "MS:Error: Unknown RIP-Relative (%.2x %.2x)", decode.opcode, decode.opcode2);
-                    goto copy;
-                }
-            } else
-#endif
-
-            if (backup[offset] == 0xe8) {
-                int32_t relative(*reinterpret_cast<int32_t *>(backup + offset + 1));
-                if (relative == 0)
-                    MSPushPointer(current, area + offset + decode.len);
-                else {
-                    MSWrite<uint8_t>(current, 0xe8);
-                    MSWrite<int32_t>(current, MSSizeOfSkip());
-                    void *destiny(area + offset + decode.len + relative);
-                    MSWriteSkip(current, MSSizeOfJump(destiny, current + MSSizeOfSkip()));
-                    MSWriteJump(current, destiny);
-                }
-            } else if (backup[offset] == 0xeb)
-                MSWriteJump(current, area + offset + decode.len + *reinterpret_cast<int8_t *>(backup + offset + 1));
-            else if (backup[offset] == 0xe9)
-                MSWriteJump(current, area + offset + decode.len + *reinterpret_cast<int32_t *>(backup + offset + 1));
-            else if (
-                backup[offset] == 0xe3 ||
-                (backup[offset] & 0xf0) == 0x70
-            ) {
-                MSWrite<uint8_t>(current, backup[offset]);
-                MSWrite<uint8_t>(current, 2);
-                MSWrite<uint8_t>(current, 0xeb);
-                void *destiny(area + offset + decode.len + *reinterpret_cast<int8_t *>(backup + offset + 1));
-                MSWrite<uint8_t>(current, MSSizeOfJump(destiny, current + 1));
-                MSWriteJump(current, destiny);
-            } else
-#ifdef __LP64__
-                copy:
-#endif
-            {
-                MSWrite(current, backup + offset, width);
-            }
-        }
-
-        MSWriteJump(current, area + used);
-    }
-
-    if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) {
-        MSLog(MSLogLevelError, "MS:Error:mprotect():%d", errno);
-        goto fail;
-    }
-
-    *result = buffer;
-
-    if (MSDebug) {
-        char name[16];
-        sprintf(name, "%p", *result);
-        MSLogHex(buffer, length, name);
-    }
-
-    }
-
-    {
-        SubstrateHookMemory code(process, area, used);
-
-        uint8_t *current(area);
-        MSWriteJump(current, target);
-        for (unsigned offset(0); offset != blank; ++offset)
-            MSWrite<uint8_t>(current, 0x90);
-    }
-
-    if (MSDebug) {
-        char name[16];
-        sprintf(name, "%p", area);
-        MSLogHex(area, used + sizeof(uint16_t), name);
-    }
-}
-
diff --git a/VirtualApp/lib/src/main/jni/MSHook/x86_64.h b/VirtualApp/lib/src/main/jni/MSHook/x86_64.h
deleted file mode 100644
index b22b5726a..000000000
--- a/VirtualApp/lib/src/main/jni/MSHook/x86_64.h
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * x86_64.h
- *
- *  Created on: 2016��2��22��
- *      Author: peng
- */
-
-#ifndef X86_64_H_
-#define X86_64_H_
-
-/*
- * Hacker Disassembler Engine 64 C
- * Copyright (c) 2008-2009, Vyacheslav Patkov.
- * All rights reserved.
- *
- */
-#define C_NONE    0x00
-#define C_MODRM   0x01
-#define C_IMM8    0x02
-#define C_IMM16   0x04
-#define C_IMM_P66 0x10
-#define C_REL8    0x20
-#define C_REL32   0x40
-#define C_GROUP   0x80
-#define C_ERROR   0xff
-
-#define PRE_ANY  0x00
-#define PRE_NONE 0x01
-#define PRE_F2   0x02
-#define PRE_F3   0x04
-#define PRE_66   0x08
-#define PRE_67   0x10
-#define PRE_LOCK 0x20
-#define PRE_SEG  0x40
-#define PRE_ALL  0xff
-
-#define DELTA_OPCODES      0x4a
-#define DELTA_FPU_REG      0xfd
-#define DELTA_FPU_MODRM    0x104
-#define DELTA_PREFIXES     0x13c
-#define DELTA_OP_LOCK_OK   0x1ae
-#define DELTA_OP2_LOCK_OK  0x1c6
-#define DELTA_OP_ONLY_MEM  0x1d8
-#define DELTA_OP2_ONLY_MEM 0x1e7
-
-/* stdint.h - C99 standard header
- * http://en.wikipedia.org/wiki/stdint.h
- *
- * if your compiler doesn't contain "stdint.h" header (for
- * example, Microsoft Visual C++), you can download file:
- *   http://www.azillionmonkeys.com/qed/pstdint.h
- * and change next line to:
- *   #include "pstdint.h"
- */
-#include <stdint.h>
-
-#define F_MODRM         0x00000001
-#define F_SIB           0x00000002
-#define F_IMM8          0x00000004
-#define F_IMM16         0x00000008
-#define F_IMM32         0x00000010
-#define F_IMM64         0x00000020
-#define F_DISP8         0x00000040
-#define F_DISP16        0x00000080
-#define F_DISP32        0x00000100
-#define F_RELATIVE      0x00000200
-#define F_ERROR         0x00001000
-#define F_ERROR_OPCODE  0x00002000
-#define F_ERROR_LENGTH  0x00004000
-#define F_ERROR_LOCK    0x00008000
-#define F_ERROR_OPERAND 0x00010000
-#define F_PREFIX_REPNZ  0x01000000
-#define F_PREFIX_REPX   0x02000000
-#define F_PREFIX_REP    0x03000000
-#define F_PREFIX_66     0x04000000
-#define F_PREFIX_67     0x08000000
-#define F_PREFIX_LOCK   0x10000000
-#define F_PREFIX_SEG    0x20000000
-#define F_PREFIX_REX    0x40000000
-#define F_PREFIX_ANY    0x7f000000
-
-#define PREFIX_SEGMENT_CS   0x2e
-#define PREFIX_SEGMENT_SS   0x36
-#define PREFIX_SEGMENT_DS   0x3e
-#define PREFIX_SEGMENT_ES   0x26
-#define PREFIX_SEGMENT_FS   0x64
-#define PREFIX_SEGMENT_GS   0x65
-#define PREFIX_LOCK         0xf0
-#define PREFIX_REPNZ        0xf2
-#define PREFIX_REPX         0xf3
-#define PREFIX_OPERAND_SIZE 0x66
-#define PREFIX_ADDRESS_SIZE 0x67
-
-#pragma pack(push,1)
-
-typedef struct {
-    uint8_t len;
-    uint8_t p_rep;
-    uint8_t p_lock;
-    uint8_t p_seg;
-    uint8_t p_66;
-    uint8_t p_67;
-    uint8_t rex;
-    uint8_t rex_w;
-    uint8_t rex_r;
-    uint8_t rex_x;
-    uint8_t rex_b;
-    uint8_t opcode;
-    uint8_t opcode2;
-    uint8_t modrm;
-    uint8_t modrm_mod;
-    uint8_t modrm_reg;
-    uint8_t modrm_rm;
-    uint8_t sib;
-    uint8_t sib_scale;
-    uint8_t sib_index;
-    uint8_t sib_base;
-    union {
-        uint8_t imm8;
-        uint16_t imm16;
-        uint32_t imm32;
-        uint64_t imm64;
-    } imm;
-    union {
-        uint8_t disp8;
-        uint16_t disp16;
-        uint32_t disp32;
-    } disp;
-    uint32_t flags;
-} hde64s;
-
-#pragma pack(pop)
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* __cdecl */
-unsigned int hde64_disasm(const void *code, hde64s *hs);
-
-#ifdef __cplusplus
-}
-#endif
-
-
-#endif /* X86_64_H_ */
diff --git a/VirtualApp/lib/src/main/jni/Substrate/Buffer.hpp b/VirtualApp/lib/src/main/jni/Substrate/Buffer.hpp
new file mode 100755
index 000000000..34d9df32e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Substrate/Buffer.hpp
@@ -0,0 +1,38 @@
+/* Cydia Substrate - Powerful Code Insertion Platform
+ * Copyright (C) 2008-2011  Jay Freeman (saurik)
+*/
+
+/* GNU Lesser General Public License, Version 3 {{{ */
+/*
+ * Substrate is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Substrate is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
+**/
+/* }}} */
+
+#ifndef SUBSTRATE_BUFFER_HPP
+#define SUBSTRATE_BUFFER_HPP
+
+#include <string.h>
+
+template <typename Type_>
+_disused static _finline void MSWrite(uint8_t *&buffer, Type_ value) {
+    *reinterpret_cast<Type_ *>(buffer) = value;
+    buffer += sizeof(Type_);
+}
+
+_disused static _finline void MSWrite(uint8_t *&buffer, uint8_t *data, size_t size) {
+    memcpy(buffer, data, size);
+    buffer += size;
+}
+
+#endif//SUBSTRATE_BUFFER_HPP
diff --git a/VirtualApp/lib/src/main/jni/Substrate/CydiaSubstrate.h b/VirtualApp/lib/src/main/jni/Substrate/CydiaSubstrate.h
new file mode 100755
index 000000000..bb806aa9f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Substrate/CydiaSubstrate.h
@@ -0,0 +1,152 @@
+/* Cydia Substrate - Powerful Code Insertion Platform
+ * Copyright (C) 2008-2011  Jay Freeman (saurik)
+*/
+
+/* GNU Lesser General Public License, Version 3 {{{ */
+/*
+ * Substrate is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Substrate is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
+**/
+/* }}} */
+
+#ifndef SUBSTRATE_H_
+#define SUBSTRATE_H_
+
+#ifdef __APPLE__
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <mach-o/nlist.h>
+#ifdef __cplusplus
+}
+#endif
+
+#include <objc/runtime.h>
+#include <objc/message.h>
+#endif
+
+#include <dlfcn.h>
+#include <stdlib.h>
+
+#define _finline \
+    inline __attribute__((__always_inline__))
+#define _disused \
+    __attribute__((__unused__))
+
+#define _extern \
+    extern "C" __attribute__((__visibility__("default")))
+
+#ifdef __cplusplus
+#define _default(value) = value
+#else
+#define _default(value)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+bool MSHookProcess(pid_t pid, const char *library);
+
+typedef const void *MSImageRef;
+
+MSImageRef MSGetImageByName(const char *file);
+void *MSFindSymbol(MSImageRef image, const char *name);
+
+void MSHookFunction(void *symbol, void *replace, void **result);
+
+#ifdef __APPLE__
+#ifdef __arm__
+__attribute__((__deprecated__))
+IMP MSHookMessage(Class _class, SEL sel, IMP imp, const char *prefix _default(NULL));
+#endif
+void MSHookMessageEx(Class _class, SEL sel, IMP imp, IMP *result);
+#endif
+
+#ifdef SubstrateInternal
+typedef void *SubstrateAllocatorRef;
+typedef struct __SubstrateProcess *SubstrateProcessRef;
+typedef struct __SubstrateMemory *SubstrateMemoryRef;
+
+SubstrateProcessRef SubstrateProcessCreate(SubstrateAllocatorRef allocator, pid_t pid);
+void SubstrateProcessRelease(SubstrateProcessRef process);
+
+SubstrateMemoryRef SubstrateMemoryCreate(SubstrateAllocatorRef allocator, SubstrateProcessRef process, void *data, size_t size);
+void SubstrateMemoryRelease(SubstrateMemoryRef memory);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef __cplusplus
+
+#ifdef SubstrateInternal
+struct SubstrateHookMemory {
+    SubstrateMemoryRef handle_;
+
+    SubstrateHookMemory(SubstrateProcessRef process, void *data, size_t size) :
+        handle_(SubstrateMemoryCreate(NULL, NULL, data, size))
+    {
+    }
+
+    ~SubstrateHookMemory() {
+        if (handle_ != NULL)
+            SubstrateMemoryRelease(handle_);
+    }
+};
+#endif
+
+
+template<typename Type_>
+static inline void MSHookFunction(Type_ *symbol, Type_ *replace, Type_ **result) {
+    MSHookFunction(
+            reinterpret_cast<void *>(symbol),
+            reinterpret_cast<void *>(replace),
+            reinterpret_cast<void **>(result)
+    );
+}
+
+template<typename Type_>
+static inline void MSHookFunction(Type_ *symbol, Type_ *replace) {
+    return MSHookFunction(symbol, replace, reinterpret_cast<Type_ **>(NULL));
+}
+
+template<typename Type_>
+static inline void MSHookSymbol(Type_ *&value, const char *name, MSImageRef image = NULL) {
+    value = reinterpret_cast<Type_ *>(MSFindSymbol(image, name));
+}
+
+template<typename Type_>
+static inline void MSHookFunction(const char *name, Type_ *replace, Type_ **result = NULL) {
+    Type_ *symbol;
+    MSHookSymbol(symbol, name);
+    return MSHookFunction(symbol, replace, result);
+}
+
+#endif
+
+#define MSHook(type, name, args...) \
+    _disused static type (*_ ## name)(args); \
+    static type $ ## name(args)
+
+#ifdef __cplusplus
+#define MSHake(name) \
+    &$ ## name, &_ ## name
+#else
+#define MSHake(name) \
+    &$ ## name, (void **) &_ ## name
+#endif
+
+
+#endif//SUBSTRATE_H_
diff --git a/VirtualApp/lib/src/main/jni/MSHook/ARM.h b/VirtualApp/lib/src/main/jni/Substrate/SubstrateARM.hpp
old mode 100644
new mode 100755
similarity index 83%
rename from VirtualApp/lib/src/main/jni/MSHook/ARM.h
rename to VirtualApp/lib/src/main/jni/Substrate/SubstrateARM.hpp
index 9cf63b90a..7dd313900
--- a/VirtualApp/lib/src/main/jni/MSHook/ARM.h
+++ b/VirtualApp/lib/src/main/jni/Substrate/SubstrateARM.hpp
@@ -22,15 +22,6 @@
 #ifndef SUBSTRATE_ARM_HPP
 #define SUBSTRATE_ARM_HPP
 
-#include "CydiaSubstrate.h"
-#include "Log.h"
-#include "Debug.h"
-#include <sys/mman.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <stdint.h>
-
 enum A$r {
     A$r0, A$r1, A$r2, A$r3,
     A$r4, A$r5, A$r6, A$r7,
@@ -71,11 +62,4 @@ enum A$c {
 #define A$stmia_sp$_$r0$  0xe8ad0001 /* stmia sp!, {r0}   */
 #define A$bx_r0           0xe12fff10 /* bx r0             */
 
-static inline bool A$pcrel$r(uint32_t ic) {
-	return (ic & 0x0c000000) == 0x04000000 && (ic & 0xf0000000) != 0xf0000000 && (ic & 0x000f0000) == 0x000f0000;
-}
-
-namespace ARM{
-	extern "C" void SubstrateHookFunctionARM(SubstrateProcessRef process, void *symbol, void *replace, void **result);
-}
 #endif//SUBSTRATE_ARM_HPP
diff --git a/VirtualApp/lib/src/main/jni/MSHook/Debug.cpp b/VirtualApp/lib/src/main/jni/Substrate/SubstrateDebug.cpp
old mode 100644
new mode 100755
similarity index 88%
rename from VirtualApp/lib/src/main/jni/MSHook/Debug.cpp
rename to VirtualApp/lib/src/main/jni/Substrate/SubstrateDebug.cpp
index bf7a73e40..2df6ef487
--- a/VirtualApp/lib/src/main/jni/MSHook/Debug.cpp
+++ b/VirtualApp/lib/src/main/jni/Substrate/SubstrateDebug.cpp
@@ -19,9 +19,11 @@
 **/
 /* }}} */
 
-#include "CydiaSubstrate.h"
-#include "Debug.h"
+#include "SubstrateHook.h"
+#include "SubstrateDebug.hpp"
 
+#include <stdint.h>
+#include <stdlib.h>
 #include <stdio.h>
 
 _extern bool MSDebug;
@@ -34,8 +36,7 @@ static char _MSHexChar(uint8_t value) {
 #define HexWidth_ 16
 #define HexDepth_ 4
 
-
-void MSLogHexExInner(const void *vdata, size_t size, size_t stride, const char *mark) {
+void MSLogHexEx(const void *vdata, size_t size, size_t stride, const char *mark) {
     const uint8_t *data((const uint8_t *) vdata);
 
     size_t i(0), j;
@@ -47,7 +48,7 @@ void MSLogHexExInner(const void *vdata, size_t size, size_t stride, const char *
     while (i != size) {
         if (i % HexWidth_ == 0) {
             if (mark != NULL)
-                b += sprintf(d + b, "[%s] ", mark);
+                b += sprintf(d + b, "\n[%s] ", mark);
             b += sprintf(d + b, "0x%.3zx:", i);
         }
 
@@ -90,14 +91,6 @@ void MSLogHexExInner(const void *vdata, size_t size, size_t stride, const char *
     }
 }
 
-void MSLogHexEx(const void *vdata, size_t size, size_t stride, const char *mark) {
-    if (MSDebug) {
-        MSLogHexExInner(vdata, size, stride, mark);
-    }
-}
-
 void MSLogHex(const void *vdata, size_t size, const char *mark) {
-    if (MSDebug) {
-        MSLogHexEx(vdata, size, 1, mark);
-    }
+    return MSLogHexEx(vdata, size, 1, mark);
 }
diff --git a/VirtualApp/lib/src/main/jni/MSHook/Debug.h b/VirtualApp/lib/src/main/jni/Substrate/SubstrateDebug.hpp
old mode 100644
new mode 100755
similarity index 96%
rename from VirtualApp/lib/src/main/jni/MSHook/Debug.h
rename to VirtualApp/lib/src/main/jni/Substrate/SubstrateDebug.hpp
index f01e6a21c..9c554c851
--- a/VirtualApp/lib/src/main/jni/MSHook/Debug.h
+++ b/VirtualApp/lib/src/main/jni/Substrate/SubstrateDebug.hpp
@@ -22,8 +22,7 @@
 #ifndef SUBSTRATE_DEBUG_HPP
 #define SUBSTRATE_DEBUG_HPP
 
-#include "Log.h"
-#include <stddef.h>
+#include "SubstrateLog.hpp"
 #define lprintf(format, ...) \
     MSLog(MSLogLevelNotice, format, ## __VA_ARGS__)
 
diff --git a/VirtualApp/lib/src/main/jni/Substrate/SubstrateHook.cpp b/VirtualApp/lib/src/main/jni/Substrate/SubstrateHook.cpp
new file mode 100755
index 000000000..8264375ff
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Substrate/SubstrateHook.cpp
@@ -0,0 +1,934 @@
+/* Cydia Substrate - Powerful Code Insertion Platform
+ * Copyright (C) 2008-2011  Jay Freeman (saurik)
+*/
+
+/* GNU Lesser General Public License, Version 3 {{{ */
+/*
+ * Substrate is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Substrate is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
+**/
+/* }}} */
+
+#define SubstrateInternal
+#include "CydiaSubstrate.h"
+
+#include <sys/mman.h>
+
+#define _trace() do { \
+    MSLog(MSLogLevelNotice, "_trace(%u)", __LINE__); \
+} while (false)
+
+#if defined(__i386__) || defined(__x86_64__)
+#include "hde64.h"
+#endif
+
+#include "SubstrateDebug.hpp"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __arm__
+/* WebCore (ARM) PC-Relative:
+X    1  ldr r*,[pc,r*] !=
+     2 fldd d*,[pc,#*]
+X    5  str r*,[pc,r*] !=
+     8 flds s*,[pc,#*]
+   400  ldr r*,[pc,r*] ==
+   515  add r*, pc,r*  ==
+X 4790  ldr r*,[pc,#*]    */
+
+// x=0; while IFS= read -r line; do if [[ ${#line} -ne 0 && $line == +([^\;]): ]]; then x=2; elif [[ $line == ' +'* && $x -ne 0 ]]; then ((--x)); echo "$x${line}"; fi; done <WebCore.asm >WebCore.pc
+// grep pc WebCore.pc | cut -c 40- | sed -Ee 's/^ldr *(ip|r[0-9]*),\[pc,\#0x[0-9a-f]*\].*/ ldr r*,[pc,#*]/;s/^add *r[0-9]*,pc,r[0-9]*.*/ add r*, pc,r*/;s/^(st|ld)r *r([0-9]*),\[pc,r([0-9]*)\].*/ \1r r\2,[pc,r\3]/;s/^fld(s|d) *(s|d)[0-9]*,\[pc,#0x[0-9a-f]*].*/fld\1 \2*,[pc,#*]/' | sort | uniq -c | sort -n
+
+#include "SubstrateARM.hpp"
+
+#define T$Label(l, r) \
+    (((r) - (l)) * 2 - 4 + ((l) % 2 == 0 ? 0 : 2))
+
+#define T$pop_$r0$ 0xbc01 // pop {r0}
+#define T$b(im) /* b im */ \
+    (0xde00 | (im & 0xff))
+#define T$blx(rm) /* blx rm */ \
+    (0x4780 | (rm << 3))
+#define T$bx(rm) /* bx rm */ \
+    (0x4700 | (rm << 3))
+#define T$nop /* nop */ \
+    (0x46c0)
+
+#define T$add_rd_rm(rd, rm) /* add rd, rm */ \
+    (0x4400 | (((rd) & 0x8) >> 3 << 7) | (((rm) & 0x8) >> 3 << 6) | (((rm) & 0x7) << 3) | ((rd) & 0x7))
+#define T$push_r(r) /* push r... */ \
+    (0xb400 | (((r) & (1 << A$lr)) >> A$lr << 8) | ((r) & 0xff))
+#define T$pop_r(r) /* pop r... */ \
+    (0xbc00 | (((r) & (1 << A$pc)) >> A$pc << 8) | ((r) & 0xff))
+#define T$mov_rd_rm(rd, rm) /* mov rd, rm */ \
+    (0x4600 | (((rd) & 0x8) >> 3 << 7) | (((rm) & 0x8) >> 3 << 6) | (((rm) & 0x7) << 3) | ((rd) & 0x7))
+#define T$ldr_rd_$rn_im_4$(rd, rn, im) /* ldr rd, [rn, #im * 4] */ \
+    (0x6800 | (((im) & 0x1f) << 6) | ((rn) << 3) | (rd))
+#define T$ldr_rd_$pc_im_4$(rd, im) /* ldr rd, [PC, #im * 4] */ \
+    (0x4800 | ((rd) << 8) | ((im) & 0xff))
+#define T$cmp_rn_$im(rn, im) /* cmp rn, #im */ \
+    (0x2000 | ((rn) << 8) | ((im) & 0xff))
+#define T$it$_cd(cd, ms) /* it<ms>, cd */ \
+    (0xbf00 | ((cd) << 4) | (ms))
+#define T$cbz$_rn_$im(op,rn,im) /* cb<op>z rn, #im */ \
+    (0xb100 | ((op) << 11) | (((im) & 0x40) >> 6 << 9) | (((im) & 0x3e) >> 1 << 3) | (rn))
+#define T$b$_$im(cond,im) /* b<cond> #im */ \
+    (cond == A$al ? 0xe000 | (((im) >> 1) & 0x7ff) : 0xd000 | ((cond) << 8) | (((im) >> 1) & 0xff))
+
+#define T1$ldr_rt_$rn_im$(rt, rn, im) /* ldr rt, [rn, #im] */ \
+    (0xf850 | ((im < 0 ? 0 : 1) << 7) | (rn))
+#define T2$ldr_rt_$rn_im$(rt, rn, im) /* ldr rt, [rn, #im] */ \
+    (((rt) << 12) | abs(im))
+
+#define T1$mrs_rd_apsr(rd) /* mrs rd, apsr */ \
+    (0xf3ef)
+#define T2$mrs_rd_apsr(rd) /* mrs rd, apsr */ \
+    (0x8000 | ((rd) << 8))
+
+#define T1$msr_apsr_nzcvqg_rn(rn) /* msr apsr, rn */ \
+    (0xf380 | (rn))
+#define T2$msr_apsr_nzcvqg_rn(rn) /* msr apsr, rn */ \
+    (0x8c00)
+#define T$msr_apsr_nzcvqg_rn(rn) /* msr apsr, rn */ \
+    (T2$msr_apsr_nzcvqg_rn(rn) << 16 | T1$msr_apsr_nzcvqg_rn(rn))
+
+static inline bool A$pcrel$r(uint32_t ic) {
+    return (ic & 0x0c000000) == 0x04000000 && (ic & 0xf0000000) != 0xf0000000 && (ic & 0x000f0000) == 0x000f0000;
+}
+
+static inline bool T$32bit$i(uint16_t ic) {
+    return ((ic & 0xe000) == 0xe000 && (ic & 0x1800) != 0x0000);
+}
+
+static inline bool T$pcrel$cbz(uint16_t ic) {
+    return (ic & 0xf500) == 0xb100;
+}
+
+static inline bool T$pcrel$b(uint16_t ic) {
+    return (ic & 0xf000) == 0xd000 && (ic & 0x0e00) != 0x0e00;
+}
+
+static inline bool T2$pcrel$b(uint16_t *ic) {
+    return (ic[0] & 0xf800) == 0xf000 && ((ic[1] & 0xd000) == 0x9000 || (ic[1] & 0xd000) == 0x8000 && (ic[0] & 0x0380) != 0x0380);
+}
+
+static inline bool T$pcrel$bl(uint16_t *ic) {
+    return (ic[0] & 0xf800) == 0xf000 && ((ic[1] & 0xd000) == 0xd000 || (ic[1] & 0xd001) == 0xc000);
+}
+
+static inline bool T$pcrel$ldr(uint16_t ic) {
+    return (ic & 0xf800) == 0x4800;
+}
+
+static inline bool T$pcrel$add(uint16_t ic) {
+    return (ic & 0xff78) == 0x4478;
+}
+
+static inline bool T$pcrel$ldrw(uint16_t ic) {
+    return (ic & 0xff7f) == 0xf85f;
+}
+
+static size_t MSGetInstructionWidthThumb(void *start) {
+    uint16_t *thumb(reinterpret_cast<uint16_t *>(start));
+    return T$32bit$i(thumb[0]) ? 4 : 2;
+}
+
+static size_t MSGetInstructionWidthARM(void *start) {
+    return 4;
+}
+
+extern "C" size_t MSGetInstructionWidth(void *start) {
+    if ((reinterpret_cast<uintptr_t>(start) & 0x1) == 0)
+        return MSGetInstructionWidthARM(start);
+    else
+        return MSGetInstructionWidthThumb(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(start) & ~0x1));
+}
+
+static size_t SubstrateHookFunctionThumb(SubstrateProcessRef process, void *symbol, void *replace, void **result) {
+    if (symbol == NULL)
+        return 0;
+printf("SubstrateHookFunctionThumb\n");
+    uint16_t *area(reinterpret_cast<uint16_t *>(symbol));
+
+    unsigned align((reinterpret_cast<uintptr_t>(area) & 0x2) == 0 ? 0 : 1);
+    uint16_t *thumb(area + align);
+
+    uint32_t *arm(reinterpret_cast<uint32_t *>(thumb + 2));
+    uint16_t *trail(reinterpret_cast<uint16_t *>(arm + 2));
+
+    if (
+        (align == 0 || area[0] == T$nop) &&
+        thumb[0] == T$bx(A$pc) &&
+        thumb[1] == T$nop &&
+        arm[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8)
+    ) {
+        if (result != NULL)
+            *result = reinterpret_cast<void *>(arm[1]);
+
+        SubstrateHookMemory code(process, arm + 1, sizeof(uint32_t) * 1);
+
+        arm[1] = reinterpret_cast<uint32_t>(replace);
+
+        return sizeof(arm[0]);
+    }
+
+    size_t required((trail - area) * sizeof(uint16_t));
+
+    size_t used(0);
+    while (used < required)
+        used += MSGetInstructionWidthThumb(reinterpret_cast<uint8_t *>(area) + used);
+    used = (used + sizeof(uint16_t) - 1) / sizeof(uint16_t) * sizeof(uint16_t);
+
+    size_t blank((used - required) / sizeof(uint16_t));
+
+    uint16_t backup[used / sizeof(uint16_t)];
+    memcpy(backup, area, used);
+
+    if (MSDebug) {
+        char name[16];
+        sprintf(name, "%p", area);
+        MSLogHexEx(area, used + sizeof(uint16_t), 2, name);
+    }
+
+    if (result != NULL) {
+
+    size_t length(used);
+    for (unsigned offset(0); offset != used / sizeof(uint16_t); ++offset)
+        if (T$pcrel$ldr(backup[offset]))
+            length += 3 * sizeof(uint16_t);
+        else if (T$pcrel$b(backup[offset]))
+            length += 6 * sizeof(uint16_t);
+        else if (T2$pcrel$b(backup + offset)) {
+            length += 5 * sizeof(uint16_t);
+            ++offset;
+        } else if (T$pcrel$bl(backup + offset)) {
+            length += 5 * sizeof(uint16_t);
+            ++offset;
+        } else if (T$pcrel$cbz(backup[offset])) {
+            length += 16 * sizeof(uint16_t);
+        } else if (T$pcrel$ldrw(backup[offset])) {
+            length += 4 * sizeof(uint16_t);
+            ++offset;
+        } else if (T$pcrel$add(backup[offset]))
+            length += 6 * sizeof(uint16_t);
+        else if (T$32bit$i(backup[offset]))
+            ++offset;
+
+    unsigned pad((length & 0x2) == 0 ? 0 : 1);
+    length += (pad + 2) * sizeof(uint16_t) + 2 * sizeof(uint32_t);
+
+    uint16_t *buffer(reinterpret_cast<uint16_t *>(mmap(
+        NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0
+    )));
+
+    if (buffer == MAP_FAILED) {
+        MSLog(MSLogLevelError, "MS:Error:mmap() = %d", errno);
+        *result = NULL;
+        return 0;
+    }
+
+    if (false) fail: {
+        munmap(buffer, length);
+        *result = NULL;
+        return 0;
+    }
+
+    size_t start(pad), end(length / sizeof(uint16_t));
+    uint32_t *trailer(reinterpret_cast<uint32_t *>(buffer + end));
+    for (unsigned offset(0); offset != used / sizeof(uint16_t); ++offset) {
+        if (T$pcrel$ldr(backup[offset])) {
+            union {
+                uint16_t value;
+
+                struct {
+                    uint16_t immediate : 8;
+                    uint16_t rd : 3;
+                    uint16_t : 5;
+                };
+            } bits = {backup[offset+0]};
+
+            buffer[start+0] = T$ldr_rd_$pc_im_4$(bits.rd, T$Label(start+0, end-2) / 4);
+            buffer[start+1] = T$ldr_rd_$rn_im_4$(bits.rd, bits.rd, 0);
+
+            // XXX: this code "works", but is "wrong": the mechanism is more complex than this
+            *--trailer = ((reinterpret_cast<uint32_t>(area + offset) + 4) & ~0x2) + bits.immediate * 4;
+
+            start += 2;
+            end -= 2;
+        } else if (T$pcrel$b(backup[offset])) {
+            union {
+                uint16_t value;
+
+                struct {
+                    uint16_t imm8 : 8;
+                    uint16_t cond : 4;
+                    uint16_t /*1101*/ : 4;
+                };
+            } bits = {backup[offset+0]};
+
+            intptr_t jump(bits.imm8 << 1);
+            jump |= 1;
+            jump <<= 23;
+            jump >>= 23;
+
+            buffer[start+0] = T$b$_$im(bits.cond, (end-6 - (start+0)) * 2 - 4);
+
+            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 4 + jump;
+            *--trailer = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
+            *--trailer = T$nop << 16 | T$bx(A$pc);
+
+            start += 1;
+            end -= 6;
+        } else if (T2$pcrel$b(backup + offset)) {
+            union {
+                uint16_t value;
+
+                struct {
+                    uint16_t imm6 : 6;
+                    uint16_t cond : 4;
+                    uint16_t s : 1;
+                    uint16_t : 5;
+                };
+            } bits = {backup[offset+0]};
+
+            union {
+                uint16_t value;
+
+                struct {
+                    uint16_t imm11 : 11;
+                    uint16_t j2 : 1;
+                    uint16_t a : 1;
+                    uint16_t j1 : 1;
+                    uint16_t : 2;
+                };
+            } exts = {backup[offset+1]};
+
+            intptr_t jump(1);
+            jump |= exts.imm11 << 1;
+            jump |= bits.imm6 << 12;
+
+            if (exts.a) {
+                jump |= bits.s << 24;
+                jump |= (~(bits.s ^ exts.j1) & 0x1) << 23;
+                jump |= (~(bits.s ^ exts.j2) & 0x1) << 22;
+                jump |= bits.cond << 18;
+                jump <<= 7;
+                jump >>= 7;
+            } else {
+                jump |= bits.s << 20;
+                jump |= exts.j2 << 19;
+                jump |= exts.j1 << 18;
+                jump <<= 11;
+                jump >>= 11;
+            }
+
+            buffer[start+0] = T$b$_$im(exts.a ? A$al : bits.cond, (end-6 - (start+0)) * 2 - 4);
+
+            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 4 + jump;
+            *--trailer = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
+            *--trailer = T$nop << 16 | T$bx(A$pc);
+
+            ++offset;
+            start += 1;
+            end -= 6;
+        } else if (T$pcrel$bl(backup + offset)) {
+            union {
+                uint16_t value;
+
+                struct {
+                    uint16_t immediate : 10;
+                    uint16_t s : 1;
+                    uint16_t : 5;
+                };
+            } bits = {backup[offset+0]};
+
+            union {
+                uint16_t value;
+
+                struct {
+                    uint16_t immediate : 11;
+                    uint16_t j2 : 1;
+                    uint16_t x : 1;
+                    uint16_t j1 : 1;
+                    uint16_t : 2;
+                };
+            } exts = {backup[offset+1]};
+
+            int32_t jump(0);
+            jump |= bits.s << 24;
+            jump |= (~(bits.s ^ exts.j1) & 0x1) << 23;
+            jump |= (~(bits.s ^ exts.j2) & 0x1) << 22;
+            jump |= bits.immediate << 12;
+            jump |= exts.immediate << 1;
+            jump |= exts.x;
+            jump <<= 7;
+            jump >>= 7;
+
+            buffer[start+0] = T$push_r(1 << A$r7);
+            buffer[start+1] = T$ldr_rd_$pc_im_4$(A$r7, ((end-2 - (start+1)) * 2 - 4 + 2) / 4);
+            buffer[start+2] = T$mov_rd_rm(A$lr, A$r7);
+            buffer[start+3] = T$pop_r(1 << A$r7);
+            buffer[start+4] = T$blx(A$lr);
+
+            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 4 + jump;
+
+            ++offset;
+            start += 5;
+            end -= 2;
+        } else if (T$pcrel$cbz(backup[offset])) {
+            union {
+                uint16_t value;
+
+                struct {
+                    uint16_t rn : 3;
+                    uint16_t immediate : 5;
+                    uint16_t : 1;
+                    uint16_t i : 1;
+                    uint16_t : 1;
+                    uint16_t op : 1;
+                    uint16_t : 4;
+                };
+            } bits = {backup[offset+0]};
+
+            intptr_t jump(1);
+            jump |= bits.i << 6;
+            jump |= bits.immediate << 1;
+
+            //jump <<= 24;
+            //jump >>= 24;
+
+            unsigned rn(bits.rn);
+            unsigned rt(rn == A$r7 ? A$r6 : A$r7);
+
+            buffer[start+0] = T$push_r(1 << rt);
+            buffer[start+1] = T1$mrs_rd_apsr(rt);
+            buffer[start+2] = T2$mrs_rd_apsr(rt);
+            buffer[start+3] = T$cbz$_rn_$im(bits.op, rn, (end-10 - (start+3)) * 2 - 4);
+            buffer[start+4] = T1$msr_apsr_nzcvqg_rn(rt);
+            buffer[start+5] = T2$msr_apsr_nzcvqg_rn(rt);
+            buffer[start+6] = T$pop_r(1 << rt);
+
+            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 4 + jump;
+            *--trailer = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
+            *--trailer = T$nop << 16 | T$bx(A$pc);
+            *--trailer = T$nop << 16 | T$pop_r(1 << rt);
+            *--trailer = T$msr_apsr_nzcvqg_rn(rt);
+
+#if 0
+            if ((start & 0x1) == 0)
+                buffer[start++] = T$nop;
+            buffer[start++] = T$bx(A$pc);
+            buffer[start++] = T$nop;
+
+            uint32_t *arm(reinterpret_cast<uint32_t *>(buffer + start));
+            arm[0] = A$add(A$lr, A$pc, 1);
+            arm[1] = A$ldr_rd_$rn_im$(A$pc, A$pc, (trailer - arm) * sizeof(uint32_t) - 8);
+#endif
+
+            start += 7;
+            end -= 10;
+        } else if (T$pcrel$ldrw(backup[offset])) {
+            union {
+                uint16_t value;
+
+                struct {
+                    uint16_t : 7;
+                    uint16_t u : 1;
+                    uint16_t : 8;
+                };
+            } bits = {backup[offset+0]};
+
+            union {
+                uint16_t value;
+
+                struct {
+                    uint16_t immediate : 12;
+                    uint16_t rt : 4;
+                };
+            } exts = {backup[offset+1]};
+
+            buffer[start+0] = T1$ldr_rt_$rn_im$(exts.rt, A$pc, T$Label(start+0, end-2));
+            buffer[start+1] = T2$ldr_rt_$rn_im$(exts.rt, A$pc, (int)T$Label(start+0, end-2));
+
+            buffer[start+2] = T1$ldr_rt_$rn_im$(exts.rt, exts.rt, 0);
+            buffer[start+3] = T2$ldr_rt_$rn_im$(exts.rt, exts.rt, 0);
+
+            // XXX: this code "works", but is "wrong": the mechanism is more complex than this
+            *--trailer = ((reinterpret_cast<uint32_t>(area + offset) + 4) & ~0x2) + (bits.u == 0 ? -exts.immediate : exts.immediate);
+
+            ++offset;
+            start += 4;
+            end -= 2;
+        } else if (T$pcrel$add(backup[offset])) {
+            union {
+                uint16_t value;
+
+                struct {
+                    uint16_t rd : 3;
+                    uint16_t rm : 3;
+                    uint16_t h2 : 1;
+                    uint16_t h1 : 1;
+                    uint16_t : 8;
+                };
+            } bits = {backup[offset+0]};
+
+            if (bits.h1) {
+                MSLog(MSLogLevelError, "MS:Error:pcrel(%u):add (rd > r7)", offset);
+                goto fail;
+            }
+
+            unsigned rt(bits.rd == A$r7 ? A$r6 : A$r7);
+
+            buffer[start+0] = T$push_r(1 << rt);
+            buffer[start+1] = T$mov_rd_rm(rt, (bits.h1 << 3) | bits.rd);
+            buffer[start+2] = T$ldr_rd_$pc_im_4$(bits.rd, T$Label(start+2, end-2) / 4);
+            buffer[start+3] = T$add_rd_rm((bits.h1 << 3) | bits.rd, rt);
+            buffer[start+4] = T$pop_r(1 << rt);
+            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 4;
+
+            start += 5;
+            end -= 2;
+        } else if (T$32bit$i(backup[offset])) {
+            buffer[start++] = backup[offset];
+            buffer[start++] = backup[++offset];
+        } else {
+            buffer[start++] = backup[offset];
+        }
+    }
+
+    buffer[start++] = T$bx(A$pc);
+    buffer[start++] = T$nop;
+
+    uint32_t *transfer = reinterpret_cast<uint32_t *>(buffer + start);
+    transfer[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
+    transfer[1] = reinterpret_cast<uint32_t>(area + used / sizeof(uint16_t)) + 1;
+
+    if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) {
+        MSLog(MSLogLevelError, "MS:Error:mprotect():%d", errno);
+        return 0;
+    }
+
+    *result = reinterpret_cast<uint8_t *>(buffer + pad) + 1;
+
+    if (MSDebug) {
+        char name[16];
+        sprintf(name, "%p", *result);
+        MSLogHexEx(buffer, length, 2, name);
+    }
+
+    }
+
+    {
+        SubstrateHookMemory code(process, area, used);
+
+        if (align != 0)
+            area[0] = T$nop;
+
+        thumb[0] = T$bx(A$pc);
+        thumb[1] = T$nop;
+
+        arm[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
+        arm[1] = reinterpret_cast<uint32_t>(replace);
+
+        for (unsigned offset(0); offset != blank; ++offset)
+            trail[offset] = T$nop;
+    }
+
+    if (MSDebug) {
+        char name[16];
+        sprintf(name, "%p", area);
+        MSLogHexEx(area, used + sizeof(uint16_t), 2, name);
+    }
+	
+	return used;
+}
+
+static size_t SubstrateHookFunctionARM(SubstrateProcessRef process, void *symbol, void *replace, void **result) {
+    if (symbol == NULL)
+        return 0;
+printf("SubstrateHookFunctionARM\n");
+    uint32_t *area(reinterpret_cast<uint32_t *>(symbol));
+    uint32_t *arm(area);
+
+    const size_t used(8);
+
+    uint32_t backup[used / sizeof(uint32_t)] = {arm[0], arm[1]};
+
+    if (MSDebug) {
+        char name[16];
+        sprintf(name, "%p", area);
+        MSLogHexEx(area, used + sizeof(uint32_t), 4, name);
+    }
+
+    if (result != NULL) {
+
+    if (backup[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8)) {
+        *result = reinterpret_cast<void *>(backup[1]);
+        
+		return sizeof(backup[0]);
+    }
+
+    size_t length(used);
+    for (unsigned offset(0); offset != used / sizeof(uint32_t); ++offset)
+        if (A$pcrel$r(backup[offset])) {
+            if ((backup[offset] & 0x02000000) == 0 || (backup[offset] & 0x0000f000 >> 12) != (backup[offset] & 0x0000000f))
+                length += 2 * sizeof(uint32_t);
+            else
+                length += 4 * sizeof(uint32_t);
+        }
+
+    length += 2 * sizeof(uint32_t);
+
+    uint32_t *buffer(reinterpret_cast<uint32_t *>(mmap(
+        NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0
+    )));
+
+    if (buffer == MAP_FAILED) {
+        MSLog(MSLogLevelError, "MS:Error:mmap() = %d", errno);
+        *result = NULL;
+        return 0;
+    }
+
+    if (false) fail: {
+        munmap(buffer, length);
+        *result = NULL;
+        return 0;
+    }
+
+    size_t start(0), end(length / sizeof(uint32_t));
+    uint32_t *trailer(reinterpret_cast<uint32_t *>(buffer + end));
+    for (unsigned offset(0); offset != used / sizeof(uint32_t); ++offset)
+        if (A$pcrel$r(backup[offset])) {
+            union {
+                uint32_t value;
+
+                struct {
+                    uint32_t rm : 4;
+                    uint32_t : 1;
+                    uint32_t shift : 2;
+                    uint32_t shiftamount : 5;
+                    uint32_t rd : 4;
+                    uint32_t rn : 4;
+                    uint32_t l : 1;
+                    uint32_t w : 1;
+                    uint32_t b : 1;
+                    uint32_t u : 1;
+                    uint32_t p : 1;
+                    uint32_t mode : 1;
+                    uint32_t type : 2;
+                    uint32_t cond : 4;
+                };
+            } bits = {backup[offset+0]}, copy(bits);
+
+            bool guard;
+            if (bits.mode == 0 || bits.rd != bits.rm) {
+                copy.rn = bits.rd;
+                guard = false;
+            } else {
+                copy.rn = bits.rm != A$r0 ? A$r0 : A$r1;
+                guard = true;
+            }
+
+            if (guard)
+                buffer[start++] = A$stmdb_sp$_$rs$((1 << copy.rn));
+
+            buffer[start+0] = A$ldr_rd_$rn_im$(copy.rn, A$pc, (int)(end-1 - (start+0)) * 4 - 8);
+            buffer[start+1] = copy.value;
+
+            start += 2;
+
+            if (guard)
+                buffer[start++] = A$ldmia_sp$_$rs$((1 << copy.rn));
+
+            *--trailer = reinterpret_cast<uint32_t>(area + offset) + 8;
+            end -= 1;
+        } else
+            buffer[start++] = backup[offset];
+
+    buffer[start+0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
+    buffer[start+1] = reinterpret_cast<uint32_t>(area + used / sizeof(uint32_t));
+
+    if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) {
+        MSLog(MSLogLevelError, "MS:Error:mprotect():%d", errno);
+        goto fail;
+    }
+
+    *result = buffer;
+
+    if (MSDebug) {
+        char name[16];
+        sprintf(name, "%p", *result);
+        MSLogHexEx(buffer, length, 4, name);
+    }
+
+    }
+
+    {
+        SubstrateHookMemory code(process, symbol, used);
+
+        arm[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8);
+        arm[1] = reinterpret_cast<uint32_t>(replace);
+    }
+
+    if (MSDebug) {
+        char name[16];
+        sprintf(name, "%p", area);
+        MSLogHexEx(area, used + sizeof(uint32_t), 4, name);
+    }
+	
+	return used;
+}
+
+static size_t SubstrateHookFunction(SubstrateProcessRef process, void *symbol, void *replace, void **result) {
+    if (MSDebug)
+        MSLog(MSLogLevelNotice, "SubstrateHookFunction(%p, %p, %p, %p)\n", process, symbol, replace, result);
+    if ((reinterpret_cast<uintptr_t>(symbol) & 0x1) == 0)
+        return SubstrateHookFunctionARM(process, symbol, replace, result);
+    else
+        return SubstrateHookFunctionThumb(process, reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(symbol) & ~0x1), replace, result);
+}
+#endif
+
+#if defined(__i386__) || defined(__x86_64__)
+
+#include "SubstrateX86.hpp"
+
+static size_t MSGetInstructionWidthIntel(void *start) {
+    hde64s decode;
+    return hde64_disasm(start, &decode);
+}
+
+static void SubstrateHookFunction(SubstrateProcessRef process, void *symbol, void *replace, void **result) {
+    if (MSDebug)
+        MSLog(MSLogLevelNotice, "MSHookFunction(%p, %p, %p)\n", symbol, replace, result);
+    if (symbol == NULL)
+        return;
+
+    uintptr_t source(reinterpret_cast<uintptr_t>(symbol));
+    uintptr_t target(reinterpret_cast<uintptr_t>(replace));
+
+    uint8_t *area(reinterpret_cast<uint8_t *>(symbol));
+
+    size_t required(MSSizeOfJump(target, source));
+
+    if (MSDebug) {
+        char name[16];
+        sprintf(name, "%p", area);
+        MSLogHex(area, 32, name);
+    }
+
+    size_t used(0);
+    while (used < required) {
+        size_t width(MSGetInstructionWidthIntel(area + used));
+        if (width == 0) {
+            MSLog(MSLogLevelError, "MS:Error:MSGetInstructionWidthIntel(%p) == 0", area + used);
+            return;
+        }
+
+        used += width;
+    }
+
+    size_t blank(used - required);
+
+    if (MSDebug) {
+        char name[16];
+        sprintf(name, "%p", area);
+        MSLogHex(area, used + sizeof(uint16_t), name);
+    }
+
+    uint8_t backup[used];
+    memcpy(backup, area, used);
+
+    if (result != NULL) {
+
+    if (backup[0] == 0xe9) {
+        *result = reinterpret_cast<void *>(source + 5 + *reinterpret_cast<uint32_t *>(backup + 1));
+        return;
+    }
+
+    if (!ia32 && backup[0] == 0xff && backup[1] == 0x25) {
+        *result = *reinterpret_cast<void **>(source + 6 + *reinterpret_cast<uint32_t *>(backup + 2));
+        return;
+    }
+
+    size_t length(used + MSSizeOfJump(source + used));
+
+    for (size_t offset(0), width; offset != used; offset += width) {
+        hde64s decode;
+        hde64_disasm(backup + offset, &decode);
+        width = decode.len;
+        //_assert(width != 0 && offset + width <= used);
+
+#ifdef __LP64__
+        if ((decode.modrm & 0xc7) == 0x05) {
+            if (decode.opcode == 0x8b) {
+                void *destiny(area + offset + width + int32_t(decode.disp.disp32));
+                uint8_t reg(decode.rex_r << 3 | decode.modrm_reg);
+                length -= decode.len;
+                length += MSSizeOfPushPointer(destiny);
+                length += MSSizeOfPop(reg);
+                length += MSSizeOfMove64();
+            } else {
+                MSLog(MSLogLevelError, "MS:Error: Unknown RIP-Relative (%.2x %.2x)", decode.opcode, decode.opcode2);
+                continue;
+            }
+        } else
+#endif
+
+        if (backup[offset] == 0xe8) {
+            int32_t relative(*reinterpret_cast<int32_t *>(backup + offset + 1));
+            void *destiny(area + offset + decode.len + relative);
+
+            if (relative == 0) {
+                length -= decode.len;
+                length += MSSizeOfPushPointer(destiny);
+            } else {
+                length += MSSizeOfSkip();
+                length += MSSizeOfJump(destiny);
+            }
+        } else if (backup[offset] == 0xeb) {
+            length -= decode.len;
+            length += MSSizeOfJump(area + offset + decode.len + *reinterpret_cast<int8_t *>(backup + offset + 1));
+        } else if (backup[offset] == 0xe9) {
+            length -= decode.len;
+            length += MSSizeOfJump(area + offset + decode.len + *reinterpret_cast<int32_t *>(backup + offset + 1));
+        } else if (
+            backup[offset] == 0xe3 ||
+            (backup[offset] & 0xf0) == 0x70
+            // XXX: opcode2 & 0xf0 is 0x80?
+        ) {
+            length += decode.len;
+            length += MSSizeOfJump(area + offset + decode.len + *reinterpret_cast<int8_t *>(backup + offset + 1));
+        }
+    }
+
+    uint8_t *buffer(reinterpret_cast<uint8_t *>(mmap(
+        NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0
+    )));
+
+    if (buffer == MAP_FAILED) {
+        MSLog(MSLogLevelError, "MS:Error:mmap() = %d", errno);
+        *result = NULL;
+        return;
+    }
+
+    if (false) fail: {
+        munmap(buffer, length);
+        *result = NULL;
+        return;
+    }
+
+    {
+        uint8_t *current(buffer);
+
+        for (size_t offset(0), width; offset != used; offset += width) {
+            hde64s decode;
+            hde64_disasm(backup + offset, &decode);
+            width = decode.len;
+            //_assert(width != 0 && offset + width <= used);
+
+#ifdef __LP64__
+            if ((decode.modrm & 0xc7) == 0x05) {
+                if (decode.opcode == 0x8b) {
+                    void *destiny(area + offset + width + int32_t(decode.disp.disp32));
+                    uint8_t reg(decode.rex_r << 3 | decode.modrm_reg);
+                    MSPushPointer(current, destiny);
+                    MSWritePop(current, reg);
+                    MSWriteMove64(current, reg, reg);
+                } else {
+                    MSLog(MSLogLevelError, "MS:Error: Unknown RIP-Relative (%.2x %.2x)", decode.opcode, decode.opcode2);
+                    goto copy;
+                }
+            } else
+#endif
+
+            if (backup[offset] == 0xe8) {
+                int32_t relative(*reinterpret_cast<int32_t *>(backup + offset + 1));
+                if (relative == 0)
+                    MSPushPointer(current, area + offset + decode.len);
+                else {
+                    MSWrite<uint8_t>(current, 0xe8);
+                    MSWrite<int32_t>(current, MSSizeOfSkip());
+                    void *destiny(area + offset + decode.len + relative);
+                    MSWriteSkip(current, MSSizeOfJump(destiny, current + MSSizeOfSkip()));
+                    MSWriteJump(current, destiny);
+                }
+            } else if (backup[offset] == 0xeb)
+                MSWriteJump(current, area + offset + decode.len + *reinterpret_cast<int8_t *>(backup + offset + 1));
+            else if (backup[offset] == 0xe9)
+                MSWriteJump(current, area + offset + decode.len + *reinterpret_cast<int32_t *>(backup + offset + 1));
+            else if (
+                backup[offset] == 0xe3 ||
+                (backup[offset] & 0xf0) == 0x70
+            ) {
+                MSWrite<uint8_t>(current, backup[offset]);
+                MSWrite<uint8_t>(current, 2);
+                MSWrite<uint8_t>(current, 0xeb);
+                void *destiny(area + offset + decode.len + *reinterpret_cast<int8_t *>(backup + offset + 1));
+                MSWrite<uint8_t>(current, MSSizeOfJump(destiny, current + 1));
+                MSWriteJump(current, destiny);
+            } else
+#ifdef __LP64__
+                copy:
+#endif
+            {
+                MSWrite(current, backup + offset, width);
+            }
+        }
+
+        MSWriteJump(current, area + used);
+    }
+
+    if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) {
+        MSLog(MSLogLevelError, "MS:Error:mprotect():%d", errno);
+        goto fail;
+    }
+
+    *result = buffer;
+
+    if (MSDebug) {
+        char name[16];
+        sprintf(name, "%p", *result);
+        MSLogHex(buffer, length, name);
+    }
+
+    }
+
+    {
+        SubstrateHookMemory code(process, area, used);
+
+        uint8_t *current(area);
+        MSWriteJump(current, target);
+        for (unsigned offset(0); offset != blank; ++offset)
+            MSWrite<uint8_t>(current, 0x90);
+    }
+
+    if (MSDebug) {
+        char name[16];
+        sprintf(name, "%p", area);
+        MSLogHex(area, used + sizeof(uint16_t), name);
+    }
+}
+#endif
+
+_extern void MSHookFunction(void *symbol, void *replace, void **result) {
+     SubstrateHookFunction(NULL, symbol, replace, result);
+}
+
+#if defined(__APPLE__) && defined(__arm__)
+_extern void _Z14MSHookFunctionPvS_PS_(void *symbol, void *replace, void **result) {
+    return MSHookFunction(symbol, replace, result);
+}
+#endif
diff --git a/VirtualApp/lib/src/main/jni/Substrate/SubstrateHook.h b/VirtualApp/lib/src/main/jni/Substrate/SubstrateHook.h
new file mode 100755
index 000000000..40a0296cb
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Substrate/SubstrateHook.h
@@ -0,0 +1,19 @@
+#ifndef __SUBSTRATEHOOK_H__
+#define __SUBSTRATEHOOK_H__
+
+
+#include <stdlib.h>
+
+#define _extern extern "C" __attribute__((__visibility__("default")))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void MSHookFunction(void *symbol, void *replace, void **result);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/VirtualApp/lib/src/main/jni/Substrate/SubstrateLog.hpp b/VirtualApp/lib/src/main/jni/Substrate/SubstrateLog.hpp
new file mode 100755
index 000000000..3e5728014
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Substrate/SubstrateLog.hpp
@@ -0,0 +1,40 @@
+/* Cydia Substrate - Powerful Code Insertion Platform
+ * Copyright (C) 2008-2011  Jay Freeman (saurik)
+*/
+
+/* GNU Lesser General Public License, Version 3 {{{ */
+/*
+ * Substrate is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Substrate is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
+**/
+/* }}} */
+
+#ifndef SUBSTRATE_LOG_HPP
+#define SUBSTRATE_LOG_HPP
+
+#if 0
+#include <android/log.h>
+
+#define MSLog(level, format, ...) ((void)__android_log_print(level, "NNNN", format, __VA_ARGS__))
+
+#define MSLogLevelNotice ANDROID_LOG_INFO
+#define MSLogLevelWarning ANDROID_LOG_WARN
+#define MSLogLevelError ANDROID_LOG_ERROR
+
+#else
+
+#define MSLog(level, format, ...) printf(format, __VA_ARGS__)
+
+#endif
+
+#endif//SUBSTRATE_LOG_HPP
diff --git a/VirtualApp/lib/src/main/jni/Substrate/SubstratePosixMemory.cpp b/VirtualApp/lib/src/main/jni/Substrate/SubstratePosixMemory.cpp
new file mode 100755
index 000000000..709cb228e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Substrate/SubstratePosixMemory.cpp
@@ -0,0 +1,75 @@
+/* Cydia Substrate - Powerful Code Insertion Platform
+ * Copyright (C) 2008-2011  Jay Freeman (saurik)
+*/
+
+/* GNU Lesser General Public License, Version 3 {{{ */
+/*
+ * Substrate is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Substrate is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
+**/
+/* }}} */
+
+#define SubstrateInternal
+#include "CydiaSubstrate.h"
+#include "SubstrateLog.hpp"
+
+#include <sys/mman.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+extern "C" void __clear_cache (void *beg, void *end);
+
+struct __SubstrateMemory {
+    void *address_;
+    size_t width_;
+
+    __SubstrateMemory(void *address, size_t width) :
+        address_(address),
+        width_(width)
+    {
+    }
+};
+
+extern "C" SubstrateMemoryRef SubstrateMemoryCreate(SubstrateAllocatorRef allocator, SubstrateProcessRef process, void *data, size_t size) {
+    if (allocator != NULL) {
+        MSLog(MSLogLevelError, "MS:Error:allocator != %d", 0);
+        return NULL;
+    }
+
+    if (size == 0)
+        return NULL;
+
+    long page(sysconf(_SC_PAGESIZE)); // Portable applications should employ sysconf(_SC_PAGESIZE) instead of getpagesize
+
+    uintptr_t base(reinterpret_cast<uintptr_t>(data) / page * page);
+    size_t width(((reinterpret_cast<uintptr_t>(data) + size - 1) / page + 1) * page - base);
+    void *address(reinterpret_cast<void *>(base));
+
+    if (mprotect(address, width, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) {
+        MSLog(MSLogLevelError, "MS:Error:mprotect() = %d", errno);
+        return NULL;
+    }
+
+    return new __SubstrateMemory(address, width);
+}
+
+extern "C" void SubstrateMemoryRelease(SubstrateMemoryRef memory) {
+    if (mprotect(memory->address_, memory->width_, PROT_READ | PROT_WRITE | PROT_EXEC) == -1)
+        MSLog(MSLogLevelError, "MS:Error:mprotect() = %d", errno);
+
+    __clear_cache(reinterpret_cast<char *>(memory->address_), reinterpret_cast<char *>(memory->address_) + memory->width_);
+
+    delete memory;
+}
diff --git a/VirtualApp/lib/src/main/jni/MSHook/x86.h b/VirtualApp/lib/src/main/jni/Substrate/SubstrateX86.hpp
old mode 100644
new mode 100755
similarity index 78%
rename from VirtualApp/lib/src/main/jni/MSHook/x86.h
rename to VirtualApp/lib/src/main/jni/Substrate/SubstrateX86.hpp
index 4f4e6bb9d..ffe2b06e3
--- a/VirtualApp/lib/src/main/jni/MSHook/x86.h
+++ b/VirtualApp/lib/src/main/jni/Substrate/SubstrateX86.hpp
@@ -1,33 +1,28 @@
+/* Cydia Substrate - Powerful Code Insertion Platform
+ * Copyright (C) 2008-2011  Jay Freeman (saurik)
+*/
+
+/* GNU Lesser General Public License, Version 3 {{{ */
 /*
- * x86.h
+ * Substrate is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Substrate is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
  *
- *  Created on: 2016��2��22��
- *      Author: peng
- */
-
-#ifndef X86_H_
-#define X86_H_
-
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <sys/mman.h>
-#include "CydiaSubstrate.h"
-#include "PosixMemory.h"
-#include "Log.h"
-#include "Debug.h"
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
+**/
+/* }}} */
 
-template <typename Type_>
-_disused static _finline void MSWrite(uint8_t *&buffer, Type_ value) {
-    *reinterpret_cast<Type_ *>(buffer) = value;
-    buffer += sizeof(Type_);
-}
+#ifndef SUBSTRATE_X86_HPP
+#define SUBSTRATE_X86_HPP
 
-_disused static _finline void MSWrite(uint8_t *&buffer, uint8_t *data, size_t size) {
-    memcpy(buffer, data, size);
-    buffer += size;
-}
+#include "Buffer.hpp"
 
 #ifdef __LP64__
 static const bool ia32 = false;
@@ -60,7 +55,7 @@ _disused static size_t MSSizeOfPushPointer(void *target) {
 }
 
 _disused static size_t MSSizeOfJump(bool blind, uintptr_t target, uintptr_t source = 0) {
-    if (ia32 || (!blind && MSIs32BitOffset(target, source + 5)))
+    if (ia32 || !blind && MSIs32BitOffset(target, source + 5))
         return MSSizeOfSkip();
     else
         return MSSizeOfPushPointer(target) + 1;
@@ -108,7 +103,7 @@ _disused static void MSWriteCall(uint8_t *&current, I$r target) {
     if (target >> 3 != 0)
         MSWrite<uint8_t>(current, 0x40 | (target & 0x08) >> 3);
     MSWrite<uint8_t>(current, 0xff);
-    MSWrite<uint8_t>(current, 0xd0 | (target & 0x07));
+    MSWrite<uint8_t>(current, 0xd0 | target & 0x07);
 }
 
 _disused static void MSWriteCall(uint8_t *&current, uintptr_t target) {
@@ -156,13 +151,13 @@ _disused static void MSWriteJump(uint8_t *&current, I$r target) {
     if (target >> 3 != 0)
         MSWrite<uint8_t>(current, 0x40 | (target & 0x08) >> 3);
     MSWrite<uint8_t>(current, 0xff);
-    MSWrite<uint8_t>(current, 0xe0 | (target & 0x07));
+    MSWrite<uint8_t>(current, 0xe0 | target & 0x07);
 }
 
 _disused static void MSWritePop(uint8_t *&current, uint8_t target) {
     if (target >> 3 != 0)
         MSWrite<uint8_t>(current, 0x40 | (target & 0x08) >> 3);
-    MSWrite<uint8_t>(current, 0x58 | (target & 0x07));
+    MSWrite<uint8_t>(current, 0x58 | target & 0x07);
 }
 
 _disused static size_t MSSizeOfPop(uint8_t target) {
@@ -172,18 +167,18 @@ _disused static size_t MSSizeOfPop(uint8_t target) {
 _disused static void MSWritePush(uint8_t *&current, I$r target) {
     if (target >> 3 != 0)
         MSWrite<uint8_t>(current, 0x40 | (target & 0x08) >> 3);
-    MSWrite<uint8_t>(current, 0x50 | (target & 0x07));
+    MSWrite<uint8_t>(current, 0x50 | target & 0x07);
 }
 
 _disused static void MSWriteAdd(uint8_t *&current, I$r target, uint8_t source) {
     MSWrite<uint8_t>(current, 0x83);
-    MSWrite<uint8_t>(current, 0xc4 | (target & 0x07));
+    MSWrite<uint8_t>(current, 0xc4 | target & 0x07);
     MSWrite<uint8_t>(current, source);
 }
 
 _disused static void MSWriteSet64(uint8_t *&current, I$r target, uintptr_t source) {
     MSWrite<uint8_t>(current, 0x48 | (target & 0x08) >> 3 << 2);
-    MSWrite<uint8_t>(current, 0xb8 | (target & 0x7));
+    MSWrite<uint8_t>(current, 0xb8 | target & 0x7);
     MSWrite<uint64_t>(current, source);
 }
 
@@ -195,15 +190,11 @@ _disused static void MSWriteSet64(uint8_t *&current, I$r target, Type_ *source)
 _disused static void MSWriteMove64(uint8_t *&current, uint8_t source, uint8_t target) {
     MSWrite<uint8_t>(current, 0x48 | (target & 0x08) >> 3 << 2 | (source & 0x08) >> 3);
     MSWrite<uint8_t>(current, 0x8b);
-    MSWrite<uint8_t>(current, (target & 0x07) << 3 | (source & 0x07));
+    MSWrite<uint8_t>(current, (target & 0x07) << 3 | source & 0x07);
 }
 
 _disused static size_t MSSizeOfMove64() {
     return 3;
 }
 
-namespace x86{
-	extern "C" void SubstrateHookFunctionx86(SubstrateProcessRef process, void *symbol, void *replace, void **result);
-}
-
-#endif /* X86_H_ */
+#endif//SUBSTRATE_X86_HPP
diff --git a/VirtualApp/lib/src/main/jni/MSHook/x86_64.cpp b/VirtualApp/lib/src/main/jni/Substrate/hde64.c
old mode 100644
new mode 100755
similarity index 76%
rename from VirtualApp/lib/src/main/jni/MSHook/x86_64.cpp
rename to VirtualApp/lib/src/main/jni/Substrate/hde64.c
index 60af6158f..d69f0c68e
--- a/VirtualApp/lib/src/main/jni/MSHook/x86_64.cpp
+++ b/VirtualApp/lib/src/main/jni/Substrate/hde64.c
@@ -1,55 +1,16 @@
-#ifndef X86_64_CPP_
-#define X86_64_CPP_
-
-#include <stdint.h>
-#include <string.h>
-#include "x86_64.h"
-
-unsigned char hde64_table[] = {
-  0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5,
-  0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1,
-  0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea,
-  0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0,
-  0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab,
-  0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92,
-  0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90,
-  0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b,
-  0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,
-  0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc,
-  0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20,
-  0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff,
-  0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00,
-  0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01,
-  0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10,
-  0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00,
-  0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00,
-  0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00,
-  0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00,
-  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,
-  0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00,
-  0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40,
-  0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43,
-  0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,
-  0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40,
-  0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06,
-  0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07,
-  0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04,
-  0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10,
-  0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00,
-  0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb,
-  0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff,
-  0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09,
-  0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff,
-  0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08,
-  0x00,0xf0,0x02,0x00
-};
-
 /*
  * Hacker Disassembler Engine 64 C
  * Copyright (c) 2008-2009, Vyacheslav Patkov.
  * All rights reserved.
  *
  */
+
+#include <stdint.h>
+#include <string.h>
+
+#include "hde64.h"
+#include "table64.h"
+
 unsigned int hde64_disasm(const void *code, hde64s *hs)
 {
     uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0;
@@ -57,6 +18,7 @@ unsigned int hde64_disasm(const void *code, hde64s *hs)
     uint8_t op64 = 0;
 
     memset(hs,0,sizeof(hde64s));
+    char *tmp=(char*)hs;
 
     for (x = 16; x; x--)
         switch (c = *p++) {
@@ -368,7 +330,3 @@ unsigned int hde64_disasm(const void *code, hde64s *hs)
 
     return (unsigned int)hs->len;
 }
-
-
-
-#endif /* X86_64_CPP_ */
diff --git a/VirtualApp/lib/src/main/jni/MSHook/hde64.h b/VirtualApp/lib/src/main/jni/Substrate/hde64.h
old mode 100644
new mode 100755
similarity index 100%
rename from VirtualApp/lib/src/main/jni/MSHook/hde64.h
rename to VirtualApp/lib/src/main/jni/Substrate/hde64.h
diff --git a/VirtualApp/lib/src/main/jni/Substrate/table64.h b/VirtualApp/lib/src/main/jni/Substrate/table64.h
new file mode 100755
index 000000000..144f29076
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/Substrate/table64.h
@@ -0,0 +1,74 @@
+/*
+ * Hacker Disassembler Engine 64 C
+ * Copyright (c) 2008-2009, Vyacheslav Patkov.
+ * All rights reserved.
+ *
+ */
+
+#define C_NONE    0x00
+#define C_MODRM   0x01
+#define C_IMM8    0x02
+#define C_IMM16   0x04
+#define C_IMM_P66 0x10
+#define C_REL8    0x20
+#define C_REL32   0x40
+#define C_GROUP   0x80
+#define C_ERROR   0xff
+
+#define PRE_ANY  0x00
+#define PRE_NONE 0x01
+#define PRE_F2   0x02
+#define PRE_F3   0x04
+#define PRE_66   0x08
+#define PRE_67   0x10
+#define PRE_LOCK 0x20
+#define PRE_SEG  0x40
+#define PRE_ALL  0xff
+
+#define DELTA_OPCODES      0x4a
+#define DELTA_FPU_REG      0xfd
+#define DELTA_FPU_MODRM    0x104
+#define DELTA_PREFIXES     0x13c
+#define DELTA_OP_LOCK_OK   0x1ae
+#define DELTA_OP2_LOCK_OK  0x1c6
+#define DELTA_OP_ONLY_MEM  0x1d8
+#define DELTA_OP2_ONLY_MEM 0x1e7
+
+unsigned char hde64_table[] = {
+  0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5,
+  0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1,
+  0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea,
+  0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0,
+  0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab,
+  0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92,
+  0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90,
+  0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b,
+  0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,
+  0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc,
+  0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20,
+  0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff,
+  0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00,
+  0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01,
+  0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10,
+  0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00,
+  0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00,
+  0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00,
+  0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,
+  0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00,
+  0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40,
+  0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43,
+  0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,
+  0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40,
+  0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06,
+  0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07,
+  0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04,
+  0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10,
+  0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00,
+  0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb,
+  0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff,
+  0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09,
+  0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff,
+  0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08,
+  0x00,0xf0,0x02,0x00
+};
diff --git a/VirtualApp/lib/src/main/jni/fb/Android.mk b/VirtualApp/lib/src/main/jni/fb/Android.mk
new file mode 100644
index 000000000..b256e9d95
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/Android.mk
@@ -0,0 +1,45 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+       assert.cpp \
+       jni/ByteBuffer.cpp \
+       jni/Countable.cpp \
+       jni/Environment.cpp \
+       jni/Exceptions.cpp \
+       jni/fbjni.cpp \
+       jni/Hybrid.cpp \
+       jni/jni_helpers.cpp \
+       jni/LocalString.cpp \
+       jni/OnLoad.cpp \
+       jni/References.cpp \
+       jni/WeakReference.cpp \
+       log.cpp \
+       lyra/lyra.cpp \
+       onload.cpp \
+
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
+
+LOCAL_CFLAGS := -DLOG_TAG=\"libfb\" -DDISABLE_CPUCAP -DDISABLE_XPLAT -fexceptions -frtti
+LOCAL_CFLAGS += -Wall -Werror
+# include/utils/threads.h has unused parameters
+LOCAL_CFLAGS += -Wno-unused-parameter
+ifeq ($(TOOLCHAIN_PERMISSIVE),true)
+  LOCAL_CFLAGS += -Wno-error=unused-but-set-variable
+endif
+LOCAL_CFLAGS += -DHAVE_POSIX_CLOCKS
+
+CXX11_FLAGS := -std=gnu++11
+LOCAL_CFLAGS += $(CXX11_FLAGS)
+
+LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS)
+
+LOCAL_LDLIBS := -llog -ldl -landroid
+LOCAL_EXPORT_LDLIBS := -llog
+
+LOCAL_MODULE := libfb
+
+include $(BUILD_STATIC_LIBRARY)
+
diff --git a/VirtualApp/lib/src/main/jni/fb/BUCK b/VirtualApp/lib/src/main/jni/fb/BUCK
new file mode 100644
index 000000000..4ffcf0c9c
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/BUCK
@@ -0,0 +1,27 @@
+include_defs("//ReactAndroid/DEFS")
+
+# This target is only used in open source
+if IS_OSS_BUILD:
+  cxx_library(
+    name = 'jni',
+    soname = 'libfb.$(ext)',
+    srcs = glob(['*.cpp', 'jni/*.cpp', 'lyra/*.cpp']),
+    header_namespace = '',
+    compiler_flags = [
+      '-fno-omit-frame-pointer',
+      '-fexceptions',
+      '-Wall',
+      '-Werror',
+      '-std=c++11',
+      '-DDISABLE_CPUCAP',
+      '-DDISABLE_XPLAT',
+    ],
+    exported_headers = subdir_glob([
+      ('include', 'fb/**/*.h'),
+      ('include', 'jni/*.h'),
+    ]),
+    deps = [
+      JNI_TARGET,
+    ],
+    visibility = ['PUBLIC'],
+  )
diff --git a/VirtualApp/lib/src/main/jni/fb/Doxyfile b/VirtualApp/lib/src/main/jni/fb/Doxyfile
new file mode 100644
index 000000000..b44118dd5
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/Doxyfile
@@ -0,0 +1,15 @@
+PROJECT_NAME           = "Facebook Android Support"
+JAVADOC_AUTOBRIEF      = YES
+EXTRACT_ALL            = YES
+RECURSIVE              = YES
+EXCLUDE                = tests
+EXCLUDE_PATTERNS       = *.cpp
+GENERATE_HTML          = YES
+GENERATE_LATEX         = NO
+ENABLE_PREPROCESSING   = YES
+HIDE_UNDOC_MEMBERS     = YES
+HIDE_SCOPE_NAMES       = YES
+HIDE_FRIEND_COMPOUNDS  = YES
+HIDE_UNDOC_CLASSES     = YES
+SHOW_INCLUDE_FILES     = NO
+#ENABLED_SECTIONS       = INTERNAL
diff --git a/VirtualApp/lib/src/main/jni/fb/assert.cpp b/VirtualApp/lib/src/main/jni/fb/assert.cpp
new file mode 100644
index 000000000..db9a43159
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/assert.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <cstdarg>
+#include <stdio.h>
+
+#include <fb/assert.h>
+#include <fb/log.h>
+
+namespace facebook {
+
+#define ASSERT_BUF_SIZE 4096
+static char sAssertBuf[ASSERT_BUF_SIZE];
+static AssertHandler gAssertHandler;
+
+void assertInternal(const char* formatstr ...) {
+    va_list va_args;
+    va_start(va_args, formatstr);
+    vsnprintf(sAssertBuf, sizeof(sAssertBuf), formatstr, va_args);
+    va_end(va_args);
+    if (gAssertHandler != NULL) {
+        gAssertHandler(sAssertBuf);
+    }
+    FBLOG(LOG_FATAL, "fbassert", "%s", sAssertBuf);
+    // crash at this specific address so that we can find our crashes easier
+    *(int*)0xdeadb00c = 0;
+    // let the compiler know we won't reach the end of the function
+     __builtin_unreachable();
+}
+
+void setAssertHandler(AssertHandler assertHandler) {
+    gAssertHandler = assertHandler;
+}
+
+} // namespace facebook
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/ALog.h b/VirtualApp/lib/src/main/jni/fb/include/fb/ALog.h
new file mode 100644
index 000000000..5986ca3ed
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/ALog.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/** @file ALog.h
+ *
+ *  Very simple android only logging. Define LOG_TAG to enable the macros.
+ */
+
+#pragma once
+
+
+#ifdef __ANDROID__
+
+#include <android/log.h>
+
+namespace facebook {
+namespace alog {
+
+template<typename... ARGS>
+inline void log(int level, const char* tag, const char* msg, ARGS... args) noexcept {
+  __android_log_print(level, tag, msg, args...);
+}
+
+template<typename... ARGS>
+inline void log(int level, const char* tag, const char* msg) noexcept {
+  __android_log_write(level, tag, msg);
+}
+
+template<typename... ARGS>
+inline void logv(const char* tag, const char* msg, ARGS... args) noexcept {
+  log(ANDROID_LOG_VERBOSE, tag, msg, args...);
+}
+
+template<typename... ARGS>
+inline void logd(const char* tag, const char* msg, ARGS... args) noexcept {
+  log(ANDROID_LOG_DEBUG, tag, msg, args...);
+}
+
+template<typename... ARGS>
+inline void logi(const char* tag, const char* msg, ARGS... args) noexcept {
+  log(ANDROID_LOG_INFO, tag, msg, args...);
+}
+
+template<typename... ARGS>
+inline void logw(const char* tag, const char* msg, ARGS... args) noexcept {
+  log(ANDROID_LOG_WARN, tag, msg, args...);
+}
+
+template<typename... ARGS>
+inline void loge(const char* tag, const char* msg, ARGS... args) noexcept {
+  log(ANDROID_LOG_ERROR, tag, msg, args...);
+}
+
+template<typename... ARGS>
+inline void logf(const char* tag, const char* msg, ARGS... args) noexcept {
+  log(ANDROID_LOG_FATAL, tag, msg, args...);
+}
+
+
+#ifdef LOG_TAG
+# define ALOGV(...) ::facebook::alog::logv(LOG_TAG, __VA_ARGS__)
+# define ALOGD(...) ::facebook::alog::logd(LOG_TAG, __VA_ARGS__)
+# define ALOGI(...) ::facebook::alog::logi(LOG_TAG, __VA_ARGS__)
+# define ALOGW(...) ::facebook::alog::logw(LOG_TAG, __VA_ARGS__)
+# define ALOGE(...) ::facebook::alog::loge(LOG_TAG, __VA_ARGS__)
+# define ALOGF(...) ::facebook::alog::logf(LOG_TAG, __VA_ARGS__)
+#endif
+
+}}
+
+#else
+# define ALOGV(...) ((void)0)
+# define ALOGD(...) ((void)0)
+# define ALOGI(...) ((void)0)
+# define ALOGW(...) ((void)0)
+# define ALOGE(...) ((void)0)
+# define ALOGF(...) ((void)0)
+#endif
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/Build.h b/VirtualApp/lib/src/main/jni/fb/include/fb/Build.h
new file mode 100644
index 000000000..98ad55746
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/Build.h
@@ -0,0 +1,26 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <stdlib.h>
+
+#if defined(__ANDROID__)
+#  include <sys/system_properties.h>
+#endif
+
+namespace facebook {
+namespace build {
+
+struct Build {
+  static int getAndroidSdk() {
+    static auto android_sdk = ([] {
+       char sdk_version_str[PROP_VALUE_MAX];
+       __system_property_get("ro.build.version.sdk", sdk_version_str);
+       return atoi(sdk_version_str);
+    })();
+    return android_sdk;
+  }
+};
+
+} // build
+} // facebook
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/Countable.h b/VirtualApp/lib/src/main/jni/fb/include/fb/Countable.h
new file mode 100644
index 000000000..1e402a3fc
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/Countable.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+#include <atomic>
+#include <fb/assert.h>
+#include <fb/noncopyable.h>
+#include <fb/nonmovable.h>
+#include <fb/RefPtr.h>
+
+namespace facebook {
+
+class Countable : public noncopyable, public nonmovable {
+public:
+  // RefPtr expects refcount to start at 0
+  Countable() : m_refcount(0) {}
+  virtual ~Countable()
+  {
+    FBASSERT(m_refcount == 0);
+  }
+
+private:
+  void ref() {
+    ++m_refcount;
+  }
+
+  void unref() {
+    if (0 == --m_refcount) {
+      delete this;
+    }
+  }
+
+  bool hasOnlyOneRef() const {
+    return m_refcount == 1;
+  }
+
+  template <typename T> friend class RefPtr;
+  std::atomic<int> m_refcount;
+};
+
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/Doxyfile b/VirtualApp/lib/src/main/jni/fb/include/fb/Doxyfile
new file mode 100644
index 000000000..8b4df6a7c
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/Doxyfile
@@ -0,0 +1,18 @@
+PROJECT_NAME           = "Facebook JNI"
+PROJECT_BRIEF          = "Helper library to provide safe and convenient access to JNI with very low overhead"
+JAVADOC_AUTOBRIEF      = YES
+EXTRACT_ALL            = YES
+RECURSIVE              = YES
+EXCLUDE                = tests Asserts.h Countable.h GlobalReference.h LocalReference.h LocalString.h Registration.h WeakReference.h jni_helpers.h Environment.h
+EXCLUDE_PATTERNS       = *-inl.h *.cpp
+GENERATE_HTML          = YES
+GENERATE_LATEX         = NO
+ENABLE_PREPROCESSING   = YES
+HIDE_UNDOC_MEMBERS     = YES
+HIDE_SCOPE_NAMES       = YES
+HIDE_FRIEND_COMPOUNDS  = YES
+HIDE_UNDOC_CLASSES     = YES
+SHOW_INCLUDE_FILES     = NO
+PREDEFINED             = LOG_TAG=fbjni
+EXAMPLE_PATH           = samples
+#ENABLED_SECTIONS       = INTERNAL
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/Environment.h b/VirtualApp/lib/src/main/jni/fb/include/fb/Environment.h
new file mode 100644
index 000000000..34864fc32
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/Environment.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+#include <functional>
+#include <string>
+#include <jni.h>
+
+#include <fb/visibility.h>
+
+namespace facebook {
+namespace jni {
+
+namespace internal {
+  struct CacheEnvTag {};
+}
+
+// Keeps a thread-local reference to the current thread's JNIEnv.
+struct Environment {
+  // May be null if this thread isn't attached to the JVM
+  FBEXPORT static JNIEnv* current();
+  static void initialize(JavaVM* vm);
+
+  // There are subtle issues with calling the next functions directly. It is
+  // much better to always use a ThreadScope to manage attaching/detaching for
+  // you.
+  FBEXPORT static JNIEnv* ensureCurrentThreadIsAttached();
+  FBEXPORT static void detachCurrentThread();
+};
+
+/**
+ * RAII Object that attaches a thread to the JVM. Failing to detach from a thread before it
+ * exits will cause a crash, as will calling Detach an extra time, and this guard class helps
+ * keep that straight. In addition, it remembers whether it performed the attach or not, so it
+ * is safe to nest it with itself or with non-fbjni code that manages the attachment correctly.
+ *
+ * Potential concerns:
+ *  - Attaching to the JVM is fast (~100us on MotoG), but ideally you would attach while the
+ *    app is not busy.
+ *  - Having a thread detach at arbitrary points is not safe in Dalvik; you need to be sure that
+ *    there is no Java code on the current stack or you run the risk of a crash like:
+ *      ERROR: detaching thread with interp frames (count=18)
+ *    (More detail at https://groups.google.com/forum/#!topic/android-ndk/2H8z5grNqjo)
+ *    ThreadScope won't do a detach if the thread was already attached before the guard is
+ *    instantiated, but there's probably some usage that could trip this up.
+ *  - Newly attached C++ threads only get the bootstrap class loader -- i.e. java language
+ *    classes, not any of our application's classes. This will be different behavior than threads
+ *    that were initiated on the Java side. A workaround is to pass a global reference for a
+ *    class or instance to the new thread; this bypasses the need for the class loader.
+ *    (See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread)
+ *    If you need access to the application's classes, you can use ThreadScope::WithClassLoader.
+ */
+class FBEXPORT ThreadScope {
+ public:
+  ThreadScope();
+  ThreadScope(ThreadScope&) = delete;
+  ThreadScope(ThreadScope&&) = default;
+  ThreadScope& operator=(ThreadScope&) = delete;
+  ThreadScope& operator=(ThreadScope&&) = delete;
+  ~ThreadScope();
+
+  /**
+   * This runs the closure in a scope with fbjni's classloader. This should be
+   * the same classloader as the rest of the application and thus anything
+   * running in the closure will have access to the same classes as in a normal
+   * java-create thread.
+   */
+  static void WithClassLoader(std::function<void()>&& runnable);
+
+  static void OnLoad();
+
+  // This constructor is only used internally by fbjni.
+  ThreadScope(JNIEnv*, internal::CacheEnvTag);
+ private:
+  friend struct Environment;
+  ThreadScope* previous_;
+  // If the JNIEnv* is set, it is guaranteed to be valid at least through the
+  // lifetime of this ThreadScope. The only case where that guarantee can be
+  // made is when there is a java frame in the stack below this.
+  JNIEnv* env_;
+  bool attachedWithThisScope_;
+};
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/ProgramLocation.h b/VirtualApp/lib/src/main/jni/fb/include/fb/ProgramLocation.h
new file mode 100644
index 000000000..36f7737f6
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/ProgramLocation.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+#include <cstring>
+#include <string>
+#include <sstream>
+
+namespace facebook {
+
+#define FROM_HERE facebook::ProgramLocation(__FUNCTION__, __FILE__, __LINE__)
+
+class ProgramLocation {
+public:
+  ProgramLocation() : m_functionName("Unspecified"), m_fileName("Unspecified"), m_lineNumber(0) {}
+
+  ProgramLocation(const char* functionName, const char* fileName, int line) :
+      m_functionName(functionName),
+      m_fileName(fileName),
+      m_lineNumber(line)
+    {}
+
+  const char* functionName() const { return m_functionName; }
+  const char* fileName() const { return m_fileName; }
+  int lineNumber() const { return m_lineNumber; }
+
+  std::string asFormattedString() const {
+    std::stringstream str;
+    str << "Function " << m_functionName << " in file " << m_fileName << ":" << m_lineNumber;
+    return str.str();
+  }
+
+  bool operator==(const ProgramLocation& other) const {
+    // Assumes that the strings are static
+    return (m_functionName == other.m_functionName) && (m_fileName == other.m_fileName) && m_lineNumber == other.m_lineNumber;
+  }
+
+private:
+  const char* m_functionName;
+  const char* m_fileName;
+  int m_lineNumber;
+};
+
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/RefPtr.h b/VirtualApp/lib/src/main/jni/fb/include/fb/RefPtr.h
new file mode 100644
index 000000000..d21fe697e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/RefPtr.h
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+#include <utility>
+#include <fb/assert.h>
+
+namespace facebook {
+
+// Reference counting smart pointer. This is designed to work with the
+// Countable class or other implementations in the future. It is designed in a
+// way to be both efficient and difficult to misuse. Typical usage is very
+// simple once you learn the patterns (and the compiler will help!):
+//
+// By default, the internal pointer is null.
+//   RefPtr<Foo> ref;
+//
+// Object creation requires explicit construction:
+//   RefPtr<Foo> ref = createNew<Foo>(...);
+//
+// Or if the constructor is not public:
+//   RefPtr<Foo> ref = adoptRef(new Foo(...));
+//
+// But you can implicitly create from nullptr:
+//   RefPtr<Foo> maybeRef = cond ? ref : nullptr;
+//
+// Move/Copy Construction/Assignment are straightforward:
+//   RefPtr<Foo> ref2 = ref;
+//   ref = std::move(ref2);
+//
+// Destruction automatically drops the RefPtr's reference as expected.
+//
+// Upcasting is implicit but downcasting requires an explicit cast:
+//   struct Bar : public Foo {};
+//   RefPtr<Bar> barRef = static_cast<RefPtr<Bar>>(ref);
+//   ref = barRef;
+//
+template <class T>
+class RefPtr {
+public:
+  constexpr RefPtr() :
+    m_ptr(nullptr)
+  {}
+
+  // Allow implicit construction from a pointer only from nullptr
+  constexpr RefPtr(std::nullptr_t ptr) :
+    m_ptr(nullptr)
+  {}
+
+  RefPtr(const RefPtr<T>& ref) :
+    m_ptr(ref.m_ptr)
+  {
+    refIfNecessary(m_ptr);
+  }
+
+  // Only allow implicit upcasts. A downcast will result in a compile error
+  // unless you use static_cast (which will end up invoking the explicit
+  // operator below).
+  template <typename U>
+  RefPtr(const RefPtr<U>& ref, typename std::enable_if<std::is_base_of<T,U>::value, U>::type* = nullptr) :
+    m_ptr(ref.get())
+  {
+    refIfNecessary(m_ptr);
+  }
+
+  RefPtr(RefPtr<T>&& ref) :
+    m_ptr(nullptr)
+  {
+    *this = std::move(ref);
+  }
+
+  // Only allow implicit upcasts. A downcast will result in a compile error
+  // unless you use static_cast (which will end up invoking the explicit
+  // operator below).
+  template <typename U>
+  RefPtr(RefPtr<U>&& ref, typename std::enable_if<std::is_base_of<T,U>::value, U>::type* = nullptr) :
+    m_ptr(nullptr)
+  {
+    *this = std::move(ref);
+  }
+
+  ~RefPtr() {
+    unrefIfNecessary(m_ptr);
+    m_ptr = nullptr;
+  }
+
+  RefPtr<T>& operator=(const RefPtr<T>& ref) {
+    if (m_ptr != ref.m_ptr) {
+      unrefIfNecessary(m_ptr);
+      m_ptr = ref.m_ptr;
+      refIfNecessary(m_ptr);
+    }
+    return *this;
+  }
+
+  // The STL assumes rvalue references are unique and for simplicity's sake, we
+  // make the same assumption here, that &ref != this.
+  RefPtr<T>& operator=(RefPtr<T>&& ref) {
+    unrefIfNecessary(m_ptr);
+    m_ptr = ref.m_ptr;
+    ref.m_ptr = nullptr;
+    return *this;
+  }
+
+  template <typename U>
+  RefPtr<T>& operator=(RefPtr<U>&& ref) {
+    unrefIfNecessary(m_ptr);
+    m_ptr = ref.m_ptr;
+    ref.m_ptr = nullptr;
+    return *this;
+  }
+
+  void reset() {
+    unrefIfNecessary(m_ptr);
+    m_ptr = nullptr;
+  }
+
+  T* get() const {
+    return m_ptr;
+  }
+
+  T* operator->() const {
+    return m_ptr;
+  }
+
+  T& operator*() const {
+    return *m_ptr;
+  }
+
+  template <typename U>
+  explicit operator RefPtr<U> () const;
+
+  explicit operator bool() const {
+    return m_ptr ? true : false;
+  }
+
+  bool isTheLastRef() const {
+    FBASSERT(m_ptr);
+    return m_ptr->hasOnlyOneRef();
+  }
+
+  // Creates a strong reference from a raw pointer, assuming that is already
+  // referenced from some other RefPtr. This should be used sparingly.
+  static inline RefPtr<T> assumeAlreadyReffed(T* ptr) {
+    return RefPtr<T>(ptr, ConstructionMode::External);
+  }
+
+  // Creates a strong reference from a raw pointer, assuming that it points to a
+  // freshly-created object. See the documentation for RefPtr for usage.
+  static inline RefPtr<T> adoptRef(T* ptr) {
+    return RefPtr<T>(ptr, ConstructionMode::Adopted);
+  }
+
+private:
+  enum class ConstructionMode {
+    Adopted,
+    External
+  };
+
+  RefPtr(T* ptr, ConstructionMode mode) :
+    m_ptr(ptr)
+  {
+    FBASSERTMSGF(ptr, "Got null pointer in %s construction mode", mode == ConstructionMode::Adopted ? "adopted" : "external");
+    ptr->ref();
+    if (mode == ConstructionMode::Adopted) {
+      FBASSERT(ptr->hasOnlyOneRef());
+    }
+  }
+
+  static inline void refIfNecessary(T* ptr) {
+    if (ptr) {
+      ptr->ref();
+    }
+  }
+  static inline void unrefIfNecessary(T* ptr) {
+    if (ptr) {
+      ptr->unref();
+    }
+  }
+
+  template <typename U> friend class RefPtr;
+
+  T* m_ptr;
+};
+
+// Creates a strong reference from a raw pointer, assuming that is already
+// referenced from some other RefPtr and that it is non-null. This should be
+// used sparingly.
+template <typename T>
+static inline RefPtr<T> assumeAlreadyReffed(T* ptr) {
+  return RefPtr<T>::assumeAlreadyReffed(ptr);
+}
+
+// As above, but tolerant of nullptr.
+template <typename T>
+static inline RefPtr<T> assumeAlreadyReffedOrNull(T* ptr) {
+  return ptr ? RefPtr<T>::assumeAlreadyReffed(ptr) : nullptr;
+}
+
+// Creates a strong reference from a raw pointer, assuming that it points to a
+// freshly-created object. See the documentation for RefPtr for usage.
+template <typename T>
+static inline RefPtr<T> adoptRef(T* ptr) {
+  return RefPtr<T>::adoptRef(ptr);
+}
+
+template <typename T, typename ...Args>
+static inline RefPtr<T> createNew(Args&&... arguments) {
+  return RefPtr<T>::adoptRef(new T(std::forward<Args>(arguments)...));
+}
+
+template <typename T> template <typename U>
+RefPtr<T>::operator RefPtr<U>() const {
+  static_assert(std::is_base_of<T, U>::value, "Invalid static cast");
+  return assumeAlreadyReffedOrNull<U>(static_cast<U*>(m_ptr));
+}
+
+template <typename T, typename U>
+inline bool operator==(const RefPtr<T>& a, const RefPtr<U>& b) {
+  return a.get() == b.get();
+}
+
+template <typename T, typename U>
+inline bool operator!=(const RefPtr<T>& a, const RefPtr<U>& b) {
+  return a.get() != b.get();
+}
+
+template <typename T, typename U>
+inline bool operator==(const RefPtr<T>& ref, U* ptr) {
+  return ref.get() == ptr;
+}
+
+template <typename T, typename U>
+inline bool operator!=(const RefPtr<T>& ref, U* ptr) {
+  return ref.get() != ptr;
+}
+
+template <typename T, typename U>
+inline bool operator==(U* ptr, const RefPtr<T>& ref) {
+  return ref.get() == ptr;
+}
+
+template <typename T, typename U>
+inline bool operator!=(U* ptr, const RefPtr<T>& ref) {
+  return ref.get() != ptr;
+}
+
+template <typename T>
+inline bool operator==(const RefPtr<T>& ref, std::nullptr_t ptr) {
+  return ref.get() == ptr;
+}
+
+template <typename T>
+inline bool operator!=(const RefPtr<T>& ref, std::nullptr_t ptr) {
+  return ref.get() != ptr;
+}
+
+template <typename T>
+inline bool operator==(std::nullptr_t ptr, const RefPtr<T>& ref) {
+  return ref.get() == ptr;
+}
+
+template <typename T>
+inline bool operator!=(std::nullptr_t ptr, const RefPtr<T>& ref) {
+  return ref.get() != ptr;
+}
+
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/StaticInitialized.h b/VirtualApp/lib/src/main/jni/fb/include/fb/StaticInitialized.h
new file mode 100644
index 000000000..6d943972a
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/StaticInitialized.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+#include <fb/assert.h>
+#include <utility>
+
+namespace facebook {
+
+// Class that lets you declare a global but does not add a static constructor
+// to the binary. Eventually I'd like to have this auto-initialize in a
+// multithreaded environment but for now it's easiest just to use manual
+// initialization.
+template <typename T>
+class StaticInitialized {
+public:
+  constexpr StaticInitialized() :
+    m_instance(nullptr)
+  {}
+
+  template <typename ...Args>
+  void initialize(Args&&... arguments) {
+    FBASSERT(!m_instance);
+    m_instance = new T(std::forward<Args>(arguments)...);
+  }
+
+  T* operator->() const {
+    return m_instance;
+  }
+private:
+  T* m_instance;
+};
+
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/ThreadLocal.h b/VirtualApp/lib/src/main/jni/fb/include/fb/ThreadLocal.h
new file mode 100644
index 000000000..d86a2f0de
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/ThreadLocal.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <pthread.h>
+#include <errno.h>
+
+#include <fb/assert.h>
+
+namespace facebook {
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A thread-local object is a "global" object within a thread. This is useful
+ * for writing apartment-threaded code, where nothing is actullay shared
+ * between different threads (hence no locking) but those variables are not
+ * on stack in local scope. To use it, just do something like this,
+ *
+ *   ThreadLocal<MyClass> static_object;
+ *     static_object->data_ = ...;
+ *     static_object->doSomething();
+ *
+ *   ThreadLocal<int> static_number;
+ *     int value = *static_number;
+ *
+ * So, syntax-wise it's similar to pointers. T can be primitive types, and if
+ * it's a class, there has to be a default constructor.
+ */
+template<typename T>
+class ThreadLocal {
+public:
+  /**
+   * Constructor that has to be called from a thread-neutral place.
+   */
+  ThreadLocal() :
+    m_key(0),
+    m_cleanup(OnThreadExit) {
+    initialize();
+  }
+
+  /**
+   * As above but with a custom cleanup function
+   */
+  typedef void (*CleanupFunction)(void* obj);
+  explicit ThreadLocal(CleanupFunction cleanup) :
+    m_key(0),
+    m_cleanup(cleanup) {
+    FBASSERT(cleanup);
+    initialize();
+  }
+
+  /**
+   * Access object's member or method through this operator overload.
+   */
+  T *operator->() const {
+    return get();
+  }
+
+  T &operator*() const {
+    return *get();
+  }
+
+  T *get() const {
+    return (T*)pthread_getspecific(m_key);
+  }
+
+  T* release() {
+    T* obj = get();
+    pthread_setspecific(m_key, NULL);
+    return obj;
+  }
+
+  void reset(T* other = NULL) {
+    T* old = (T*)pthread_getspecific(m_key);
+    if (old != other) {
+      FBASSERT(m_cleanup);
+      m_cleanup(old);
+      pthread_setspecific(m_key, other);
+    }
+  }
+
+private:
+  void initialize() {
+    int ret = pthread_key_create(&m_key, m_cleanup);
+    if (ret != 0) {
+      const char *msg = "(unknown error)";
+      switch (ret) {
+      case EAGAIN:
+        msg = "PTHREAD_KEYS_MAX (1024) is exceeded";
+        break;
+      case ENOMEM:
+        msg = "Out-of-memory";
+        break;
+      }
+      (void) msg;
+      FBASSERTMSGF(0, "pthread_key_create failed: %d %s", ret, msg);
+    }
+  }
+
+  static void OnThreadExit(void *obj) {
+    if (NULL != obj) {
+      delete (T*)obj;
+    }
+  }
+
+  pthread_key_t m_key;
+  CleanupFunction m_cleanup;
+};
+
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/assert.h b/VirtualApp/lib/src/main/jni/fb/include/fb/assert.h
new file mode 100644
index 000000000..1ff0740ac
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/assert.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#ifndef FBASSERT_H
+#define FBASSERT_H
+
+#include <fb/visibility.h>
+
+namespace facebook {
+#define ENABLE_FBASSERT 1
+
+#if ENABLE_FBASSERT
+#define FBASSERTMSGF(expr, msg, ...) !(expr) ? facebook::assertInternal("Assert (%s:%d): " msg, __FILE__, __LINE__, ##__VA_ARGS__) : (void) 0
+#else
+#define FBASSERTMSGF(expr, msg, ...)
+#endif // ENABLE_FBASSERT
+
+#define FBASSERT(expr) FBASSERTMSGF(expr, "%s", #expr)
+
+#define FBCRASH(msg, ...) facebook::assertInternal("Fatal error (%s:%d): " msg, __FILE__, __LINE__, ##__VA_ARGS__)
+#define FBUNREACHABLE() facebook::assertInternal("This code should be unreachable (%s:%d)", __FILE__, __LINE__)
+
+FBEXPORT void assertInternal(const char* formatstr, ...) __attribute__((noreturn));
+
+// This allows storing the assert message before the current process terminates due to a crash
+typedef void (*AssertHandler)(const char* message);
+void setAssertHandler(AssertHandler assertHandler);
+
+} // namespace facebook
+#endif // FBASSERT_H
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni.h
new file mode 100644
index 000000000..66944c1ca
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+#include <fb/Environment.h>
+#include <fb/ALog.h>
+#include <fb/fbjni/Common.h>
+#include <fb/fbjni/Exceptions.h>
+#include <fb/fbjni/ReferenceAllocators.h>
+#include <fb/fbjni/References.h>
+#include <fb/fbjni/Meta.h>
+#include <fb/fbjni/CoreClasses.h>
+#include <fb/fbjni/Iterator.h>
+#include <fb/fbjni/Hybrid.h>
+#include <fb/fbjni/Registration.h>
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Boxed.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Boxed.h
new file mode 100644
index 000000000..c816924c5
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Boxed.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include "CoreClasses.h"
+
+namespace facebook {
+namespace jni {
+
+namespace detail {
+template <typename T, typename jprim>
+struct JPrimitive : JavaClass<T> {
+  using typename JavaClass<T>::javaobject;
+  using JavaClass<T>::javaClassStatic;
+  static local_ref<javaobject> valueOf(jprim val) {
+    static auto cls = javaClassStatic();
+    static auto method =
+      cls->template getStaticMethod<javaobject(jprim)>("valueOf");
+    return method(cls, val);
+  }
+  jprim value() const {
+    static auto method =
+      javaClassStatic()->template getMethod<jprim()>(T::kValueMethod);
+    return method(this->self());
+  }
+};
+
+} // namespace detail
+
+
+#define DEFINE_BOXED_PRIMITIVE(LITTLE, BIG)                          \
+  struct J ## BIG : detail::JPrimitive<J ## BIG, j ## LITTLE> {      \
+    static auto constexpr kJavaDescriptor = "Ljava/lang/" #BIG ";";  \
+    static auto constexpr kValueMethod = #LITTLE "Value";            \
+    j ## LITTLE LITTLE ## Value() const {                            \
+      return value();                                                \
+    }                                                                \
+  };                                                                 \
+  inline local_ref<jobject> autobox(j ## LITTLE val) {               \
+    return J ## BIG::valueOf(val);                                   \
+  }
+
+DEFINE_BOXED_PRIMITIVE(boolean, Boolean)
+DEFINE_BOXED_PRIMITIVE(byte, Byte)
+DEFINE_BOXED_PRIMITIVE(char, Character)
+DEFINE_BOXED_PRIMITIVE(short, Short)
+DEFINE_BOXED_PRIMITIVE(int, Integer)
+DEFINE_BOXED_PRIMITIVE(long, Long)
+DEFINE_BOXED_PRIMITIVE(float, Float)
+DEFINE_BOXED_PRIMITIVE(double, Double)
+
+#undef DEFINE_BOXED_PRIMITIVE
+
+struct JVoid : public jni::JavaClass<JVoid> {
+  static auto constexpr kJavaDescriptor = "Ljava/lang/Void;";
+};
+
+inline local_ref<jobject> autobox(alias_ref<jobject> val) {
+  return make_local(val);
+}
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/ByteBuffer.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/ByteBuffer.h
new file mode 100644
index 000000000..26ae3589a
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/ByteBuffer.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <fb/visibility.h>
+
+#include "CoreClasses.h"
+#include "References-forward.h"
+
+namespace facebook {
+namespace jni {
+
+// JNI's NIO support has some awkward preconditions and error reporting. This
+// class provides much more user-friendly access.
+class FBEXPORT JByteBuffer : public JavaClass<JByteBuffer> {
+ public:
+  static constexpr const char* kJavaDescriptor = "Ljava/nio/ByteBuffer;";
+
+  static local_ref<JByteBuffer> wrapBytes(uint8_t* data, size_t size);
+
+  bool isDirect() const;
+
+  uint8_t* getDirectBytes() const;
+  size_t getDirectSize() const;
+};
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Common.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Common.h
new file mode 100644
index 000000000..9da51c406
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Common.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/** @file Common.h
+ *
+ * Defining the stuff that don't deserve headers of their own...
+ */
+
+#pragma once
+
+#include <functional>
+
+#include <jni.h>
+
+#include <fb/visibility.h>
+#include <fb/Environment.h>
+
+#ifdef FBJNI_DEBUG_REFS
+# ifdef __ANDROID__
+#  include <android/log.h>
+# else
+#  include <cstdio>
+# endif
+#endif
+
+// If a pending JNI Java exception is found, wraps it in a JniException object and throws it as
+// a C++ exception.
+#define FACEBOOK_JNI_THROW_PENDING_EXCEPTION() \
+  ::facebook::jni::throwPendingJniExceptionAsCppException()
+
+// If the condition is true, throws a JniException object, which wraps the pending JNI Java
+// exception if any. If no pending exception is found, throws a JniException object that wraps a
+// RuntimeException throwable. 
+#define FACEBOOK_JNI_THROW_EXCEPTION_IF(CONDITION) \
+  ::facebook::jni::throwCppExceptionIf(CONDITION)
+
+/// @cond INTERNAL
+
+namespace facebook {
+namespace jni {
+
+FBEXPORT void throwPendingJniExceptionAsCppException();
+FBEXPORT void throwCppExceptionIf(bool condition);
+
+[[noreturn]] FBEXPORT void throwNewJavaException(jthrowable);
+[[noreturn]] FBEXPORT void throwNewJavaException(const char* throwableName, const char* msg);
+template<typename... Args>
+[[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args);
+
+
+/**
+ * This needs to be called at library load time, typically in your JNI_OnLoad method.
+ *
+ * The intended use is to return the result of initialize() directly
+ * from JNI_OnLoad and to do nothing else there. Library specific
+ * initialization code should go in the function passed to initialize
+ * (which can be, and probably should be, a C++ lambda). This approach
+ * provides correct error handling and translation errors during
+ * initialization into Java exceptions when appropriate.
+ *
+ * Failure to call this will cause your code to crash in a remarkably
+ * unhelpful way (typically a segfault) while trying to handle an exception
+ * which occurs later.
+ */
+FBEXPORT jint initialize(JavaVM*, std::function<void()>&&) noexcept;
+
+namespace internal {
+
+/**
+ * Retrieve a pointer the JNI environment of the current thread.
+ *
+ * @pre The current thread must be attached to the VM
+ */
+inline JNIEnv* getEnv() noexcept {
+  // TODO(T6594868) Benchmark against raw JNI access
+  return Environment::current();
+}
+
+// Define to get extremely verbose logging of references and to enable reference stats
+#ifdef FBJNI_DEBUG_REFS
+template<typename... Args>
+inline void dbglog(const char* msg, Args... args) {
+# ifdef __ANDROID__
+  __android_log_print(ANDROID_LOG_VERBOSE, "fbjni_dbg", msg, args...);
+# else
+  std::fprintf(stderr, msg, args...);
+# endif
+}
+
+#else
+
+template<typename... Args>
+inline void dbglog(const char*, Args...) {
+}
+
+#endif
+
+}}}
+
+/// @endcond
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Context.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Context.h
new file mode 100644
index 000000000..8630aa6bf
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Context.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include "CoreClasses.h"
+#include "File.h"
+
+namespace facebook {
+namespace jni {
+
+class AContext : public JavaClass<AContext> {
+ public:
+  static constexpr const char* kJavaDescriptor = "Landroid/content/Context;";
+
+  // Define a method that calls into the represented Java class
+  local_ref<JFile::javaobject> getCacheDir() {
+    static auto method = getClass()->getMethod<JFile::javaobject()>("getCacheDir");
+    return method(self());
+  }
+
+  local_ref<JFile::javaobject> getFilesDir() {
+    static auto method = getClass()->getMethod<JFile::javaobject()>("getFilesDir");
+    return method(self());
+  }
+};
+
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/CoreClasses-inl.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/CoreClasses-inl.h
new file mode 100644
index 000000000..9149df24e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/CoreClasses-inl.h
@@ -0,0 +1,661 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <string.h>
+#include <type_traits>
+#include <stdlib.h>
+
+#include "Common.h"
+#include "Exceptions.h"
+#include "Meta.h"
+#include "MetaConvert.h"
+
+namespace facebook {
+namespace jni {
+
+// jobject /////////////////////////////////////////////////////////////////////////////////////////
+
+inline bool isSameObject(alias_ref<JObject> lhs, alias_ref<JObject> rhs) noexcept {
+  return internal::getEnv()->IsSameObject(lhs.get(), rhs.get()) != JNI_FALSE;
+}
+
+inline local_ref<JClass> JObject::getClass() const noexcept {
+  return adopt_local(internal::getEnv()->GetObjectClass(self()));
+}
+
+inline bool JObject::isInstanceOf(alias_ref<JClass> cls) const noexcept {
+  return internal::getEnv()->IsInstanceOf(self(), cls.get()) != JNI_FALSE;
+}
+
+template<typename T>
+inline T JObject::getFieldValue(JField<T> field) const noexcept {
+  return field.get(self());
+}
+
+template<typename T>
+inline local_ref<T*> JObject::getFieldValue(JField<T*> field) const noexcept {
+  return adopt_local(field.get(self()));
+}
+
+template<typename T>
+inline void JObject::setFieldValue(JField<T> field, T value) noexcept {
+  field.set(self(), value);
+}
+
+inline std::string JObject::toString() const {
+  static auto method = findClassLocal("java/lang/Object")->getMethod<jstring()>("toString");
+
+  return method(self())->toStdString();
+}
+
+
+// Class is here instead of CoreClasses.h because we need
+// alias_ref to be complete.
+class MonitorLock {
+ public:
+  inline MonitorLock() noexcept;
+  inline MonitorLock(alias_ref<JObject> object) noexcept;
+  inline ~MonitorLock() noexcept;
+
+  inline MonitorLock(MonitorLock&& other) noexcept;
+  inline MonitorLock& operator=(MonitorLock&& other) noexcept;
+
+  inline MonitorLock(const MonitorLock&) = delete;
+  inline MonitorLock& operator=(const MonitorLock&) = delete;
+
+ private:
+  inline void reset() noexcept;
+  alias_ref<JObject> owned_;
+};
+
+MonitorLock::MonitorLock() noexcept : owned_(nullptr) {}
+
+MonitorLock::MonitorLock(alias_ref<JObject> object) noexcept
+    : owned_(object) {
+  internal::getEnv()->MonitorEnter(object.get());
+}
+
+void MonitorLock::reset() noexcept {
+  if (owned_) {
+    internal::getEnv()->MonitorExit(owned_.get());
+    if (internal::getEnv()->ExceptionCheck()) {
+      abort(); // Lock mismatch
+    }
+    owned_ = nullptr;
+  }
+}
+
+MonitorLock::~MonitorLock() noexcept {
+  reset();
+}
+
+MonitorLock::MonitorLock(MonitorLock&& other) noexcept
+    : owned_(other.owned_)
+{
+  other.owned_ = nullptr;
+}
+
+MonitorLock& MonitorLock::operator=(MonitorLock&& other) noexcept {
+  reset();
+  owned_ = other.owned_;
+  other.owned_ = nullptr;
+  return *this;
+}
+
+inline MonitorLock JObject::lock() const noexcept {
+  return MonitorLock(this_);
+}
+
+inline jobject JObject::self() const noexcept {
+  return this_;
+}
+
+inline void swap(JObject& a, JObject& b) noexcept {
+  using std::swap;
+  swap(a.this_, b.this_);
+}
+
+// JavaClass ///////////////////////////////////////////////////////////////////////////////////////
+
+namespace detail {
+template<typename JC, typename... Args>
+static local_ref<JC> newInstance(Args... args) {
+  static auto cls = JC::javaClassStatic();
+  static auto constructor = cls->template getConstructor<typename JC::javaobject(Args...)>();
+  return cls->newObject(constructor, args...);
+}
+}
+
+
+template <typename T, typename B, typename J>
+auto JavaClass<T, B, J>::self() const noexcept -> javaobject {
+  return static_cast<javaobject>(JObject::self());
+}
+
+// jclass //////////////////////////////////////////////////////////////////////////////////////////
+
+namespace detail {
+
+// This is not a real type.  It is used so people won't accidentally
+// use a void* to initialize a NativeMethod.
+struct NativeMethodWrapper;
+
+}
+
+struct NativeMethod {
+  const char* name;
+  std::string descriptor;
+  detail::NativeMethodWrapper* wrapper;
+};
+
+inline local_ref<JClass> JClass::getSuperclass() const noexcept {
+  return adopt_local(internal::getEnv()->GetSuperclass(self()));
+}
+
+inline void JClass::registerNatives(std::initializer_list<NativeMethod> methods) {
+  const auto env = internal::getEnv();
+
+  JNINativeMethod jnimethods[methods.size()];
+  size_t i = 0;
+  for (auto it = methods.begin(); it < methods.end(); ++it, ++i) {
+    jnimethods[i].name = it->name;
+    jnimethods[i].signature = it->descriptor.c_str();
+    jnimethods[i].fnPtr = reinterpret_cast<void*>(it->wrapper);
+  }
+
+  auto result = env->RegisterNatives(self(), jnimethods, methods.size());
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(result != JNI_OK);
+}
+
+inline bool JClass::isAssignableFrom(alias_ref<JClass> other) const noexcept {
+  const auto env = internal::getEnv();
+  // Ths method has behavior compatible with the
+  // java.lang.Class#isAssignableFrom method.  The order of the
+  // arguments to the JNI IsAssignableFrom C function is "opposite"
+  // from what some might expect, which makes this code look a little
+  // odd, but it is correct.
+  const auto result = env->IsAssignableFrom(other.get(), self());
+  return result;
+}
+
+template<typename F>
+inline JConstructor<F> JClass::getConstructor() const {
+  return getConstructor<F>(jmethod_traits_from_cxx<F>::constructor_descriptor().c_str());
+}
+
+template<typename F>
+inline JConstructor<F> JClass::getConstructor(const char* descriptor) const {
+  constexpr auto constructor_method_name = "<init>";
+  return getMethod<F>(constructor_method_name, descriptor);
+}
+
+template<typename F>
+inline JMethod<F> JClass::getMethod(const char* name) const {
+  return getMethod<F>(name, jmethod_traits_from_cxx<F>::descriptor().c_str());
+}
+
+template<typename F>
+inline JMethod<F> JClass::getMethod(
+    const char* name,
+    const char* descriptor) const {
+  const auto env = internal::getEnv();
+  const auto method = env->GetMethodID(self(), name, descriptor);
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!method);
+  return JMethod<F>{method};
+}
+
+template<typename F>
+inline JStaticMethod<F> JClass::getStaticMethod(const char* name) const {
+  return getStaticMethod<F>(name, jmethod_traits_from_cxx<F>::descriptor().c_str());
+}
+
+template<typename F>
+inline JStaticMethod<F> JClass::getStaticMethod(
+    const char* name,
+    const char* descriptor) const {
+  const auto env = internal::getEnv();
+  const auto method = env->GetStaticMethodID(self(), name, descriptor);
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!method);
+  return JStaticMethod<F>{method};
+}
+
+template<typename F>
+inline JNonvirtualMethod<F> JClass::getNonvirtualMethod(const char* name) const {
+  return getNonvirtualMethod<F>(name, jmethod_traits_from_cxx<F>::descriptor().c_str());
+}
+
+template<typename F>
+inline JNonvirtualMethod<F> JClass::getNonvirtualMethod(
+    const char* name,
+    const char* descriptor) const {
+  const auto env = internal::getEnv();
+  const auto method = env->GetMethodID(self(), name, descriptor);
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!method);
+  return JNonvirtualMethod<F>{method};
+}
+
+template<typename T>
+inline JField<enable_if_t<IsJniScalar<T>(), T>>
+JClass::getField(const char* name) const {
+  return getField<T>(name, jtype_traits<T>::descriptor().c_str());
+}
+
+template<typename T>
+inline JField<enable_if_t<IsJniScalar<T>(), T>> JClass::getField(
+    const char* name,
+    const char* descriptor) const {
+  const auto env = internal::getEnv();
+  auto field = env->GetFieldID(self(), name, descriptor);
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!field);
+  return JField<T>{field};
+}
+
+template<typename T>
+inline JStaticField<enable_if_t<IsJniScalar<T>(), T>> JClass::getStaticField(
+    const char* name) const {
+  return getStaticField<T>(name, jtype_traits<T>::descriptor().c_str());
+}
+
+template<typename T>
+inline JStaticField<enable_if_t<IsJniScalar<T>(), T>> JClass::getStaticField(
+    const char* name,
+    const char* descriptor) const {
+  const auto env = internal::getEnv();
+  auto field = env->GetStaticFieldID(self(), name, descriptor);
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!field);
+  return JStaticField<T>{field};
+}
+
+template<typename T>
+inline T JClass::getStaticFieldValue(JStaticField<T> field) const noexcept {
+  return field.get(self());
+}
+
+template<typename T>
+inline local_ref<T*> JClass::getStaticFieldValue(JStaticField<T*> field) noexcept {
+  return adopt_local(field.get(self()));
+}
+
+template<typename T>
+inline void JClass::setStaticFieldValue(JStaticField<T> field, T value) noexcept {
+  field.set(self(), value);
+}
+
+template<typename R, typename... Args>
+inline local_ref<R> JClass::newObject(
+    JConstructor<R(Args...)> constructor,
+    Args... args) const {
+  const auto env = internal::getEnv();
+  auto object = env->NewObject(self(), constructor.getId(),
+      detail::callToJni(
+        detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!object);
+  return adopt_local(static_cast<R>(object));
+}
+
+inline jclass JClass::self() const noexcept {
+  return static_cast<jclass>(JObject::self());
+}
+
+inline void registerNatives(const char* name, std::initializer_list<NativeMethod> methods) {
+  findClassLocal(name)->registerNatives(methods);
+}
+
+
+// jstring /////////////////////////////////////////////////////////////////////////////////////////
+
+inline local_ref<JString> make_jstring(const std::string& modifiedUtf8) {
+  return make_jstring(modifiedUtf8.c_str());
+}
+
+namespace detail {
+// convert to std::string from jstring
+template <>
+struct Convert<std::string> {
+  typedef jstring jniType;
+  static std::string fromJni(jniType t) {
+    return wrap_alias(t)->toStdString();
+  }
+  static jniType toJniRet(const std::string& t) {
+    return make_jstring(t).release();
+  }
+  static local_ref<JString> toCall(const std::string& t) {
+    return make_jstring(t);
+  }
+};
+
+// convert return from const char*
+template <>
+struct Convert<const char*> {
+  typedef jstring jniType;
+  // no automatic synthesis of const char*.  (It can't be freed.)
+  static jniType toJniRet(const char* t) {
+    return make_jstring(t).release();
+  }
+  static local_ref<JString> toCall(const char* t) {
+    return make_jstring(t);
+  }
+};
+}
+
+// jtypeArray //////////////////////////////////////////////////////////////////////////////////////
+
+namespace detail {
+inline size_t JArray::size() const noexcept {
+  const auto env = internal::getEnv();
+  return env->GetArrayLength(self());
+}
+}
+
+namespace detail {
+template<typename Target>
+inline ElementProxy<Target>::ElementProxy(
+    Target* target,
+    size_t idx)
+    : target_{target}, idx_{idx} {}
+
+template<typename Target>
+inline ElementProxy<Target>& ElementProxy<Target>::operator=(const T& o) {
+  target_->setElement(idx_, o);
+  return *this;
+}
+
+template<typename Target>
+inline ElementProxy<Target>& ElementProxy<Target>::operator=(alias_ref<T>& o) {
+  target_->setElement(idx_, o.get());
+  return *this;
+}
+
+template<typename Target>
+inline ElementProxy<Target>& ElementProxy<Target>::operator=(alias_ref<T>&& o) {
+  target_->setElement(idx_, o.get());
+  return *this;
+}
+
+template<typename Target>
+inline ElementProxy<Target>& ElementProxy<Target>::operator=(const ElementProxy<Target>& o) {
+  auto src = o.target_->getElement(o.idx_);
+  target_->setElement(idx_, src.get());
+  return *this;
+}
+
+template<typename Target>
+inline ElementProxy<Target>::ElementProxy::operator const local_ref<T> () const {
+  return target_->getElement(idx_);
+}
+
+template<typename Target>
+inline ElementProxy<Target>::ElementProxy::operator local_ref<T> () {
+  return target_->getElement(idx_);
+}
+}
+
+template <typename T>
+std::string JArrayClass<T>::get_instantiated_java_descriptor() {
+  return "[" + jtype_traits<T>::descriptor();
+};
+
+template <typename T>
+std::string JArrayClass<T>::get_instantiated_base_name() {
+  return get_instantiated_java_descriptor();
+};
+
+template<typename T>
+auto JArrayClass<T>::newArray(size_t size) -> local_ref<javaobject> {
+  static auto elementClass = findClassStatic(jtype_traits<T>::base_name().c_str());
+  const auto env = internal::getEnv();
+  auto rawArray = env->NewObjectArray(size, elementClass.get(), nullptr);
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!rawArray);
+  return adopt_local(static_cast<javaobject>(rawArray));
+}
+
+template<typename T>
+inline void JArrayClass<T>::setElement(size_t idx, const T& value) {
+  const auto env = internal::getEnv();
+  env->SetObjectArrayElement(this->self(), idx, value);
+}
+
+template<typename T>
+inline local_ref<T> JArrayClass<T>::getElement(size_t idx) {
+  const auto env = internal::getEnv();
+  auto rawElement = env->GetObjectArrayElement(this->self(), idx);
+  return adopt_local(static_cast<T>(rawElement));
+}
+
+template<typename T>
+inline detail::ElementProxy<JArrayClass<T>> JArrayClass<T>::operator[](size_t index) {
+  return detail::ElementProxy<JArrayClass<T>>(this, index);
+}
+
+// jarray /////////////////////////////////////////////////////////////////////////////////////////
+
+template <typename JArrayType>
+auto JPrimitiveArray<JArrayType>::getRegion(jsize start, jsize length)
+    -> std::unique_ptr<T[]> {
+  using T = typename jtype_traits<JArrayType>::entry_type;
+  auto buf = std::unique_ptr<T[]>{new T[length]};
+  getRegion(start, length, buf.get());
+  return buf;
+}
+
+template <typename JArrayType>
+std::string JPrimitiveArray<JArrayType>::get_instantiated_java_descriptor() {
+  return jtype_traits<JArrayType>::descriptor();
+}
+template <typename JArrayType>
+std::string JPrimitiveArray<JArrayType>::get_instantiated_base_name() {
+  return JPrimitiveArray::get_instantiated_java_descriptor();
+}
+
+template <typename JArrayType>
+auto JPrimitiveArray<JArrayType>::pin() -> PinnedPrimitiveArray<T, PinnedArrayAlloc<T>> {
+  return PinnedPrimitiveArray<T, PinnedArrayAlloc<T>>{this->self(), 0, 0};
+}
+
+template <typename JArrayType>
+auto JPrimitiveArray<JArrayType>::pinRegion(jsize start, jsize length)
+    -> PinnedPrimitiveArray<T, PinnedRegionAlloc<T>> {
+  return PinnedPrimitiveArray<T, PinnedRegionAlloc<T>>{this->self(), start, length};
+}
+
+template <typename JArrayType>
+auto JPrimitiveArray<JArrayType>::pinCritical()
+    -> PinnedPrimitiveArray<T, PinnedCriticalAlloc<T>> {
+  return PinnedPrimitiveArray<T, PinnedCriticalAlloc<T>>{this->self(), 0, 0};
+}
+
+template <typename T>
+class PinnedArrayAlloc {
+ public:
+  static void allocate(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      jsize start,
+      jsize length,
+      T** elements,
+      size_t* size,
+      jboolean* isCopy) {
+    (void) start;
+    (void) length;
+    *elements = array->getElements(isCopy);
+    *size = array->size();
+  }
+  static void release(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      T* elements,
+      jint start,
+      jint size,
+      jint mode) {
+    (void) start;
+    (void) size;
+    array->releaseElements(elements, mode);
+  }
+};
+
+template <typename T>
+class PinnedCriticalAlloc {
+ public:
+  static void allocate(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      jsize start,
+      jsize length,
+      T** elements,
+      size_t* size,
+      jboolean* isCopy) {
+    const auto env = internal::getEnv();
+    *elements = static_cast<T*>(env->GetPrimitiveArrayCritical(array.get(), isCopy));
+    FACEBOOK_JNI_THROW_EXCEPTION_IF(!elements);
+    *size = array->size();
+  }
+  static void release(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      T* elements,
+      jint start,
+      jint size,
+      jint mode) {
+    const auto env = internal::getEnv();
+    env->ReleasePrimitiveArrayCritical(array.get(), elements, mode);
+  }
+};
+
+template <typename T>
+class PinnedRegionAlloc {
+ public:
+  static void allocate(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      jsize start,
+      jsize length,
+      T** elements,
+      size_t* size,
+      jboolean* isCopy) {
+    auto buf = array->getRegion(start, length);
+    FACEBOOK_JNI_THROW_EXCEPTION_IF(!buf);
+    *elements = buf.release();
+    *size = length;
+    *isCopy = true;
+  }
+  static void release(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      T* elements,
+      jint start,
+      jint size,
+      jint mode) {
+    std::unique_ptr<T[]> holder;
+    if (mode == 0 || mode == JNI_ABORT) {
+      holder.reset(elements);
+    }
+    if (mode == 0 || mode == JNI_COMMIT) {
+      array->setRegion(start, size, elements);
+    }
+  }
+};
+
+// PinnedPrimitiveArray ///////////////////////////////////////////////////////////////////////////
+
+template<typename T, typename Alloc>
+PinnedPrimitiveArray<T, Alloc>::PinnedPrimitiveArray(PinnedPrimitiveArray&& o) {
+  *this = std::move(o);
+}
+
+template<typename T, typename Alloc>
+PinnedPrimitiveArray<T, Alloc>&
+PinnedPrimitiveArray<T, Alloc>::operator=(PinnedPrimitiveArray&& o) {
+  if (array_) {
+    release();
+  }
+  array_ = std::move(o.array_);
+  elements_ = o.elements_;
+  isCopy_ = o.isCopy_;
+  size_ = o.size_;
+  start_ = o.start_;
+  o.clear();
+  return *this;
+}
+
+template<typename T, typename Alloc>
+T* PinnedPrimitiveArray<T, Alloc>::get() {
+  return elements_;
+}
+
+template<typename T, typename Alloc>
+inline void PinnedPrimitiveArray<T, Alloc>::release() {
+  releaseImpl(0);
+  clear();
+}
+
+template<typename T, typename Alloc>
+inline void PinnedPrimitiveArray<T, Alloc>::commit() {
+  releaseImpl(JNI_COMMIT);
+}
+
+template<typename T, typename Alloc>
+inline void PinnedPrimitiveArray<T, Alloc>::abort() {
+  releaseImpl(JNI_ABORT);
+  clear();
+}
+
+template <typename T, typename Alloc>
+inline void PinnedPrimitiveArray<T, Alloc>::releaseImpl(jint mode) {
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(array_.get() == nullptr);
+  Alloc::release(array_, elements_, start_, size_, mode);
+}
+
+template<typename T, typename Alloc>
+inline void PinnedPrimitiveArray<T, Alloc>::clear() noexcept {
+  array_ = nullptr;
+  elements_ = nullptr;
+  isCopy_ = false;
+  start_ = 0;
+  size_ = 0;
+}
+
+template<typename T, typename Alloc>
+inline T& PinnedPrimitiveArray<T, Alloc>::operator[](size_t index) {
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(elements_ == nullptr);
+  return elements_[index];
+}
+
+template<typename T, typename Alloc>
+inline bool PinnedPrimitiveArray<T, Alloc>::isCopy() const noexcept {
+  return isCopy_ == JNI_TRUE;
+}
+
+template<typename T, typename Alloc>
+inline size_t PinnedPrimitiveArray<T, Alloc>::size() const noexcept {
+  return size_;
+}
+
+template<typename T, typename Alloc>
+inline PinnedPrimitiveArray<T, Alloc>::~PinnedPrimitiveArray() noexcept {
+  if (elements_) {
+    release();
+  }
+}
+
+template<typename T, typename Alloc>
+inline PinnedPrimitiveArray<T, Alloc>::PinnedPrimitiveArray(alias_ref<typename jtype_traits<T>::array_type> array, jint start, jint length) {
+  array_ = array;
+  start_ = start;
+  Alloc::allocate(array, start, length, &elements_, &size_, &isCopy_);
+}
+
+template<typename T, typename Base, typename JType>
+inline alias_ref<JClass> JavaClass<T, Base, JType>::javaClassStatic() {
+  static auto cls = findClassStatic(jtype_traits<typename T::javaobject>::base_name().c_str());
+  return cls;
+}
+
+template<typename T, typename Base, typename JType>
+inline local_ref<JClass> JavaClass<T, Base, JType>::javaClassLocal() {
+  std::string className(jtype_traits<typename T::javaobject>::base_name().c_str());
+  return findClassLocal(className.c_str());
+}
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/CoreClasses.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/CoreClasses.h
new file mode 100644
index 000000000..76eecb87b
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/CoreClasses.h
@@ -0,0 +1,610 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+/** @file CoreClasses.h
+ *
+ * In CoreClasses.h wrappers for the core classes (jobject, jclass, and jstring) is defined
+ * to provide access to corresponding JNI functions + some conveniance.
+ */
+
+#include "References-forward.h"
+#include "Meta-forward.h"
+#include "TypeTraits.h"
+
+#include <memory>
+
+#include <jni.h>
+
+#include <fb/visibility.h>
+
+namespace facebook {
+namespace jni {
+
+class JClass;
+class JObject;
+
+/// Lookup a class by name. Note this functions returns an alias_ref that
+/// points to a leaked global reference.  This is appropriate for classes
+/// that are never unloaded (which is any class in an Android app and most
+/// Java programs).
+///
+/// The most common use case for this is storing the result
+/// in a "static auto" variable, or a static global.
+///
+/// @return Returns a leaked global reference to the class
+FBEXPORT alias_ref<JClass> findClassStatic(const char* name);
+
+/// Lookup a class by name. Note this functions returns a local reference,
+/// which means that it must not be stored in a static variable.
+///
+/// The most common use case for this is one-time initialization
+/// (like caching method ids).
+///
+/// @return Returns a global reference to the class
+FBEXPORT local_ref<JClass> findClassLocal(const char* name);
+
+/// Check to see if two references refer to the same object. Comparison with nullptr
+/// returns true if and only if compared to another nullptr. A weak reference that
+/// refers to a reclaimed object count as nullptr.
+FBEXPORT bool isSameObject(alias_ref<JObject> lhs, alias_ref<JObject> rhs) noexcept;
+
+// Together, these classes allow convenient use of any class with the fbjni
+// helpers.  To use:
+//
+// struct MyClass : public JavaClass<MyClass> {
+//   constexpr static auto kJavaDescriptor = "Lcom/example/package/MyClass;";
+// };
+//
+// Then, an alias_ref<MyClass::javaobject> will be backed by an instance of
+// MyClass. JavaClass provides a convenient way to add functionality to these
+// smart references.
+//
+// For example:
+//
+// struct MyClass : public JavaClass<MyClass> {
+//   constexpr static auto kJavaDescriptor = "Lcom/example/package/MyClass;";
+//
+//   void foo() {
+//     static auto method = javaClassStatic()->getMethod<void()>("foo");
+//     method(self());
+//   }
+//
+//   static local_ref<javaobject> create(int i) {
+//     return newInstance(i);
+//   }
+// };
+//
+// auto obj = MyClass::create(10);
+// obj->foo();
+//
+// While users of a JavaClass-type can lookup methods and fields through the
+// underlying JClass, those calls can only be checked at runtime. It is recommended
+// that the JavaClass-type instead explicitly expose it's methods as in the example
+// above.
+
+namespace detail {
+template<typename JC, typename... Args>
+static local_ref<JC> newInstance(Args... args);
+}
+
+class MonitorLock;
+
+class FBEXPORT JObject : detail::JObjectBase {
+public:
+  static constexpr auto kJavaDescriptor = "Ljava/lang/Object;";
+
+  static constexpr const char* get_instantiated_java_descriptor() { return nullptr; }
+  static constexpr const char* get_instantiated_base_name() { return nullptr; }
+
+  /// Get a @ref local_ref of the object's class
+  local_ref<JClass> getClass() const noexcept;
+
+  /// Checks if the object is an instance of a class
+  bool isInstanceOf(alias_ref<JClass> cls) const noexcept;
+
+  /// Get the primitive value of a field
+  template<typename T>
+  T getFieldValue(JField<T> field) const noexcept;
+
+  /// Get and wrap the value of a field in a @ref local_ref
+  template<typename T>
+  local_ref<T*> getFieldValue(JField<T*> field) const noexcept;
+
+  /// Set the value of field. Any Java type is accepted, including the primitive types
+  /// and raw reference types.
+  template<typename T>
+  void setFieldValue(JField<T> field, T value) noexcept;
+
+  /// Convenience method to create a std::string representing the object
+  std::string toString() const;
+
+  // Take this object's monitor lock
+  MonitorLock lock() const noexcept;
+
+  typedef _jobject _javaobject;
+  typedef _javaobject* javaobject;
+
+protected:
+  jobject self() const noexcept;
+private:
+  friend void swap(JObject& a, JObject& b) noexcept;
+  template<typename>
+  friend struct detail::ReprAccess;
+  template<typename, typename, typename>
+  friend class JavaClass;
+
+  template <typename, typename>
+  friend class JObjectWrapper;
+};
+
+// This is only to maintain backwards compatibility with things that are
+// already providing a specialization of JObjectWrapper. Any such instances
+// should be updated to use a JavaClass.
+template<>
+class JObjectWrapper<jobject> : public JObject {
+};
+
+
+namespace detail {
+template <typename, typename Base, typename JType>
+struct JTypeFor {
+  static_assert(
+      std::is_base_of<
+        std::remove_pointer<jobject>::type,
+        typename std::remove_pointer<JType>::type
+      >::value, "");
+  using _javaobject = typename std::remove_pointer<JType>::type;
+  using javaobject = JType;
+};
+
+template <typename T, typename Base>
+struct JTypeFor<T, Base, void> {
+  // JNI pattern for jobject assignable pointer
+  struct _javaobject :  Base::_javaobject {
+    // This allows us to map back to the defining type (in ReprType, for
+    // example).
+    typedef T JniRefRepr;
+  };
+  using javaobject = _javaobject*;
+};
+}
+
+// JavaClass provides a method to inform fbjni about user-defined Java types.
+// Given a class:
+// struct Foo : JavaClass<Foo> {
+//   static constexpr auto kJavaDescriptor = "Lcom/example/package/Foo;";
+// };
+// fbjni can determine the java type/method signatures for Foo::javaobject and
+// smart refs (like alias_ref<Foo::javaobject>) will hold an instance of Foo
+// and provide access to it through the -> and * operators.
+//
+// The "Base" template argument can be used to specify the JavaClass superclass
+// of this type (for instance, JString's Base is JObject).
+//
+// The "JType" template argument is used to provide a jni type (like jstring,
+// jthrowable) to be used as javaobject. This should only be necessary for
+// built-in jni types and not user-defined ones.
+template <typename T, typename Base = JObject, typename JType = void>
+class FBEXPORT JavaClass : public Base {
+  using JObjType = typename detail::JTypeFor<T, Base, JType>;
+public:
+  using _javaobject = typename JObjType::_javaobject;
+  using javaobject = typename JObjType::javaobject;
+
+  using JavaBase = JavaClass;
+
+  static alias_ref<JClass> javaClassStatic();
+  static local_ref<JClass> javaClassLocal();
+protected:
+  /// Allocates a new object and invokes the specified constructor
+  /// Like JClass's getConstructor, this function can only check at runtime if
+  /// the class actually has a constructor that accepts the corresponding types.
+  /// While a JavaClass-type can expose this function directly, it is recommended
+  /// to instead to use this to explicitly only expose those constructors that
+  /// the Java class actually has (i.e. with static create() functions).
+  template<typename... Args>
+  static local_ref<T> newInstance(Args... args) {
+    return detail::newInstance<T>(args...);
+  }
+
+  javaobject self() const noexcept;
+};
+
+/// Wrapper to provide functionality to jclass references
+struct NativeMethod;
+
+class FBEXPORT JClass : public JavaClass<JClass, JObject, jclass> {
+ public:
+  /// Java type descriptor
+  static constexpr const char* kJavaDescriptor = "Ljava/lang/Class;";
+
+  /// Get a @local_ref to the super class of this class
+  local_ref<JClass> getSuperclass() const noexcept;
+
+  /// Register native methods for the class.  Usage looks like this:
+  ///
+  /// classRef->registerNatives({
+  ///     makeNativeMethod("nativeMethodWithAutomaticDescriptor",
+  ///                      methodWithAutomaticDescriptor),
+  ///     makeNativeMethod("nativeMethodWithExplicitDescriptor",
+  ///                      "(Lcom/facebook/example/MyClass;)V",
+  ///                      methodWithExplicitDescriptor),
+  ///  });
+  ///
+  /// By default, C++ exceptions raised will be converted to Java exceptions.
+  /// To avoid this and get the "standard" JNI behavior of a crash when a C++
+  /// exception is crashing out of the JNI method, declare the method noexcept.
+  void registerNatives(std::initializer_list<NativeMethod> methods);
+
+  /// Check to see if the class is assignable from another class
+  /// @pre cls != nullptr
+  bool isAssignableFrom(alias_ref<JClass> cls) const noexcept;
+
+  /// Convenience method to lookup the constructor with descriptor as specified by the
+  /// type arguments
+  template<typename F>
+  JConstructor<F> getConstructor() const;
+
+  /// Convenience method to lookup the constructor with specified descriptor
+  template<typename F>
+  JConstructor<F> getConstructor(const char* descriptor) const;
+
+  /// Look up the method with given name and descriptor as specified with the type arguments
+  template<typename F>
+  JMethod<F> getMethod(const char* name) const;
+
+  /// Look up the method with given name and descriptor
+  template<typename F>
+  JMethod<F> getMethod(const char* name, const char* descriptor) const;
+
+  /// Lookup the field with the given name and deduced descriptor
+  template<typename T>
+  JField<enable_if_t<IsJniScalar<T>(), T>> getField(const char* name) const;
+
+  /// Lookup the field with the given name and descriptor
+  template<typename T>
+  JField<enable_if_t<IsJniScalar<T>(), T>> getField(const char* name, const char* descriptor) const;
+
+  /// Lookup the static field with the given name and deduced descriptor
+  template<typename T>
+  JStaticField<enable_if_t<IsJniScalar<T>(), T>> getStaticField(const char* name) const;
+
+  /// Lookup the static field with the given name and descriptor
+  template<typename T>
+  JStaticField<enable_if_t<IsJniScalar<T>(), T>> getStaticField(
+      const char* name,
+      const char* descriptor) const;
+
+  /// Get the primitive value of a static field
+  template<typename T>
+  T getStaticFieldValue(JStaticField<T> field) const noexcept;
+
+  /// Get and wrap the value of a field in a @ref local_ref
+  template<typename T>
+  local_ref<T*> getStaticFieldValue(JStaticField<T*> field) noexcept;
+
+  /// Set the value of field. Any Java type is accepted, including the primitive types
+  /// and raw reference types.
+  template<typename T>
+  void setStaticFieldValue(JStaticField<T> field, T value) noexcept;
+
+  /// Allocates a new object and invokes the specified constructor
+  template<typename R, typename... Args>
+  local_ref<R> newObject(JConstructor<R(Args...)> constructor, Args... args) const;
+
+  /// Look up the static method with given name and descriptor as specified with the type arguments
+  template<typename F>
+  JStaticMethod<F> getStaticMethod(const char* name) const;
+
+  /// Look up the static method with given name and descriptor
+  template<typename F>
+  JStaticMethod<F> getStaticMethod(const char* name, const char* descriptor) const;
+
+  /// Look up the non virtual method with given name and descriptor as specified with the
+  /// type arguments
+  template<typename F>
+  JNonvirtualMethod<F> getNonvirtualMethod(const char* name) const;
+
+  /// Look up the non virtual method with given name and descriptor
+  template<typename F>
+  JNonvirtualMethod<F> getNonvirtualMethod(const char* name, const char* descriptor) const;
+
+private:
+  jclass self() const noexcept;
+};
+
+// Convenience method to register methods on a class without holding
+// onto the class object.
+void registerNatives(const char* name, std::initializer_list<NativeMethod> methods);
+
+/// Wrapper to provide functionality to jstring references
+class FBEXPORT JString : public JavaClass<JString, JObject, jstring> {
+ public:
+  /// Java type descriptor
+  static constexpr const char* kJavaDescriptor = "Ljava/lang/String;";
+
+  /// Convenience method to convert a jstring object to a std::string
+  std::string toStdString() const;
+};
+
+/// Convenience functions to convert a std::string or const char* into a @ref local_ref to a
+/// jstring
+FBEXPORT local_ref<JString> make_jstring(const char* modifiedUtf8);
+FBEXPORT local_ref<JString> make_jstring(const std::string& modifiedUtf8);
+
+namespace detail {
+template<typename Target>
+class ElementProxy {
+ private:
+  Target* target_;
+  size_t idx_;
+
+ public:
+  using T = typename Target::javaentry;
+  ElementProxy(Target* target, size_t idx);
+
+  ElementProxy& operator=(const T& o);
+
+  ElementProxy& operator=(alias_ref<T>& o);
+
+  ElementProxy& operator=(alias_ref<T>&& o);
+
+  ElementProxy& operator=(const ElementProxy& o);
+
+  operator const local_ref<T> () const;
+
+  operator local_ref<T> ();
+};
+}
+
+namespace detail {
+class FBEXPORT JArray : public JavaClass<JArray, JObject, jarray> {
+ public:
+  // This cannot be used in a scope that derives a descriptor (like in a method
+  // signature). Use a more derived type instead (like JArrayInt or
+  // JArrayClass<T>).
+  static constexpr const char* kJavaDescriptor = nullptr;
+  size_t size() const noexcept;
+};
+
+// This is used so that the JArrayClass<T> javaobject extends jni's
+// jobjectArray. This class should not be used directly. A general Object[]
+// should use JArrayClass<jobject>.
+class FBEXPORT JTypeArray : public JavaClass<JTypeArray, JArray, jobjectArray> {
+  // This cannot be used in a scope that derives a descriptor (like in a method
+  // signature).
+  static constexpr const char* kJavaDescriptor = nullptr;
+};
+}
+
+template<typename T>
+class JArrayClass : public JavaClass<JArrayClass<T>, detail::JTypeArray> {
+ public:
+  static_assert(is_plain_jni_reference<T>(), "");
+  // javaentry is the jni type of an entry in the array (i.e. jint).
+  using javaentry = T;
+  // javaobject is the jni type of the array.
+  using javaobject = typename JavaClass<JArrayClass<T>, detail::JTypeArray>::javaobject;
+  static constexpr const char* kJavaDescriptor = nullptr;
+  static std::string get_instantiated_java_descriptor();
+  static std::string get_instantiated_base_name();
+
+  /// Allocate a new array from Java heap, for passing as a JNI parameter or return value.
+  /// NOTE: if using as a return value, you want to call release() instead of get() on the
+  /// smart pointer.
+  static local_ref<javaobject> newArray(size_t count);
+
+  /// Assign an object to the array.
+  /// Typically you will use the shorthand (*ref)[idx]=value;
+  void setElement(size_t idx, const T& value);
+
+  /// Read an object from the array.
+  /// Typically you will use the shorthand
+  ///   T value = (*ref)[idx];
+  /// If you use auto, you'll get an ElementProxy, which may need to be cast.
+  local_ref<T> getElement(size_t idx);
+
+  /// EXPERIMENTAL SUBSCRIPT SUPPORT
+  /// This implementation of [] returns a proxy object which then has a bunch of specializations
+  /// (adopt_local free function, operator= and casting overloads on the ElementProxy) that can
+  /// make code look like it is dealing with a T rather than an obvious proxy. In particular, the
+  /// proxy in this iteration does not read a value and therefore does not create a LocalRef
+  /// until one of these other operators is used. There are certainly holes that you may find
+  /// by using idioms that haven't been tried yet. Consider yourself warned. On the other hand,
+  /// it does make for some idiomatic assignment code; see TestBuildStringArray in fbjni_tests
+  /// for some examples.
+  detail::ElementProxy<JArrayClass> operator[](size_t idx);
+};
+
+template <typename T>
+using jtypeArray = typename JArrayClass<T>::javaobject;
+
+template<typename T>
+local_ref<typename JArrayClass<T>::javaobject> adopt_local_array(jobjectArray ref) {
+  return adopt_local(static_cast<typename JArrayClass<T>::javaobject>(ref));
+}
+
+template<typename Target>
+local_ref<typename Target::javaentry> adopt_local(detail::ElementProxy<Target> elementProxy) {
+  return static_cast<local_ref<typename Target::javaentry>>(elementProxy);
+}
+
+template <typename T, typename PinAlloc>
+class PinnedPrimitiveArray;
+
+template <typename T> class PinnedArrayAlloc;
+template <typename T> class PinnedRegionAlloc;
+template <typename T> class PinnedCriticalAlloc;
+
+/// Wrapper to provide functionality to jarray references.
+/// This is an empty holder by itself. Construct a PinnedPrimitiveArray to actually interact with
+/// the elements of the array.
+template <typename JArrayType>
+class FBEXPORT JPrimitiveArray :
+    public JavaClass<JPrimitiveArray<JArrayType>, detail::JArray, JArrayType> {
+  static_assert(is_jni_primitive_array<JArrayType>(), "");
+ public:
+  static constexpr const char* kJavaDescriptor = nullptr;
+  static std::string get_instantiated_java_descriptor();
+  static std::string get_instantiated_base_name();
+
+  using T = typename jtype_traits<JArrayType>::entry_type;
+
+  static local_ref<JArrayType> newArray(size_t count);
+
+  void getRegion(jsize start, jsize length, T* buf);
+  std::unique_ptr<T[]> getRegion(jsize start, jsize length);
+  void setRegion(jsize start, jsize length, const T* buf);
+
+  /// Returns a view of the underlying array. This will either be a "pinned"
+  /// version of the array (in which case changes to one immediately affect the
+  /// other) or a copy of the array (in which cases changes to the view will take
+  /// affect when destroyed or on calls to release()/commit()).
+  PinnedPrimitiveArray<T, PinnedArrayAlloc<T>> pin();
+
+  /// Returns a view of part of the underlying array. A pinned region is always
+  /// backed by a copy of the region.
+  PinnedPrimitiveArray<T, PinnedRegionAlloc<T>> pinRegion(jsize start, jsize length);
+
+  /// Returns a view of the underlying array like pin(). However, while the pin
+  /// is held, the code is considered within a "critical region". In a critical
+  /// region, native code must not call JNI functions or make any calls that may
+  /// block on other Java threads. These restrictions make it more likely that
+  /// the view will be "pinned" rather than copied (for example, the VM may
+  /// suspend garbage collection within a critical region).
+  PinnedPrimitiveArray<T, PinnedCriticalAlloc<T>> pinCritical();
+
+private:
+  friend class PinnedArrayAlloc<T>;
+  T* getElements(jboolean* isCopy);
+  void releaseElements(T* elements, jint mode);
+};
+
+FBEXPORT local_ref<jbooleanArray> make_boolean_array(jsize size);
+FBEXPORT local_ref<jbyteArray> make_byte_array(jsize size);
+FBEXPORT local_ref<jcharArray> make_char_array(jsize size);
+FBEXPORT local_ref<jshortArray> make_short_array(jsize size);
+FBEXPORT local_ref<jintArray> make_int_array(jsize size);
+FBEXPORT local_ref<jlongArray> make_long_array(jsize size);
+FBEXPORT local_ref<jfloatArray> make_float_array(jsize size);
+FBEXPORT local_ref<jdoubleArray> make_double_array(jsize size);
+
+using JArrayBoolean = JPrimitiveArray<jbooleanArray>;
+using JArrayByte = JPrimitiveArray<jbyteArray>;
+using JArrayChar = JPrimitiveArray<jcharArray>;
+using JArrayShort = JPrimitiveArray<jshortArray>;
+using JArrayInt = JPrimitiveArray<jintArray>;
+using JArrayLong = JPrimitiveArray<jlongArray>;
+using JArrayFloat = JPrimitiveArray<jfloatArray>;
+using JArrayDouble = JPrimitiveArray<jdoubleArray>;
+
+/// RAII class for pinned primitive arrays
+/// This currently only supports read/write access to existing java arrays. You can't create a
+/// primitive array this way yet. This class also pins the entire array into memory during the
+/// lifetime of the PinnedPrimitiveArray. If you need to unpin the array manually, call the
+/// release() or abort() functions. During a long-running block of code, you
+/// should unpin the array as soon as you're done with it, to avoid holding up
+/// the Java garbage collector.
+template <typename T, typename PinAlloc>
+class PinnedPrimitiveArray {
+  public:
+   static_assert(is_jni_primitive<T>::value,
+       "PinnedPrimitiveArray requires primitive jni type.");
+
+   using ArrayType = typename jtype_traits<T>::array_type;
+
+   PinnedPrimitiveArray(PinnedPrimitiveArray&&);
+   PinnedPrimitiveArray(const PinnedPrimitiveArray&) = delete;
+   ~PinnedPrimitiveArray() noexcept;
+
+   PinnedPrimitiveArray& operator=(PinnedPrimitiveArray&&);
+   PinnedPrimitiveArray& operator=(const PinnedPrimitiveArray&) = delete;
+
+   T* get();
+   void release();
+   /// Unpins the array. If the array is a copy, pending changes are discarded.
+   void abort();
+   /// If the array is a copy, copies pending changes to the underlying java array.
+   void commit();
+
+   bool isCopy() const noexcept;
+
+   const T& operator[](size_t index) const;
+   T& operator[](size_t index);
+   size_t size() const noexcept;
+
+  private:
+   alias_ref<ArrayType> array_;
+   size_t start_;
+   T* elements_;
+   jboolean isCopy_;
+   size_t size_;
+
+   void allocate(alias_ref<ArrayType>, jint start, jint length);
+   void releaseImpl(jint mode);
+   void clear() noexcept;
+
+   PinnedPrimitiveArray(alias_ref<ArrayType>, jint start, jint length);
+
+   friend class JPrimitiveArray<typename jtype_traits<T>::array_type>;
+};
+
+struct FBEXPORT JStackTraceElement : JavaClass<JStackTraceElement> {
+  static auto constexpr kJavaDescriptor = "Ljava/lang/StackTraceElement;";
+
+  static local_ref<javaobject> create(const std::string& declaringClass, const std::string& methodName, const std::string& file, int line);
+
+  std::string getClassName() const;
+  std::string getMethodName() const;
+  std::string getFileName() const;
+  int getLineNumber() const;
+};
+
+/// Wrapper to provide functionality to jthrowable references
+class FBEXPORT JThrowable : public JavaClass<JThrowable, JObject, jthrowable> {
+ public:
+  static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;";
+
+  using JStackTrace = JArrayClass<JStackTraceElement::javaobject>;
+
+  local_ref<JThrowable> initCause(alias_ref<JThrowable> cause);
+  local_ref<JStackTrace> getStackTrace();
+  void setStackTrace(alias_ref<JArrayClass<JStackTraceElement::javaobject>>);
+};
+
+#pragma push_macro("PlainJniRefMap")
+#undef PlainJniRefMap
+#define PlainJniRefMap(rtype, jtype) \
+namespace detail { \
+template<> \
+struct RefReprType<jtype> { \
+  using type = rtype; \
+}; \
+}
+
+PlainJniRefMap(JArrayBoolean, jbooleanArray);
+PlainJniRefMap(JArrayByte, jbyteArray);
+PlainJniRefMap(JArrayChar, jcharArray);
+PlainJniRefMap(JArrayShort, jshortArray);
+PlainJniRefMap(JArrayInt, jintArray);
+PlainJniRefMap(JArrayLong, jlongArray);
+PlainJniRefMap(JArrayFloat, jfloatArray);
+PlainJniRefMap(JArrayDouble, jdoubleArray);
+PlainJniRefMap(JObject, jobject);
+PlainJniRefMap(JClass, jclass);
+PlainJniRefMap(JString, jstring);
+PlainJniRefMap(JThrowable, jthrowable);
+
+#pragma pop_macro("PlainJniRefMap")
+
+}}
+
+#include "CoreClasses-inl.h"
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Exceptions.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Exceptions.h
new file mode 100644
index 000000000..b1089139d
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Exceptions.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/**
+ * @file Exceptions.h
+ *
+ * After invoking a JNI function that can throw a Java exception, the macro
+ * @ref FACEBOOK_JNI_THROW_PENDING_EXCEPTION() or @ref FACEBOOK_JNI_THROW_EXCEPTION_IF()
+ * should be invoked.
+ *
+ * IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT!
+ * To use these methods you MUST call initExceptionHelpers() when your library is loaded.
+ */
+
+#pragma once
+
+#include <alloca.h>
+#include <stdexcept>
+#include <string>
+
+#include <jni.h>
+
+#include <fb/visibility.h>
+
+#include "Common.h"
+#include "References.h"
+#include "CoreClasses.h"
+
+#if defined(__ANDROID__) && defined(__ARM_ARCH_5TE__) && !defined(FBJNI_NO_EXCEPTION_PTR)
+// ARMv5 NDK does not support exception_ptr so we cannot use that when building for it.
+#define FBJNI_NO_EXCEPTION_PTR
+#endif
+
+namespace facebook {
+namespace jni {
+
+class JThrowable;
+
+class JCppException : public JavaClass<JCppException, JThrowable> {
+ public:
+  static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppException;";
+
+  static local_ref<JCppException> create(const char* str) {
+    return newInstance(make_jstring(str));
+  }
+
+  static local_ref<JCppException> create(const std::exception& ex) {
+    return newInstance(make_jstring(ex.what()));
+  }
+};
+
+// JniException ////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This class wraps a Java exception into a C++ exception; if the exception is routed back
+ * to the Java side, it can be unwrapped and just look like a pure Java interaction. The class
+ * is resilient to errors while creating the exception, falling back to some pre-allocated
+ * exceptions if a new one cannot be allocated or populated.
+ *
+ * Note: the what() method of this class is not thread-safe (t6900503).
+ */
+class FBEXPORT JniException : public std::exception {
+ public:
+  JniException();
+  ~JniException();
+
+  explicit JniException(alias_ref<jthrowable> throwable);
+
+  JniException(JniException &&rhs);
+
+  JniException(const JniException &other);
+
+  local_ref<JThrowable> getThrowable() const noexcept;
+
+  virtual const char* what() const noexcept;
+
+  void setJavaException() const noexcept;
+
+ private:
+  global_ref<JThrowable> throwable_;
+  mutable std::string what_;
+  mutable bool isMessageExtracted_;
+  const static std::string kExceptionMessageFailure_;
+
+  void populateWhat() const noexcept;
+};
+
+// Exception throwing & translating functions //////////////////////////////////////////////////////
+
+// Functions that throw C++ exceptions
+
+static const int kMaxExceptionMessageBufferSize = 512;
+
+// These methods are the preferred way to throw a Java exception from
+// a C++ function.  They create and throw a C++ exception which wraps
+// a Java exception, so the C++ flow is interrupted. Then, when
+// translatePendingCppExceptionToJavaException is called at the
+// topmost level of the native stack, the wrapped Java exception is
+// thrown to the java caller.
+template<typename... Args>
+[[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args) {
+  int msgSize = snprintf(nullptr, 0, fmt, args...);
+
+  char *msg = (char*) alloca(msgSize + 1);
+  snprintf(msg, kMaxExceptionMessageBufferSize, fmt, args...);
+  throwNewJavaException(throwableName, msg);
+}
+
+// Identifies any pending C++ exception and throws it as a Java exception. If the exception can't
+// be thrown, it aborts the program.
+FBEXPORT void translatePendingCppExceptionToJavaException();
+
+#ifndef FBJNI_NO_EXCEPTION_PTR
+FBEXPORT local_ref<JThrowable> getJavaExceptionForCppException(std::exception_ptr ptr);
+#endif
+
+FBEXPORT local_ref<JThrowable> getJavaExceptionForCppBackTrace();
+
+FBEXPORT local_ref<JThrowable> getJavaExceptionForCppBackTrace(const char* msg);
+// For convenience, some exception names in java.lang are available here.
+
+const char* const gJavaLangIllegalArgumentException = "java/lang/IllegalArgumentException";
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/File.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/File.h
new file mode 100644
index 000000000..29fc9850a
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/File.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include "CoreClasses.h"
+
+namespace facebook {
+namespace jni {
+
+class JFile : public JavaClass<JFile> {
+ public:
+  static constexpr const char* kJavaDescriptor = "Ljava/io/File;";
+
+  // Define a method that calls into the represented Java class
+  std::string getAbsolutePath() {
+    static auto method = getClass()->getMethod<jstring()>("getAbsolutePath");
+    return method(self())->toStdString();
+  }
+
+};
+
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Hybrid.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Hybrid.h
new file mode 100644
index 000000000..e2cac448f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Hybrid.h
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <memory>
+#include <type_traits>
+
+#include <fb/assert.h>
+#include <fb/visibility.h>
+
+#include "CoreClasses.h"
+
+namespace facebook {
+namespace jni {
+
+namespace detail {
+
+class BaseHybridClass {
+public:
+  virtual ~BaseHybridClass() {}
+};
+
+struct FBEXPORT HybridData : public JavaClass<HybridData> {
+  constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridData;";
+  static local_ref<HybridData> create();
+};
+
+class HybridDestructor : public JavaClass<HybridDestructor> {
+  public:
+    static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/HybridData$Destructor;";
+
+  template <typename T=detail::BaseHybridClass>
+  T* getNativePointer() {
+    static auto pointerField = javaClassStatic()->getField<jlong>("mNativePointer");
+    auto* value = reinterpret_cast<detail::BaseHybridClass*>(getFieldValue(pointerField));
+    if (!value) {
+      throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
+    }
+    return value;
+  }
+
+  template <typename T=detail::BaseHybridClass>
+  void setNativePointer(std::unique_ptr<T> new_value) {
+    static auto pointerField = javaClassStatic()->getField<jlong>("mNativePointer");
+    auto old_value = std::unique_ptr<T>(reinterpret_cast<T*>(getFieldValue(pointerField)));
+    if (new_value && old_value) {
+        FBCRASH("Attempt to set C++ native pointer twice");
+    }
+    setFieldValue(pointerField, reinterpret_cast<jlong>(new_value.release()));
+  }
+};
+
+template<typename T>
+detail::BaseHybridClass* getNativePointer(T t) {
+  return getHolder(t)->getNativePointer();
+}
+
+template<typename T>
+void setNativePointer(T t, std::unique_ptr<detail::BaseHybridClass> new_value) {
+  getHolder(t)->setNativePointer(std::move(new_value));
+}
+
+template<typename T>
+local_ref<HybridDestructor> getHolder(T t) {
+  static auto holderField = t->getClass()->template getField<HybridDestructor::javaobject>("mDestructor");
+  return t->getFieldValue(holderField);
+}
+
+// JavaClass for HybridClassBase
+struct FBEXPORT HybridClassBase : public JavaClass<HybridClassBase> {
+  constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridClassBase;";
+
+  static bool isHybridClassBase(alias_ref<jclass> jclass) {
+    return HybridClassBase::javaClassStatic()->isAssignableFrom(jclass);
+  }
+};
+
+template <typename Base, typename Enabled = void>
+struct HybridTraits {
+  // This static assert should actually always fail if we don't use one of the
+  // specializations below.
+  static_assert(
+      std::is_base_of<JObject, Base>::value ||
+      std::is_base_of<BaseHybridClass, Base>::value,
+      "The base of a HybridClass must be either another HybridClass or derived from JObject.");
+};
+
+template <>
+struct HybridTraits<BaseHybridClass> {
+ using CxxBase = BaseHybridClass;
+ using JavaBase = JObject;
+};
+
+template <typename Base>
+struct HybridTraits<
+    Base,
+    typename std::enable_if<std::is_base_of<BaseHybridClass, Base>::value>::type> {
+ using CxxBase = Base;
+ using JavaBase = typename Base::JavaPart;
+};
+
+template <typename Base>
+struct HybridTraits<
+    Base,
+    typename std::enable_if<std::is_base_of<JObject, Base>::value>::type> {
+ using CxxBase = BaseHybridClass;
+ using JavaBase = Base;
+};
+
+// convert to HybridClass* from jhybridobject
+template <typename T>
+struct FBEXPORT Convert<
+  T, typename std::enable_if<
+    std::is_base_of<BaseHybridClass, typename std::remove_pointer<T>::type>::value>::type> {
+  typedef typename std::remove_pointer<T>::type::jhybridobject jniType;
+  static T fromJni(jniType t) {
+    if (t == nullptr) {
+      return nullptr;
+    }
+    return wrap_alias(t)->cthis();
+  }
+  // There is no automatic return conversion for objects.
+};
+
+template<typename T>
+struct RefReprType<T, typename std::enable_if<std::is_base_of<BaseHybridClass, T>::value, void>::type> {
+  static_assert(std::is_same<T, void>::value,
+      "HybridFoo (where HybridFoo derives from HybridClass<HybridFoo>) is not supported in this context. "
+      "For an xxx_ref<HybridFoo>, you may want: xxx_ref<HybridFoo::javaobject> or HybridFoo*.");
+  using Repr = T;
+};
+
+
+}
+
+template <typename T, typename Base = detail::BaseHybridClass>
+class FBEXPORT HybridClass : public detail::HybridTraits<Base>::CxxBase {
+public:
+  struct JavaPart : JavaClass<JavaPart, typename detail::HybridTraits<Base>::JavaBase> {
+    // At this point, T is incomplete, and so we cannot access
+    // T::kJavaDescriptor directly. jtype_traits support this escape hatch for
+    // such a case.
+    static constexpr const char* kJavaDescriptor = nullptr;
+    static std::string get_instantiated_java_descriptor();
+    static std::string get_instantiated_base_name();
+
+    using HybridType = T;
+
+    // This will reach into the java object and extract the C++ instance from
+    // the mHybridData and return it.
+    T* cthis();
+
+    friend class HybridClass;
+  };
+
+  using jhybridobject = typename JavaPart::javaobject;
+  using javaobject = typename JavaPart::javaobject;
+  typedef detail::HybridData::javaobject jhybriddata;
+
+  static alias_ref<JClass> javaClassStatic() {
+    return JavaPart::javaClassStatic();
+  }
+
+  static local_ref<JClass> javaClassLocal() {
+    std::string className(T::kJavaDescriptor + 1, strlen(T::kJavaDescriptor) - 2);
+    return findClassLocal(className.c_str());
+  }
+
+protected:
+  typedef HybridClass HybridBase;
+
+  // This ensures that a C++ hybrid part cannot be created on its own
+  // by default.  If a hybrid wants to enable this, it can provide its
+  // own public ctor, or change the accessibility of this to public.
+  using detail::HybridTraits<Base>::CxxBase::CxxBase;
+
+  static void registerHybrid(std::initializer_list<NativeMethod> methods) {
+    javaClassStatic()->registerNatives(methods);
+  }
+
+  static local_ref<detail::HybridData> makeHybridData(std::unique_ptr<T> cxxPart) {
+    auto hybridData = detail::HybridData::create();
+    setNativePointer(hybridData, std::move(cxxPart));
+    return hybridData;
+  }
+
+  template <typename... Args>
+  static local_ref<detail::HybridData> makeCxxInstance(Args&&... args) {
+    return makeHybridData(std::unique_ptr<T>(new T(std::forward<Args>(args)...)));
+  }
+
+  template <typename... Args>
+  static void setCxxInstance(alias_ref<jhybridobject> o, Args&&... args) {
+    setNativePointer(o, std::unique_ptr<T>(new T(std::forward<Args>(args)...)));
+  }
+
+public:
+  // Factory method for creating a hybrid object where the arguments
+  // are used to initialize the C++ part directly without passing them
+  // through java.  This method requires the Java part to have a ctor
+  // which takes a HybridData, and for the C++ part to have a ctor
+  // compatible with the arguments passed here.  For safety, the ctor
+  // can be private, and the hybrid declared a friend of its base, so
+  // the hybrid can only be created from here.
+  //
+  // Exception behavior: This can throw an exception if creating the
+  // C++ object fails, or any JNI methods throw.
+  template <typename... Args>
+  static local_ref<JavaPart> newObjectCxxArgs(Args&&... args) {
+    static bool isHybrid = detail::HybridClassBase::isHybridClassBase(javaClassStatic());
+    auto cxxPart = std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+
+    local_ref<JavaPart> result;
+    if (isHybrid) {
+      result = JavaPart::newInstance();
+      setNativePointer(result, std::move(cxxPart));
+    }
+    else {
+      auto hybridData = makeHybridData(std::move(cxxPart));
+      result = JavaPart::newInstance(hybridData);
+    }
+
+    return result;
+  }
+
+ // TODO? Create reusable interface for Allocatable classes and use it to
+  // strengthen type-checking (and possibly provide a default
+  // implementation of allocate().)
+  template <typename... Args>
+  static local_ref<jhybridobject> allocateWithCxxArgs(Args&&... args) {
+    auto hybridData = makeCxxInstance(std::forward<Args>(args)...);
+    static auto allocateMethod =
+        javaClassStatic()->template getStaticMethod<jhybridobject(jhybriddata)>("allocate");
+    return allocateMethod(javaClassStatic(), hybridData.get());
+  }
+
+  // Factory method for creating a hybrid object where the arguments
+  // are passed to the java ctor.
+  template <typename... Args>
+  static local_ref<JavaPart> newObjectJavaArgs(Args&&... args) {
+    return JavaPart::newInstance(std::move(args)...);
+  }
+
+  // If a hybrid class throws an exception which derives from
+  // std::exception, it will be passed to mapException on the hybrid
+  // class, or nearest ancestor.  This allows boilerplate exception
+  // translation code (for example, calling throwNewJavaException on a
+  // particular java class) to be hoisted to a common function.  If
+  // mapException returns, then the std::exception will be translated
+  // to Java.
+  static void mapException(const std::exception& ex) {}
+};
+
+template <typename T, typename B>
+inline T* HybridClass<T, B>::JavaPart::cthis() {
+  detail::BaseHybridClass* result = 0;
+  static bool isHybrid = detail::HybridClassBase::isHybridClassBase(this->getClass());
+  if (isHybrid) {
+    result = getNativePointer(this);
+  } else {
+    static auto field =
+      HybridClass<T, B>::JavaPart::javaClassStatic()->template getField<detail::HybridData::javaobject>("mHybridData");
+    auto hybridData = this->getFieldValue(field);
+    if (!hybridData) {
+      throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
+    }
+
+    result = getNativePointer(hybridData);
+  }
+
+  // This would require some serious programmer error.
+  FBASSERTMSGF(result != 0, "Incorrect C++ type in hybrid field");
+  // I'd like to use dynamic_cast here, but -fno-rtti is the default.
+  return static_cast<T*>(result);
+};
+
+template <typename T, typename B>
+/* static */ inline std::string HybridClass<T, B>::JavaPart::get_instantiated_java_descriptor() {
+  return T::kJavaDescriptor;
+}
+
+template <typename T, typename B>
+/* static */ inline std::string HybridClass<T, B>::JavaPart::get_instantiated_base_name() {
+  auto name = get_instantiated_java_descriptor();
+  return name.substr(1, name.size() - 2);
+}
+
+// Given a *_ref object which refers to a hybrid class, this will reach inside
+// of it, find the mHybridData, extract the C++ instance pointer, cast it to
+// the appropriate type, and return it.
+template <typename T>
+inline auto cthis(T jthis) -> decltype(jthis->cthis()) {
+  return jthis->cthis();
+}
+
+void HybridDataOnLoad();
+
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Iterator-inl.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Iterator-inl.h
new file mode 100644
index 000000000..206d2b4b1
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Iterator-inl.h
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+namespace facebook {
+namespace jni {
+
+namespace detail {
+
+template <typename E>
+struct IteratorHelper : public JavaClass<IteratorHelper<E>> {
+  constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/IteratorHelper;";
+
+  typedef local_ref<E> value_type;
+  typedef ptrdiff_t difference_type;
+  typedef value_type* pointer;
+  typedef value_type& reference;
+  typedef std::forward_iterator_tag iterator_category;
+
+  typedef JavaClass<IteratorHelper<E>> JavaBase_;
+
+  bool hasNext() const {
+    static auto hasNextMethod =
+      JavaBase_::javaClassStatic()->template getMethod<jboolean()>("hasNext");
+    return hasNextMethod(JavaBase_::self());
+  }
+
+  value_type next() {
+    static auto elementField =
+      JavaBase_::javaClassStatic()->template getField<jobject>("mElement");
+    return dynamic_ref_cast<E>(JavaBase_::getFieldValue(elementField));
+  }
+
+  static void reset(value_type& v) {
+    v.reset();
+  }
+};
+
+template <typename K, typename V>
+struct MapIteratorHelper : public JavaClass<MapIteratorHelper<K,V>> {
+  constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/MapIteratorHelper;";
+
+  typedef std::pair<local_ref<K>, local_ref<V>> value_type;
+
+  typedef JavaClass<MapIteratorHelper<K,V>> JavaBase_;
+
+  bool hasNext() const {
+    static auto hasNextMethod =
+      JavaBase_::javaClassStatic()->template getMethod<jboolean()>("hasNext");
+    return hasNextMethod(JavaBase_::self());
+  }
+
+  value_type next() {
+    static auto keyField = JavaBase_::javaClassStatic()->template getField<jobject>("mKey");
+    static auto valueField = JavaBase_::javaClassStatic()->template getField<jobject>("mValue");
+    return std::make_pair(dynamic_ref_cast<K>(JavaBase_::getFieldValue(keyField)),
+                          dynamic_ref_cast<V>(JavaBase_::getFieldValue(valueField)));
+  }
+
+  static void reset(value_type& v) {
+    v.first.reset();
+    v.second.reset();
+  }
+};
+
+template <typename T>
+class Iterator {
+ public:
+  typedef typename T::value_type value_type;
+  typedef ptrdiff_t difference_type;
+  typedef value_type* pointer;
+  typedef value_type& reference;
+  typedef std::input_iterator_tag iterator_category;
+
+  // begin ctor
+  Iterator(global_ref<typename T::javaobject>&& helper)
+      : helper_(std::move(helper))
+      , i_(-1) {
+    ++(*this);
+  }
+
+  // end ctor
+  Iterator()
+      : i_(-1) {}
+
+  bool operator==(const Iterator& it) const { return i_ == it.i_; }
+  bool operator!=(const Iterator& it) const { return !(*this == it); }
+  const value_type& operator*() const { assert(i_ != -1); return entry_; }
+  const value_type* operator->() const { assert(i_ != -1); return &entry_; }
+  Iterator& operator++() {  // preincrement
+    bool hasNext = helper_->hasNext();
+    if (hasNext) {
+      ++i_;
+      entry_ = helper_->next();
+    } else {
+      i_ = -1;
+      helper_->reset(entry_);
+    }
+    return *this;
+  }
+  Iterator operator++(int) {  // postincrement
+    Iterator ret;
+    ret.i_ = i_;
+    ret.entry_ = std::move(entry_);
+    ++(*this);
+    return ret;
+  }
+
+  global_ref<typename T::javaobject> helper_;
+  // set to -1 at end
+  std::ptrdiff_t i_;
+  value_type entry_;
+};
+
+}
+
+template <typename E>
+struct JIterator<E>::Iterator : public detail::Iterator<detail::IteratorHelper<E>> {
+  using detail::Iterator<detail::IteratorHelper<E>>::Iterator;
+};
+
+template <typename E>
+typename JIterator<E>::Iterator JIterator<E>::begin() const {
+  static auto ctor = detail::IteratorHelper<E>::javaClassStatic()->
+    template getConstructor<typename detail::IteratorHelper<E>::javaobject(
+                              typename JIterator<E>::javaobject)>();
+  return Iterator(
+    make_global(
+      detail::IteratorHelper<E>::javaClassStatic()->newObject(ctor, this->self())));
+}
+
+template <typename E>
+typename JIterator<E>::Iterator JIterator<E>::end() const {
+  return Iterator();
+}
+
+template <typename E>
+struct JIterable<E>::Iterator : public detail::Iterator<detail::IteratorHelper<E>> {
+  using detail::Iterator<detail::IteratorHelper<E>>::Iterator;
+};
+
+template <typename E>
+typename JIterable<E>::Iterator JIterable<E>::begin() const {
+  static auto ctor = detail::IteratorHelper<E>::javaClassStatic()->
+    template getConstructor<typename detail::IteratorHelper<E>::javaobject(
+                              typename JIterable<E>::javaobject)>();
+  return Iterator(
+    make_global(
+      detail::IteratorHelper<E>::javaClassStatic()->newObject(ctor, this->self())));
+}
+
+template <typename E>
+typename JIterable<E>::Iterator JIterable<E>::end() const {
+  return Iterator();
+}
+
+template <typename E>
+size_t JCollection<E>::size() const {
+  static auto sizeMethod =
+    JCollection<E>::javaClassStatic()->template getMethod<jint()>("size");
+  return sizeMethod(this->self());
+}
+
+template <typename K, typename V>
+struct JMap<K,V>::Iterator : public detail::Iterator<detail::MapIteratorHelper<K,V>> {
+  using detail::Iterator<detail::MapIteratorHelper<K,V>>::Iterator;
+};
+
+template <typename K, typename V>
+size_t JMap<K,V>::size() const {
+  static auto sizeMethod =
+    JMap<K,V>::javaClassStatic()->template getMethod<jint()>("size");
+  return sizeMethod(this->self());
+}
+
+template <typename K, typename V>
+typename JMap<K,V>::Iterator JMap<K,V>::begin() const {
+  static auto ctor = detail::MapIteratorHelper<K,V>::javaClassStatic()->
+    template getConstructor<typename detail::MapIteratorHelper<K,V>::javaobject(
+                              typename JMap<K,V>::javaobject)>();
+  return Iterator(
+    make_global(
+      detail::MapIteratorHelper<K,V>::javaClassStatic()->newObject(ctor, this->self())));
+}
+
+template <typename K, typename V>
+typename JMap<K,V>::Iterator JMap<K,V>::end() const {
+  return Iterator();
+}
+
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Iterator.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Iterator.h
new file mode 100644
index 000000000..aa3652666
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Iterator.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include "CoreClasses.h"
+
+namespace facebook {
+namespace jni {
+
+/**
+ * JavaClass which represents a reference to a java.util.Iterator instance.  It
+ * provides begin()/end() methods to provide C++-style iteration over the
+ * underlying collection.  The class has a template parameter for the element
+ * type, which defaults to jobject.  For example:
+ *
+ * alias_ref<JIterator<jstring>::javaobject> my_iter = ...;
+ *
+ * In the simplest case, it can be used just as alias_ref<JIterator<>::javaobject>,
+ * for example in a method declaration.
+ */
+template <typename E = jobject>
+struct JIterator : JavaClass<JIterator<E>> {
+  constexpr static auto kJavaDescriptor = "Ljava/util/Iterator;";
+
+  struct Iterator;
+
+  /**
+   * To iterate:
+   *
+   * for (const auto& element : *jiter) { ... }
+   *
+   * The JIterator iterator value_type is local_ref<E>, containing a reference
+   * to an element instance.
+   *
+   * If the Iterator returns objects whch are not convertible to the given
+   * element type, iteration will throw a java ClassCastException.
+   *
+   * For example, to convert an iterator over a collection of java strings to
+   * an std::vector of std::strings:
+   *
+   * std::vector<std::string> vs;
+   * for (const auto& elem : *jiter) {
+   *    vs.push_back(elem->toStdString());
+   * }
+   *
+   * Or if you prefer using std algorithms:
+   *
+   * std::vector<std::string> vs;
+   * std::transform(jiter->begin(), jiter->end(), std::back_inserter(vs),
+   *                [](const local_ref<jstring>& elem) { return elem->toStdString(); });
+   *
+   * The iterator is a InputIterator.
+   */
+  Iterator begin() const;
+  Iterator end() const;
+};
+
+/**
+ * Similar to JIterator, except this represents any object which implements the
+ * java.lang.Iterable interface. It will create the Java Iterator as a part of
+ * begin().
+ */
+template <typename E = jobject>
+struct JIterable : JavaClass<JIterable<E>> {
+  constexpr static auto kJavaDescriptor = "Ljava/lang/Iterable;";
+
+  struct Iterator;
+
+  Iterator begin() const;
+  Iterator end() const;
+};
+
+/**
+ * JavaClass types which represent Collection, List, and Set are also provided.
+ * These preserve the Java class heirarchy.
+ */
+template <typename E = jobject>
+struct JCollection : JavaClass<JCollection<E>, JIterable<E>> {
+  constexpr static auto kJavaDescriptor = "Ljava/util/Collection;";
+
+  /**
+   * Returns the number of elements in the collection.
+   */
+  size_t size() const;
+};
+
+template <typename E = jobject>
+struct JList : JavaClass<JList<E>, JCollection<E>> {
+  constexpr static auto kJavaDescriptor = "Ljava/util/List;";
+};
+
+template <typename E = jobject>
+struct JSet : JavaClass<JSet<E>, JCollection<E>> {
+  constexpr static auto kJavaDescriptor = "Ljava/util/Set;";
+};
+
+/**
+ * JavaClass which represents a reference to a java.util.Map instance.  It adds
+ * wrappers around Java methods, including begin()/end() methods to provide
+ * C++-style iteration over the Java Map.  The class has template parameters
+ * for the key and value types, which default to jobject.  For example:
+ *
+ * alias_ref<JMap<jstring, MyJClass::javaobject>::javaobject> my_map = ...;
+ *
+ * In the simplest case, it can be used just as alias_ref<JMap<>::javaobject>,
+ * for example in a method declaration.
+ */
+template <typename K = jobject, typename V = jobject>
+struct JMap : JavaClass<JMap<K,V>> {
+  constexpr static auto kJavaDescriptor = "Ljava/util/Map;";
+
+  struct Iterator;
+
+  /**
+   * Returns the number of pairs in the map.
+   */
+  size_t size() const;
+
+  /**
+   * To iterate over the Map:
+   *
+   * for (const auto& entry : *jmap) { ... }
+   *
+   * The JMap iterator value_type is std::pair<local_ref<K>, local_ref<V>>
+   * containing references to key and value instances.
+   *
+   * If the Map contains objects whch are not convertible to the given key and
+   * value types, iteration will throw a java ClassCastException.
+   *
+   * The iterator is a InputIterator.
+   */
+  Iterator begin() const;
+  Iterator end() const;
+};
+
+}
+}
+
+#include "Iterator-inl.h"
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/JThread.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/JThread.h
new file mode 100644
index 000000000..60b3cac20
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/JThread.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include "CoreClasses.h"
+#include "NativeRunnable.h"
+
+namespace facebook {
+namespace jni {
+
+class JThread : public JavaClass<JThread> {
+ public:
+  static constexpr const char* kJavaDescriptor = "Ljava/lang/Thread;";
+
+  void start() {
+    static auto method = javaClassStatic()->getMethod<void()>("start");
+    method(self());
+  }
+
+  void join() {
+    static auto method = javaClassStatic()->getMethod<void()>("join");
+    method(self());
+  }
+
+  static local_ref<JThread> create(std::function<void()>&& runnable) {
+    auto jrunnable = JNativeRunnable::newObjectCxxArgs(std::move(runnable));
+    return newInstance(static_ref_cast<JRunnable::javaobject>(jrunnable));
+  }
+};
+
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/JWeakReference.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/JWeakReference.h
new file mode 100644
index 000000000..aca55f203
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/JWeakReference.h
@@ -0,0 +1,37 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <fb/visibility.h>
+
+#include "CoreClasses.h"
+
+namespace facebook {
+namespace jni {
+
+/**
+ * Wrap Java's WeakReference instead of using JNI WeakGlobalRefs.
+ * A WeakGlobalRef can yield a strong reference even after the object has been
+  * finalized. See comment in the djinni library.
+ * https://github.com/dropbox/djinni/blob/master/support-lib/jni/djinni_support.hpp
+ */
+template<typename T = jobject>
+class JWeakReference : public JavaClass<JWeakReference<T>> {
+
+ typedef JavaClass<JWeakReference<T>> JavaBase_;
+
+ public:
+  static constexpr const char* kJavaDescriptor = "Ljava/lang/ref/WeakReference;";
+
+  static local_ref<JWeakReference<T>> newInstance(alias_ref<T> object) {
+    return JavaBase_::newInstance(static_ref_cast<jobject>(object));
+  }
+
+  local_ref<T> get() const {
+    static auto method = JavaBase_::javaClassStatic()->template getMethod<jobject()>("get");
+    return static_ref_cast<T>(method(JavaBase_::self()));
+  }
+};
+
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Meta-forward.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Meta-forward.h
new file mode 100644
index 000000000..ff08aff46
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Meta-forward.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+namespace facebook {
+namespace jni {
+
+template<typename F>
+class JMethod;
+template<typename F>
+class JStaticMethod;
+template<typename F>
+class JNonvirtualMethod;
+template<typename F>
+struct JConstructor;
+template<typename F>
+class JField;
+template<typename F>
+class JStaticField;
+
+/// Type traits for Java types (currently providing Java type descriptors)
+template<typename T>
+struct jtype_traits;
+
+/// Type traits for Java methods (currently providing Java type descriptors)
+template<typename F>
+struct jmethod_traits;
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Meta-inl.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Meta-inl.h
new file mode 100644
index 000000000..05ecd237e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Meta-inl.h
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+#include "Common.h"
+#include "Exceptions.h"
+#include "MetaConvert.h"
+#include "References.h"
+#include "Boxed.h"
+
+#if defined(__ANDROID__)
+#  include <fb/Build.h>
+#endif
+
+namespace facebook {
+namespace jni {
+
+// JMethod /////////////////////////////////////////////////////////////////////////////////////////
+
+inline JMethodBase::JMethodBase(jmethodID method_id) noexcept
+  : method_id_{method_id}
+{}
+
+inline JMethodBase::operator bool() const noexcept {
+  return method_id_ != nullptr;
+}
+
+inline jmethodID JMethodBase::getId() const noexcept {
+  return method_id_;
+}
+
+namespace {
+
+template <int idx, typename... Args>
+struct ArgsArraySetter;
+
+template <int idx, typename Arg, typename... Args>
+struct ArgsArraySetter<idx, Arg, Args...> {
+  static void set(alias_ref<JArrayClass<jobject>::javaobject> array, Arg arg0, Args... args) {
+    // TODO(xxxxxxxx): Use Convert<Args>... to do conversions like the fast path.
+    (*array)[idx] = autobox(arg0);
+    ArgsArraySetter<idx + 1, Args...>::set(array, args...);
+  }
+};
+
+template <int idx>
+struct ArgsArraySetter<idx> {
+  static void set(alias_ref<JArrayClass<jobject>::javaobject> array) {
+  }
+};
+
+template <typename... Args>
+local_ref<JArrayClass<jobject>::javaobject> makeArgsArray(Args... args) {
+  auto arr = JArrayClass<jobject>::newArray(sizeof...(args));
+  ArgsArraySetter<0, Args...>::set(arr, args...);
+  return arr;
+}
+
+
+inline bool needsSlowPath(alias_ref<jobject> obj) {
+#if defined(__ANDROID__)
+  // On Android 6.0, art crashes when attempting to call a function on a Proxy.
+  // So, when we detect that case we must use the safe, slow workaround. That is,
+  // we resolve the method id to the corresponding java.lang.reflect.Method object
+  // and make the call via it's invoke() method.
+  static auto is_bad_android = build::Build::getAndroidSdk() == 23;
+
+  if (!is_bad_android) return false;
+  static auto proxy_class = findClassStatic("java/lang/reflect/Proxy");
+  return obj->isInstanceOf(proxy_class);
+#else
+  return false;
+#endif
+}
+
+}
+
+template<typename... Args>
+inline void JMethod<void(Args...)>::operator()(alias_ref<jobject> self, Args... args) {
+  const auto env = Environment::current();
+  env->CallVoidMethod(
+        self.get(),
+        getId(),
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+}
+
+#pragma push_macro("DEFINE_PRIMITIVE_CALL")
+#undef DEFINE_PRIMITIVE_CALL
+#define DEFINE_PRIMITIVE_CALL(TYPE, METHOD)                                                    \
+template<typename... Args>                                                                     \
+inline TYPE JMethod<TYPE(Args...)>::operator()(alias_ref<jobject> self, Args... args) {        \
+  const auto env = internal::getEnv();                                                         \
+  auto result = env->Call ## METHOD ## Method(                                                 \
+        self.get(),                                                                            \
+        getId(),                                                                               \
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...); \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                      \
+  return result;                                                                               \
+}
+
+DEFINE_PRIMITIVE_CALL(jboolean, Boolean)
+DEFINE_PRIMITIVE_CALL(jbyte, Byte)
+DEFINE_PRIMITIVE_CALL(jchar, Char)
+DEFINE_PRIMITIVE_CALL(jshort, Short)
+DEFINE_PRIMITIVE_CALL(jint, Int)
+DEFINE_PRIMITIVE_CALL(jlong, Long)
+DEFINE_PRIMITIVE_CALL(jfloat, Float)
+DEFINE_PRIMITIVE_CALL(jdouble, Double)
+#pragma pop_macro("DEFINE_PRIMITIVE_CALL")
+
+/// JMethod specialization for references that wraps the return value in a @ref local_ref
+template<typename R, typename... Args>
+class JMethod<R(Args...)> : public JMethodBase {
+ public:
+   // TODO: static_assert is jobject-derived or local_ref jobject
+  using JniRet = typename detail::Convert<typename std::decay<R>::type>::jniType;
+  static_assert(IsPlainJniReference<JniRet>(), "JniRet must be a JNI reference");
+  using JMethodBase::JMethodBase;
+  JMethod() noexcept {};
+  JMethod(const JMethod& other) noexcept = default;
+
+  /// Invoke a method and return a local reference wrapping the result
+  local_ref<JniRet> operator()(alias_ref<jobject> self, Args... args);
+
+  friend class JClass;
+};
+
+template<typename R, typename... Args>
+inline auto JMethod<R(Args...)>::operator()(alias_ref<jobject> self, Args... args) -> local_ref<JniRet> {
+  const auto env = Environment::current();
+  auto result = env->CallObjectMethod(
+      self.get(),
+      getId(),
+      detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  return adopt_local(static_cast<JniRet>(result));
+}
+
+template<typename... Args>
+inline void JStaticMethod<void(Args...)>::operator()(alias_ref<jclass> cls, Args... args) {
+  const auto env = internal::getEnv();
+  env->CallStaticVoidMethod(
+        cls.get(),
+        getId(),
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+}
+
+#pragma push_macro("DEFINE_PRIMITIVE_STATIC_CALL")
+#undef DEFINE_PRIMITIVE_STATIC_CALL
+#define DEFINE_PRIMITIVE_STATIC_CALL(TYPE, METHOD)                                             \
+template<typename... Args>                                                                     \
+inline TYPE JStaticMethod<TYPE(Args...)>::operator()(alias_ref<jclass> cls, Args... args) {    \
+  const auto env = internal::getEnv();                                                         \
+  auto result = env->CallStatic ## METHOD ## Method(                                           \
+        cls.get(),                                                                             \
+        getId(),                                                                               \
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...); \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                      \
+        return result;                                                                         \
+}
+
+DEFINE_PRIMITIVE_STATIC_CALL(jboolean, Boolean)
+DEFINE_PRIMITIVE_STATIC_CALL(jbyte, Byte)
+DEFINE_PRIMITIVE_STATIC_CALL(jchar, Char)
+DEFINE_PRIMITIVE_STATIC_CALL(jshort, Short)
+DEFINE_PRIMITIVE_STATIC_CALL(jint, Int)
+DEFINE_PRIMITIVE_STATIC_CALL(jlong, Long)
+DEFINE_PRIMITIVE_STATIC_CALL(jfloat, Float)
+DEFINE_PRIMITIVE_STATIC_CALL(jdouble, Double)
+#pragma pop_macro("DEFINE_PRIMITIVE_STATIC_CALL")
+
+/// JStaticMethod specialization for references that wraps the return value in a @ref local_ref
+template<typename R, typename... Args>
+class JStaticMethod<R(Args...)> : public JMethodBase {
+
+ public:
+  using JniRet = typename detail::Convert<typename std::decay<R>::type>::jniType;
+  static_assert(IsPlainJniReference<JniRet>(), "T* must be a JNI reference");
+  using JMethodBase::JMethodBase;
+  JStaticMethod() noexcept {};
+  JStaticMethod(const JStaticMethod& other) noexcept = default;
+
+  /// Invoke a method and return a local reference wrapping the result
+  local_ref<JniRet> operator()(alias_ref<jclass> cls, Args... args) {
+    const auto env = internal::getEnv();
+    auto result = env->CallStaticObjectMethod(
+          cls.get(),
+          getId(),
+          detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
+    FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+    return adopt_local(static_cast<JniRet>(result));
+  }
+
+  friend class JClass;
+};
+
+template<typename... Args>
+inline void
+JNonvirtualMethod<void(Args...)>::operator()(alias_ref<jobject> self, alias_ref<jclass> cls, Args... args) {
+  const auto env = internal::getEnv();
+  env->CallNonvirtualVoidMethod(
+        self.get(),
+        cls.get(),
+        getId(),
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+}
+
+#pragma push_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_CALL")
+#undef DEFINE_PRIMITIVE_NON_VIRTUAL_CALL
+#define DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(TYPE, METHOD)                                                      \
+template<typename... Args>                                                                                   \
+inline TYPE                                                                                                  \
+JNonvirtualMethod<TYPE(Args...)>::operator()(alias_ref<jobject> self, alias_ref<jclass> cls, Args... args) { \
+  const auto env = internal::getEnv();                                                                       \
+  auto result = env->CallNonvirtual ## METHOD ## Method(                                                     \
+        self.get(),                                                                                          \
+        cls.get(),                                                                                           \
+        getId(),                                                                                             \
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);               \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                                    \
+  return result;                                                                                             \
+}
+
+DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jboolean, Boolean)
+DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jbyte, Byte)
+DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jchar, Char)
+DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jshort, Short)
+DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jint, Int)
+DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jlong, Long)
+DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jfloat, Float)
+DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jdouble, Double)
+#pragma pop_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_CALL")
+
+/// JNonvirtualMethod specialization for references that wraps the return value in a @ref local_ref
+template<typename R, typename... Args>
+class JNonvirtualMethod<R(Args...)> : public JMethodBase {
+ public:
+  using JniRet = typename detail::Convert<typename std::decay<R>::type>::jniType;
+  static_assert(IsPlainJniReference<JniRet>(), "T* must be a JNI reference");
+  using JMethodBase::JMethodBase;
+  JNonvirtualMethod() noexcept {};
+  JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default;
+
+  /// Invoke a method and return a local reference wrapping the result
+  local_ref<JniRet> operator()(alias_ref<jobject> self, alias_ref<jclass> cls, Args... args){
+    const auto env = internal::getEnv();
+    auto result = env->CallNonvirtualObjectMethod(
+          self.get(),
+          cls.get(),
+          getId(),
+          detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
+    FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+    return adopt_local(static_cast<JniRet>(result));
+  }
+
+  friend class JClass;
+};
+
+template <typename... Args>
+local_ref<jobject> slowCall(jmethodID method_id, alias_ref<jobject> self, Args... args) {
+    static auto invoke = findClassStatic("java/lang/reflect/Method")
+      ->getMethod<jobject(jobject, JArrayClass<jobject>::javaobject)>("invoke");
+    // TODO(xxxxxxx): Provide fbjni interface to ToReflectedMethod.
+    auto reflected = adopt_local(Environment::current()->ToReflectedMethod(self->getClass().get(), method_id, JNI_FALSE));
+    FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+    if (!reflected) throw std::runtime_error("Unable to get reflected java.lang.reflect.Method");
+    auto argsArray = makeArgsArray(args...);
+    // No need to check for exceptions since invoke is itself a JMethod that will do that for us.
+    return invoke(reflected, self.get(), argsArray.get());
+}
+
+
+// JField<T> ///////////////////////////////////////////////////////////////////////////////////////
+
+template<typename T>
+inline JField<T>::JField(jfieldID field) noexcept
+  : field_id_{field}
+{}
+
+template<typename T>
+inline JField<T>::operator bool() const noexcept {
+  return field_id_ != nullptr;
+}
+
+template<typename T>
+inline jfieldID JField<T>::getId() const noexcept {
+  return field_id_;
+}
+
+#pragma push_macro("DEFINE_FIELD_PRIMITIVE_GET_SET")
+#undef DEFINE_FIELD_PRIMITIVE_GET_SET
+#define DEFINE_FIELD_PRIMITIVE_GET_SET(TYPE, METHOD)                 \
+template<>                                                           \
+inline TYPE JField<TYPE>::get(jobject object) const noexcept {       \
+  const auto env = internal::getEnv();                               \
+  return env->Get ## METHOD ## Field(object, field_id_);             \
+}                                                                    \
+                                                                     \
+template<>                                                           \
+inline void JField<TYPE>::set(jobject object, TYPE value) noexcept { \
+  const auto env = internal::getEnv();                               \
+  env->Set ## METHOD ## Field(object, field_id_, value);             \
+}
+
+DEFINE_FIELD_PRIMITIVE_GET_SET(jboolean, Boolean)
+DEFINE_FIELD_PRIMITIVE_GET_SET(jbyte, Byte)
+DEFINE_FIELD_PRIMITIVE_GET_SET(jchar, Char)
+DEFINE_FIELD_PRIMITIVE_GET_SET(jshort, Short)
+DEFINE_FIELD_PRIMITIVE_GET_SET(jint, Int)
+DEFINE_FIELD_PRIMITIVE_GET_SET(jlong, Long)
+DEFINE_FIELD_PRIMITIVE_GET_SET(jfloat, Float)
+DEFINE_FIELD_PRIMITIVE_GET_SET(jdouble, Double)
+#pragma pop_macro("DEFINE_FIELD_PRIMITIVE_GET_SET")
+
+template<typename T>
+inline T JField<T>::get(jobject object) const noexcept {
+  return static_cast<T>(internal::getEnv()->GetObjectField(object, field_id_));
+}
+
+template<typename T>
+inline void JField<T>::set(jobject object, T value) noexcept {
+  internal::getEnv()->SetObjectField(object, field_id_, static_cast<jobject>(value));
+}
+
+// JStaticField<T> /////////////////////////////////////////////////////////////////////////////////
+
+template<typename T>
+inline JStaticField<T>::JStaticField(jfieldID field) noexcept
+  : field_id_{field}
+{}
+
+template<typename T>
+inline JStaticField<T>::operator bool() const noexcept {
+  return field_id_ != nullptr;
+}
+
+template<typename T>
+inline jfieldID JStaticField<T>::getId() const noexcept {
+  return field_id_;
+}
+
+#pragma push_macro("DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET")
+#undef DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET
+#define DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(TYPE, METHOD)                \
+template<>                                                                 \
+inline TYPE JStaticField<TYPE>::get(jclass jcls) const noexcept {          \
+  const auto env = internal::getEnv();                                     \
+  return env->GetStatic ## METHOD ## Field(jcls, field_id_);               \
+}                                                                          \
+                                                                           \
+template<>                                                                 \
+inline void JStaticField<TYPE>::set(jclass jcls, TYPE value) noexcept {    \
+  const auto env = internal::getEnv();                                     \
+  env->SetStatic ## METHOD ## Field(jcls, field_id_, value);               \
+}
+
+DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jboolean, Boolean)
+DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jbyte, Byte)
+DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jchar, Char)
+DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jshort, Short)
+DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jint, Int)
+DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jlong, Long)
+DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jfloat, Float)
+DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jdouble, Double)
+#pragma pop_macro("DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET")
+
+template<typename T>
+inline T JStaticField<T>::get(jclass jcls) const noexcept {
+  const auto env = internal::getEnv();
+  return static_cast<T>(env->GetStaticObjectField(jcls, field_id_));
+}
+
+template<typename T>
+inline void JStaticField<T>::set(jclass jcls, T value) noexcept {
+  internal::getEnv()->SetStaticObjectField(jcls, field_id_, value);
+}
+
+
+// jmethod_traits //////////////////////////////////////////////////////////////////////////////////
+
+// TODO(T6608405) Adapt this to implement a register natives method that requires no descriptor
+namespace internal {
+
+template<typename Head>
+inline std::string JavaDescriptor() {
+  return jtype_traits<Head>::descriptor();
+}
+
+template<typename Head, typename Elem, typename... Tail>
+inline std::string JavaDescriptor() {
+  return JavaDescriptor<Head>() + JavaDescriptor<Elem, Tail...>();
+}
+
+template<typename R, typename Arg1, typename... Args>
+inline std::string JMethodDescriptor() {
+  return "(" + JavaDescriptor<Arg1, Args...>() + ")" + JavaDescriptor<R>();
+}
+
+template<typename R>
+inline std::string JMethodDescriptor() {
+  return "()" + JavaDescriptor<R>();
+}
+
+} // internal
+
+template<typename R, typename... Args>
+inline std::string jmethod_traits<R(Args...)>::descriptor() {
+  return internal::JMethodDescriptor<R, Args...>();
+}
+
+template<typename R, typename... Args>
+inline std::string jmethod_traits<R(Args...)>::constructor_descriptor() {
+  return internal::JMethodDescriptor<void, Args...>();
+}
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Meta.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Meta.h
new file mode 100644
index 000000000..4aefb0ca1
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Meta.h
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/** @file meta.h
+ *
+ * Provides wrappers for meta data such as methods and fields.
+ */
+
+#pragma once
+
+#include <type_traits>
+#include <string>
+
+#include <jni.h>
+
+#include "References-forward.h"
+
+#ifdef __ANDROID__
+# include <android/log.h>
+# define XLOG_TAG "fb-jni"
+# define XLOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, XLOG_TAG, __VA_ARGS__)
+# define XLOGD(...) __android_log_print(ANDROID_LOG_DEBUG, XLOG_TAG, __VA_ARGS__)
+# define XLOGI(...) __android_log_print(ANDROID_LOG_INFO, XLOG_TAG, __VA_ARGS__)
+# define XLOGW(...) __android_log_print(ANDROID_LOG_WARN, XLOG_TAG, __VA_ARGS__)
+# define XLOGE(...) __android_log_print(ANDROID_LOG_ERROR, XLOG_TAG, __VA_ARGS__)
+# define XLOGWTF(...) __android_log_print(ANDROID_LOG_FATAL, XLOG_TAG, __VA_ARGS__)
+#endif
+
+namespace facebook {
+namespace jni {
+
+// This will get the reflected Java Method from the method_id, get it's invoke
+// method, and call the method via that. This shouldn't ever be needed, but
+// Android 6.0 crashes when calling a method on a java.lang.Proxy via jni.
+template <typename... Args>
+local_ref<jobject> slowCall(jmethodID method_id, alias_ref<jobject> self, Args... args);
+
+class JObject;
+
+
+/// Wrapper of a jmethodID. Provides a common base for JMethod specializations
+class JMethodBase {
+ public:
+  /// Verify that the method is valid
+  explicit operator bool() const noexcept;
+
+  /// Access the wrapped id
+  jmethodID getId() const noexcept;
+
+ protected:
+  /// Create a wrapper of a method id
+  explicit JMethodBase(jmethodID method_id = nullptr) noexcept;
+
+ private:
+  jmethodID method_id_;
+};
+
+
+/// Representation of a jmethodID
+template<typename F>
+class JMethod;
+
+/// @cond INTERNAL
+#pragma push_macro("DEFINE_PRIMITIVE_METHOD_CLASS")
+
+#undef DEFINE_PRIMITIVE_METHOD_CLASS
+
+// Defining JMethod specializations based on return value
+#define DEFINE_PRIMITIVE_METHOD_CLASS(TYPE)                                      \
+template<typename... Args>                                                       \
+class JMethod<TYPE(Args...)> : public JMethodBase {                              \
+ public:                                                                         \
+  static_assert(std::is_void<TYPE>::value || IsJniPrimitive<TYPE>(),             \
+      "TYPE must be primitive or void");                                         \
+                                                                                 \
+  using JMethodBase::JMethodBase;                                                \
+  JMethod() noexcept {};                                                         \
+  JMethod(const JMethod& other) noexcept = default;                              \
+                                                                                 \
+  TYPE operator()(alias_ref<jobject> self, Args... args);                        \
+                                                                                 \
+  friend class JClass;                                                           \
+}
+
+DEFINE_PRIMITIVE_METHOD_CLASS(void);
+DEFINE_PRIMITIVE_METHOD_CLASS(jboolean);
+DEFINE_PRIMITIVE_METHOD_CLASS(jbyte);
+DEFINE_PRIMITIVE_METHOD_CLASS(jchar);
+DEFINE_PRIMITIVE_METHOD_CLASS(jshort);
+DEFINE_PRIMITIVE_METHOD_CLASS(jint);
+DEFINE_PRIMITIVE_METHOD_CLASS(jlong);
+DEFINE_PRIMITIVE_METHOD_CLASS(jfloat);
+DEFINE_PRIMITIVE_METHOD_CLASS(jdouble);
+
+#pragma pop_macro("DEFINE_PRIMITIVE_METHOD_CLASS")
+/// @endcond
+
+
+/// Convenience type representing constructors
+/// These should only be used with JClass::getConstructor and JClass::newObject.
+template<typename F>
+struct JConstructor : private JMethod<F> {
+  using JMethod<F>::JMethod;
+ private:
+  JConstructor(const JMethod<F>& other) : JMethod<F>(other.getId()) {}
+  friend class JClass;
+};
+
+/// Representation of a jStaticMethodID
+template<typename F>
+class JStaticMethod;
+
+/// @cond INTERNAL
+#pragma push_macro("DEFINE_PRIMITIVE_STATIC_METHOD_CLASS")
+
+#undef DEFINE_PRIMITIVE_STATIC_METHOD_CLASS
+
+// Defining JStaticMethod specializations based on return value
+#define DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(TYPE)                          \
+template<typename... Args>                                                  \
+class JStaticMethod<TYPE(Args...)> : public JMethodBase {                   \
+  static_assert(std::is_void<TYPE>::value || IsJniPrimitive<TYPE>(),        \
+      "T must be a JNI primitive or void");                                 \
+                                                                            \
+ public:                                                                    \
+  using JMethodBase::JMethodBase;                                           \
+  JStaticMethod() noexcept {};                                              \
+  JStaticMethod(const JStaticMethod& other) noexcept = default;             \
+                                                                            \
+  TYPE operator()(alias_ref<jclass> cls, Args... args);                     \
+                                                                            \
+  friend class JClass;                                                      \
+}
+
+DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(void);
+DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jboolean);
+DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jbyte);
+DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jchar);
+DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jshort);
+DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jint);
+DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jlong);
+DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jfloat);
+DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jdouble);
+
+#pragma pop_macro("DEFINE_PRIMITIVE_STATIC_METHOD_CLASS")
+/// @endcond
+
+
+/// Representation of a jNonvirtualMethodID
+template<typename F>
+class JNonvirtualMethod;
+
+/// @cond INTERNAL
+#pragma push_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS")
+
+#undef DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS
+
+// Defining JNonvirtualMethod specializations based on return value
+#define DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(TYPE)                     \
+template<typename... Args>                                                  \
+class JNonvirtualMethod<TYPE(Args...)> : public JMethodBase {               \
+  static_assert(std::is_void<TYPE>::value || IsJniPrimitive<TYPE>(),        \
+      "T must be a JNI primitive or void");                                 \
+                                                                            \
+ public:                                                                    \
+  using JMethodBase::JMethodBase;                                           \
+  JNonvirtualMethod() noexcept {};                                          \
+  JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default;     \
+                                                                            \
+  TYPE operator()(alias_ref<jobject> self, alias_ref<jclass> cls, Args... args);       \
+                                                                            \
+  friend class JClass;                                                      \
+}
+
+DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(void);
+DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jboolean);
+DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jbyte);
+DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jchar);
+DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jshort);
+DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jint);
+DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jlong);
+DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jfloat);
+DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jdouble);
+
+#pragma pop_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS")
+/// @endcond
+
+
+/**
+ * JField represents typed fields and simplifies their access. Note that object types return
+ * raw pointers which generally should promptly get a wrap_local treatment.
+ */
+template<typename T>
+class JField {
+  static_assert(IsJniScalar<T>(), "T must be a JNI scalar");
+
+ public:
+  /// Wraps an existing field id
+  explicit JField(jfieldID field = nullptr) noexcept;
+
+  /// Verify that the id is valid
+  explicit operator bool() const noexcept;
+
+  /// Access the wrapped id
+  jfieldID getId() const noexcept;
+
+ private:
+  jfieldID field_id_;
+
+  /// Get field value
+  /// @pre object != nullptr
+  T get(jobject object) const noexcept;
+
+  /// Set field value
+  /// @pre object != nullptr
+  void set(jobject object, T value) noexcept;
+
+  friend class JObject;
+};
+
+
+/**
+ * JStaticField represents typed fields and simplifies their access. Note that object types
+ * return raw pointers which generally should promptly get a wrap_local treatment.
+ */
+template<typename T>
+class JStaticField {
+  static_assert(IsJniScalar<T>(), "T must be a JNI scalar");
+
+ public:
+  /// Wraps an existing field id
+  explicit JStaticField(jfieldID field = nullptr) noexcept;
+
+  /// Verify that the id is valid
+  explicit operator bool() const noexcept;
+
+  /// Access the wrapped id
+  jfieldID getId() const noexcept;
+
+ private:
+  jfieldID field_id_;
+
+  /// Get field value
+  /// @pre object != nullptr
+  T get(jclass jcls) const noexcept;
+
+  /// Set field value
+  /// @pre object != nullptr
+  void set(jclass jcls, T value) noexcept;
+
+  friend class JClass;
+  friend class JObject;
+};
+
+
+/// Template magic to provide @ref jmethod_traits
+template<typename R, typename... Args>
+struct jmethod_traits<R(Args...)> {
+  static std::string descriptor();
+  static std::string constructor_descriptor();
+};
+
+
+// jtype_traits ////////////////////////////////////////////////////////////////////////////////////
+
+template<typename T>
+struct jtype_traits {
+private:
+  using Repr = ReprType<T>;
+public:
+  // The jni type signature (described at
+  // http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html).
+  static std::string descriptor() {
+    std::string descriptor;
+    if (Repr::kJavaDescriptor == nullptr) {
+      descriptor = Repr::get_instantiated_java_descriptor();
+    } else {
+      descriptor = Repr::kJavaDescriptor;
+    }
+    return descriptor;
+  }
+
+  // The signature used for class lookups. See
+  // http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#getName().
+  static std::string base_name() {
+    if (Repr::kJavaDescriptor != nullptr) {
+      std::string base_name = Repr::kJavaDescriptor;
+      return base_name.substr(1, base_name.size() - 2);
+    }
+    return Repr::get_instantiated_base_name();
+  }
+};
+
+#pragma push_macro("DEFINE_FIELD_AND_ARRAY_TRAIT")
+#undef DEFINE_FIELD_AND_ARRAY_TRAIT
+
+#define DEFINE_FIELD_AND_ARRAY_TRAIT(TYPE, DSC)                     \
+template<>                                                          \
+struct jtype_traits<TYPE> {                                         \
+  static std::string descriptor() { return std::string{#DSC}; }     \
+  static std::string base_name() { return descriptor(); }           \
+  using array_type = TYPE ## Array;                                 \
+};                                                                  \
+template<>                                                          \
+struct jtype_traits<TYPE ## Array> {                                \
+  static std::string descriptor() { return std::string{"[" #DSC}; } \
+  static std::string base_name() { return descriptor(); }           \
+  using entry_type = TYPE;                                          \
+};
+
+// There is no voidArray, handle that without the macro.
+template<>
+struct jtype_traits<void> {
+  static std::string descriptor() { return std::string{"V"}; };
+};
+
+DEFINE_FIELD_AND_ARRAY_TRAIT(jboolean, Z)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jbyte,    B)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jchar,    C)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jshort,   S)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jint,     I)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jlong,    J)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jfloat,   F)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jdouble,  D)
+
+#pragma pop_macro("DEFINE_FIELD_AND_ARRAY_TRAIT")
+
+
+template <typename T>
+struct jmethod_traits_from_cxx;
+
+}}
+
+#include "Meta-inl.h"
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/MetaConvert.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/MetaConvert.h
new file mode 100644
index 000000000..69d1babb5
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/MetaConvert.h
@@ -0,0 +1,136 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <jni.h>
+
+#include "Common.h"
+#include "References.h"
+
+namespace facebook {
+namespace jni {
+
+namespace detail {
+
+// In order to avoid potentially filling the jni locals table,
+// temporary objects (right now, this is just jstrings) need to be
+// released. This is done by returning a holder which autoconverts to
+// jstring.
+template <typename T>
+inline T callToJni(T&& t) {
+  return t;
+}
+
+template <typename T>
+inline JniType<T> callToJni(local_ref<T>&& sref) {
+  return sref.get();
+}
+
+// Normally, pass through types unmolested.
+template <typename T, typename Enabled = void>
+struct Convert {
+  typedef T jniType;
+  static jniType fromJni(jniType t) {
+    return t;
+  }
+  static jniType toJniRet(jniType t) {
+    return t;
+  }
+  static jniType toCall(jniType t) {
+    return t;
+  }
+};
+
+// This is needed for return conversion
+template <>
+struct Convert<void> {
+  typedef void jniType;
+};
+
+// jboolean is an unsigned char, not a bool. Allow it to work either way.
+template<>
+struct Convert<bool> {
+  typedef jboolean jniType;
+  static bool fromJni(jniType t) {
+    return t;
+  }
+  static jniType toJniRet(bool t) {
+    return t;
+  }
+  static jniType toCall(bool t) {
+    return t;
+  }
+};
+
+// convert to alias_ref<T> from T
+template <typename T>
+struct Convert<alias_ref<T>> {
+  typedef JniType<T> jniType;
+  static alias_ref<jniType> fromJni(jniType t) {
+    return wrap_alias(t);
+  }
+  static jniType toJniRet(alias_ref<jniType> t) {
+    return t.get();
+  }
+  static jniType toCall(alias_ref<jniType> t) {
+    return t.get();
+  }
+};
+
+// convert return from local_ref<T>
+template <typename T>
+struct Convert<local_ref<T>> {
+  typedef JniType<T> jniType;
+  // No automatic synthesis of local_ref
+  static jniType toJniRet(local_ref<jniType> t) {
+    return t.release();
+  }
+  static jniType toCall(local_ref<jniType> t) {
+    return t.get();
+  }
+};
+
+// convert return from global_ref<T>
+template <typename T>
+struct Convert<global_ref<T>> {
+  typedef JniType<T> jniType;
+  // No automatic synthesis of global_ref
+  static jniType toJniRet(global_ref<jniType>&& t) {
+    // If this gets called, ownership the global_ref was passed in here.  (It's
+    // probably a copy of a persistent global_ref made when a function was
+    // declared to return a global_ref, but it could moved out or otherwise not
+    // referenced elsewhere.  Doesn't matter.)  Either way, the only safe way
+    // to return it is to make a local_ref, release it, and return the
+    // underlying local jobject.
+    auto ret = make_local(t);
+    return ret.release();
+  }
+  static jniType toJniRet(const global_ref<jniType>& t) {
+    // If this gets called, the function was declared to return const&.  We
+    // have a ref to a global_ref whose lifetime will exceed this call, so we
+    // can just get the underlying jobject and return it to java without
+    // needing to make a local_ref.
+    return t.get();
+  }
+  static jniType toCall(global_ref<jniType> t) {
+    return t.get();
+  }
+};
+
+template <typename T> struct jni_sig_from_cxx_t;
+template <typename R, typename... Args>
+struct jni_sig_from_cxx_t<R(Args...)> {
+  using JniRet = typename Convert<typename std::decay<R>::type>::jniType;
+  using JniSig = JniRet(typename Convert<typename std::decay<Args>::type>::jniType...);
+};
+
+template <typename T>
+using jni_sig_from_cxx = typename jni_sig_from_cxx_t<T>::JniSig;
+
+} // namespace detail
+
+template <typename R, typename... Args>
+struct jmethod_traits_from_cxx<R(Args...)> : jmethod_traits<detail::jni_sig_from_cxx<R(Args...)>> {
+};
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/NativeRunnable.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/NativeRunnable.h
new file mode 100644
index 000000000..68da6a25f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/NativeRunnable.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include "CoreClasses.h"
+#include "Hybrid.h"
+#include "Registration.h"
+
+#include <functional>
+
+namespace facebook {
+namespace jni {
+
+struct JRunnable : public JavaClass<JRunnable> {
+  static auto constexpr kJavaDescriptor = "Ljava/lang/Runnable;";
+};
+
+struct JNativeRunnable : public HybridClass<JNativeRunnable, JRunnable> {
+ public:
+  static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/NativeRunnable;";
+
+  JNativeRunnable(std::function<void()>&& runnable) : runnable_(std::move(runnable)) {}
+
+  static void OnLoad() {
+    registerHybrid({
+        makeNativeMethod("run", JNativeRunnable::run),
+      });
+  }
+
+  void run() {
+    runnable_();
+  }
+
+ private:
+  std::function<void()> runnable_;
+};
+
+
+} // namespace jni
+} // namespace facebook
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/ReferenceAllocators-inl.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/ReferenceAllocators-inl.h
new file mode 100644
index 000000000..cd1be640d
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/ReferenceAllocators-inl.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <new>
+#include <atomic>
+
+namespace facebook {
+namespace jni {
+
+/// @cond INTERNAL
+namespace internal {
+
+// Statistics mostly provided for test (only updated if FBJNI_DEBUG_REFS is defined)
+struct ReferenceStats {
+  std::atomic_uint locals_created, globals_created, weaks_created,
+                   locals_deleted, globals_deleted, weaks_deleted;
+
+  void reset() noexcept;
+};
+
+extern ReferenceStats g_reference_stats;
+}
+/// @endcond
+
+
+// LocalReferenceAllocator /////////////////////////////////////////////////////////////////////////
+
+inline jobject LocalReferenceAllocator::newReference(jobject original) const {
+  internal::dbglog("Local new: %p", original);
+  #ifdef FBJNI_DEBUG_REFS
+    ++internal::g_reference_stats.locals_created;
+  #endif
+  auto ref = internal::getEnv()->NewLocalRef(original);
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  return ref;
+}
+
+inline void LocalReferenceAllocator::deleteReference(jobject reference) const noexcept {
+  internal::dbglog("Local release: %p", reference);
+
+  if (reference) {
+    #ifdef FBJNI_DEBUG_REFS
+      ++internal::g_reference_stats.locals_deleted;
+    #endif
+    assert(verifyReference(reference));
+    internal::getEnv()->DeleteLocalRef(reference);
+  }
+}
+
+inline bool LocalReferenceAllocator::verifyReference(jobject reference) const noexcept {
+  if (!reference || !internal::doesGetObjectRefTypeWork()) {
+    return true;
+  }
+  return internal::getEnv()->GetObjectRefType(reference) == JNILocalRefType;
+}
+
+
+// GlobalReferenceAllocator ////////////////////////////////////////////////////////////////////////
+
+inline jobject GlobalReferenceAllocator::newReference(jobject original) const {
+  internal::dbglog("Global new: %p", original);
+  #ifdef FBJNI_DEBUG_REFS
+    ++internal::g_reference_stats.globals_created;
+  #endif
+  auto ref = internal::getEnv()->NewGlobalRef(original);
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  return ref;
+}
+
+inline void GlobalReferenceAllocator::deleteReference(jobject reference) const noexcept {
+  internal::dbglog("Global release: %p", reference);
+
+  if (reference) {
+    #ifdef FBJNI_DEBUG_REFS
+      ++internal::g_reference_stats.globals_deleted;
+    #endif
+    assert(verifyReference(reference));
+    internal::getEnv()->DeleteGlobalRef(reference);
+  }
+}
+
+inline bool GlobalReferenceAllocator::verifyReference(jobject reference) const noexcept {
+  if (!reference || !internal::doesGetObjectRefTypeWork()) {
+    return true;
+  }
+  return internal::getEnv()->GetObjectRefType(reference) == JNIGlobalRefType;
+}
+
+
+// WeakGlobalReferenceAllocator ////////////////////////////////////////////////////////////////////
+
+inline jobject WeakGlobalReferenceAllocator::newReference(jobject original) const {
+  internal::dbglog("Weak global new: %p", original);
+  #ifdef FBJNI_DEBUG_REFS
+    ++internal::g_reference_stats.weaks_created;
+  #endif
+  auto ref = internal::getEnv()->NewWeakGlobalRef(original);
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  return ref;
+}
+
+inline void WeakGlobalReferenceAllocator::deleteReference(jobject reference) const noexcept {
+  internal::dbglog("Weak Global release: %p", reference);
+
+  if (reference) {
+    #ifdef FBJNI_DEBUG_REFS
+      ++internal::g_reference_stats.weaks_deleted;
+    #endif
+    assert(verifyReference(reference));
+    internal::getEnv()->DeleteWeakGlobalRef(reference);
+  }
+}
+
+inline bool WeakGlobalReferenceAllocator::verifyReference(jobject reference) const noexcept {
+  if (!reference || !internal::doesGetObjectRefTypeWork()) {
+    return true;
+  }
+  return internal::getEnv()->GetObjectRefType(reference) == JNIWeakGlobalRefType;
+}
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/ReferenceAllocators.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/ReferenceAllocators.h
new file mode 100644
index 000000000..024ba797f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/ReferenceAllocators.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+/**
+ * @file ReferenceAllocators.h
+ *
+ * Reference allocators are used to create and delete various classes of JNI references (local,
+ * global, and weak global).
+ */
+
+#pragma once
+
+#include <fb/visibility.h>
+
+#include "Common.h"
+
+namespace facebook { namespace jni {
+
+/// Allocator that handles local references
+class FBEXPORT LocalReferenceAllocator {
+ public:
+  jobject newReference(jobject original) const;
+  void deleteReference(jobject reference) const noexcept;
+  bool verifyReference(jobject reference) const noexcept;
+};
+
+/// Allocator that handles global references
+class FBEXPORT GlobalReferenceAllocator {
+ public:
+  jobject newReference(jobject original) const;
+  void deleteReference(jobject reference) const noexcept;
+  bool verifyReference(jobject reference) const noexcept;
+};
+
+/// Allocator that handles weak global references
+class FBEXPORT WeakGlobalReferenceAllocator {
+ public:
+  jobject newReference(jobject original) const;
+  void deleteReference(jobject reference) const noexcept;
+  bool verifyReference(jobject reference) const noexcept;
+};
+
+/// @cond INTERNAL
+namespace internal {
+
+/**
+ * @return true iff env->GetObjectRefType is expected to work properly.
+ */
+FBEXPORT bool doesGetObjectRefTypeWork();
+
+}
+/// @endcond
+
+}}
+
+#include "ReferenceAllocators-inl.h"
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/References-forward.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/References-forward.h
new file mode 100644
index 000000000..8dabf67cb
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/References-forward.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include "ReferenceAllocators.h"
+
+namespace facebook {
+namespace jni {
+
+template<typename T, typename Enable = void>
+class JObjectWrapper;
+
+namespace detail {
+struct JObjectBase {
+  jobject get() const noexcept;
+  void set(jobject reference) noexcept;
+  jobject this_;
+};
+
+// RefReprType maps a type to the representation used by fbjni smart references.
+template <typename T, typename Enable = void>
+struct RefReprType;
+
+template <typename T>
+struct JavaObjectType;
+
+template <typename T>
+struct ReprAccess;
+}
+
+// Given T, either a jobject-like type or a JavaClass-derived type, ReprType<T>
+// is the corresponding JavaClass-derived type and JniType<T> is the
+// jobject-like type.
+template <typename T>
+using ReprType = typename detail::RefReprType<T>::type;
+
+template <typename T>
+using JniType = typename detail::JavaObjectType<T>::type;
+
+template<typename T, typename Alloc>
+class base_owned_ref;
+
+template<typename T, typename Alloc>
+class basic_strong_ref;
+
+template<typename T>
+class weak_ref;
+
+template<typename T>
+class alias_ref;
+
+/// A smart unique reference owning a local JNI reference
+template<typename T>
+using local_ref = basic_strong_ref<T, LocalReferenceAllocator>;
+
+/// A smart unique reference owning a global JNI reference
+template<typename T>
+using global_ref = basic_strong_ref<T, GlobalReferenceAllocator>;
+
+}} // namespace facebook::jni
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/References-inl.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/References-inl.h
new file mode 100644
index 000000000..74a01e184
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/References-inl.h
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <new>
+#include "CoreClasses.h"
+
+namespace facebook {
+namespace jni {
+
+template<typename T>
+inline enable_if_t<IsPlainJniReference<T>(), T> getPlainJniReference(T ref) {
+  return ref;
+}
+
+template<typename T>
+inline JniType<T> getPlainJniReference(alias_ref<T> ref) {
+  return ref.get();
+}
+
+template<typename T, typename A>
+inline JniType<T> getPlainJniReference(const base_owned_ref<T, A>& ref) {
+  return ref.get();
+}
+
+
+namespace detail {
+template <typename Repr>
+struct ReprAccess {
+  using javaobject = JniType<Repr>;
+  static void set(Repr& repr, javaobject obj) noexcept {
+    repr.JObjectBase::set(obj);
+  }
+  static javaobject get(const Repr& repr) {
+    return static_cast<javaobject>(repr.JObject::get());
+  }
+};
+
+namespace {
+template <typename Repr>
+void StaticAssertValidRepr() noexcept {
+  static_assert(std::is_base_of<JObject, Repr>::value,
+      "A smart ref representation must be derived from JObject.");
+  static_assert(IsPlainJniReference<JniType<Repr>>(), "T must be a JNI reference");
+  static_assert(sizeof(Repr) == sizeof(JObjectBase), "");
+  static_assert(alignof(Repr) == alignof(JObjectBase), "");
+}
+}
+
+template <typename Repr>
+ReprStorage<Repr>::ReprStorage(JniType<Repr> obj) noexcept {
+  StaticAssertValidRepr<Repr>();
+  set(obj);
+}
+
+template <typename Repr>
+void ReprStorage<Repr>::set(JniType<Repr> obj) noexcept {
+  new (&storage_) Repr;
+  ReprAccess<Repr>::set(get(), obj);
+}
+
+template <typename Repr>
+Repr& ReprStorage<Repr>::get() noexcept {
+  return *reinterpret_cast<Repr*>(&storage_);
+}
+
+template <typename Repr>
+const Repr& ReprStorage<Repr>::get() const noexcept {
+  return *reinterpret_cast<const Repr*>(&storage_);
+}
+
+template <typename Repr>
+JniType<Repr> ReprStorage<Repr>::jobj() const noexcept {
+  ReprAccess<Repr>::get(get());
+  return ReprAccess<Repr>::get(get());
+}
+
+template <typename Repr>
+void ReprStorage<Repr>::swap(ReprStorage& other) noexcept {
+  StaticAssertValidRepr<Repr>();
+  using std::swap;
+  swap(get(), other.get());
+}
+
+inline void JObjectBase::set(jobject reference) noexcept {
+  this_ = reference;
+}
+
+inline jobject JObjectBase::get() const noexcept {
+  return this_;
+}
+
+template<typename T, typename Alloc>
+enable_if_t<IsNonWeakReference<T>(), plain_jni_reference_t<T>> make_ref(const T& reference) {
+  auto old_reference = getPlainJniReference(reference);
+  if (!old_reference) {
+    return nullptr;
+  }
+
+  auto ref = Alloc{}.newReference(old_reference);
+  if (!ref) {
+    // Note that we end up here if we pass a weak ref that refers to a collected object.
+    // Thus, it's hard to come up with a reason why this function should be used with
+    // weak references.
+    throw std::bad_alloc{};
+  }
+
+  return static_cast<plain_jni_reference_t<T>>(ref);
+}
+
+} // namespace detail
+
+template<typename T>
+inline local_ref<T> adopt_local(T ref) noexcept {
+  static_assert(IsPlainJniReference<T>(), "T must be a plain jni reference");
+  return local_ref<T>{ref};
+}
+
+template<typename T>
+inline global_ref<T> adopt_global(T ref) noexcept {
+  static_assert(IsPlainJniReference<T>(), "T must be a plain jni reference");
+  return global_ref<T>{ref};
+}
+
+template<typename T>
+inline weak_ref<T> adopt_weak_global(T ref) noexcept {
+  static_assert(IsPlainJniReference<T>(), "T must be a plain jni reference");
+  return weak_ref<T>{ref};
+}
+
+
+template<typename T>
+inline enable_if_t<IsPlainJniReference<T>(), alias_ref<T>> wrap_alias(T ref) noexcept {
+  return alias_ref<T>(ref);
+}
+
+
+template<typename T>
+enable_if_t<IsPlainJniReference<T>(), alias_ref<T>> wrap_alias(T ref) noexcept;
+
+
+template<typename T>
+enable_if_t<IsNonWeakReference<T>(), local_ref<plain_jni_reference_t<T>>>
+make_local(const T& ref) {
+  return adopt_local(detail::make_ref<T, LocalReferenceAllocator>(ref));
+}
+
+template<typename T>
+enable_if_t<IsNonWeakReference<T>(), global_ref<plain_jni_reference_t<T>>>
+make_global(const T& ref) {
+  return adopt_global(detail::make_ref<T, GlobalReferenceAllocator>(ref));
+}
+
+template<typename T>
+enable_if_t<IsNonWeakReference<T>(), weak_ref<plain_jni_reference_t<T>>>
+make_weak(const T& ref) {
+  return adopt_weak_global(detail::make_ref<T, WeakGlobalReferenceAllocator>(ref));
+}
+
+template<typename T1, typename T2>
+inline enable_if_t<IsNonWeakReference<T1>() && IsNonWeakReference<T2>(), bool>
+operator==(const T1& a, const T2& b) {
+  return isSameObject(getPlainJniReference(a), getPlainJniReference(b));
+}
+
+template<typename T1, typename T2>
+inline enable_if_t<IsNonWeakReference<T1>() && IsNonWeakReference<T2>(), bool>
+operator!=(const T1& a, const T2& b) {
+  return !(a == b);
+}
+
+
+// base_owned_ref ///////////////////////////////////////////////////////////////////////
+
+template<typename T, typename Alloc>
+inline base_owned_ref<T, Alloc>::base_owned_ref() noexcept
+  : base_owned_ref(nullptr)
+{}
+
+template<typename T, typename Alloc>
+inline base_owned_ref<T, Alloc>::base_owned_ref(std::nullptr_t t) noexcept
+  : base_owned_ref(static_cast<javaobject>(nullptr))
+{}
+
+template<typename T, typename Alloc>
+inline base_owned_ref<T, Alloc>::base_owned_ref(const base_owned_ref& other)
+  : storage_{static_cast<javaobject>(Alloc{}.newReference(other.get()))}
+{}
+
+template<typename T, typename Alloc>
+template<typename U>
+inline base_owned_ref<T, Alloc>::base_owned_ref(const base_owned_ref<U, Alloc>& other)
+  : storage_{static_cast<javaobject>(Alloc{}.newReference(other.get()))}
+{
+  static_assert(std::is_convertible<JniType<U>, javaobject>::value, "");
+}
+
+template<typename T, typename Alloc>
+inline facebook::jni::base_owned_ref<T, Alloc>::base_owned_ref(
+    javaobject reference) noexcept
+  : storage_(reference) {
+  assert(Alloc{}.verifyReference(reference));
+  internal::dbglog("New wrapped ref=%p this=%p", get(), this);
+}
+
+template<typename T, typename Alloc>
+inline base_owned_ref<T, Alloc>::base_owned_ref(
+    base_owned_ref<T, Alloc>&& other) noexcept
+  : storage_(other.get()) {
+  internal::dbglog("New move from ref=%p other=%p", other.get(), &other);
+  internal::dbglog("New move to ref=%p this=%p", get(), this);
+  // JObject is a simple type and does not support move semantics so we explicitly
+  // clear other
+  other.set(nullptr);
+}
+
+template<typename T, typename Alloc>
+template<typename U>
+base_owned_ref<T, Alloc>::base_owned_ref(base_owned_ref<U, Alloc>&& other) noexcept
+  : storage_(other.get()) {
+  internal::dbglog("New move from ref=%p other=%p", other.get(), &other);
+  internal::dbglog("New move to ref=%p this=%p", get(), this);
+  // JObject is a simple type and does not support move semantics so we explicitly
+  // clear other
+  other.set(nullptr);
+}
+
+template<typename T, typename Alloc>
+inline base_owned_ref<T, Alloc>::~base_owned_ref() noexcept {
+  reset();
+  internal::dbglog("Ref destruct ref=%p this=%p", get(), this);
+}
+
+template<typename T, typename Alloc>
+inline auto base_owned_ref<T, Alloc>::release() noexcept -> javaobject {
+  auto value = get();
+  internal::dbglog("Ref release ref=%p this=%p", value, this);
+  set(nullptr);
+  return value;
+}
+
+template<typename T, typename Alloc>
+inline void base_owned_ref<T,Alloc>::reset() noexcept {
+  reset(nullptr);
+}
+
+template<typename T, typename Alloc>
+inline void base_owned_ref<T,Alloc>::reset(javaobject reference) noexcept {
+  if (get()) {
+    assert(Alloc{}.verifyReference(reference));
+    Alloc{}.deleteReference(get());
+  }
+  set(reference);
+}
+
+template<typename T, typename Alloc>
+inline auto base_owned_ref<T, Alloc>::get() const noexcept -> javaobject {
+  return storage_.jobj();
+}
+
+template<typename T, typename Alloc>
+inline void base_owned_ref<T, Alloc>::set(javaobject ref) noexcept {
+  storage_.set(ref);
+}
+
+
+// weak_ref ///////////////////////////////////////////////////////////////////////
+
+template<typename T>
+inline weak_ref<T>& weak_ref<T>::operator=(
+    const weak_ref& other) {
+  auto otherCopy = other;
+  swap(*this, otherCopy);
+  return *this;
+}
+
+template<typename T>
+inline weak_ref<T>& weak_ref<T>::operator=(
+    weak_ref<T>&& other) noexcept {
+  internal::dbglog("Op= move ref=%p this=%p oref=%p other=%p",
+      get(), this, other.get(), &other);
+  reset(other.release());
+  return *this;
+}
+
+template<typename T>
+local_ref<T> weak_ref<T>::lockLocal() const {
+  return adopt_local(
+      static_cast<javaobject>(LocalReferenceAllocator{}.newReference(get())));
+}
+
+template<typename T>
+global_ref<T> weak_ref<T>::lockGlobal() const {
+  return adopt_global(
+      static_cast<javaobject>(GlobalReferenceAllocator{}.newReference(get())));
+}
+
+template<typename T>
+inline void swap(
+    weak_ref<T>& a,
+    weak_ref<T>& b) noexcept {
+  internal::dbglog("Ref swap a.ref=%p a=%p b.ref=%p b=%p",
+      a.get(), &a, b.get(), &b);
+  a.storage_.swap(b.storage_);
+}
+
+
+// basic_strong_ref ////////////////////////////////////////////////////////////////////////////
+
+template<typename T, typename Alloc>
+inline basic_strong_ref<T, Alloc>& basic_strong_ref<T, Alloc>::operator=(
+    const basic_strong_ref& other) {
+  auto otherCopy = other;
+  swap(*this, otherCopy);
+  return *this;
+}
+
+template<typename T, typename Alloc>
+inline basic_strong_ref<T, Alloc>& basic_strong_ref<T, Alloc>::operator=(
+    basic_strong_ref<T, Alloc>&& other) noexcept {
+  internal::dbglog("Op= move ref=%p this=%p oref=%p other=%p",
+      get(), this, other.get(), &other);
+  reset(other.release());
+  return *this;
+}
+
+template<typename T, typename Alloc>
+inline alias_ref<T> basic_strong_ref<T, Alloc>::releaseAlias() noexcept {
+  return wrap_alias(release());
+}
+
+template<typename T, typename Alloc>
+inline basic_strong_ref<T, Alloc>::operator bool() const noexcept {
+  return get() != nullptr;
+}
+
+template<typename T, typename Alloc>
+inline auto basic_strong_ref<T, Alloc>::operator->() noexcept -> Repr* {
+  return &storage_.get();
+}
+
+template<typename T, typename Alloc>
+inline auto basic_strong_ref<T, Alloc>::operator->() const noexcept -> const Repr* {
+  return &storage_.get();
+}
+
+template<typename T, typename Alloc>
+inline auto basic_strong_ref<T, Alloc>::operator*() noexcept -> Repr& {
+  return storage_.get();
+}
+
+template<typename T, typename Alloc>
+inline auto basic_strong_ref<T, Alloc>::operator*() const noexcept -> const Repr& {
+  return storage_.get();
+}
+
+template<typename T, typename Alloc>
+inline void swap(
+    basic_strong_ref<T, Alloc>& a,
+    basic_strong_ref<T, Alloc>& b) noexcept {
+  internal::dbglog("Ref swap a.ref=%p a=%p b.ref=%p b=%p",
+      a.get(), &a, b.get(), &b);
+  using std::swap;
+  a.storage_.swap(b.storage_);
+}
+
+
+// alias_ref //////////////////////////////////////////////////////////////////////////////
+
+template<typename T>
+inline alias_ref<T>::alias_ref() noexcept
+  : storage_{nullptr}
+{}
+
+template<typename T>
+inline alias_ref<T>::alias_ref(std::nullptr_t) noexcept
+  : storage_{nullptr}
+{}
+
+template<typename T>
+inline alias_ref<T>::alias_ref(const alias_ref& other) noexcept
+  : storage_{other.get()}
+{}
+
+template<typename T>
+inline alias_ref<T>::alias_ref(javaobject ref) noexcept
+  : storage_(ref) {
+  assert(
+      LocalReferenceAllocator{}.verifyReference(ref) ||
+      GlobalReferenceAllocator{}.verifyReference(ref));
+}
+
+template<typename T>
+template<typename TOther, typename /* for SFINAE */>
+inline alias_ref<T>::alias_ref(alias_ref<TOther> other) noexcept
+  : storage_{other.get()}
+{}
+
+template<typename T>
+template<typename TOther, typename AOther, typename /* for SFINAE */>
+inline alias_ref<T>::alias_ref(const basic_strong_ref<TOther, AOther>& other) noexcept
+  : storage_{other.get()}
+{}
+
+template<typename T>
+inline alias_ref<T>& alias_ref<T>::operator=(alias_ref other) noexcept {
+  swap(*this, other);
+  return *this;
+}
+
+template<typename T>
+inline alias_ref<T>::operator bool() const noexcept {
+  return get() != nullptr;
+}
+
+template<typename T>
+inline auto facebook::jni::alias_ref<T>::get() const noexcept -> javaobject {
+  return storage_.jobj();
+}
+
+template<typename T>
+inline auto alias_ref<T>::operator->() noexcept -> Repr* {
+  return &(**this);
+}
+
+template<typename T>
+inline auto alias_ref<T>::operator->() const noexcept -> const Repr* {
+  return &(**this);
+}
+
+template<typename T>
+inline auto alias_ref<T>::operator*() noexcept -> Repr& {
+  return storage_.get();
+}
+
+template<typename T>
+inline auto alias_ref<T>::operator*() const noexcept -> const Repr& {
+  return storage_.get();
+}
+
+template<typename T>
+inline void alias_ref<T>::set(javaobject ref) noexcept {
+  storage_.set(ref);
+}
+
+template<typename T>
+inline void swap(alias_ref<T>& a, alias_ref<T>& b) noexcept {
+  a.storage_.swap(b.storage_);
+}
+
+// Could reduce code duplication by using a pointer-to-function
+// template argument.  I'm not sure whether that would make the code
+// more maintainable (DRY), or less (too clever/confusing.).
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), local_ref<T>>
+static_ref_cast(const local_ref<U>& ref) noexcept
+{
+  T p = static_cast<T>(ref.get());
+  return make_local(p);
+}
+
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), global_ref<T>>
+static_ref_cast(const global_ref<U>& ref) noexcept
+{
+  T p = static_cast<T>(ref.get());
+  return make_global(p);
+}
+
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), alias_ref<T>>
+static_ref_cast(const alias_ref<U>& ref) noexcept
+{
+  T p = static_cast<T>(ref.get());
+  return wrap_alias(p);
+}
+
+template<typename T, typename RefType>
+auto dynamic_ref_cast(const RefType& ref) ->
+enable_if_t<IsPlainJniReference<T>(), decltype(static_ref_cast<T>(ref))>
+{
+  if (!ref) {
+    return decltype(static_ref_cast<T>(ref))();
+  }
+
+  static alias_ref<jclass> target_class = findClassStatic(jtype_traits<T>::base_name().c_str());
+  if (!target_class) {
+    throwNewJavaException("java/lang/ClassCastException",
+                          "Could not find class %s.",
+                          jtype_traits<T>::base_name().c_str());
+
+  }
+
+  local_ref<jclass> source_class = ref->getClass();
+
+  if (!target_class->isAssignableFrom(source_class)) {
+    throwNewJavaException("java/lang/ClassCastException",
+                          "Tried to cast from %s to %s.",
+                          source_class->toString().c_str(),
+                          jtype_traits<T>::base_name().c_str());
+  }
+
+  return static_ref_cast<T>(ref);
+}
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/References.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/References.h
new file mode 100644
index 000000000..796b80296
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/References.h
@@ -0,0 +1,588 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+
+/** @file References.h
+ *
+ * Functionality similar to smart pointers, but for references into the VM. Four main reference
+ * types are provided: local_ref, global_ref, weak_ref, and alias_ref. All are generic
+ * templates that and refer to objects in the jobject hierarchy. The type of the referred objects
+ * are specified using the template parameter. All reference types except alias_ref own their
+ * underlying reference, just as a std smart pointer owns the underlying raw pointer. In the context
+ * of std smart pointers, these references behave like unique_ptr, and have basically the same
+ * interface. Thus, when the reference is destructed, the plain JNI reference, i.e. the underlying
+ * JNI reference (like the parameters passed directly to JNI functions), is released. The alias
+ * references provides no ownership and is a simple wrapper for plain JNI references.
+ *
+ * All but the weak references provides access to the underlying object using dereferencing, and a
+ * get() method. It is also possible to convert these references to booleans to test for nullity.
+ * To access the underlying object of a weak reference, the reference must either be released, or
+ * the weak reference can be used to create a local or global reference.
+ *
+ * An owning reference is created either by moving the reference from an existing owned reference,
+ * by copying an existing owned reference (which creates a new underlying reference), by using the
+ * default constructor which initialize the reference to nullptr, or by using a helper function. The
+ * helper function exist in two flavors: make_XXX or adopt_XXX.
+ *
+ * Adopting takes a plain JNI reference and wrap it in an owned reference. It takes ownership of the
+ * plain JNI reference so be sure that no one else owns the reference when you adopt it, and make
+ * sure that you know what kind of reference it is.
+ *
+ * New owned references can be created from existing plain JNI references, alias references, local
+ * references, and global references (i.e. non-weak references) using the make_local, make_global,
+ * and make_weak functions.
+ *
+ * Alias references can be implicitly initialized using global, local and plain JNI references using
+ * the wrap_alias function. Here, we don't assume ownership of the passed-in reference, but rather
+ * create a separate reference that we do own, leaving the passed-in reference to its fate.
+ *
+ * Similar rules apply for assignment. An owned reference can be copy or move assigned using a smart
+ * reference of the same type. In the case of copy assignment a new reference is created. Alias
+ * reference can also be assigned new values, but since they are simple wrappers of plain JNI
+ * references there is no move semantics involved.
+ *
+ * Alias references are special in that they do not own the object and can therefore safely be
+ * converted to and from its corresponding plain JNI reference. They are useful as parameters of
+ * functions that do not affect the lifetime of a reference. Usage can be compared with using plain
+ * JNI pointers as parameters where a function does not take ownership of the underlying object.
+ *
+ * The local, global, and alias references makes it possible to access methods in the underlying
+ * objects. A core set of classes are implemented in CoreClasses.h, and user defined wrappers are
+ * supported (see example below). The wrappers also supports inheritance so a wrapper can inherit
+ * from another wrapper to gain access to its functionality. As an example the jstring wrapper
+ * inherits from the jobject wrapper, so does the jclass wrapper. That means that you can for
+ * example call the toString() method using the jclass wrapper, or any other class that inherits
+ * from the jobject wrapper.
+ *
+ * Note that the wrappers are parameterized on the static type of your (jobject) pointer, thus if
+ * you have a jobject that refers to a Java String you will need to cast it to jstring to get the
+ * jstring wrapper. This also mean that if you make a down cast that is invalid there will be no one
+ * stopping you and the wrappers currently does not detect this which can cause crashes. Thus, cast
+ * wisely.
+ *
+ * @include WrapperSample.cpp
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <type_traits>
+
+#include <jni.h>
+
+#include <fb/visibility.h>
+
+#include "ReferenceAllocators.h"
+#include "TypeTraits.h"
+#include "References-forward.h"
+
+namespace facebook {
+namespace jni {
+
+/// Convenience function to wrap an existing local reference
+template<typename T>
+local_ref<T> adopt_local(T ref) noexcept;
+
+/// Convenience function to wrap an existing global reference
+template<typename T>
+global_ref<T> adopt_global(T ref) noexcept;
+
+/// Convenience function to wrap an existing weak reference
+template<typename T>
+weak_ref<T> adopt_weak_global(T ref) noexcept;
+
+
+/// Swaps two owning references of the same type
+template<typename T>
+void swap(weak_ref<T>& a, weak_ref<T>& b) noexcept;
+
+/// Swaps two owning references of the same type
+template<typename T, typename Alloc>
+void swap(basic_strong_ref<T, Alloc>& a, basic_strong_ref<T, Alloc>& b) noexcept;
+
+/**
+ * Retrieve the plain reference from a plain reference.
+ */
+template<typename T>
+enable_if_t<IsPlainJniReference<T>(), T> getPlainJniReference(T ref);
+
+/**
+ * Retrieve the plain reference from an alias reference.
+ */
+template<typename T>
+JniType<T> getPlainJniReference(alias_ref<T> ref);
+
+/**
+ * Retrieve the plain JNI reference from any reference owned reference.
+ */
+template<typename T, typename Alloc>
+JniType<T> getPlainJniReference(const base_owned_ref<T, Alloc>& ref);
+
+class JObject;
+class JClass;
+
+namespace detail {
+
+template <typename T, typename Enable = void>
+struct HasJniRefRepr : std::false_type {};
+
+template <typename T>
+struct HasJniRefRepr<T, typename std::enable_if<!std::is_same<typename T::JniRefRepr, void>::value, void>::type> : std::true_type {
+  using type = typename T::JniRefRepr;
+};
+
+template <typename T>
+struct RefReprType<T*> {
+  using type = typename std::conditional<HasJniRefRepr<T>::value, typename HasJniRefRepr<T>::type, JObjectWrapper<T*>>::type;
+  static_assert(std::is_base_of<JObject, type>::value,
+      "Repr type missing JObject base.");
+  static_assert(std::is_same<type, typename RefReprType<type>::type>::value,
+      "RefReprType<T> not idempotent");
+};
+
+template <typename T>
+struct RefReprType<T, typename std::enable_if<std::is_base_of<JObject, T>::value, void>::type> {
+  using type = T;
+  static_assert(std::is_base_of<JObject, type>::value,
+      "Repr type missing JObject base.");
+  static_assert(std::is_same<type, typename RefReprType<type>::type>::value,
+      "RefReprType<T> not idempotent");
+};
+
+template <typename T>
+struct JavaObjectType {
+  using type = typename RefReprType<T>::type::javaobject;
+  static_assert(IsPlainJniReference<type>(),
+      "JavaObjectType<T> not a plain jni reference");
+  static_assert(std::is_same<type, typename JavaObjectType<type>::type>::value,
+      "JavaObjectType<T> not idempotent");
+};
+
+template <typename T>
+struct JavaObjectType<JObjectWrapper<T>> {
+  using type = T;
+  static_assert(IsPlainJniReference<type>(),
+      "JavaObjectType<T> not a plain jni reference");
+  static_assert(std::is_same<type, typename JavaObjectType<type>::type>::value,
+      "JavaObjectType<T> not idempotent");
+};
+
+template <typename T>
+struct JavaObjectType<T*> {
+  using type = T*;
+  static_assert(IsPlainJniReference<type>(),
+      "JavaObjectType<T> not a plain jni reference");
+  static_assert(std::is_same<type, typename JavaObjectType<type>::type>::value,
+      "JavaObjectType<T> not idempotent");
+};
+
+template <typename Repr>
+struct ReprStorage {
+  explicit ReprStorage(JniType<Repr> obj) noexcept;
+
+  void set(JniType<Repr> obj) noexcept;
+
+  Repr& get() noexcept;
+  const Repr& get() const noexcept;
+  JniType<Repr> jobj() const noexcept;
+
+  void swap(ReprStorage& other) noexcept;
+ private:
+  ReprStorage() = delete;
+  ReprStorage(const ReprStorage&) = delete;
+  ReprStorage(ReprStorage&&) = delete;
+  ReprStorage& operator=(const ReprStorage&) = delete;
+  ReprStorage& operator=(ReprStorage&&) = delete;
+
+  using Storage = typename std::aligned_storage<sizeof(JObjectBase), alignof(JObjectBase)>::type;
+  Storage storage_;
+};
+
+} // namespace detail
+
+/**
+ * Create a new local reference from an existing reference
+ *
+ * @param ref a plain JNI, alias, or strong reference
+ * @return an owned local reference (referring to null if the input does)
+ * @throws std::bad_alloc if the JNI reference could not be created
+ */
+template<typename T>
+enable_if_t<IsNonWeakReference<T>(), local_ref<plain_jni_reference_t<T>>>
+make_local(const T& r);
+
+/**
+ * Create a new global reference from an existing reference
+ *
+ * @param ref a plain JNI, alias, or strong reference
+ * @return an owned global reference (referring to null if the input does)
+ * @throws std::bad_alloc if the JNI reference could not be created
+ */
+template<typename T>
+enable_if_t<IsNonWeakReference<T>(), global_ref<plain_jni_reference_t<T>>>
+make_global(const T& r);
+
+/**
+ * Create a new weak global reference from an existing reference
+ *
+ * @param ref a plain JNI, alias, or strong reference
+ * @return an owned weak global reference (referring to null if the input does)
+ * @throws std::bad_alloc if the returned reference is null
+ */
+template<typename T>
+enable_if_t<IsNonWeakReference<T>(), weak_ref<plain_jni_reference_t<T>>>
+make_weak(const T& r);
+
+/**
+ * Compare two references to see if they refer to the same object
+ */
+template<typename T1, typename T2>
+enable_if_t<IsNonWeakReference<T1>() && IsNonWeakReference<T2>(), bool>
+operator==(const T1& a, const T2& b);
+
+/**
+ * Compare two references to see if they don't refer to the same object
+ */
+template<typename T1, typename T2>
+enable_if_t<IsNonWeakReference<T1>() && IsNonWeakReference<T2>(), bool>
+operator!=(const T1& a, const T2& b);
+
+template<typename T, typename Alloc>
+class base_owned_ref {
+ public:
+  using javaobject = JniType<T>;
+
+  /**
+   * Release the ownership and set the reference to null. Thus no deleter is invoked.
+   * @return Returns the reference
+   */
+  javaobject release() noexcept;
+
+  /**
+   * Reset the reference to refer to nullptr.
+   */
+  void reset() noexcept;
+
+ protected:
+  using Repr = ReprType<T>;
+  detail::ReprStorage<Repr> storage_;
+
+  javaobject get() const noexcept;
+  void set(javaobject ref) noexcept;
+
+  /*
+   * Wrap an existing reference and transfers its ownership to the newly created unique reference.
+   * NB! Does not create a new reference
+   */
+  explicit base_owned_ref(javaobject reference) noexcept;
+
+  /// Create a null reference
+  base_owned_ref() noexcept;
+
+  /// Create a null reference
+  explicit base_owned_ref(std::nullptr_t) noexcept;
+
+  /// Copy constructor (note creates a new reference)
+  base_owned_ref(const base_owned_ref& other);
+  template<typename U>
+  base_owned_ref(const base_owned_ref<U, Alloc>& other);
+
+  /// Transfers ownership of an underlying reference from one unique reference to another
+  base_owned_ref(base_owned_ref&& other) noexcept;
+  template<typename U>
+  base_owned_ref(base_owned_ref<U, Alloc>&& other) noexcept;
+
+  /// The delete the underlying reference if applicable
+  ~base_owned_ref() noexcept;
+
+
+  /// Assignment operator (note creates a new reference)
+  base_owned_ref& operator=(const base_owned_ref& other);
+
+  /// Assignment by moving a reference thus not creating a new reference
+  base_owned_ref& operator=(base_owned_ref&& rhs) noexcept;
+
+  void reset(javaobject reference) noexcept;
+
+  friend javaobject jni::getPlainJniReference<>(const base_owned_ref& ref);
+
+  template<typename U, typename UAlloc>
+  friend class base_owned_ref;
+};
+
+
+/**
+ * A smart reference that owns its underlying JNI reference. The class provides basic
+ * functionality to handle a reference but gives no access to it unless the reference is
+ * released, thus no longer owned. The API is stolen with pride from unique_ptr and the
+ * semantics should be basically the same. This class should not be used directly, instead use
+ * @ref weak_ref
+ */
+template<typename T>
+class weak_ref : public base_owned_ref<T, WeakGlobalReferenceAllocator> {
+ public:
+  using javaobject = JniType<T>;
+
+  using Allocator = WeakGlobalReferenceAllocator;
+
+  // This inherits non-default, non-copy, non-move ctors.
+  using base_owned_ref<T, Allocator>::base_owned_ref;
+
+  /// Create a null reference
+  weak_ref() noexcept
+    : base_owned_ref<T, Allocator>{} {}
+
+  /// Create a null reference
+  /* implicit */ weak_ref(std::nullptr_t) noexcept
+    : base_owned_ref<T, Allocator>{nullptr} {}
+
+  /// Copy constructor (note creates a new reference)
+  weak_ref(const weak_ref& other)
+    : base_owned_ref<T, Allocator>{other} {}
+
+  // This needs to be explicit to change its visibility.
+  template<typename U>
+  weak_ref(const weak_ref<U>& other)
+    : base_owned_ref<T, Allocator>{other} {}
+
+  /// Transfers ownership of an underlying reference from one unique reference to another
+  weak_ref(weak_ref&& other) noexcept
+    : base_owned_ref<T, Allocator>{std::move(other)} {}
+
+
+  /// Assignment operator (note creates a new reference)
+  weak_ref& operator=(const weak_ref& other);
+
+  /// Assignment by moving a reference thus not creating a new reference
+  weak_ref& operator=(weak_ref&& rhs) noexcept;
+
+  // Creates an owned local reference to the referred object or to null if the object is reclaimed
+  local_ref<T> lockLocal() const;
+
+  // Creates an owned global reference to the referred object or to null if the object is reclaimed
+  global_ref<T> lockGlobal() const;
+
+ private:
+  // get/release/reset on weak_ref are not exposed to users.
+  using base_owned_ref<T, Allocator>::get;
+  using base_owned_ref<T, Allocator>::release;
+  using base_owned_ref<T, Allocator>::reset;
+  /*
+   * Wrap an existing reference and transfers its ownership to the newly created unique reference.
+   * NB! Does not create a new reference
+   */
+  explicit weak_ref(javaobject reference) noexcept
+    : base_owned_ref<T, Allocator>{reference} {}
+
+  template<typename T2> friend class weak_ref;
+  friend weak_ref<javaobject> adopt_weak_global<javaobject>(javaobject ref) noexcept;
+  friend void swap<T>(weak_ref& a, weak_ref& b) noexcept;
+};
+
+
+/**
+ * A class representing owned strong references to Java objects. This class
+ * should not be used directly, instead use @ref local_ref, or @ref global_ref.
+ */
+template<typename T, typename Alloc>
+class basic_strong_ref : public base_owned_ref<T, Alloc> {
+  using typename base_owned_ref<T, Alloc>::Repr;
+ public:
+  using javaobject = JniType<T>;
+
+  using Allocator = Alloc;
+
+  // This inherits non-default, non-copy, non-move ctors.
+  using base_owned_ref<T, Alloc>::base_owned_ref;
+  using base_owned_ref<T, Alloc>::release;
+  using base_owned_ref<T, Alloc>::reset;
+
+  /// Create a null reference
+  basic_strong_ref() noexcept
+    : base_owned_ref<T, Alloc>{} {}
+
+  /// Create a null reference
+  /* implicit */ basic_strong_ref(std::nullptr_t) noexcept
+    : base_owned_ref<T, Alloc>{nullptr} {}
+
+  /// Copy constructor (note creates a new reference)
+  basic_strong_ref(const basic_strong_ref& other)
+    : base_owned_ref<T, Alloc>{other} {}
+
+  // This needs to be explicit to change its visibility.
+  template<typename U>
+  basic_strong_ref(const basic_strong_ref<U, Alloc>& other)
+    : base_owned_ref<T, Alloc>{other} {}
+
+  /// Transfers ownership of an underlying reference from one unique reference to another
+  basic_strong_ref(basic_strong_ref&& other) noexcept
+    : base_owned_ref<T, Alloc>{std::move(other)} {}
+
+  /// Assignment operator (note creates a new reference)
+  basic_strong_ref& operator=(const basic_strong_ref& other);
+
+  /// Assignment by moving a reference thus not creating a new reference
+  basic_strong_ref& operator=(basic_strong_ref&& rhs) noexcept;
+
+  /// Get the plain JNI reference
+  using base_owned_ref<T, Allocator>::get;
+
+  /// Release the ownership of the reference and return the wrapped reference in an alias
+  alias_ref<T> releaseAlias() noexcept;
+
+  /// Checks if the reference points to a non-null object
+  explicit operator bool() const noexcept;
+
+  /// Access the functionality provided by the object wrappers
+  Repr* operator->() noexcept;
+
+  /// Access the functionality provided by the object wrappers
+  const Repr* operator->() const noexcept;
+
+  /// Provide a reference to the underlying wrapper (be sure that it is non-null before invoking)
+  Repr& operator*() noexcept;
+
+  /// Provide a const reference to the underlying wrapper (be sure that it is non-null
+  /// before invoking)
+  const Repr& operator*() const noexcept;
+
+ private:
+
+  using base_owned_ref<T, Alloc>::storage_;
+
+  /*
+   * Wrap an existing reference and transfers its ownership to the newly created unique reference.
+   * NB! Does not create a new reference
+   */
+  explicit basic_strong_ref(javaobject reference) noexcept
+    : base_owned_ref<T, Alloc>{reference} {}
+
+
+  friend local_ref<T> adopt_local<T>(T ref) noexcept;
+  friend global_ref<T> adopt_global<T>(T ref) noexcept;
+  friend void swap<T, Alloc>(basic_strong_ref& a, basic_strong_ref& b) noexcept;
+};
+
+
+template<typename T>
+enable_if_t<IsPlainJniReference<T>(), alias_ref<T>> wrap_alias(T ref) noexcept;
+
+/// Swaps to alias reference of the same type
+template<typename T>
+void swap(alias_ref<T>& a, alias_ref<T>& b) noexcept;
+
+/**
+ * A non-owning variant of the smart references (a dumb reference). These references still provide
+ * access to the functionality of the @ref JObjectWrapper specializations including exception
+ * handling and ease of use. Use this representation when you don't want to claim ownership of the
+ * underlying reference (compare to using raw pointers instead of smart pointers.) For symmetry use
+ * @ref alias_ref instead of this class.
+ */
+template<typename T>
+class alias_ref {
+  using Repr = ReprType<T>;
+
+ public:
+  using javaobject = JniType<T>;
+
+  /// Create a null reference
+  alias_ref() noexcept;
+
+  /// Create a null reference
+  /* implicit */ alias_ref(std::nullptr_t) noexcept;
+
+  /// Copy constructor
+  alias_ref(const alias_ref& other) noexcept;
+
+  /// Wrap an existing plain JNI reference
+  /* implicit */ alias_ref(javaobject ref) noexcept;
+
+  /// Wrap an existing smart reference of any type convertible to T
+  template<
+    typename TOther,
+    typename = enable_if_t<
+      IsConvertible<JniType<TOther>, javaobject>(), T>
+    >
+  alias_ref(alias_ref<TOther> other) noexcept;
+
+  /// Wrap an existing alias reference of a type convertible to T
+  template<
+    typename TOther,
+    typename AOther,
+    typename = enable_if_t<
+      IsConvertible<JniType<TOther>, javaobject>(), T>
+    >
+  alias_ref(const basic_strong_ref<TOther, AOther>& other) noexcept;
+
+  /// Assignment operator
+  alias_ref& operator=(alias_ref other) noexcept;
+
+  /// Checks if the reference points to a non-null object
+  explicit operator bool() const noexcept;
+
+  /// Converts back to a plain JNI reference
+  javaobject get() const noexcept;
+
+  /// Access the functionality provided by the object wrappers
+  Repr* operator->() noexcept;
+
+  /// Access the functionality provided by the object wrappers
+  const Repr* operator->() const noexcept;
+
+  /// Provide a guaranteed non-null reference (be sure that it is non-null before invoking)
+  Repr& operator*() noexcept;
+
+  /// Provide a guaranteed non-null reference (be sure that it is non-null before invoking)
+  const Repr& operator*() const noexcept;
+
+ private:
+  void set(javaobject ref) noexcept;
+
+  detail::ReprStorage<Repr> storage_;
+
+  friend void swap<T>(alias_ref& a, alias_ref& b) noexcept;
+};
+
+
+/**
+ * RAII object to create a local JNI frame, using PushLocalFrame/PopLocalFrame.
+ *
+ * This is useful when you have a call which is initiated from C++-land, and therefore
+ * doesn't automatically get a local JNI frame managed for you by the JNI framework.
+ */
+class FBEXPORT JniLocalScope {
+public:
+  JniLocalScope(JNIEnv* p_env, jint capacity);
+  ~JniLocalScope();
+
+private:
+  JNIEnv* env_;
+  bool hasFrame_;
+};
+
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), local_ref<T>>
+static_ref_cast(const local_ref<U>& ref) noexcept;
+
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), global_ref<T>>
+static_ref_cast(const global_ref<U>& ref) noexcept;
+
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), alias_ref<T>>
+static_ref_cast(const alias_ref<U>& ref) noexcept;
+
+template<typename T, typename RefType>
+auto dynamic_ref_cast(const RefType& ref) ->
+enable_if_t<IsPlainJniReference<T>(), decltype(static_ref_cast<T>(ref))> ;
+
+}}
+
+#include "References-inl.h"
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Registration-inl.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Registration-inl.h
new file mode 100644
index 000000000..f1db5733f
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Registration-inl.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include "Exceptions.h"
+#include "Hybrid.h"
+
+namespace facebook {
+namespace jni {
+
+namespace detail {
+
+#ifdef __i386__
+// X86 ABI forces 16 byte stack allignment on calls. Unfortunately
+// sometimes Dalvik chooses not to obey the ABI:
+// - https://code.google.com/p/android/issues/detail?id=61012
+// - https://android.googlesource.com/platform/ndk/+/81696d2%5E!/
+// Therefore, we tell the compiler to re-align the stack on entry
+// to our JNI functions.
+#define JNI_ENTRY_POINT __attribute__((force_align_arg_pointer))
+#else
+#define JNI_ENTRY_POINT
+#endif
+
+template <typename R>
+struct CreateDefault {
+  static R create() {
+    return R{};
+  }
+};
+
+template <>
+struct CreateDefault<void> {
+  static void create() {}
+};
+
+template <typename R>
+using Converter = Convert<typename std::decay<R>::type>;
+
+template <typename F, F func, typename R, typename... Args>
+struct WrapForVoidReturn {
+  static typename Converter<R>::jniType call(Args&&... args) {
+    return Converter<R>::toJniRet(func(std::forward<Args>(args)...));
+  }
+};
+
+template <typename F, F func, typename... Args>
+struct WrapForVoidReturn<F, func, void, Args...> {
+  static void call(Args&&... args) {
+    func(std::forward<Args>(args)...);
+  }
+};
+
+// registration wrapper for legacy JNI-style functions
+template<typename F, F func, typename C, typename R, typename... Args>
+struct BareJniWrapper {
+  JNI_ENTRY_POINT static R call(JNIEnv* env, jobject obj, Args... args) {
+    ThreadScope ts(env, internal::CacheEnvTag{});
+    try {
+      return (*func)(env, static_cast<JniType<C>>(obj), args...);
+    } catch (...) {
+      translatePendingCppExceptionToJavaException();
+      return CreateDefault<R>::create();
+    }
+  }
+};
+
+// registration wrappers for functions, with autoconversion of arguments.
+template<typename F, F func, typename C, typename R, typename... Args>
+struct FunctionWrapper {
+  using jniRet = typename Converter<R>::jniType;
+  JNI_ENTRY_POINT static jniRet call(JNIEnv* env, jobject obj, typename Converter<Args>::jniType... args) {
+    ThreadScope ts(env, internal::CacheEnvTag{});
+    try {
+      return WrapForVoidReturn<F, func, R, JniType<C>, Args...>::call(
+          static_cast<JniType<C>>(obj), Converter<Args>::fromJni(args)...);
+    } catch (...) {
+      translatePendingCppExceptionToJavaException();
+      return CreateDefault<jniRet>::create();
+    }
+  }
+};
+
+// registration wrappers for non-static methods, with autoconvertion of arguments.
+template<typename M, M method, typename C, typename R, typename... Args>
+struct MethodWrapper {
+  using jhybrid = typename C::jhybridobject;
+  static R dispatch(alias_ref<jhybrid> ref, Args&&... args) {
+    try {
+      // This is usually a noop, but if the hybrid object is a
+      // base class of other classes which register JNI methods,
+      // this will get the right type for the registered method.
+      auto cobj = static_cast<C*>(ref->cthis());
+      return (cobj->*method)(std::forward<Args>(args)...);
+    } catch (const std::exception& ex) {
+      C::mapException(ex);
+      throw;
+    }
+  }
+
+  JNI_ENTRY_POINT static typename Converter<R>::jniType call(
+      JNIEnv* env, jobject obj, typename Converter<Args>::jniType... args) {
+    return FunctionWrapper<R(*)(alias_ref<jhybrid>, Args&&...), dispatch, jhybrid, R, Args...>::call(env, obj, args...);
+  }
+};
+
+template<typename F, F func, typename C, typename R, typename... Args>
+inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(JNIEnv*, C, Args... args)) {
+  // This intentionally erases the real type; JNI will do it anyway
+  return reinterpret_cast<NativeMethodWrapper*>(&(BareJniWrapper<F, func, C, R, Args...>::call));
+}
+
+template<typename F, F func, typename C, typename R, typename... Args>
+inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(alias_ref<C>, Args... args)) {
+  // This intentionally erases the real type; JNI will do it anyway
+  return reinterpret_cast<NativeMethodWrapper*>(&(FunctionWrapper<F, func, C, R, Args...>::call));
+}
+
+template<typename M, M method, typename C, typename R, typename... Args>
+inline NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args)) {
+  // This intentionally erases the real type; JNI will do it anyway
+  return reinterpret_cast<NativeMethodWrapper*>(&(MethodWrapper<M, method, C, R, Args...>::call));
+}
+
+template<typename R, typename C, typename... Args>
+inline std::string makeDescriptor(R (*)(JNIEnv*, C, Args... args)) {
+  return jmethod_traits<R(Args...)>::descriptor();
+}
+
+template<typename R, typename C, typename... Args>
+inline std::string makeDescriptor(R (*)(alias_ref<C>, Args... args)) {
+  return jmethod_traits_from_cxx<R(Args...)>::descriptor();
+}
+
+template<typename R, typename C, typename... Args>
+inline std::string makeDescriptor(R (C::*)(Args... args)) {
+  return jmethod_traits_from_cxx<R(Args...)>::descriptor();
+}
+
+}
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Registration.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Registration.h
new file mode 100644
index 000000000..ecead9fff
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/Registration.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <jni.h>
+#include "References.h"
+
+namespace facebook {
+namespace jni {
+
+namespace detail {
+
+// This uses the real JNI function as a non-type template parameter to
+// cause a (static member) function to exist with the same signature,
+// but with try/catch exception translation.
+template<typename F, F func, typename C, typename R, typename... Args>
+NativeMethodWrapper* exceptionWrapJNIMethod(R (*func0)(JNIEnv*, jobject, Args... args));
+
+// Automatically wrap object argument, and don't take env explicitly.
+template<typename F, F func, typename C, typename R, typename... Args>
+NativeMethodWrapper* exceptionWrapJNIMethod(R (*func0)(alias_ref<C>, Args... args));
+
+// Extract C++ instance from object, and invoke given method on it,
+template<typename M, M method, typename C, typename R, typename... Args>
+NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args));
+
+// This uses deduction to figure out the descriptor name if the types
+// are primitive or have JObjectWrapper specializations.
+template<typename R, typename C, typename... Args>
+std::string makeDescriptor(R (*func)(JNIEnv*, C, Args... args));
+
+// This uses deduction to figure out the descriptor name if the types
+// are primitive or have JObjectWrapper specializations.
+template<typename R, typename C, typename... Args>
+std::string makeDescriptor(R (*func)(alias_ref<C>, Args... args));
+
+// This uses deduction to figure out the descriptor name if the types
+// are primitive or have JObjectWrapper specializations.
+template<typename R, typename C, typename... Args>
+std::string makeDescriptor(R (C::*method0)(Args... args));
+
+}
+
+// We have to use macros here, because the func needs to be used
+// as both a decltype expression argument and as a non-type template
+// parameter, since C++ provides no way for translateException
+// to deduce the type of its non-type template parameter.
+// The empty string in the macros below ensures that name
+// is always a string literal (because that syntax is only
+// valid when name is a string literal).
+#define makeNativeMethod2(name, func)                                   \
+  { name "", ::facebook::jni::detail::makeDescriptor(&func),            \
+      ::facebook::jni::detail::exceptionWrapJNIMethod<decltype(&func), &func>(&func) }
+
+#define makeNativeMethod3(name, desc, func)                             \
+  { name "", desc,                                                      \
+      ::facebook::jni::detail::exceptionWrapJNIMethod<decltype(&func), &func>(&func) }
+
+// Variadic template hacks to get macros with different numbers of
+// arguments. Usage instructions are in CoreClasses.h.
+#define makeNativeMethodN(a, b, c, count, ...) makeNativeMethod ## count
+#define makeNativeMethod(...) makeNativeMethodN(__VA_ARGS__, 3, 2)(__VA_ARGS__)
+
+}}
+
+#include "Registration-inl.h"
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/TypeTraits.h b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/TypeTraits.h
new file mode 100644
index 000000000..26472669d
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/fbjni/TypeTraits.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include "References-forward.h"
+
+namespace facebook {
+namespace jni {
+
+/// Generic std::enable_if helper
+template<bool B, typename T>
+using enable_if_t = typename std::enable_if<B, T>::type;
+
+/// Generic std::is_convertible helper
+template<typename From, typename To>
+constexpr bool IsConvertible() {
+  return std::is_convertible<From, To>::value;
+}
+
+template<template<typename...> class TT, typename T>
+struct is_instantiation_of : std::false_type {};
+
+template<template<typename...> class TT, typename... Ts>
+struct is_instantiation_of<TT, TT<Ts...>> : std::true_type {};
+
+template<template<typename...> class TT, typename... Ts>
+constexpr bool IsInstantiationOf() {
+  return is_instantiation_of<TT, Ts...>::value;
+}
+
+/// Metafunction to determine whether a type is a JNI reference or not
+template<typename T>
+struct is_plain_jni_reference :
+  std::integral_constant<bool,
+      std::is_pointer<T>::value &&
+      std::is_base_of<
+        typename std::remove_pointer<jobject>::type,
+        typename std::remove_pointer<T>::type>::value> {};
+
+/// Helper to simplify use of is_plain_jni_reference
+template<typename T>
+constexpr bool IsPlainJniReference() {
+  return is_plain_jni_reference<T>::value;
+}
+
+/// Metafunction to determine whether a type is a primitive JNI type or not
+template<typename T>
+struct is_jni_primitive :
+  std::integral_constant<bool,
+    std::is_same<jboolean, T>::value ||
+    std::is_same<jbyte, T>::value ||
+    std::is_same<jchar, T>::value ||
+    std::is_same<jshort, T>::value ||
+    std::is_same<jint, T>::value ||
+    std::is_same<jlong, T>::value ||
+    std::is_same<jfloat, T>::value ||
+    std::is_same<jdouble, T>::value> {};
+
+/// Helper to simplify use of is_jni_primitive
+template<typename T>
+constexpr bool IsJniPrimitive() {
+  return is_jni_primitive<T>::value;
+}
+
+/// Metafunction to determine whether a type is a JNI array of primitives or not
+template <typename T>
+struct is_jni_primitive_array :
+  std::integral_constant<bool,
+    std::is_same<jbooleanArray, T>::value ||
+    std::is_same<jbyteArray, T>::value ||
+    std::is_same<jcharArray, T>::value ||
+    std::is_same<jshortArray, T>::value ||
+    std::is_same<jintArray, T>::value ||
+    std::is_same<jlongArray, T>::value ||
+    std::is_same<jfloatArray, T>::value ||
+    std::is_same<jdoubleArray, T>::value> {};
+
+/// Helper to simplify use of is_jni_primitive_array
+template <typename T>
+constexpr bool IsJniPrimitiveArray() {
+  return is_jni_primitive_array<T>::value;
+}
+
+/// Metafunction to determine if a type is a scalar (primitive or reference) JNI type
+template<typename T>
+struct is_jni_scalar :
+  std::integral_constant<bool,
+    is_plain_jni_reference<T>::value ||
+    is_jni_primitive<T>::value> {};
+
+/// Helper to simplify use of is_jni_scalar
+template<typename T>
+constexpr bool IsJniScalar() {
+  return is_jni_scalar<T>::value;
+}
+
+// Metafunction to determine if a type is a JNI type
+template<typename T>
+struct is_jni_type :
+  std::integral_constant<bool,
+    is_jni_scalar<T>::value ||
+    std::is_void<T>::value> {};
+
+/// Helper to simplify use of is_jni_type
+template<typename T>
+constexpr bool IsJniType() {
+  return is_jni_type<T>::value;
+}
+
+template<typename T>
+struct is_non_weak_reference :
+  std::integral_constant<bool,
+    IsPlainJniReference<T>() ||
+    IsInstantiationOf<basic_strong_ref, T>() ||
+    IsInstantiationOf<alias_ref, T>()> {};
+
+template<typename T>
+constexpr bool IsNonWeakReference() {
+  return is_non_weak_reference<T>::value;
+}
+
+template<typename T>
+struct is_any_reference :
+  std::integral_constant<bool,
+    IsPlainJniReference<T>() ||
+    IsInstantiationOf<weak_ref, T>() ||
+    IsInstantiationOf<basic_strong_ref, T>() ||
+    IsInstantiationOf<alias_ref, T>()> {};
+
+template<typename T>
+constexpr bool IsAnyReference() {
+  return is_any_reference<T>::value;
+}
+
+template<typename T>
+struct reference_traits {
+  using plain_jni_reference_t = JniType<T>;
+  static_assert(IsPlainJniReference<plain_jni_reference_t>(), "Need a plain JNI reference");
+};
+
+template<template <typename...> class R, typename T, typename... A>
+struct reference_traits<R<T, A...>> {
+  using plain_jni_reference_t = JniType<T>;
+  static_assert(IsPlainJniReference<plain_jni_reference_t>(), "Need a plain JNI reference");
+};
+
+template<typename T>
+using plain_jni_reference_t = typename reference_traits<T>::plain_jni_reference_t;
+
+} // namespace jni
+} // namespace facebook
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/log.h b/VirtualApp/lib/src/main/jni/fb/include/fb/log.h
new file mode 100644
index 000000000..e26e716bf
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/log.h
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * FB Wrapper for logging functions.
+ *
+ * The android logging API uses the macro "LOG()" for its logic, which means
+ * that it conflicts with random other places that use LOG for their own
+ * purposes and doesn't work right half the places you include it
+ *
+ * FBLOG uses exactly the same semantics (FBLOGD for debug etc) but because of
+ * the FB prefix it's strictly better. FBLOGV also gets stripped out based on
+ * whether NDEBUG is set, but can be overridden by FBLOG_NDEBUG
+ *
+ * Most of the rest is a copy of <cutils/log.h> with minor changes.
+ */
+
+//
+// C/C++ logging functions.  See the logging documentation for API details.
+//
+// We'd like these to be available from C code (in case we import some from
+// somewhere), so this has a C interface.
+//
+// The output will be correct when the log file is shared between multiple
+// threads and/or multiple processes so long as the operating system
+// supports O_APPEND.  These calls have mutex-protected data structures
+// and so are NOT reentrant.  Do not use LOG in a signal handler.
+//
+#pragma once
+
+#include <fb/visibility.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef ANDROID
+#include <android/log.h>
+#else
+// These declarations are needed for our internal use even on non-Android
+// builds.
+// (they are borrowed from <android/log.h>)
+
+/*
+ * Android log priority values, in ascending priority order.
+ */
+typedef enum android_LogPriority {
+  ANDROID_LOG_UNKNOWN = 0,
+  ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
+  ANDROID_LOG_VERBOSE,
+  ANDROID_LOG_DEBUG,
+  ANDROID_LOG_INFO,
+  ANDROID_LOG_WARN,
+  ANDROID_LOG_ERROR,
+  ANDROID_LOG_FATAL,
+  ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
+} android_LogPriority;
+
+/*
+ * Send a simple string to the log.
+ */
+int __android_log_write(int prio, const char *tag, const char *text);
+
+/*
+ * Send a formatted string to the log, used like printf(fmt,...)
+ */
+int __android_log_print(int prio, const char *tag, const char *fmt, ...)
+#if defined(__GNUC__)
+    __attribute__((format(printf, 3, 4)))
+#endif
+    ;
+
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Normally we strip FBLOGV (VERBOSE messages) from release builds.
+ * You can modify this (for example with "#define FBLOG_NDEBUG 0"
+ * at the top of your source file) to change that behavior.
+ */
+#ifndef FBLOG_NDEBUG
+#ifdef NDEBUG
+#define FBLOG_NDEBUG 1
+#else
+#define FBLOG_NDEBUG 0
+#endif
+#endif
+
+/*
+ * This is the local tag used for the following simplified
+ * logging macros.  You can change this preprocessor definition
+ * before using the other macros to change the tag.
+ */
+#ifndef LOG_TAG
+#define LOG_TAG NULL
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose log message using the current LOG_TAG.
+ */
+#ifndef FBLOGV
+#if FBLOG_NDEBUG
+#define FBLOGV(...) ((void)0)
+#else
+#define FBLOGV(...) ((void)FBLOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond) (__builtin_expect((cond) != 0, 0))
+
+#ifndef FBLOGV_IF
+#if FBLOG_NDEBUG
+#define FBLOGV_IF(cond, ...) ((void)0)
+#else
+#define FBLOGV_IF(cond, ...)                                            \
+  ((CONDITION(cond)) ? ((void)FBLOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+                     : (void)0)
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug log message using the current LOG_TAG.
+ */
+#ifndef FBLOGD
+#define FBLOGD(...) ((void)FBLOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef FBLOGD_IF
+#define FBLOGD_IF(cond, ...) \
+  ((CONDITION(cond)) ? ((void)FBLOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) : (void)0)
+#endif
+
+/*
+ * Simplified macro to send an info log message using the current LOG_TAG.
+ */
+#ifndef FBLOGI
+#define FBLOGI(...) ((void)FBLOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef FBLOGI_IF
+#define FBLOGI_IF(cond, ...) \
+  ((CONDITION(cond)) ? ((void)FBLOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) : (void)0)
+#endif
+
+/*
+ * Simplified macro to send a warning log message using the current LOG_TAG.
+ */
+#ifndef FBLOGW
+#define FBLOGW(...) ((void)FBLOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef FBLOGW_IF
+#define FBLOGW_IF(cond, ...) \
+  ((CONDITION(cond)) ? ((void)FBLOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) : (void)0)
+#endif
+
+/*
+ * Simplified macro to send an error log message using the current LOG_TAG.
+ */
+#ifndef FBLOGE
+#define FBLOGE(...) ((void)FBLOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef FBLOGE_IF
+#define FBLOGE_IF(cond, ...) \
+  ((CONDITION(cond)) ? ((void)FBLOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) : (void)0)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * verbose priority.
+ */
+#ifndef IF_FBLOGV
+#if FBLOG_NDEBUG
+#define IF_FBLOGV() if (false)
+#else
+#define IF_FBLOGV() IF_FBLOG(LOG_VERBOSE, LOG_TAG)
+#endif
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * debug priority.
+ */
+#ifndef IF_FBLOGD
+#define IF_FBLOGD() IF_FBLOG(LOG_DEBUG, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * info priority.
+ */
+#ifndef IF_FBLOGI
+#define IF_FBLOGI() IF_FBLOG(LOG_INFO, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * warn priority.
+ */
+#ifndef IF_FBLOGW
+#define IF_FBLOGW() IF_FBLOG(LOG_WARN, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * error priority.
+ */
+#ifndef IF_FBLOGE
+#define IF_FBLOGE() IF_FBLOG(LOG_ERROR, LOG_TAG)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Log a fatal error.  If the given condition fails, this stops program
+ * execution like a normal assertion, but also generating the given message.
+ * It is NOT stripped from release builds.  Note that the condition test
+ * is -inverted- from the normal assert() semantics.
+ */
+#define FBLOG_ALWAYS_FATAL_IF(cond, ...)                                   \
+  ((CONDITION(cond)) ? ((void)fb_printAssert(#cond, LOG_TAG, __VA_ARGS__)) \
+                     : (void)0)
+
+#define FBLOG_ALWAYS_FATAL(...) \
+  (((void)fb_printAssert(NULL, LOG_TAG, __VA_ARGS__)))
+
+/*
+ * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
+ * are stripped out of release builds.
+ */
+#if FBLOG_NDEBUG
+
+#define FBLOG_FATAL_IF(cond, ...) ((void)0)
+#define FBLOG_FATAL(...) ((void)0)
+
+#else
+
+#define FBLOG_FATAL_IF(cond, ...) FBLOG_ALWAYS_FATAL_IF(cond, __VA_ARGS__)
+#define FBLOG_FATAL(...) FBLOG_ALWAYS_FATAL(__VA_ARGS__)
+
+#endif
+
+/*
+ * Assertion that generates a log message when the assertion fails.
+ * Stripped out of release builds.  Uses the current LOG_TAG.
+ */
+#define FBLOG_ASSERT(cond, ...) FBLOG_FATAL_IF(!(cond), __VA_ARGS__)
+//#define LOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond)
+
+// ---------------------------------------------------------------------
+
+/*
+ * Basic log message macro.
+ *
+ * Example:
+ *  FBLOG(LOG_WARN, NULL, "Failed with error %d", errno);
+ *
+ * The second argument may be NULL or "" to indicate the "global" tag.
+ */
+#ifndef FBLOG
+#define FBLOG(priority, tag, ...) \
+  FBLOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
+#endif
+
+#ifndef FBLOG_BY_DELIMS
+#define FBLOG_BY_DELIMS(priority, tag, delims, msg, ...) \
+  logPrintByDelims(ANDROID_##priority, tag, delims, msg, ##__VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to specify a number for the priority.
+ */
+#ifndef FBLOG_PRI
+#define FBLOG_PRI(priority, tag, ...) fb_printLog(priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to pass in a varargs ("args" is a va_list).
+ */
+#ifndef FBLOG_PRI_VA
+#define FBLOG_PRI_VA(priority, tag, fmt, args) \
+  fb_vprintLog(priority, NULL, tag, fmt, args)
+#endif
+
+/*
+ * Conditional given a desired logging priority and tag.
+ */
+#ifndef IF_FBLOG
+#define IF_FBLOG(priority, tag) if (fb_testLog(ANDROID_##priority, tag))
+#endif
+
+typedef void (*LogHandler)(int priority, const char* tag, const char* message);
+FBEXPORT void setLogHandler(LogHandler logHandler);
+
+/*
+ * ===========================================================================
+ *
+ * The stuff in the rest of this file should not be used directly.
+ */
+FBEXPORT int fb_printLog(int prio, const char* tag, const char* fmt, ...)
+#if defined(__GNUC__)
+    __attribute__((format(printf, 3, 4)))
+#endif
+    ;
+
+#define fb_vprintLog(prio, cond, tag, fmt...) \
+  __android_log_vprint(prio, tag, fmt)
+
+#define fb_printAssert(cond, tag, fmt...) __android_log_assert(cond, tag, fmt)
+
+#define fb_writeLog(prio, tag, text) __android_log_write(prio, tag, text)
+
+#define fb_bWriteLog(tag, payload, len) __android_log_bwrite(tag, payload, len)
+#define fb_btWriteLog(tag, type, payload, len) \
+  __android_log_btwrite(tag, type, payload, len)
+
+#define fb_testLog(prio, tag) (1)
+
+/*
+ * FB extensions
+ */
+void logPrintByDelims(int priority, const char* tag, const char* delims,
+                      const char* msg, ...);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/lyra.h b/VirtualApp/lib/src/main/jni/fb/include/fb/lyra.h
new file mode 100644
index 000000000..c60ff27b6
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/lyra.h
@@ -0,0 +1,168 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <fb/visibility.h>
+
+namespace facebook {
+namespace lyra {
+
+constexpr size_t kDefaultLimit = 64;
+
+using InstructionPointer = const void*;
+
+class FBEXPORT StackTraceElement {
+ public:
+  StackTraceElement(InstructionPointer absoluteProgramCounter,
+                    InstructionPointer libraryBase,
+                    InstructionPointer functionAddress, std::string libraryName,
+                    std::string functionName)
+      : absoluteProgramCounter_{absoluteProgramCounter},
+        libraryBase_{libraryBase},
+        functionAddress_{functionAddress},
+        libraryName_{std::move(libraryName)},
+        functionName_{std::move(functionName)} {}
+
+  InstructionPointer libraryBase() const noexcept { return libraryBase_; }
+
+  InstructionPointer functionAddress() const noexcept {
+    return functionAddress_;
+  }
+
+  InstructionPointer absoluteProgramCounter() const noexcept {
+    return absoluteProgramCounter_;
+  }
+
+  const std::string& libraryName() const noexcept { return libraryName_; }
+
+  const std::string& functionName() const noexcept { return functionName_; }
+
+  /**
+   * The offset of the program counter to the base of the library (i.e. the
+   * address that addr2line takes as input>
+   */
+  std::ptrdiff_t libraryOffset() const noexcept {
+    auto absoluteLibrary = static_cast<const char*>(libraryBase_);
+    auto absoluteabsoluteProgramCounter =
+        static_cast<const char*>(absoluteProgramCounter_);
+    return absoluteabsoluteProgramCounter - absoluteLibrary;
+  }
+
+  /**
+   * The offset within the current function
+   */
+  int functionOffset() const noexcept {
+    auto absoluteSymbol = static_cast<const char*>(functionAddress_);
+    auto absoluteabsoluteProgramCounter =
+        static_cast<const char*>(absoluteProgramCounter_);
+    return absoluteabsoluteProgramCounter - absoluteSymbol;
+  }
+
+ private:
+  const InstructionPointer absoluteProgramCounter_;
+  const InstructionPointer libraryBase_;
+  const InstructionPointer functionAddress_;
+  const std::string libraryName_;
+  const std::string functionName_;
+};
+
+/**
+ * Populate the vector with the current stack trace
+ *
+ * Note that this trace needs to be symbolicated to get the library offset even
+ * if it is to be symbolicated off-line.
+ *
+ * Beware of a bug on some platforms, which makes the trace loop until the
+ * buffer is full when it reaches a noexpr function. It seems to be fixed in
+ * newer versions of gcc. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56846
+ *
+ * @param stackTrace The vector that will receive the stack trace. Before
+ * filling the vector it will be cleared. The vector will never grow so the
+ * number of frames captured is limited by the capacity of it.
+ *
+ * @param skip The number of frames to skip before capturing the trace
+ */
+FBEXPORT void getStackTrace(std::vector<InstructionPointer>& stackTrace,
+                            size_t skip = 0);
+
+/**
+ * Creates a vector and populates it with the current stack trace
+ *
+ * Note that this trace needs to be symbolicated to get the library offset even
+ * if it is to be symbolicated off-line.
+ *
+ * Beware of a bug on some platforms, which makes the trace loop until the
+ * buffer is full when it reaches a noexpr function. It seems to be fixed in
+ * newer versions of gcc. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56846
+ *
+ * @param skip The number of frames to skip before capturing the trace
+ *
+ * @limit The maximum number of frames captured
+ */
+FBEXPORT inline std::vector<InstructionPointer> getStackTrace(
+    size_t skip = 0,
+    size_t limit = kDefaultLimit) {
+  auto stackTrace = std::vector<InstructionPointer>{};
+  stackTrace.reserve(limit);
+  getStackTrace(stackTrace, skip + 1);
+  return stackTrace;
+}
+
+/**
+ * Symbolicates a stack trace into a given vector
+ *
+ * @param symbols The vector to receive the output. The vector is cleared and
+ * enough room to keep the frames are reserved.
+ *
+ * @param stackTrace The input stack trace
+ */
+FBEXPORT void getStackTraceSymbols(std::vector<StackTraceElement>& symbols,
+                                   const std::vector<InstructionPointer>& trace);
+
+/**
+ * Symbolicates a stack trace into a new vector
+ *
+ * @param stackTrace The input stack trace
+ */
+FBEXPORT inline std::vector<StackTraceElement> getStackTraceSymbols(
+    const std::vector<InstructionPointer>& trace) {
+  auto symbols = std::vector<StackTraceElement>{};
+  getStackTraceSymbols(symbols, trace);
+  return symbols;
+}
+
+
+/**
+ * Captures and symbolicates a stack trace
+ *
+ * Beware of a bug on some platforms, which makes the trace loop until the
+ * buffer is full when it reaches a noexpr function. It seems to be fixed in
+ * newer versions of gcc. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56846
+ *
+ * @param skip The number of frames before capturing the trace
+ *
+ * @param limit The maximum number of frames captured
+ */
+FBEXPORT inline std::vector<StackTraceElement> getStackTraceSymbols(
+    size_t skip = 0,
+    size_t limit = kDefaultLimit) {
+  return getStackTraceSymbols(getStackTrace(skip + 1, limit));
+}
+
+/**
+ * Formatting a stack trace element
+ */
+FBEXPORT std::ostream& operator<<(std::ostream& out, const StackTraceElement& elm);
+
+/**
+ * Formatting a stack trace
+ */
+FBEXPORT std::ostream& operator<<(std::ostream& out,
+                                  const std::vector<StackTraceElement>& trace);
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/noncopyable.h b/VirtualApp/lib/src/main/jni/fb/include/fb/noncopyable.h
new file mode 100644
index 000000000..7212cc4d0
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/noncopyable.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+namespace facebook {
+
+struct noncopyable {
+  noncopyable(const noncopyable&) = delete;
+  noncopyable& operator=(const noncopyable&) = delete;
+protected:
+  noncopyable() = default;
+};
+
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/nonmovable.h b/VirtualApp/lib/src/main/jni/fb/include/fb/nonmovable.h
new file mode 100644
index 000000000..37f006a49
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/nonmovable.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+namespace facebook {
+
+struct nonmovable {
+  nonmovable(nonmovable&&) = delete;
+  nonmovable& operator=(nonmovable&&) = delete;
+protected:
+  nonmovable() = default;
+};
+
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/fb/visibility.h b/VirtualApp/lib/src/main/jni/fb/include/fb/visibility.h
new file mode 100644
index 000000000..7011a06bc
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/fb/visibility.h
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#define FBEXPORT __attribute__((visibility("default")))
diff --git a/VirtualApp/lib/src/main/jni/fb/include/jni/Countable.h b/VirtualApp/lib/src/main/jni/fb/include/jni/Countable.h
new file mode 100644
index 000000000..24f27c692
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/jni/Countable.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+#include <fb/Countable.h>
+#include <fb/RefPtr.h>
+#include <fb/visibility.h>
+
+namespace facebook {
+namespace jni {
+
+FBEXPORT const RefPtr<Countable>& countableFromJava(JNIEnv* env, jobject obj);
+
+template <typename T> RefPtr<T> extractRefPtr(JNIEnv* env, jobject obj) {
+  return static_cast<RefPtr<T>>(countableFromJava(env, obj));
+}
+
+template <typename T> RefPtr<T> extractPossiblyNullRefPtr(JNIEnv* env, jobject obj) {
+  return obj ? extractRefPtr<T>(env, obj) : nullptr;
+}
+
+FBEXPORT void setCountableForJava(JNIEnv* env, jobject obj, RefPtr<Countable>&& countable);
+
+void CountableOnLoad(JNIEnv* env);
+
+} }
+
diff --git a/VirtualApp/lib/src/main/jni/fb/include/jni/GlobalReference.h b/VirtualApp/lib/src/main/jni/fb/include/jni/GlobalReference.h
new file mode 100644
index 000000000..f0faf5fb8
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/jni/GlobalReference.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <memory>
+#include <type_traits>
+
+#include <jni.h>
+
+#include <fb/Environment.h>
+
+namespace facebook { namespace jni {
+
+template<typename T>
+class GlobalReference {
+  static_assert(std::is_convertible<T, jobject>::value,
+                "GlobalReference<T> instantiated with type that is not "
+                "convertible to jobject");
+
+ public:
+  explicit GlobalReference(T globalReference) :
+    reference_(globalReference? Environment::current()->NewGlobalRef(globalReference) : nullptr) {
+  }
+
+  ~GlobalReference() {
+    reset();
+  }
+
+  GlobalReference() :
+    reference_(nullptr) {
+  }
+
+  // enable move constructor and assignment
+  GlobalReference(GlobalReference&& rhs) :
+    reference_(std::move(rhs.reference_)) {
+    rhs.reference_ = nullptr;
+  }
+
+  GlobalReference& operator=(GlobalReference&& rhs) {
+    if (this != &rhs) {
+      reset();
+      reference_ = std::move(rhs.reference_);
+      rhs.reference_ = nullptr;
+    }
+    return *this;
+  }
+
+  GlobalReference(const GlobalReference<T>& rhs) :
+    reference_{} {
+    reset(rhs.get());
+  }
+
+  GlobalReference& operator=(const GlobalReference<T>& rhs) {
+    if (this == &rhs) {
+      return *this;
+    }
+    reset(rhs.get());
+    return *this;
+  }
+
+  explicit operator bool() const {
+    return (reference_ != nullptr);
+  }
+
+  T get() const {
+    return reinterpret_cast<T>(reference_);
+  }
+
+  void reset(T globalReference = nullptr) {
+    if (reference_) {
+      Environment::current()->DeleteGlobalRef(reference_);
+    }
+    if (globalReference) {
+      reference_ = Environment::current()->NewGlobalRef(globalReference);
+    } else {
+      reference_ = nullptr;
+    }
+  }
+
+ private:
+  jobject reference_;
+};
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/include/jni/JniTerminateHandler.h b/VirtualApp/lib/src/main/jni/fb/include/jni/JniTerminateHandler.h
new file mode 100644
index 000000000..843f988d2
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/jni/JniTerminateHandler.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <fb/visibility.h>
+
+namespace facebook {
+namespace jni {
+
+void FBEXPORT installTerminateHandler();
+}};
diff --git a/VirtualApp/lib/src/main/jni/fb/include/jni/LocalReference.h b/VirtualApp/lib/src/main/jni/fb/include/jni/LocalReference.h
new file mode 100644
index 000000000..22a558dc7
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/jni/LocalReference.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <memory>
+#include <type_traits>
+
+#include <jni.h>
+
+#include <fb/Environment.h>
+
+namespace facebook {
+namespace jni {
+
+template<class T>
+struct LocalReferenceDeleter {
+  static_assert(std::is_convertible<T, jobject>::value,
+    "LocalReferenceDeleter<T> instantiated with type that is not convertible to jobject");
+  void operator()(T localReference) {
+    if (localReference != nullptr) {
+      Environment::current()->DeleteLocalRef(localReference);
+    }
+  } 
+ };
+
+template<class T>
+using LocalReference =
+  std::unique_ptr<typename std::remove_pointer<T>::type, LocalReferenceDeleter<T>>;
+
+} }
diff --git a/VirtualApp/lib/src/main/jni/fb/include/jni/LocalString.h b/VirtualApp/lib/src/main/jni/fb/include/jni/LocalString.h
new file mode 100644
index 000000000..441cd8f45
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/jni/LocalString.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <jni.h>
+
+#include <fb/visibility.h>
+
+namespace facebook {
+namespace jni {
+
+namespace detail {
+
+void utf8ToModifiedUTF8(const uint8_t* bytes, size_t len, uint8_t* modified, size_t modifiedLength);
+size_t modifiedLength(const std::string& str);
+size_t modifiedLength(const uint8_t* str, size_t* length);
+std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept;
+std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len) noexcept;
+
+}
+
+// JNI represents strings encoded with modified version of UTF-8.  The difference between UTF-8 and
+// Modified UTF-8 is that the latter support only 1-byte, 2-byte, and 3-byte formats. Supplementary
+// character (4 bytes in unicode) needs to be represented in the form of surrogate pairs. To create
+// a Modified UTF-8 surrogate pair that Dalvik would understand we take 4-byte unicode character,
+// encode it with UTF-16 which gives us two 2 byte chars (surrogate pair) and then we encode each
+// pair as UTF-8. This result in 2 x 3 byte characters.  To convert modified UTF-8 to standard
+// UTF-8, this mus tbe reversed.
+//
+// The second difference is that Modified UTF-8 is encoding NUL byte in 2-byte format.
+//
+// In order to avoid complex error handling, only a minimum of validity checking is done to avoid
+// crashing.  If the input is invalid, the output may be invalid as well.
+//
+// Relevant links:
+//  - http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html
+//  - https://docs.oracle.com/javase/6/docs/api/java/io/DataInput.html#modified-utf-8
+
+class FBEXPORT LocalString {
+public:
+  // Assumes UTF8 encoding and make a required convertion to modified UTF-8 when the string
+  // contains unicode supplementary characters.
+  explicit LocalString(const std::string& str);
+  explicit LocalString(const char* str);
+  jstring string() const {
+    return m_string;
+  }
+  ~LocalString();
+private:
+  jstring m_string;
+};
+
+// JString to UTF16 extractor using RAII idiom
+class JStringUtf16Extractor {
+public:
+  JStringUtf16Extractor(JNIEnv* env, jstring javaString)
+  : env_(env)
+  , javaString_(javaString)
+  , length_(0)
+  , utf16String_(nullptr) {
+    if (env_ && javaString_) {
+      length_ = env_->GetStringLength(javaString_);
+      utf16String_ = env_->GetStringCritical(javaString_, nullptr);
+    }
+  }
+
+  ~JStringUtf16Extractor() {
+    if (utf16String_) {
+      env_->ReleaseStringCritical(javaString_, utf16String_);
+    }
+  }
+
+  const jsize length() const {
+    return length_;
+  }
+
+  const jchar* chars() const {
+    return utf16String_;
+  }
+
+private:
+  JNIEnv* env_;
+  jstring javaString_;
+  jsize length_;
+  const jchar* utf16String_;
+};
+
+// The string from JNI is converted to standard UTF-8 if the string contains supplementary
+// characters.
+FBEXPORT std::string fromJString(JNIEnv* env, jstring str);
+
+} }
diff --git a/VirtualApp/lib/src/main/jni/fb/include/jni/Registration.h b/VirtualApp/lib/src/main/jni/fb/include/jni/Registration.h
new file mode 100644
index 000000000..243a94788
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/jni/Registration.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+#include <jni.h>
+#include <initializer_list>
+#include <fb/assert.h>
+
+namespace facebook {
+namespace jni {
+
+static inline void registerNatives(JNIEnv* env, jclass cls, std::initializer_list<JNINativeMethod> methods) {
+  auto result = env->RegisterNatives(cls, methods.begin(), methods.size());
+  FBASSERT(result == 0);
+}
+
+static inline void registerNatives(JNIEnv* env, const char* cls, std::initializer_list<JNINativeMethod> list) {
+  registerNatives(env, env->FindClass(cls), list);
+}
+
+} }
diff --git a/VirtualApp/lib/src/main/jni/fb/include/jni/WeakReference.h b/VirtualApp/lib/src/main/jni/fb/include/jni/WeakReference.h
new file mode 100644
index 000000000..d8d59f9b7
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/jni/WeakReference.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+#include <string>
+#include <jni.h>
+#include <fb/noncopyable.h>
+#include <fb/Countable.h>
+#include <fb/visibility.h>
+
+
+namespace facebook {
+namespace jni {
+
+class FBEXPORT WeakReference : public Countable {
+public:
+  typedef RefPtr<WeakReference> Ptr;
+  WeakReference(jobject strongRef);
+  ~WeakReference();
+  jweak weakRef() {
+    return m_weakReference;
+  }
+
+private:
+  jweak m_weakReference;
+};
+
+// This class is intended to take a weak reference and turn it into a strong
+// local reference. Consequently, it should only be allocated on the stack.
+class FBEXPORT ResolvedWeakReference : public noncopyable {
+public:
+  ResolvedWeakReference(jobject weakRef);
+  ResolvedWeakReference(const RefPtr<WeakReference>& weakRef);
+  ~ResolvedWeakReference();
+
+  operator jobject () {
+    return m_strongReference;
+  }
+
+  explicit operator bool () {
+    return m_strongReference != nullptr;
+  }
+
+private:
+  jobject m_strongReference;
+};
+
+} }
+
diff --git a/VirtualApp/lib/src/main/jni/fb/include/jni/jni_helpers.h b/VirtualApp/lib/src/main/jni/fb/include/jni/jni_helpers.h
new file mode 100644
index 000000000..808593ab3
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/include/jni/jni_helpers.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+#include <fb/visibility.h>
+
+namespace facebook {
+
+/**
+ * Instructs the JNI environment to throw an exception.
+ *
+ * @param pEnv JNI environment
+ * @param szClassName class name to throw
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+FBEXPORT jint throwException(JNIEnv* pEnv, const char* szClassName, const char* szFmt, va_list va_args);
+
+/**
+ * Instructs the JNI environment to throw a NoClassDefFoundError.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+FBEXPORT jint throwNoClassDefError(JNIEnv* pEnv, const char* szFmt, ...);
+
+/**
+ * Instructs the JNI environment to throw a RuntimeException.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+FBEXPORT jint throwRuntimeException(JNIEnv* pEnv, const char* szFmt, ...);
+
+/**
+ * Instructs the JNI environment to throw a IllegalArgumentException.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+FBEXPORT jint throwIllegalArgumentException(JNIEnv* pEnv, const char* szFmt, ...);
+
+/**
+ * Instructs the JNI environment to throw a IllegalStateException.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+FBEXPORT jint throwIllegalStateException(JNIEnv* pEnv, const char* szFmt, ...);
+
+/**
+ * Instructs the JNI environment to throw an IOException.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+FBEXPORT jint throwIOException(JNIEnv* pEnv, const char* szFmt, ...);
+
+/**
+ * Instructs the JNI environment to throw an AssertionError.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+FBEXPORT jint throwAssertionError(JNIEnv* pEnv, const char* szFmt, ...);
+
+/**
+ * Instructs the JNI environment to throw an OutOfMemoryError.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+FBEXPORT jint throwOutOfMemoryError(JNIEnv* pEnv, const char* szFmt, ...);
+
+/**
+ * Finds the specified class. If it's not found, instructs the JNI environment to throw an
+ * exception.
+ *
+ * @param pEnv JNI environment
+ * @param szClassName the classname to find in JNI format (e.g. "java/lang/String")
+ * @return the class or NULL if not found (in which case a pending exception will be queued). This
+ *     returns a global reference (JNIEnv::NewGlobalRef).
+ */
+FBEXPORT jclass findClassOrThrow(JNIEnv *pEnv, const char* szClassName);
+
+/**
+ * Finds the specified field of the specified class. If it's not found, instructs the JNI
+ * environment to throw an exception.
+ *
+ * @param pEnv JNI environment
+ * @param clazz the class to lookup the field in
+ * @param szFieldName the name of the field to find
+ * @param szSig the signature of the field
+ * @return the field or NULL if not found (in which case a pending exception will be queued)
+ */
+FBEXPORT jfieldID getFieldIdOrThrow(JNIEnv* pEnv, jclass clazz, const char* szFieldName, const char* szSig);
+
+/**
+ * Finds the specified method of the specified class. If it's not found, instructs the JNI
+ * environment to throw an exception.
+ *
+ * @param pEnv JNI environment
+ * @param clazz the class to lookup the method in
+ * @param szMethodName the name of the method to find
+ * @param szSig the signature of the method
+ * @return the method or NULL if not found (in which case a pending exception will be queued)
+ */
+FBEXPORT jmethodID getMethodIdOrThrow(
+    JNIEnv* pEnv,
+    jclass clazz,
+    const char* szMethodName,
+    const char* szSig);
+
+} // namespace facebook
+
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/ByteBuffer.cpp b/VirtualApp/lib/src/main/jni/fb/jni/ByteBuffer.cpp
new file mode 100644
index 000000000..2e839ecf4
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/ByteBuffer.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <fb/fbjni/ByteBuffer.h>
+
+#include <stdexcept>
+
+#include <fb/fbjni/References.h>
+
+namespace facebook {
+namespace jni {
+
+namespace {
+local_ref<JByteBuffer> createEmpty() {
+  static auto cls = JByteBuffer::javaClassStatic();
+  static auto meth = cls->getStaticMethod<JByteBuffer::javaobject(int)>("allocateDirect");
+  return meth(cls, 0);
+}
+}
+
+local_ref<JByteBuffer> JByteBuffer::wrapBytes(uint8_t* data, size_t size) {
+  // env->NewDirectByteBuffer requires that size is positive. Android's
+  // dalvik returns an invalid result and Android's art aborts if size == 0.
+  // Workaround this by using a slow path through Java in that case.
+  if (!size) {
+    return createEmpty();
+  }
+  auto res = adopt_local(static_cast<javaobject>(Environment::current()->NewDirectByteBuffer(data, size)));
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  if (!res) {
+    throw std::runtime_error("Direct byte buffers are unsupported.");
+  }
+  return res;
+}
+
+uint8_t* JByteBuffer::getDirectBytes() const {
+  if (!self()) {
+    throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
+  }
+  void* bytes = Environment::current()->GetDirectBufferAddress(self());
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  if (!bytes) {
+    throw std::runtime_error(
+        isDirect() ?
+          "Attempt to get direct bytes of non-direct byte buffer." :
+          "Error getting direct bytes of byte buffer.");
+  }
+  return static_cast<uint8_t*>(bytes);
+}
+
+size_t JByteBuffer::getDirectSize() const {
+  if (!self()) {
+    throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
+  }
+  int size = Environment::current()->GetDirectBufferCapacity(self());
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  if (size < 0) {
+    throw std::runtime_error(
+        isDirect() ?
+          "Attempt to get direct size of non-direct byte buffer." :
+          "Error getting direct size of byte buffer.");
+  }
+  return static_cast<size_t>(size);
+}
+
+bool JByteBuffer::isDirect() const {
+  static auto meth = javaClassStatic()->getMethod<jboolean()>("isDirect");
+  return meth(self());
+}
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/Countable.cpp b/VirtualApp/lib/src/main/jni/fb/jni/Countable.cpp
new file mode 100644
index 000000000..0d7c388f5
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/Countable.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <cstdint>
+#include <jni/Countable.h>
+#include <fb/Environment.h>
+#include <jni/Registration.h>
+
+namespace facebook {
+namespace jni {
+
+static jfieldID gCountableNativePtr;
+
+static RefPtr<Countable>* rawCountableFromJava(JNIEnv* env, jobject obj) {
+  FBASSERT(obj);
+  return reinterpret_cast<RefPtr<Countable>*>(env->GetLongField(obj, gCountableNativePtr));
+}
+
+const RefPtr<Countable>& countableFromJava(JNIEnv* env, jobject obj) {
+  FBASSERT(obj);
+  return *rawCountableFromJava(env, obj);
+}
+
+void setCountableForJava(JNIEnv* env, jobject obj, RefPtr<Countable>&& countable) {
+  int oldValue = env->GetLongField(obj, gCountableNativePtr);
+  FBASSERTMSGF(oldValue == 0, "Cannot reinitialize object; expected nullptr, got %x", oldValue);
+
+  FBASSERT(countable);
+  uintptr_t fieldValue = (uintptr_t) new RefPtr<Countable>(std::move(countable));
+  env->SetLongField(obj, gCountableNativePtr, fieldValue);
+}
+
+/**
+ * NB: THREAD SAFETY (this comment also exists at Countable.java)
+ *
+ * This method deletes the corresponding native object on whatever thread the method is called
+ * on. In the common case when this is called by Countable#finalize(), this will be called on the
+ * system finalizer thread. If you manually call dispose on the Java object, the native object 
+ * will be deleted synchronously on that thread.
+ */
+void dispose(JNIEnv* env, jobject obj) {
+  // Grab the pointer
+  RefPtr<Countable>* countable = rawCountableFromJava(env, obj);
+  if (!countable) {
+    // That was easy.
+    return;
+  }
+
+  // Clear out the old value to avoid double-frees
+  env->SetLongField(obj, gCountableNativePtr, 0);
+
+  delete countable;
+}
+
+void CountableOnLoad(JNIEnv* env) {
+  jclass countable = env->FindClass("com/facebook/jni/Countable");
+  gCountableNativePtr = env->GetFieldID(countable, "mInstance", "J");
+  registerNatives(env, countable, {
+    { "dispose", "()V", (void*) dispose },
+  });
+}
+
+} }
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/Environment.cpp b/VirtualApp/lib/src/main/jni/fb/jni/Environment.cpp
new file mode 100644
index 000000000..48060107e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/Environment.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <fb/log.h>
+#include <fb/ThreadLocal.h>
+#include <fb/Environment.h>
+#include <fb/fbjni/CoreClasses.h>
+#include <fb/fbjni/NativeRunnable.h>
+
+#include <functional>
+
+namespace facebook {
+namespace jni {
+
+namespace {
+
+ThreadLocal<ThreadScope>& scopeStorage() {
+  // We don't want the ThreadLocal to delete the ThreadScopes.
+  static ThreadLocal<ThreadScope> scope([] (void*) {});
+  return scope;
+}
+
+ThreadScope* currentScope() {
+  return scopeStorage().get();
+}
+
+JavaVM* g_vm = nullptr;
+
+struct EnvironmentInitializer {
+  EnvironmentInitializer(JavaVM* vm) {
+      FBASSERT(!g_vm);
+      FBASSERT(vm);
+      g_vm = vm;
+  }
+};
+
+int getEnv(JNIEnv** env) {
+  FBASSERT(g_vm);
+  // g_vm->GetEnv() might not clear the env* in failure cases.
+  *env = nullptr;
+  return g_vm->GetEnv((void**)env, JNI_VERSION_1_6);
+}
+
+JNIEnv* attachCurrentThread() {
+  JavaVMAttachArgs args{JNI_VERSION_1_6, nullptr, nullptr};
+  JNIEnv* env = nullptr;
+  auto result = g_vm->AttachCurrentThread(&env, &args);
+  FBASSERT(result == JNI_OK);
+  return env;
+}
+}
+
+/* static */
+void Environment::initialize(JavaVM* vm) {
+  static EnvironmentInitializer init(vm);
+}
+
+/* static */
+JNIEnv* Environment::current() {
+  auto scope = currentScope();
+  if (scope && scope->env_) {
+    return scope->env_;
+  }
+
+  JNIEnv* env;
+  if (getEnv(&env) != JNI_OK) {
+    // If there's a ThreadScope in the stack, we should be attached and able to
+    // retrieve a JNIEnv*.
+    FBASSERT(!scope);
+
+    // TODO(cjhopman): this should probably be a hard failure, too.
+    FBLOGE("Unable to retrieve jni environment. Is the thread attached?");
+  }
+  return env;
+}
+
+/* static */
+void Environment::detachCurrentThread() {
+  FBASSERT(g_vm);
+  // The thread shouldn't be detached while a ThreadScope is in the stack.
+  FBASSERT(!currentScope());
+  g_vm->DetachCurrentThread();
+}
+
+/* static */
+JNIEnv* Environment::ensureCurrentThreadIsAttached() {
+  auto scope = currentScope();
+  if (scope && scope->env_) {
+    return scope->env_;
+  }
+
+  JNIEnv* env;
+  // We should be able to just get the JNIEnv* by just calling
+  // AttachCurrentThread, but the spec is unclear (and using getEnv is probably
+  // generally more reliable).
+  auto result = getEnv(&env);
+  // We don't know how to deal with anything other than JNI_OK or JNI_DETACHED.
+  FBASSERT(result == JNI_OK || result == JNI_EDETACHED);
+  if (result == JNI_EDETACHED) {
+    // The thread should not be detached while a ThreadScope is in the stack.
+    FBASSERT(!scope);
+    env = attachCurrentThread();
+  }
+  FBASSERT(env);
+  return env;
+}
+
+ThreadScope::ThreadScope() : ThreadScope(nullptr, internal::CacheEnvTag{}) {}
+
+ThreadScope::ThreadScope(JNIEnv* env, internal::CacheEnvTag)
+    : previous_(nullptr), env_(nullptr), attachedWithThisScope_(false) {
+  auto& storage = scopeStorage();
+  previous_ = storage.get();
+  storage.reset(this);
+
+  if (previous_ && previous_->env_) {
+    FBASSERT(!env || env == previous_->env_);
+    env = previous_->env_;
+  }
+
+  env_ = env;
+  if (env_) {
+    return;
+  }
+
+  // Check if the thread is attached by someone else.
+  auto result = getEnv(&env);
+  if (result == JNI_OK) {
+    return;
+  }
+
+  // We don't know how to deal with anything other than JNI_OK or JNI_DETACHED.
+  FBASSERT(result == JNI_EDETACHED);
+
+  // If there's already a ThreadScope on the stack, then the thread should be attached.
+  FBASSERT(!previous_);
+  attachCurrentThread();
+  attachedWithThisScope_ = true;
+}
+
+ThreadScope::~ThreadScope() {
+  auto& storage = scopeStorage();
+  // ThreadScopes should be destroyed in the reverse order they are created
+  // (that is, just put them on the stack).
+  FBASSERT(this == storage.get());
+  storage.reset(previous_);
+  if (attachedWithThisScope_) {
+    Environment::detachCurrentThread();
+  }
+}
+
+namespace {
+struct JThreadScopeSupport : JavaClass<JThreadScopeSupport> {
+  static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/ThreadScopeSupport;";
+
+  // These reinterpret_casts are a totally dangerous pattern. Don't use them. Use HybridData instead.
+  static void runStdFunction(std::function<void()>&& func) {
+    static auto method = javaClassStatic()->getStaticMethod<void(jlong)>("runStdFunction");
+    method(javaClassStatic(), reinterpret_cast<jlong>(&func));
+  }
+
+  static void runStdFunctionImpl(alias_ref<JClass>, jlong ptr) {
+    (*reinterpret_cast<std::function<void()>*>(ptr))();
+  }
+
+  static void OnLoad() {
+    // We need the javaClassStatic so that the class lookup is cached and that
+    // runStdFunction can be called from a ThreadScope-attached thread.
+    javaClassStatic()->registerNatives({
+        makeNativeMethod("runStdFunctionImpl", runStdFunctionImpl),
+      });
+  }
+};
+}
+
+/* static */
+void ThreadScope::OnLoad() {
+  // These classes are required for ScopeWithClassLoader. Ensure they are looked up when loading.
+  JThreadScopeSupport::OnLoad();
+}
+
+/* static */
+void ThreadScope::WithClassLoader(std::function<void()>&& runnable) {
+  // TODO(cjhopman): If the classloader is already available in this scope, we
+  // shouldn't have to jump through java. It should be enough to check if the
+  // attach state env* is set.
+  ThreadScope ts;
+  JThreadScopeSupport::runStdFunction(std::move(runnable));
+}
+
+} }
+
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/Exceptions.cpp b/VirtualApp/lib/src/main/jni/fb/jni/Exceptions.cpp
new file mode 100644
index 000000000..3dca10b76
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/Exceptions.cpp
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <fb/fbjni/CoreClasses.h>
+
+#include <fb/assert.h>
+#include <fb/log.h>
+
+#ifdef USE_LYRA
+#include <fb/lyra.h>
+#include <fb/lyra_exceptions.h>
+#endif
+
+#include <alloca.h>
+#include <cstdlib>
+#include <ios>
+#include <stdexcept>
+#include <stdio.h>
+#include <string>
+#include <system_error>
+
+#include <jni.h>
+
+namespace facebook {
+namespace jni {
+
+namespace {
+class JRuntimeException : public JavaClass<JRuntimeException, JThrowable> {
+ public:
+  static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;";
+
+  static local_ref<JRuntimeException> create(const char* str) {
+    return newInstance(make_jstring(str));
+  }
+
+  static local_ref<JRuntimeException> create() {
+    return newInstance();
+  }
+};
+
+class JIOException : public JavaClass<JIOException, JThrowable> {
+ public:
+  static auto constexpr kJavaDescriptor = "Ljava/io/IOException;";
+
+  static local_ref<JIOException> create(const char* str) {
+    return newInstance(make_jstring(str));
+  }
+};
+
+class JOutOfMemoryError : public JavaClass<JOutOfMemoryError, JThrowable> {
+ public:
+  static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;";
+
+  static local_ref<JOutOfMemoryError> create(const char* str) {
+    return newInstance(make_jstring(str));
+  }
+};
+
+class JArrayIndexOutOfBoundsException : public JavaClass<JArrayIndexOutOfBoundsException, JThrowable> {
+ public:
+  static auto constexpr kJavaDescriptor = "Ljava/lang/ArrayIndexOutOfBoundsException;";
+
+  static local_ref<JArrayIndexOutOfBoundsException> create(const char* str) {
+    return newInstance(make_jstring(str));
+  }
+};
+
+class JUnknownCppException : public JavaClass<JUnknownCppException, JThrowable> {
+ public:
+  static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;";
+
+  static local_ref<JUnknownCppException> create() {
+    return newInstance();
+  }
+
+  static local_ref<JUnknownCppException> create(const char* str) {
+    return newInstance(make_jstring(str));
+  }
+};
+
+class JCppSystemErrorException : public JavaClass<JCppSystemErrorException, JThrowable> {
+ public:
+  static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppSystemErrorException;";
+
+  static local_ref<JCppSystemErrorException> create(const std::system_error& e) {
+    return newInstance(make_jstring(e.what()), e.code().value());
+  }
+};
+
+// Exception throwing & translating functions //////////////////////////////////////////////////////
+
+// Functions that throw Java exceptions
+
+void setJavaExceptionAndAbortOnFailure(alias_ref<JThrowable> throwable) {
+  auto env = Environment::current();
+  if (throwable) {
+    env->Throw(throwable.get());
+  }
+  if (env->ExceptionCheck() != JNI_TRUE) {
+    std::abort();
+  }
+}
+
+}
+
+// Functions that throw C++ exceptions
+
+// TODO(T6618159) Inject the c++ stack into the exception's stack trace. One
+// issue: when a java exception is created, it captures the full java stack
+// across jni boundaries. lyra will only capture the c++ stack to the jni
+// boundary. So, as we pass the java exception up to c++, we need to capture
+// the c++ stack and then insert it into the correct place in the java stack
+// trace. Then, as the exception propagates across the boundaries, we will
+// slowly fill in the c++ parts of the trace.
+void throwPendingJniExceptionAsCppException() {
+  JNIEnv* env = Environment::current();
+  if (env->ExceptionCheck() == JNI_FALSE) {
+    return;
+  }
+
+  auto throwable = adopt_local(env->ExceptionOccurred());
+  if (!throwable) {
+    throw std::runtime_error("Unable to get pending JNI exception.");
+  }
+  env->ExceptionClear();
+
+  throw JniException(throwable);
+}
+
+void throwCppExceptionIf(bool condition) {
+  if (!condition) {
+    return;
+  }
+
+  auto env = Environment::current();
+  if (env->ExceptionCheck() == JNI_TRUE) {
+    throwPendingJniExceptionAsCppException();
+    return;
+  }
+
+  throw JniException();
+}
+
+void throwNewJavaException(jthrowable throwable) {
+  throw JniException(wrap_alias(throwable));
+}
+
+void throwNewJavaException(const char* throwableName, const char* msg) {
+  // If anything of the fbjni calls fail, an exception of a suitable
+  // form will be thrown, which is what we want.
+  auto throwableClass = findClassLocal(throwableName);
+  auto throwable = throwableClass->newObject(
+    throwableClass->getConstructor<jthrowable(jstring)>(),
+    make_jstring(msg).release());
+  throwNewJavaException(throwable.get());
+}
+
+// jthrowable //////////////////////////////////////////////////////////////////////////////////////
+
+local_ref<JThrowable> JThrowable::initCause(alias_ref<JThrowable> cause) {
+  static auto meth = javaClassStatic()->getMethod<javaobject(alias_ref<javaobject>)>("initCause");
+  return meth(self(), cause);
+}
+
+auto JThrowable::getStackTrace() -> local_ref<JStackTrace> {
+  static auto meth = javaClassStatic()->getMethod<JStackTrace::javaobject()>("getStackTrace");
+  return meth(self());
+}
+
+void JThrowable::setStackTrace(alias_ref<JStackTrace> stack) {
+  static auto meth = javaClassStatic()->getMethod<void(alias_ref<JStackTrace>)>("setStackTrace");
+  return meth(self(), stack);
+}
+
+auto JStackTraceElement::create(
+    const std::string& declaringClass, const std::string& methodName, const std::string& file, int line)
+    -> local_ref<javaobject> {
+  return newInstance(declaringClass, methodName, file, line);
+}
+
+std::string JStackTraceElement::getClassName() const {
+  static auto meth = javaClassStatic()->getMethod<local_ref<JString>()>("getClassName");
+  return meth(self())->toStdString();
+}
+
+std::string JStackTraceElement::getMethodName() const {
+  static auto meth = javaClassStatic()->getMethod<local_ref<JString>()>("getMethodName");
+  return meth(self())->toStdString();
+}
+
+std::string JStackTraceElement::getFileName() const {
+  static auto meth = javaClassStatic()->getMethod<local_ref<JString>()>("getFileName");
+  return meth(self())->toStdString();
+}
+
+int JStackTraceElement::getLineNumber() const {
+  static auto meth = javaClassStatic()->getMethod<jint()>("getLineNumber");
+  return meth(self());
+}
+
+// Translate C++ to Java Exception
+
+namespace {
+
+// For each exception in the chain of the exception_ptr argument, func
+// will be called with that exception (in reverse order, i.e. innermost first).
+#ifndef FBJNI_NO_EXCEPTION_PTR
+void denest(const std::function<void(std::exception_ptr)>& func, std::exception_ptr ptr) {
+  FBASSERT(ptr);
+  try {
+    std::rethrow_exception(ptr);
+  } catch (const std::nested_exception& e) {
+    denest(func, e.nested_ptr());
+  } catch (...) {
+    // ignored.
+  }
+  func(ptr);
+  }
+#endif
+
+} // namespace
+
+
+#ifdef USE_LYRA
+local_ref<JStackTraceElement> createJStackTraceElement(const lyra::StackTraceElement& cpp) {
+  return JStackTraceElement::create(
+      "|lyra|{" + cpp.libraryName() + "}", cpp.functionName(), cpp.buildId(), cpp.libraryOffset());
+}
+#endif
+
+#ifndef FBJNI_NO_EXCEPTION_PTR
+void addCppStacktraceToJavaException(alias_ref<JThrowable> java, std::exception_ptr cpp) {
+#ifdef USE_LYRA
+  auto cppStack = lyra::getStackTraceSymbols(
+                    (cpp == nullptr) ?
+                      lyra::getStackTrace()
+                      : lyra::getExceptionTrace(cpp));
+
+  auto javaStack = java->getStackTrace();
+  auto newStack = JThrowable::JStackTrace::newArray(javaStack->size() + cppStack.size());
+  size_t i = 0;
+  for (size_t j = 0; j < cppStack.size(); j++, i++) {
+    (*newStack)[i] = createJStackTraceElement(cppStack[j]);
+  }
+  for (size_t j = 0; j < javaStack->size(); j++, i++) {
+    (*newStack)[i] = (*javaStack)[j];
+  }
+  java->setStackTrace(newStack);
+#endif
+}
+
+local_ref<JThrowable> convertCppExceptionToJavaException(std::exception_ptr ptr) {
+  FBASSERT(ptr);
+  local_ref<JThrowable> current;
+  bool addCppStack = true;
+  try {
+    std::rethrow_exception(ptr);
+    addCppStack = false;
+  } catch (const JniException& ex) {
+    current = ex.getThrowable();
+  } catch (const std::ios_base::failure& ex) {
+    current = JIOException::create(ex.what());
+  } catch (const std::bad_alloc& ex) {
+    current = JOutOfMemoryError::create(ex.what());
+  } catch (const std::out_of_range& ex) {
+    current = JArrayIndexOutOfBoundsException::create(ex.what());
+  } catch (const std::system_error& ex) {
+    current = JCppSystemErrorException::create(ex);
+  } catch (const std::runtime_error& ex) {
+    current = JRuntimeException::create(ex.what());
+  } catch (const std::exception& ex) {
+    current = JCppException::create(ex.what());
+  } catch (const char* msg) {
+    current = JUnknownCppException::create(msg);
+  } catch (...) {
+    current = JUnknownCppException::create();
+  }
+
+  if (addCppStack) {
+    addCppStacktraceToJavaException(current, ptr);
+  }
+  return current;
+  }
+#endif
+
+local_ref<JThrowable> getJavaExceptionForCppBackTrace() {
+  return getJavaExceptionForCppBackTrace(nullptr);
+}
+
+local_ref<JThrowable> getJavaExceptionForCppBackTrace(const char* msg) {
+  local_ref<JThrowable> current =
+      msg ? JUnknownCppException::create(msg) : JUnknownCppException::create();
+#ifndef FBJNI_NO_EXCEPTION_PTR
+  addCppStacktraceToJavaException(current, nullptr);
+#endif
+  return current;
+}
+
+
+#ifndef FBJNI_NO_EXCEPTION_PTR
+local_ref<JThrowable> getJavaExceptionForCppException(std::exception_ptr ptr) {
+  FBASSERT(ptr);
+  local_ref<JThrowable> previous;
+  auto func = [&previous] (std::exception_ptr ptr) {
+    auto current = convertCppExceptionToJavaException(ptr);
+    if (previous) {
+      current->initCause(previous);
+    }
+    previous = current;
+  };
+  denest(func, ptr);
+  return previous;
+}
+#endif
+
+void translatePendingCppExceptionToJavaException() {
+  try {
+#ifndef FBJNI_NO_EXCEPTION_PTR
+    auto exc = getJavaExceptionForCppException(std::current_exception());
+#else
+    auto exc = JUnknownCppException::create();
+#endif
+    setJavaExceptionAndAbortOnFailure(exc);
+  } catch (...) {
+#ifdef USE_LYRA
+    FBLOGE("Unexpected error in translatePendingCppExceptionToJavaException(): %s",
+        lyra::toString(std::current_exception()).c_str());
+#endif
+    std::terminate();
+  }
+}
+
+// JniException ////////////////////////////////////////////////////////////////////////////////////
+
+const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message.";
+
+JniException::JniException() : JniException(JRuntimeException::create()) { }
+
+JniException::JniException(alias_ref<jthrowable> throwable) : isMessageExtracted_(false) {
+  throwable_ = make_global(throwable);
+}
+
+JniException::JniException(JniException &&rhs)
+    : throwable_(std::move(rhs.throwable_)),
+      what_(std::move(rhs.what_)),
+      isMessageExtracted_(rhs.isMessageExtracted_) {
+}
+
+JniException::JniException(const JniException &rhs)
+    : what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) {
+  throwable_ = make_global(rhs.throwable_);
+}
+
+JniException::~JniException() {
+  try {
+    ThreadScope ts;
+    throwable_.reset();
+  } catch (...) {
+    FBLOGE("Exception in ~JniException()");
+    std::terminate();
+  }
+}
+
+local_ref<JThrowable> JniException::getThrowable() const noexcept {
+  return make_local(throwable_);
+}
+
+// TODO 6900503: consider making this thread-safe.
+void JniException::populateWhat() const noexcept {
+  try {
+    ThreadScope ts;
+    what_ = throwable_->toString();
+    isMessageExtracted_ = true;
+  } catch(...) {
+    what_ = kExceptionMessageFailure_;
+  }
+}
+
+const char* JniException::what() const noexcept {
+  if (!isMessageExtracted_) {
+    populateWhat();
+  }
+  return what_.c_str();
+}
+
+void JniException::setJavaException() const noexcept {
+  setJavaExceptionAndAbortOnFailure(throwable_);
+}
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/Hybrid.cpp b/VirtualApp/lib/src/main/jni/fb/jni/Hybrid.cpp
new file mode 100644
index 000000000..9d7cdcbf1
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/Hybrid.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "fb/fbjni.h"
+
+
+namespace facebook {
+namespace jni {
+
+namespace detail {
+
+local_ref<HybridData> HybridData::create() {
+  return newInstance();
+}
+
+}
+
+namespace {
+void deleteNative(alias_ref<jclass>, jlong ptr) {
+  delete reinterpret_cast<detail::BaseHybridClass*>(ptr);
+}
+}
+
+void HybridDataOnLoad() {
+  registerNatives("com/facebook/jni/HybridData$Destructor", {
+      makeNativeMethod("deleteNative", deleteNative),
+  });
+}
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/LocalString.cpp b/VirtualApp/lib/src/main/jni/fb/jni/LocalString.cpp
new file mode 100644
index 000000000..7e60d2642
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/LocalString.cpp
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <jni/LocalString.h>
+#include <fb/Environment.h>
+#include <fb/assert.h>
+
+#include <vector>
+
+namespace facebook {
+namespace jni {
+
+namespace {
+
+const uint16_t kUtf8OneByteBoundary       = 0x80;
+const uint16_t kUtf8TwoBytesBoundary      = 0x800;
+const uint16_t kUtf16HighSubLowBoundary   = 0xD800;
+const uint16_t kUtf16HighSubHighBoundary  = 0xDC00;
+const uint16_t kUtf16LowSubHighBoundary   = 0xE000;
+
+inline void encode3ByteUTF8(char32_t code, uint8_t* out) {
+  FBASSERTMSGF((code & 0xffff0000) == 0, "3 byte utf-8 encodings only valid for up to 16 bits");
+
+  out[0] = 0xE0 | (code >> 12);
+  out[1] = 0x80 | ((code >> 6) & 0x3F);
+  out[2] = 0x80 | (code & 0x3F);
+}
+
+inline char32_t decode3ByteUTF8(const uint8_t* in) {
+  return (((in[0] & 0x0f) << 12) |
+          ((in[1] & 0x3f) << 6) |
+          ( in[2] & 0x3f));
+}
+
+inline void encode4ByteUTF8(char32_t code, std::string& out, size_t offset) {
+  FBASSERTMSGF((code & 0xfff80000) == 0, "4 byte utf-8 encodings only valid for up to 21 bits");
+
+  out[offset] =     (char) (0xF0 | (code >> 18));
+  out[offset + 1] = (char) (0x80 | ((code >> 12) & 0x3F));
+  out[offset + 2] = (char) (0x80 | ((code >> 6) & 0x3F));
+  out[offset + 3] = (char) (0x80 | (code & 0x3F));
+}
+
+template <typename T>
+inline bool isFourByteUTF8Encoding(const T* utf8) {
+  return ((*utf8 & 0xF8) == 0xF0);
+}
+
+}
+
+namespace detail {
+
+size_t modifiedLength(const std::string& str) {
+  // Scan for supplementary characters
+  size_t j = 0;
+  for (size_t i = 0; i < str.size(); ) {
+    if (str[i] == 0) {
+      i += 1;
+      j += 2;
+    } else if (i + 4 > str.size() ||
+               !isFourByteUTF8Encoding(&(str[i]))) {
+      // See the code in utf8ToModifiedUTF8 for what's happening here.
+      i += 1;
+      j += 1;
+    } else {
+      i += 4;
+      j += 6;
+    }
+  }
+
+  return j;
+}
+
+// returns modified utf8 length; *length is set to strlen(str)
+size_t modifiedLength(const uint8_t* str, size_t* length) {
+  // NUL-terminated: Scan for length and supplementary characters
+  size_t i = 0;
+  size_t j = 0;
+  if (str != nullptr) {
+    while (str[i] != 0) {
+      if (str[i + 1] == 0 ||
+          str[i + 2] == 0 ||
+          str[i + 3] == 0 ||
+          !isFourByteUTF8Encoding(&(str[i]))) {
+        i += 1;
+        j += 1;
+      } else {
+        i += 4;
+        j += 6;
+      }
+    }
+  }
+
+  *length = i;
+  return j;
+}
+
+void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size_t modifiedBufLen)
+{
+  size_t j = 0;
+  for (size_t i = 0; i < len; ) {
+    FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short");
+    if (utf8[i] == 0) {
+      FBASSERTMSGF(j + 1 < modifiedBufLen, "output buffer is too short");
+      modified[j] = 0xc0;
+      modified[j + 1] = 0x80;
+      i += 1;
+      j += 2;
+      continue;
+    }
+
+    if (i + 4 > len ||
+        !isFourByteUTF8Encoding(utf8 + i)) {
+      // If the input is too short for this to be a four-byte
+      // encoding, or it isn't one for real, just copy it on through.
+      modified[j] = utf8[i];
+      i++;
+      j++;
+      continue;
+    }
+
+    // Convert 4 bytes of input to 2 * 3 bytes of output
+    char32_t code = (((utf8[i]     & 0x07) << 18) |
+                     ((utf8[i + 1] & 0x3f) << 12) |
+                     ((utf8[i + 2] & 0x3f) << 6) |
+                     ( utf8[i + 3] & 0x3f));
+    char32_t first;
+    char32_t second;
+
+    if (code > 0x10ffff) {
+      // These could be valid utf-8, but cannot be represented as modified UTF-8, due to the 20-bit
+      // limit on that representation.  Encode two replacement characters, so the expected output
+      // length lines up.
+      const char32_t kUnicodeReplacementChar = 0xfffd;
+      first = kUnicodeReplacementChar;
+      second = kUnicodeReplacementChar;
+    } else {
+      // split into surrogate pair
+      first = ((code - 0x010000) >> 10) | 0xd800;
+      second = ((code - 0x010000) & 0x3ff) | 0xdc00;
+    }
+
+    // encode each as a 3 byte surrogate value
+    FBASSERTMSGF(j + 5 < modifiedBufLen, "output buffer is too short");
+    encode3ByteUTF8(first, modified + j);
+    encode3ByteUTF8(second, modified + j + 3);
+    i += 4;
+    j += 6;
+  }
+
+  FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short");
+  modified[j++] = '\0';
+}
+
+std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept {
+  // Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient
+  std::string utf8(len, 0);
+  size_t j = 0;
+  for (size_t i = 0; i < len; ) {
+    // surrogate pair: 1101 10xx  xxxx xxxx  1101 11xx  xxxx xxxx
+    // encoded pair: 1110 1101  1010 xxxx  10xx xxxx  1110 1101  1011 xxxx  10xx xxxx
+
+    if (len >= i + 6 &&
+        modified[i] == 0xed &&
+        (modified[i + 1] & 0xf0) == 0xa0 &&
+        modified[i + 3] == 0xed &&
+        (modified[i + 4] & 0xf0) == 0xb0) {
+      // Valid surrogate pair
+      char32_t pair1 = decode3ByteUTF8(modified + i);
+      char32_t pair2 = decode3ByteUTF8(modified + i + 3);
+      char32_t ch = 0x10000 + (((pair1 & 0x3ff) << 10) |
+                               ( pair2 & 0x3ff));
+      encode4ByteUTF8(ch, utf8, j);
+      i += 6;
+      j += 4;
+      continue;
+    } else if (len >= i + 2 &&
+               modified[i] == 0xc0 &&
+               modified[i + 1] == 0x80) {
+      utf8[j] = 0;
+      i += 2;
+      j += 1;
+      continue;
+    }
+
+    // copy one byte.  This might be a one, two, or three-byte encoding.  It might be an invalid
+    // encoding of some sort, but garbage in garbage out is ok.
+
+    utf8[j] = (char) modified[i];
+    i++;
+    j++;
+  }
+
+  utf8.resize(j);
+
+  return utf8;
+}
+
+// Calculate how many bytes are needed to convert an UTF16 string into UTF8
+// UTF16 string
+size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) {
+  if (!utf16String || utf16StringLen == 0) {
+    return 0;
+  }
+
+  uint32_t utf8StringLen = 0;
+  auto utf16StringEnd = utf16String + utf16StringLen;
+  auto idx16 = utf16String;
+  while (idx16 < utf16StringEnd) {
+    auto ch = *idx16++;
+    if (ch < kUtf8OneByteBoundary) {
+      utf8StringLen++;
+    } else if (ch < kUtf8TwoBytesBoundary) {
+      utf8StringLen += 2;
+    } else if (
+        (ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
+        (idx16 < utf16StringEnd) &&
+        (*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
+      utf8StringLen += 4;
+      idx16++;
+    } else {
+      utf8StringLen += 3;
+    }
+  }
+
+  return utf8StringLen;
+}
+
+std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noexcept {
+  if (!utf16String || utf16StringLen <= 0) {
+    return "";
+  }
+
+  std::string utf8String(utf16toUTF8Length(utf16String, utf16StringLen), '\0');
+  auto idx8 = utf8String.begin();
+  auto idx16 = utf16String;
+  auto utf16StringEnd = utf16String + utf16StringLen;
+  while (idx16 < utf16StringEnd) {
+    auto ch = *idx16++;
+    if (ch < kUtf8OneByteBoundary) {
+      *idx8++ = (ch & 0x7F);
+    } else if (ch < kUtf8TwoBytesBoundary) {
+      *idx8++ = 0b11000000 | (ch >> 6);
+      *idx8++ = 0b10000000 | (ch & 0x3F);
+    } else if (
+        (ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
+        (idx16 < utf16StringEnd) &&
+        (*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
+      auto ch2 = *idx16++;
+      uint8_t trunc_byte = (((ch >> 6) & 0x0F) + 1);
+      *idx8++ = 0b11110000 | (trunc_byte >> 2);
+      *idx8++ = 0b10000000 | ((trunc_byte & 0x03) << 4) | ((ch >> 2) & 0x0F);
+      *idx8++ = 0b10000000 | ((ch & 0x03) << 4) | ((ch2 >> 6) & 0x0F);
+      *idx8++ = 0b10000000 | (ch2 & 0x3F);
+    } else {
+      *idx8++ = 0b11100000 | (ch >> 12);
+      *idx8++ = 0b10000000 | ((ch >> 6) & 0x3F);
+      *idx8++ = 0b10000000 | (ch & 0x3F);
+    }
+  }
+
+  return utf8String;
+}
+
+}
+
+LocalString::LocalString(const std::string& str)
+{
+  size_t modlen = detail::modifiedLength(str);
+  if (modlen == str.size()) {
+    // no supplementary characters, build jstring from input buffer
+    m_string = Environment::current()->NewStringUTF(str.data());
+    return;
+  }
+  auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
+  detail::utf8ToModifiedUTF8(
+    reinterpret_cast<const uint8_t*>(str.data()), str.size(),
+    reinterpret_cast<uint8_t*>(modified.data()), modified.size());
+  m_string = Environment::current()->NewStringUTF(modified.data());
+}
+
+LocalString::LocalString(const char* str)
+{
+  size_t len;
+  size_t modlen = detail::modifiedLength(reinterpret_cast<const uint8_t*>(str), &len);
+  if (modlen == len) {
+    // no supplementary characters, build jstring from input buffer
+    m_string = Environment::current()->NewStringUTF(str);
+    return;
+  }
+  auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
+  detail::utf8ToModifiedUTF8(
+    reinterpret_cast<const uint8_t*>(str), len,
+    reinterpret_cast<uint8_t*>(modified.data()), modified.size());
+  m_string = Environment::current()->NewStringUTF(modified.data());
+}
+
+LocalString::~LocalString() {
+  Environment::current()->DeleteLocalRef(m_string);
+}
+
+std::string fromJString(JNIEnv* env, jstring str) {
+  auto utf16String = JStringUtf16Extractor(env, str);
+  return detail::utf16toUTF8(utf16String.chars(), utf16String.length());
+}
+
+} }
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/OnLoad.cpp b/VirtualApp/lib/src/main/jni/fb/jni/OnLoad.cpp
new file mode 100644
index 000000000..0334729c8
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/OnLoad.cpp
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <jni/Countable.h>
+#include <fb/Environment.h>
+#include <fb/fbjni.h>
+#include <fb/fbjni/NativeRunnable.h>
+
+using namespace facebook::jni;
+
+void initialize_fbjni() {
+  CountableOnLoad(Environment::current());
+  HybridDataOnLoad();
+  JNativeRunnable::OnLoad();
+  ThreadScope::OnLoad();
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/References.cpp b/VirtualApp/lib/src/main/jni/fb/jni/References.cpp
new file mode 100644
index 000000000..0ef4b0d6d
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/References.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <fb/fbjni/References.h>
+
+namespace facebook {
+namespace jni {
+
+JniLocalScope::JniLocalScope(JNIEnv* env, jint capacity)
+    : env_(env) {
+  hasFrame_ = false;
+  auto pushResult = env->PushLocalFrame(capacity);
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(pushResult < 0);
+  hasFrame_ = true;
+}
+
+JniLocalScope::~JniLocalScope() {
+  if (hasFrame_) {
+    env_->PopLocalFrame(nullptr);
+  }
+}
+
+namespace internal {
+
+// Default implementation always returns true.
+// Platform-specific sources can override this.
+bool doesGetObjectRefTypeWork() __attribute__ ((weak));
+bool doesGetObjectRefTypeWork() {
+  return true;
+}
+
+}
+
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/WeakReference.cpp b/VirtualApp/lib/src/main/jni/fb/jni/WeakReference.cpp
new file mode 100644
index 000000000..1fd4e50ac
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/WeakReference.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <fb/Environment.h>
+#include <jni/WeakReference.h>
+
+namespace facebook {
+namespace jni {
+
+WeakReference::WeakReference(jobject strongRef) :
+  m_weakReference(Environment::current()->NewWeakGlobalRef(strongRef))
+{
+}
+
+WeakReference::~WeakReference() {
+  auto env = Environment::current();
+  FBASSERTMSGF(env, "Attempt to delete jni::WeakReference from non-JNI thread");
+  env->DeleteWeakGlobalRef(m_weakReference);
+}
+
+ResolvedWeakReference::ResolvedWeakReference(jobject weakRef) :
+  m_strongReference(Environment::current()->NewLocalRef(weakRef))
+{
+}
+
+ResolvedWeakReference::ResolvedWeakReference(const RefPtr<WeakReference>& weakRef) :
+  m_strongReference(Environment::current()->NewLocalRef(weakRef->weakRef()))
+{
+}
+
+ResolvedWeakReference::~ResolvedWeakReference() {
+  if (m_strongReference)
+    Environment::current()->DeleteLocalRef(m_strongReference);
+}
+
+} }
+
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/android/CpuCapabilities.cpp b/VirtualApp/lib/src/main/jni/fb/jni/android/CpuCapabilities.cpp
new file mode 100644
index 000000000..f16fbeef6
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/android/CpuCapabilities.cpp
@@ -0,0 +1,70 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include <fb/CpuCapabilities.h>
+#include <cpu-features.h>
+#include <fb/Environment.h>
+#include <glog/logging.h>
+#include <jni/Registration.h>
+
+namespace facebook { namespace jni {
+
+// =========================================
+// returns true if this device supports NEON calls, false otherwise
+jboolean nativeDeviceSupportsNeon(JNIEnv* env, jobject obj) {
+  if (android_getCpuFamily() != ANDROID_CPU_FAMILY_ARM) {
+    VLOG(2) << "NEON disabled, not an ARM CPU";
+    return false;
+  }
+  uint64_t cpufeatures = android_getCpuFeatures();
+  if ((cpufeatures & ANDROID_CPU_ARM_FEATURE_ARMv7) == 0) {
+    VLOG(2) << "NEON disabled, not an ARMv7 CPU";
+    return false;
+  }
+  if ((cpufeatures & ANDROID_CPU_ARM_FEATURE_NEON) == 0) {
+    VLOG(2) << "NEON disabled, not supported";
+    return false;
+  }
+
+  VLOG(2) << "NEON supported and enabled";
+  return true;
+}
+
+// =========================================
+// returns true if this device supports VFP_FP16, false otherwise.
+jboolean nativeDeviceSupportsVFPFP16(JNIEnv *env, jobject obj) {
+  uint64_t cpufeatures = android_getCpuFeatures();
+  if ((cpufeatures & ANDROID_CPU_ARM_FEATURE_VFP_FP16) == 0) {
+    VLOG(2) << "VPF_FP16 disabled, not supported";
+    return false;
+  }
+  VLOG(2) << "VFP_FP16 supported and enabled";
+  return true;
+}
+
+// =========================================
+// returns true if this device is x86 based, false otherwise
+jboolean nativeDeviceSupportsX86(JNIEnv* env, jobject obj) {
+  return (android_getCpuFamily() == ANDROID_CPU_FAMILY_X86);
+}
+
+// =========================================
+// register native methods
+void initialize_cpucapabilities() {
+  facebook::jni::registerNatives(
+      Environment::current(),
+      "com/facebook/jni/CpuCapabilitiesJni",
+      {
+        { "nativeDeviceSupportsNeon",
+          "()Z",
+          (void*) nativeDeviceSupportsNeon },
+        { "nativeDeviceSupportsVFPFP16",
+          "()Z",
+          (void*) nativeDeviceSupportsVFPFP16 },
+        { "nativeDeviceSupportsX86",
+          "()Z",
+          (void*) nativeDeviceSupportsX86 },
+      }
+  );
+}
+
+} } // namespace facebook::jni
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/android/ReferenceChecking.cpp b/VirtualApp/lib/src/main/jni/fb/jni/android/ReferenceChecking.cpp
new file mode 100644
index 000000000..9893db41d
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/android/ReferenceChecking.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#ifndef __ANDROID__
+#error "This file should only be compiled for Android."
+#endif
+
+#include <fb/fbjni/References.h>
+#include <fb/fbjni/CoreClasses.h>
+
+namespace facebook {
+namespace jni {
+namespace internal {
+
+static int32_t getApiLevel() {
+  auto cls = findClassLocal("android/os/Build$VERSION");
+  auto fld = cls->getStaticField<int32_t>("SDK_INT");
+  if (fld) {
+    return cls->getStaticFieldValue(fld);
+  }
+  return 0;
+}
+
+bool doesGetObjectRefTypeWork() {
+  static auto level = getApiLevel();
+  return level >= 14;
+}
+
+}
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/fbjni.cpp b/VirtualApp/lib/src/main/jni/fb/jni/fbjni.cpp
new file mode 100644
index 000000000..62e27a756
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/fbjni.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <fb/fbjni.h>
+
+#include <mutex>
+#include <vector>
+#include <jni/LocalString.h>
+#include <fb/log.h>
+
+namespace facebook {
+namespace jni {
+
+jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept {
+  static std::once_flag flag{};
+  // TODO (t7832883): DTRT when we have exception pointers
+  static auto error_msg = std::string{"Failed to initialize fbjni"};
+  static auto error_occured = false;
+
+  std::call_once(flag, [vm] {
+    try {
+      Environment::initialize(vm);
+    } catch (std::exception& ex) {
+      error_occured = true;
+      try {
+        error_msg = std::string{"Failed to initialize fbjni: "} + ex.what();
+      } catch (...) {
+        // Ignore, we already have a fall back message
+      }
+    } catch (...) {
+      error_occured = true;
+    }
+  });
+
+  try {
+    if (error_occured) {
+      throw std::runtime_error(error_msg);
+    }
+
+    init_fn();
+  } catch (const std::exception& e) {
+    FBLOGE("error %s", e.what());
+    translatePendingCppExceptionToJavaException();
+  } catch (...) {
+    translatePendingCppExceptionToJavaException();
+    // So Java will handle the translated exception, fall through and
+    // return a good version number.
+  }
+  return JNI_VERSION_1_6;
+}
+
+alias_ref<JClass> findClassStatic(const char* name) {
+  const auto env = internal::getEnv();
+  if (!env) {
+    throw std::runtime_error("Unable to retrieve JNIEnv*.");
+  }
+  local_ref<jclass> cls = adopt_local(env->FindClass(name));
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
+  auto leaking_ref = (jclass)env->NewGlobalRef(cls.get());
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!leaking_ref);
+  return wrap_alias(leaking_ref);
+}
+
+local_ref<JClass> findClassLocal(const char* name) {
+  const auto env = internal::getEnv();
+  if (!env) {
+    throw std::runtime_error("Unable to retrieve JNIEnv*.");
+  }
+  auto cls = env->FindClass(name);
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
+  return adopt_local(cls);
+}
+
+
+// jstring /////////////////////////////////////////////////////////////////////////////////////////
+
+std::string JString::toStdString() const {
+  const auto env = internal::getEnv();
+  auto utf16String = JStringUtf16Extractor(env, self());
+  return detail::utf16toUTF8(utf16String.chars(), utf16String.length());
+}
+
+local_ref<JString> make_jstring(const char* utf8) {
+  if (!utf8) {
+    return {};
+  }
+  const auto env = internal::getEnv();
+  size_t len;
+  size_t modlen = detail::modifiedLength(reinterpret_cast<const uint8_t*>(utf8), &len);
+  jstring result;
+  if (modlen == len) {
+    // The only difference between utf8 and modifiedUTF8 is in encoding 4-byte UTF8 chars
+    // and '\0' that is encoded on 2 bytes.
+    //
+    // Since modifiedUTF8-encoded string can be no shorter than it's UTF8 conterpart we
+    // know that if those two strings are of the same length we don't need to do any
+    // conversion -> no 4-byte chars nor '\0'.
+    result = env->NewStringUTF(utf8);
+  } else {
+    auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
+    detail::utf8ToModifiedUTF8(
+      reinterpret_cast<const uint8_t*>(utf8), len,
+      reinterpret_cast<uint8_t*>(modified.data()), modified.size());
+    result = env->NewStringUTF(modified.data());
+  }
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  return adopt_local(result);
+}
+
+
+// JniPrimitiveArrayFunctions //////////////////////////////////////////////////////////////////////
+
+#pragma push_macro("DEFINE_PRIMITIVE_METHODS")
+#undef DEFINE_PRIMITIVE_METHODS
+#define DEFINE_PRIMITIVE_METHODS(TYPE, NAME, SMALLNAME)                        \
+                                                                               \
+template<>                                                                     \
+FBEXPORT                                                                       \
+TYPE* JPrimitiveArray<TYPE ## Array>::getElements(jboolean* isCopy) {          \
+  auto env = internal::getEnv();                                               \
+  TYPE* res =  env->Get ## NAME ## ArrayElements(self(), isCopy);              \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                      \
+  return res;                                                                  \
+}                                                                              \
+                                                                               \
+template<>                                                                     \
+FBEXPORT                                                                       \
+void JPrimitiveArray<TYPE ## Array>::releaseElements(                          \
+    TYPE* elements, jint mode) {                                               \
+  auto env = internal::getEnv();                                               \
+  env->Release ## NAME ## ArrayElements(self(), elements, mode);               \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                      \
+}                                                                              \
+                                                                               \
+template<>                                                                     \
+FBEXPORT                                                                       \
+void JPrimitiveArray<TYPE ## Array>::getRegion(                                \
+    jsize start, jsize length, TYPE* buf) {                                    \
+  auto env = internal::getEnv();                                               \
+  env->Get ## NAME ## ArrayRegion(self(), start, length, buf);                 \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                      \
+}                                                                              \
+                                                                               \
+template<>                                                                     \
+FBEXPORT                                                                       \
+void JPrimitiveArray<TYPE ## Array>::setRegion(                                \
+    jsize start, jsize length, const TYPE* elements) {                         \
+  auto env = internal::getEnv();                                               \
+  env->Set ## NAME ## ArrayRegion(self(), start, length, elements);            \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                      \
+}                                                                              \
+                                                                               \
+FBEXPORT                                                                       \
+local_ref<TYPE ## Array> make_ ## SMALLNAME ## _array(jsize size) {            \
+  auto array = internal::getEnv()->New ## NAME ## Array(size);                 \
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!array);                                     \
+  return adopt_local(array);                                                   \
+}                                                                              \
+                                                                               \
+template<>                                                                     \
+FBEXPORT                                                                       \
+local_ref<TYPE ## Array> JArray ## NAME::newArray(size_t count) {              \
+  return make_ ## SMALLNAME ## _array(count);                                  \
+}                                                                              \
+                                                                               \
+
+DEFINE_PRIMITIVE_METHODS(jboolean, Boolean, boolean)
+DEFINE_PRIMITIVE_METHODS(jbyte, Byte, byte)
+DEFINE_PRIMITIVE_METHODS(jchar, Char, char)
+DEFINE_PRIMITIVE_METHODS(jshort, Short, short)
+DEFINE_PRIMITIVE_METHODS(jint, Int, int)
+DEFINE_PRIMITIVE_METHODS(jlong, Long, long)
+DEFINE_PRIMITIVE_METHODS(jfloat, Float, float)
+DEFINE_PRIMITIVE_METHODS(jdouble, Double, double)
+#pragma pop_macro("DEFINE_PRIMITIVE_METHODS")
+
+// Internal debug /////////////////////////////////////////////////////////////////////////////////
+
+namespace internal {
+
+FBEXPORT ReferenceStats g_reference_stats;
+
+FBEXPORT void facebook::jni::internal::ReferenceStats::reset() noexcept {
+  locals_deleted = globals_deleted = weaks_deleted = 0;
+}
+
+}
+
+}}
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/java/BUCK b/VirtualApp/lib/src/main/jni/fb/jni/java/BUCK
new file mode 100644
index 000000000..c1bdbb70e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/java/BUCK
@@ -0,0 +1,10 @@
+include_defs("//ReactAndroid/DEFS")
+
+java_library(
+    name = "java",
+    srcs = glob(["**/*.java"]),
+    visibility = ["PUBLIC"],
+    deps = [
+        "//java/com/facebook/proguard/annotations:annotations",
+    ],
+)
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/java/CppException.java b/VirtualApp/lib/src/main/jni/fb/jni/java/CppException.java
new file mode 100644
index 000000000..75f0144c8
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/java/CppException.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package fb.jni.java;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+
+@DoNotStrip
+public class CppException extends RuntimeException {
+  @DoNotStrip
+  public CppException(String message) {
+    super(message);
+  }
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/java/CppSystemErrorException.java b/VirtualApp/lib/src/main/jni/fb/jni/java/CppSystemErrorException.java
new file mode 100644
index 000000000..23e7ff784
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/java/CppSystemErrorException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package fb.jni.java;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+
+@DoNotStrip
+public class CppSystemErrorException extends CppException {
+  int errorCode;
+
+  @DoNotStrip
+  public CppSystemErrorException(String message, int errorCode) {
+    super(message);
+    this.errorCode = errorCode;
+  }
+
+  public int getErrorCode() {
+    return errorCode;
+  }
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/java/UnknownCppException.java b/VirtualApp/lib/src/main/jni/fb/jni/java/UnknownCppException.java
new file mode 100644
index 000000000..b1ab5677e
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/java/UnknownCppException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package fb.jni.java;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+
+@DoNotStrip
+public class UnknownCppException extends CppException {
+  @DoNotStrip
+  public UnknownCppException() {
+    super("Unknown");
+  }
+
+  @DoNotStrip
+  public UnknownCppException(String message) {
+    super(message);
+  }
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/jni/jni_helpers.cpp b/VirtualApp/lib/src/main/jni/fb/jni/jni_helpers.cpp
new file mode 100644
index 000000000..8bf7c3542
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/jni/jni_helpers.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <jni.h>
+#include <stddef.h>
+#include <cstdio>
+
+#include <jni/jni_helpers.h>
+
+#define MSG_SIZE 1024
+
+namespace facebook {
+
+/**
+ * Instructs the JNI environment to throw an exception.
+ *
+ * @param pEnv JNI environment
+ * @param szClassName class name to throw
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+jint throwException(JNIEnv* pEnv, const char* szClassName, const char* szFmt, va_list va_args) {
+  char szMsg[MSG_SIZE];
+  vsnprintf(szMsg, MSG_SIZE, szFmt, va_args);
+  jclass exClass = pEnv->FindClass(szClassName);
+  return pEnv->ThrowNew(exClass, szMsg);
+}
+
+/**
+ * Instructs the JNI environment to throw a NoClassDefFoundError.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+jint throwNoClassDefError(JNIEnv* pEnv, const char* szFmt, ...) {
+  va_list va_args;
+  va_start(va_args, szFmt);
+  jint ret = throwException(pEnv, "java/lang/NoClassDefFoundError", szFmt, va_args);
+  va_end(va_args);
+  return ret;
+}
+
+/**
+ * Instructs the JNI environment to throw a RuntimeException.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+jint throwRuntimeException(JNIEnv* pEnv, const char* szFmt, ...) {
+  va_list va_args;
+  va_start(va_args, szFmt);
+  jint ret = throwException(pEnv, "java/lang/RuntimeException", szFmt, va_args);
+  va_end(va_args);
+  return ret;
+}
+
+/**
+ * Instructs the JNI environment to throw an IllegalArgumentException.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+jint throwIllegalArgumentException(JNIEnv* pEnv, const char* szFmt, ...) {
+  va_list va_args;
+  va_start(va_args, szFmt);
+  jint ret = throwException(pEnv, "java/lang/IllegalArgumentException", szFmt, va_args);
+  va_end(va_args);
+  return ret;
+}
+
+/**
+ * Instructs the JNI environment to throw an IllegalStateException.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+jint throwIllegalStateException(JNIEnv* pEnv, const char* szFmt, ...) {
+  va_list va_args;
+  va_start(va_args, szFmt);
+  jint ret = throwException(pEnv, "java/lang/IllegalStateException", szFmt, va_args);
+  va_end(va_args);
+  return ret;
+}
+
+/**
+ * Instructs the JNI environment to throw an OutOfMemoryError.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+jint throwOutOfMemoryError(JNIEnv* pEnv, const char* szFmt, ...) {
+  va_list va_args;
+  va_start(va_args, szFmt);
+  jint ret = throwException(pEnv, "java/lang/OutOfMemoryError", szFmt, va_args);
+  va_end(va_args);
+  return ret;
+}
+
+/**
+ * Instructs the JNI environment to throw an AssertionError.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+jint throwAssertionError(JNIEnv* pEnv, const char* szFmt, ...) {
+  va_list va_args;
+  va_start(va_args, szFmt);
+  jint ret = throwException(pEnv, "java/lang/AssertionError", szFmt, va_args);
+  va_end(va_args);
+  return ret;
+}
+
+/**
+ * Instructs the JNI environment to throw an IOException.
+ *
+ * @param pEnv JNI environment
+ * @param szFmt sprintf-style format string
+ * @param ... sprintf-style args
+ * @return 0 on success; a negative value on failure
+ */
+jint throwIOException(JNIEnv* pEnv, const char* szFmt, ...) {
+  va_list va_args;
+  va_start(va_args, szFmt);
+  jint ret = throwException(pEnv, "java/io/IOException", szFmt, va_args);
+  va_end(va_args);
+  return ret;
+}
+
+/**
+ * Finds the specified class. If it's not found, instructs the JNI environment to throw an
+ * exception.
+ *
+ * @param pEnv JNI environment
+ * @param szClassName the classname to find in JNI format (e.g. "java/lang/String")
+ * @return the class or NULL if not found (in which case a pending exception will be queued). This
+ *     returns a global reference (JNIEnv::NewGlobalRef).
+ */
+jclass findClassOrThrow(JNIEnv* pEnv, const char* szClassName) {
+  jclass clazz = pEnv->FindClass(szClassName);
+  if (!clazz) {
+    return NULL;
+  }
+  return (jclass) pEnv->NewGlobalRef(clazz);
+}
+
+/**
+ * Finds the specified field of the specified class. If it's not found, instructs the JNI
+ * environment to throw an exception.
+ *
+ * @param pEnv JNI environment
+ * @param clazz the class to lookup the field in
+ * @param szFieldName the name of the field to find
+ * @param szSig the signature of the field
+ * @return the field or NULL if not found (in which case a pending exception will be queued)
+ */
+jfieldID getFieldIdOrThrow(JNIEnv* pEnv, jclass clazz, const char* szFieldName, const char* szSig) {
+  return pEnv->GetFieldID(clazz, szFieldName, szSig);
+}
+
+/**
+ * Finds the specified method of the specified class. If it's not found, instructs the JNI
+ * environment to throw an exception.
+ *
+ * @param pEnv JNI environment
+ * @param clazz the class to lookup the method in
+ * @param szMethodName the name of the method to find
+ * @param szSig the signature of the method
+ * @return the method or NULL if not found (in which case a pending exception will be queued)
+ */
+jmethodID getMethodIdOrThrow(
+    JNIEnv* pEnv,
+    jclass clazz,
+    const char* szMethodName,
+    const char* szSig) {
+  return pEnv->GetMethodID(clazz, szMethodName, szSig);
+}
+
+} // namespace facebook
diff --git a/VirtualApp/lib/src/main/jni/fb/log.cpp b/VirtualApp/lib/src/main/jni/fb/log.cpp
new file mode 100644
index 000000000..b58b7ac94
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/log.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <fb/log.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#define LOG_BUFFER_SIZE 4096
+static LogHandler gLogHandler;
+
+void setLogHandler(LogHandler logHandler) {
+  gLogHandler = logHandler;
+}
+
+int fb_printLog(int prio, const char *tag,  const char *fmt, ...) {
+  char logBuffer[LOG_BUFFER_SIZE];
+
+  va_list va_args;
+  va_start(va_args, fmt);
+  int result = vsnprintf(logBuffer, sizeof(logBuffer), fmt, va_args);
+  va_end(va_args);
+  if (gLogHandler != NULL) {
+      gLogHandler(prio, tag, logBuffer);
+  }
+  __android_log_write(prio, tag, logBuffer);
+  return result;
+}
+
+void logPrintByDelims(int priority, const char* tag, const char* delims,
+        const char* msg, ...)
+{
+    va_list ap;
+    char buf[32768];
+    char* context;
+    char* tok;
+
+    va_start(ap, msg);
+    vsnprintf(buf, sizeof(buf), msg, ap);
+    va_end(ap);
+
+    tok = strtok_r(buf, delims, &context);
+
+    if (!tok) {
+        return;
+    }
+
+    do {
+        __android_log_write(priority, tag, tok);
+    } while ((tok = strtok_r(NULL, delims, &context)));
+}
+
+#ifndef ANDROID
+
+// Implementations of the basic android logging functions for non-android platforms.
+
+static char logTagChar(int prio) {
+  switch (prio) {
+    default:
+    case ANDROID_LOG_UNKNOWN:
+    case ANDROID_LOG_DEFAULT:
+    case ANDROID_LOG_SILENT:
+      return ' ';
+    case ANDROID_LOG_VERBOSE:
+      return 'V';
+    case ANDROID_LOG_DEBUG:
+      return 'D';
+    case ANDROID_LOG_INFO:
+      return 'I';
+    case ANDROID_LOG_WARN:
+      return 'W';
+    case ANDROID_LOG_ERROR:
+      return 'E';
+    case ANDROID_LOG_FATAL:
+      return 'F';
+  }
+}
+
+int __android_log_write(int prio, const char *tag, const char *text) {
+  return fprintf(stderr, "[%c/%.16s] %s\n", logTagChar(prio), tag, text);
+}
+
+int __android_log_print(int prio, const char *tag,  const char *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+
+  int res = fprintf(stderr, "[%c/%.16s] ", logTagChar(prio), tag);
+  res += vfprintf(stderr, "%s\n", ap);
+
+  va_end(ap);
+  return res;
+}
+
+#endif
diff --git a/VirtualApp/lib/src/main/jni/fb/lyra/lyra.cpp b/VirtualApp/lib/src/main/jni/fb/lyra/lyra.cpp
new file mode 100644
index 000000000..f915ecef2
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/lyra/lyra.cpp
@@ -0,0 +1,120 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include <fb/lyra.h>
+
+#include <ios>
+#include <memory>
+#include <vector>
+
+#include <dlfcn.h>
+#include <unwind.h>
+
+using namespace std;
+
+namespace facebook {
+namespace lyra {
+
+namespace {
+
+class IosFlagsSaver {
+  ios_base& ios_;
+  ios_base::fmtflags flags_;
+
+ public:
+  IosFlagsSaver(ios_base& ios)
+  : ios_(ios),
+    flags_(ios.flags())
+  {}
+
+  ~IosFlagsSaver() {
+    ios_.flags(flags_);
+  }
+};
+
+struct BacktraceState {
+  size_t skip;
+  vector<InstructionPointer>& stackTrace;
+};
+
+_Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) {
+  BacktraceState* state = reinterpret_cast<BacktraceState*>(arg);
+  auto absoluteProgramCounter =
+      reinterpret_cast<InstructionPointer>(_Unwind_GetIP(context));
+
+  if (state->skip > 0) {
+    --state->skip;
+    return _URC_NO_REASON;
+  }
+
+  if (state->stackTrace.size() == state->stackTrace.capacity()) {
+    return _URC_END_OF_STACK;
+  }
+
+  state->stackTrace.push_back(absoluteProgramCounter);
+
+  return _URC_NO_REASON;
+}
+
+void captureBacktrace(size_t skip, vector<InstructionPointer>& stackTrace) {
+  // Beware of a bug on some platforms, which makes the trace loop until the
+  // buffer is full when it reaches a noexcept function. It seems to be fixed in
+  // newer versions of gcc. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56846
+  // TODO(t10738439): Investigate workaround for the stack trace bug
+  BacktraceState state = {skip, stackTrace};
+  _Unwind_Backtrace(unwindCallback, &state);
+}
+}
+
+void getStackTrace(vector<InstructionPointer>& stackTrace, size_t skip) {
+  stackTrace.clear();
+  captureBacktrace(skip + 1, stackTrace);
+}
+
+// TODO(t10737622): Improve on-device symbolification
+void getStackTraceSymbols(vector<StackTraceElement>& symbols,
+                          const vector<InstructionPointer>& trace) {
+  symbols.clear();
+  symbols.reserve(trace.size());
+
+  for (size_t i = 0; i < trace.size(); ++i) {
+    Dl_info info;
+    if (dladdr(trace[i], &info)) {
+      symbols.emplace_back(trace[i], info.dli_fbase, info.dli_saddr,
+                           info.dli_fname ? info.dli_fname : "",
+                           info.dli_sname ? info.dli_sname : "");
+    }
+  }
+}
+
+ostream& operator<<(ostream& out, const StackTraceElement& elm) {
+  IosFlagsSaver flags{out};
+
+  // TODO(t10748683): Add build id to the output
+  out << "{dso=" << elm.libraryName() << " offset=" << hex
+      << showbase << elm.libraryOffset();
+
+  if (!elm.functionName().empty()) {
+    out << " func=" << elm.functionName() << "()+" << elm.functionOffset();
+  }
+
+  out << " build-id=" << hex << setw(8) << 0
+      << "}";
+
+  return out;
+}
+
+// TODO(t10737667): The implement a tool that parse the stack trace and
+// symbolicate it
+ostream& operator<<(ostream& out, const vector<StackTraceElement>& trace) {
+  IosFlagsSaver flags{out};
+
+  auto i = 0;
+  out << "Backtrace:\n";
+  for (auto& elm : trace) {
+    out << "    #" << dec << setfill('0') << setw(2) << i++ << " " << elm << '\n';
+  }
+
+  return out;
+}
+}
+}
diff --git a/VirtualApp/lib/src/main/jni/fb/onload.cpp b/VirtualApp/lib/src/main/jni/fb/onload.cpp
new file mode 100644
index 000000000..2caebc37a
--- /dev/null
+++ b/VirtualApp/lib/src/main/jni/fb/onload.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <jni.h>
+#ifndef DISABLE_CPUCAP
+#include <fb/CpuCapabilities.h>
+#endif
+#include <fb/fbjni.h>
+
+using namespace facebook::jni;
+
+void initialize_xplatinit();
+void initialize_fbjni();
+
+JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+  return facebook::jni::initialize(vm, [] {
+    initialize_fbjni();
+#ifndef DISABLE_XPLAT
+    initialize_xplatinit();
+#endif
+#ifndef DISABLE_CPUCAP
+    initialize_cpucapabilities();
+#endif
+  });
+}
diff --git a/VirtualApp/lib/src/main/jniLibs/armeabi-v7a/libdexinterpret.so b/VirtualApp/lib/src/main/jniLibs/armeabi-v7a/libdexinterpret.so
deleted file mode 100644
index 3a7131e90..000000000
Binary files a/VirtualApp/lib/src/main/jniLibs/armeabi-v7a/libdexinterpret.so and /dev/null differ
diff --git a/VirtualApp/lib/src/main/jniLibs/armeabi/libdexinterpret.so b/VirtualApp/lib/src/main/jniLibs/armeabi/libdexinterpret.so
deleted file mode 100644
index 9556b3062..000000000
Binary files a/VirtualApp/lib/src/main/jniLibs/armeabi/libdexinterpret.so and /dev/null differ
diff --git a/VirtualApp/lib/src/main/jniLibs/x86/libdexinterpret.so b/VirtualApp/lib/src/main/jniLibs/x86/libdexinterpret.so
deleted file mode 100644
index 7c68bd6ef..000000000
Binary files a/VirtualApp/lib/src/main/jniLibs/x86/libdexinterpret.so and /dev/null differ
diff --git a/VirtualApp/lib/src/main/libs/android-art-interpret-3.0.0.jar b/VirtualApp/lib/src/main/libs/android-art-interpret-3.0.0.jar
deleted file mode 100644
index 31ac0781c..000000000
Binary files a/VirtualApp/lib/src/main/libs/android-art-interpret-3.0.0.jar and /dev/null differ
diff --git a/VirtualApp/lib/src/main/res/layout/app_not_authorized.xml b/VirtualApp/lib/src/main/res/layout/app_not_authorized.xml
new file mode 100644
index 000000000..c7e936172
--- /dev/null
+++ b/VirtualApp/lib/src/main/res/layout/app_not_authorized.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <!-- Customizable description text -->
+    <TextView android:id="@+id/description"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_gravity="start|center_vertical"
+        android:paddingTop="16dip"
+        android:paddingBottom="16dip"
+        android:paddingStart="16dip"
+        android:paddingEnd="16dip"
+        android:text="Change not allowed"
+    />
+
+    <!-- Horizontal divider line -->
+    <View android:layout_height="1dip"
+        android:layout_width="match_parent"
+        android:background="?android:attr/dividerHorizontal" />
+
+    <!-- Alert dialog style buttons along the bottom. -->
+    <LinearLayout android:id="@+id/button_bar"
+        style="?android:attr/buttonBarStyle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:measureWithLargestChild="true">
+        <Button android:id="@android:id/button1"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_width="0dp" android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@android:string/yes"
+            android:onClick="onCancelButtonClicked" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/VirtualApp/lib/src/main/res/layout/choose_account_row.xml b/VirtualApp/lib/src/main/res/layout/choose_account_row.xml
new file mode 100644
index 000000000..fb50f9e22
--- /dev/null
+++ b/VirtualApp/lib/src/main/res/layout/choose_account_row.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingLeft="16dp"
+    android:paddingStart="16dp"
+    android:paddingRight="16dp"
+    android:paddingEnd="16dp"
+    android:orientation="horizontal" >
+
+   <ImageView android:id="@+id/account_row_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:paddingRight="8dip"
+        android:paddingEnd="8dip" />
+
+    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/account_row_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:gravity="center_vertical"
+        android:minHeight="?android:listPreferredItemHeight" />
+
+</LinearLayout>
diff --git a/VirtualApp/lib/src/main/res/layout/choose_account_type.xml b/VirtualApp/lib/src/main/res/layout/choose_account_type.xml
new file mode 100644
index 000000000..b1b2e99e2
--- /dev/null
+++ b/VirtualApp/lib/src/main/res/layout/choose_account_type.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <View android:layout_height="3dip"
+          android:layout_width="match_parent"
+          android:background="#323232"/>
+
+    <ListView android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:drawSelectorOnTop="false"
+        android:scrollbarAlwaysDrawVerticalTrack="true" />
+
+</LinearLayout>
diff --git a/VirtualApp/lib/src/main/res/layout/choose_type_and_account.xml b/VirtualApp/lib/src/main/res/layout/choose_type_and_account.xml
new file mode 100644
index 000000000..08e7bf1d8
--- /dev/null
+++ b/VirtualApp/lib/src/main/res/layout/choose_type_and_account.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <!-- Customizable description text -->
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start|center_vertical"
+        android:paddingBottom="16dip"
+        android:paddingEnd="16dip"
+        android:paddingStart="16dip"
+        android:paddingTop="16dip"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    <!-- List of accounts, with "Add new account" as the last item -->
+    <ListView
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:choiceMode="singleChoice"
+        android:drawSelectorOnTop="false"
+        android:scrollbarAlwaysDrawVerticalTrack="true" />
+
+    <!-- Horizontal divider line -->
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:background="?android:attr/dividerHorizontal" />
+
+    <!-- Alert dialog style buttons along the bottom. -->
+    <LinearLayout
+        android:id="@+id/button_bar"
+        style="?android:attr/buttonBarStyle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:measureWithLargestChild="true">
+
+        <Button
+            android:id="@android:id/button1"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:onClick="onCancelButtonClicked"
+            android:text="@android:string/no" />
+
+        <Button
+            android:id="@android:id/button2"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:onClick="onOkButtonClicked"
+            android:text="@android:string/yes" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/VirtualApp/lib/src/main/res/values-ru/strings.xml b/VirtualApp/lib/src/main/res/values-ru/strings.xml
new file mode 100644
index 000000000..2bfaec764
--- /dev/null
+++ b/VirtualApp/lib/src/main/res/values-ru/strings.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+	<string name="virtual_installer">Установщик VirtualPackage</string>
+	<string name="owner_name">Админ</string>
+	<string name="choose">Выбрать</string>
+	<string name="choose_empty">Выбор пуст</string>
+	<string name="noApplications">Нет приложений для поиска</string>
+	<string name="add_account_button_label">Добавить аккаунт</string>
+	<string name="create_shortcut_already_exist">Этот ярлык уже существует</string>
+</resources>
diff --git a/VirtualApp/lib/src/main/res/values/strings.xml b/VirtualApp/lib/src/main/res/values/strings.xml
index de494ce4e..ba5c9c039 100644
--- a/VirtualApp/lib/src/main/res/values/strings.xml
+++ b/VirtualApp/lib/src/main/res/values/strings.xml
@@ -1,8 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
+    <string name="engine_process_name">:x</string>
     <string name="virtual_installer">VirtualPackage Installer</string>
     <string name="owner_name">Admin</string>
     <string name="choose">Choose</string>
     <string name="choose_empty">Chooser is Empty</string>
     <string name="noApplications">No find applications</string>
+    <string name="add_account_button_label">Add account</string>
+    <string name="create_shortcut_already_exist">The shortcut already exists</string>
 </resources>
diff --git a/VirtualApp/settings.gradle b/VirtualApp/settings.gradle
index 3a5a91932..d8895e899 100644
--- a/VirtualApp/settings.gradle
+++ b/VirtualApp/settings.gradle
@@ -1 +1,2 @@
-include ':lib', ':app'
+include ':lib', ':app', ':launcher'
+rootProject.name="VirtualXposed"