## What is VA? ##
VirtualAPP (abbreviation: VA) is a sandbox product running on Android system, which can be understood as a lightweight "Android virtual machine". Its product form is a highly extensible, customizable, integrated SDK that allows you to develop a variety of seemingly impossible projects based on or using VA. Now, VA is widely used in many technology fields as following: mini game collection, blockchain, cloud control, silent hot fix and so on. On the one hand, you can realize cloud control mobile office security and achieve military and government data isolation with VA. On the other hand, you can implement script automation, device-info-mock, and plug-in development. Meanwhile, you can realize multi space and games booster. You can also rent the mobile game account and use the mobile controller without activation by VA. **The code on Github has stopped updating in December 2017. The code of business version is continuously being updated. If you need license to obtain the latest code, please contact WeChat: 10890.**
## Terminology in VA ##
Terminology | Explanation
---- | ---
Host | The APP that integrates the VirtualAPP SDK is called host.
Host Plug-in | A host package is used to run another ABI on the same device. It also called plug-in package,extension package, host plug-in package, host extension package.
Virtual APP / VAPP | App installed in the VA space
External APP | App installed in the device
## VA Technical architecture ##

VA technology involves the APP layer, Framework layer and Native layer of Android in total.
App must be installed on the system before it can run. The APP installed inside the VA space is not actually installed into the system, so it cannot run. Then how to get it to run?
Answer: The only way to do this is to "cheat" the system into thinking it has been installed. This "cheat" process is the core work of the VA Framework, and is also the core technical principle of the VA.
**Here is the description of what did each layer do:**
Layer | Main work
---- | ---
VA Space | An internal space is provided by the VA for the installation of the APP to be run inside it, and this space is system isolated.
VA Framework | This layer is mainly a proxy for Android Framework and VAPP, which is the core of VA. And VA provides a set of VA Framework of its own, which is between Android Framework and VA APP. 1. For VAPP, all the system services it accesses have been proxied by VA Framework, which will modify the request parameters of VAPP and send all the parameters related to VAPP installation information to Android Framework after changing them to the parameters of the host (Some of the requests will be sent to their own VA Server to be processed directly, and no longer send to the Android system). This way Android Framework receives the VAPP request and checks the parameters, and it will think there is no problem.2. When the Android system finishes processing the request and returns the result, the VA Framework will also intercept the return result and restore all the parameters that have been original modified to those that were sent during the VAPP request. This way the interaction between VAPP and Android system can work.
VA Native | The main purpose of this layer is to accomplish 2 tasks: IO redirection and the request modification for VA APP to interact with Android system. 1. IO redirection is some APPs may be accessed through the hard code absolute path. But if the APP is not installed to the system, this path does not exist. Through IO redirection, it will be redirected to the path to install inside VA.2. In addition, there are some jni functions that cannot be hooked in VA Framework, so they need to be hooked in the native layer.
In summary:
As you can see from the above technical architecture, the internal VA APP actually runs on top of VA's own VA Framework. VA has intercepted all system requests from its internal APP, and through this technology it can also have full control over the APP, not just the multi space. And for the convenience of developers, VA also provides SDK and Hook SDK.
## VA Process architecture#

There are five types of processes in the VA’s runtime: CHILD process, VA Host Main process, VA Host Plugin process, VAPP Client process, and VAServer process.
To support both 32-bit and 64-bit APPs, VA needs to install two packages: a master package and a plug-in package ( In this document, the main package is 32 bits and the plug-in package is 64 bit ).
Two packages are also necessary because a package can only run in one mode, either 32-bit or 64-bit. So for 32-bit APPs, VA uses the 32-bit main package to run, and for 64-bit APPs, VA uses the 64-bit plug-in package to run.
The main package contains all the code of VA, and the plug-in package contains only one piece of code that loads the main package code for execution, no other code. So plug-in package rarely needs to be updated, just the main package.
In addition, whether the main package is chosen to use 32-bit or 64-bit can be modified in the configuration file ( For example, for users who want to access GooglePlay, it will be modified to 64-bit for the main package and 32-bit for the plug-in package ).
**The functions and explanations of the each type of process are as follows:**
Process Type | Function
---- | ---
CHILD | Other processes integrated by VA Host, such as: keepalive process, push process, etc.
VA Host Main | The process where the UI main interface of the VA main package is located. The default main package is 32-bit and the plug-in package is 64-bit, which can be modified and switched in the configuration file
VA Host Plugin | The process that supports the plug-in package of 64-bit APP. The default main package is 32-bit and the plug-in package is 64-bit, which can be modified and switched in the configuration file.
VAPP Client | The process generated by the APP installed into VA after it starts, it will modify io.busniess.va:pxxx process name to the real process name of VAPP when it runs.
VAServer | The process where the VA Server is located, it is used to handle requests in VA that are not assigned to the system for processing, such as APP installation processing.
## VA can satisfy almost all your needs ##
Through the above technical architecture, we can know that VA can fully control APP and provide Hook SDK, which can satisfy almost all your needs in various fields:
1. Satisfy the need of **dual/multi space**
VA allows you to install multiple WeChat/QQ/WhatsAPP/Facebook and other APPs on the same mobile phone, so you can have one phone with multiple accounts logged in at the same time.
2. Satisfy the need of **mobile security**
VA provides a set of internal and external isolation mechanisms, including but not limited to (file isolation / component isolation / process communication isolation). Simply speaking, VA internal is a "completely independent space".
Through VA, work affairs and personal affairs can be safely separated without mutual interference. With a little customization, you can achieve mobile security-related needs such as application behavior audit, data encryption, data acquisition, data leakage prevention, anti-attack leaks and so on.
**2.1 Application behavior audit**
The HOOK capability provided by VA can realize real-time monitoring of user usage behavior and upload violation information to the server. And it's easy to implement things like Time Fence ( whether a feature of the APP can be used in a certain time ), Geo Fence ( whether a feature of the APP can be used in a certain area ), sensitive keyword filtering interception and other functional requirements.
**2.2 Data encryption**
The HOOK capability provided by VA can realize all data/file encryption of the application, ensuring data/file landing security.
**2.3 Data acquisition**
The HOOK capability provided by VA can realize the demand for real-time silent upload of application data, such as chat records and transfer records, preventing them from being deleted afterwards without traceability.
**2.4 Data leakage prevention**
The HOOK capability provided by VA can realize application anti-copy/paste, anti-screenshot/recording, anti-sharing/forwarding, watermark traceability and other requirements.
**2.5 Anti-attack leaks**
With the application control capability provided by VA, privacy-related behaviors such as SMS/ address book/call log/ background recording/background photo/ browsing history and location information can be completely controlled in sandbox, prevent Trojan horses/malicious APPs from acquiring users' real private data, causing serious consequences such as leakage of secrets.
3. Satisfy the need of **ROOT without HOOK**
VA provides Hook capability of Java and Native. With VA, you can easily achieve functions required by various scenarios, such as virtual positioning, changing device, APP monitoring and management, mobile security and so on.
4. Satisfy the need of **silent installation**
VA provides the ability to silently install, silently upgrade and silently uninstall APPs. For example, the application store or game center can be integrated with VA to avoid the need for users to manually click to confirm the installation operation, so that it can be installed into VA immediately after downloading, bringing users an experience like "small program" , completely avoiding the problem of applications not easily installed by users.
5. Satisfy the need of **APP controlled**
You can clearly grasp the system API, sensitive data, device information, etc. accessed by the APP through VA. For example, whether the APP accesses the contacts, photo albums, call log, whether it accesses the user's geographic location and other information.
Of course, you can also control or construct custom messages to these APPs via VA, and not only that, you can also get access to the APP's private data, such as chat database and so on. In a word, through the application control capability provided by VA, you can easily control all the behaviors of the APP, even modify the content of the APP and server interaction and so on .
6. Satisfy the need of **overseas markets**
VA implements support for Google services to support overseas APPs running, such as Twitter, Messenger, WhatsAPP, Instagram, FaceBook, Youtube and so on.
7. Satisfy the need of **almost everything you can think of**
VA has complete oversight and control over the internal APP, and can meet almost any of your needs!
8. VA is also the only commercially licensed product in this technology area
**Hundreds of** licensed customers are currently paying to use the business version of VirtualAPP code, and the APP integrated with VirtualAPP code is launched more than 200 million times per day. Many Android engineers provide us with user feedback in different scenarios, and through our technical team's continuous optimization and iteration, we continue to improve product performance and compatibility.
VA Specialized capabilities
---
- Cloning ability
You can clone the APP already installed in the external system and run it internally without mutual interference. Typical application scenario is double space.
- Without installation ability
In addition to cloning already installed, VA can install (externally silent ) apk's directly internally and run them directly internally. Typical application scenarios are plug-in, standalone APP marketplace and so on.
- Double space ability
VA is not only "double space", but also has a unique multi-user mode that allows users to open the same APP internally for an unlimited number of times.
- Internal and external isolation ability
VA is a standard sandbox, or "virtual machine", that provides a set of internal and external isolation mechanisms, including but not limited to (file isolation/component isolation/process communication isolation). Simply put, the inside of a VA is a "completely separate space". Simply put, the inside of a VA is a "completely separate space". Based on it, you can realize a "virtual phone" on your cell phone with a little customization. Of course, you can also use your imagination to customize it for data encryption, data isolation, privacy protection, and enterprise management applications.
- Full control over internal APPs ability
VA has complete monitoring and control over the internal APP, which is absolutely impossible to achieve in an external environment without Root.
Details(Drop down to open)
1. Service request control. First, VA directly provides some service request interception, you can easily customize these service requests when integrating VA, including but far from limited to (APP request to install apk / APP request to open certain files / APP request for location data / APP request for phone information, etc.)
2. System API control. VA virtualizes and implements the entire Android system framework, which is the principle that VA can run apk internally without installation. And you can through modify the virtual framework's implementation to dynamically monitor and analyze the behavior of the app, etc. In addition, you can also mock some system behavior to achieve some needs that are difficult to achieve externally (e.g. game controller).
3. Memory read and write. VA can read and write the memory of internal APP processes without Root.
4. Root without debugging. VA can debug (ptrace) internal APP processes without Root, based on which you can also achieve Root-free process injection.
5. Load arbitrary "plug-in" and "behaviors". The APP process inside VA is derived from the Client side code of the VA framework, so you can insert any "load" and "control" logic into the entry code of the process. These are very simple to implement.
6. Hook. VA has a set of built-in Xposed framework and native hook framework running on all versions of Android (until AndroidQ), based on it, you can easily Hook any Java/Native of any internal APP.
7. File control. VA built in a complete file redirection, which allows easy control of reading and writing of files from internal apps. Based on it, you can realize many functions such as protection and encryption of files can be achieved.
8. Note: The above control capabilities are implemented with code or examples for reference.
VA Other features
---
- High performance
Process-level "virtual machine", VA's unique implementation model makes its performance almost the same as that of the native APP, and does not need a long startup of ordinary virtual machines.
- Full version support
Support 5.0-17.0, 32-bit/64-bit APP, ARM and X86 processor. And support Android version in the future which will be updated.
- Easy Expansion and Integration
The integration of VA is similar to the normal Android library, even if your APP has been online, you can conveniently integrate VA and enjoy the capability brought by VA.
- Support Google services
Provide support for Google services in order to support overseas APPs.
## Comparison between VA and other technical solutions ##
When doing enterprise-level mobile security, it is often necessary to control the APP, and the following is a comparison of possible technical solutions listed:
Technical solution | Principle introduction | Comment | Running performance | Compatibility stability | Project maintenance cost
---- | --- | --- | --- | --- | ---
Repackage | Repackage the target APP by decompiling it and adding your own control code | 1. Nowadays, almost all APPs have hardened or tamper-proof protection, and repackaging is already a very difficult task 2.The mobile phone system will also detect whether the APP is repackaged, if it is repackaged, it will directly prompt the user that there is a security risk, and even not allow the installation3.For each APP, even each version to go deep to reverse analysis, time-consuming and difficult to maintain | Excellent | Poor | High
Custom ROM | By customizing the system source code and compiling it to flash to the designated mobile phone | Only for specified internal mobile phones, too limited to be extended | Excellent | Excellent | High
ROOT the mobile phone | By rooting the mobile phone,flashing a framework which is similar to Xposed | 1.Now, root the mobile phone is an unlikely thing 2.In reality, it is difficult for users to root their own mobile phones | Excellent | Poor | High
VA | Lightweight virtual machine with high speed and low device requirements | No risk point mentioned above | Excellent | Excellent. Hundreds of companies testing feedback at the same time | Low.
VA provides API and a professional technical team to ensure the stable operation of the project
As you can see from the above comparison, VA is an excellent product and can reduce your development and maintenance costs.
## Integrating VA Steps ##
Step 1: Call the VA interface```VirtualCore.get().startup()```in your application to start the VA engine.
Step 2: Call VA interface```VirtualCore.get().installPackageAsUser(userId, packageName)```to install the target APP into VA.
Step 3: Call VA interface```VActivityManager.get().launchApp(userId, packageName)```to start the APP.
**With only the above 3 APIs to complete the basic use, VA has shielded the complex technical details and provided the interface API to make your development easy.**
## VA compatible stability ##
VA has been extensively tested by ** hundreds of **companies, including **high standards of testing and feedback of dozens of listed companies**, covering almost all types of equipment and scenarios at home and abroad, providing full protection for your stable operation!
Up to now, the supported system versions:
System version | Whether to support
---- | ---
5.0 | support
5.1 | support
6.0 | support
7.0 | support
8.0 | support
9.1 | support
10.0 | support
11.0 | support
12.0 | support
13.0 | support
14.0 | support
15.0 | support
16.0 | support
17.0 | support
Supported App Types:
App Type | Whether to support
---- | ---
32-bit APP | support
64-bit APP | support
Supported HOOK Types:
Hook Type | Whether to support
---- | ---
Java Hook | support
Native Hook | support
Supported CPU Types:
Hook Type | Whether to support
---- | ---
ARM 32 | support
ARM 64 | support
## How to give feedback on problems encountered with integrated VA ? ##
After the purchase of the license we will establish a WeChat group, any problems can always feedback to us, and according to the priority in the first time to deal with.
## VA Development document ##
Please refer to the VA development documentation:[Development document](doc/VADev_eng.md)
License Instructions
------
VirtualApp virtual machine technology belongs to: Jining Luohe Network Technology Co., LTD. It applied for several VirtualApp intellectual property rights from 2015 to 2026 and` is protected by the Intellectual property Law of the People's Republic of China`.When you need to use the code on Github, **please purchase a business license**,and receive the full source code of the latest VirtualApp business version.Hundreds of licensed customers are paying to use the business version of VirtualApp code, and the app integrated with VirtualApp code is launched more than 200 million times a day. Many Android engineers provided us with user feedback in different scenarios, and through our technical team's continuous optimization and iteration, VirtualApp Business Edition code has better performance and higher compatibility. `The company of that year will become one of them after obtaining the license, and enjoy the technological achievements after the continuous iteration. And we can interact and collaborate with our licensed customers operationally, technically and commercially.`
Person in charge: Mr. Zhang
WeChat:10890
Serious statement
------
If you use VirtualApp for **internal use, business profit or upload it to the application market**without licensing. We will take evidence and then report you to the police (for copyright infringement) or prosecute you. It will cause your company to undertake criminal liability and legal action, and affect your company's goodwill and investment.`Purchasing a business license can save you a lot of time developing, testing and refining compatibility, leaving you more time for innovation and profitability.`Luo He Technology has called to the police and sued a number of individuals and companies in 2020.
**In response to the national call for the protection of intellectual property rights! Anyone who reports that his or her company or other companies are using VirtualApp code to develop products without licensing will be given a cash reward upon verification. We will keep the identity of the whistleblower confidential! Reporting WeChat: 10890.**
Major updates of the business version
------
1. Support Android 17.0
2. Support Seccomp-Bpf.
3. Not easily misreported by anti-virus software
4. Framework optimization, performance greatly improved
5. Mobile system and APP compatibility greatly improved
6. Run Google services perfectly
7. Supports running pure 64-bit Apps
8. Built-in `XPosed Hook` framework
9. Add positioning mock code
10. Add code to change device
11. Nearly 700 other fixes and improvements
================================================
FILE: VirtualApp/.gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.idea
.DS_Store
/build
/captures
================================================
FILE: VirtualApp/app/.gitignore
================================================
/build
================================================
FILE: VirtualApp/app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
defaultConfig {
applicationId "io.virtualapp"
minSdkVersion 15
targetSdkVersion 22
versionCode 24
versionName "1.2.5"
multiDexEnabled true
android {
defaultConfig {
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86"
}
}
}
}
sourceSets {
main{
jniLibs.srcDirs = ['libs']
}
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
android {
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')
//Android Lib
compile 'com.android.support:multidex:1.0.2'
compile 'com.android.support:appcompat-v7:25.4.0'
compile 'com.melnykov:floatingactionbutton:1.3.0'
compile 'com.android.support:recyclerview-v7:25.4.0'
compile 'com.android.support:percent:25.4.0'
compile 'com.android.support:design:25.4.0'
compile 'com.android.support:cardview-v7:25.4.0'
//Promise Support
compile 'org.jdeferred:jdeferred-android-aar:1.2.4'
// ThirdParty
compile 'com.jonathanfinerty.once:once:1.0.3'
compile 'com.flurry.android:analytics:6.9.2'
compile 'com.kyleduo.switchbutton:library:1.4.6'
}
================================================
FILE: VirtualApp/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/lody/Desktop/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.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.**{*;}
================================================
FILE: VirtualApp/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/VApp.java
================================================
package io.virtualapp;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.multidex.MultiDexApplication;
import com.flurry.android.FlurryAgent;
import com.lody.virtual.client.core.VirtualCore;
import com.lody.virtual.client.stub.VASettings;
import io.virtualapp.delegate.MyAppRequestListener;
import io.virtualapp.delegate.MyComponentDelegate;
import io.virtualapp.delegate.MyPhoneInfoDelegate;
import io.virtualapp.delegate.MyTaskDescriptionDelegate;
import jonathanfinerty.once.Once;
/**
* @author Lody
*/
public class VApp extends MultiDexApplication {
private static VApp gApp;
private SharedPreferences mPreferences;
public static VApp getApp() {
return gApp;
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
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) {
e.printStackTrace();
}
}
@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");
}
@Override
public void onVirtualProcess() {
//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());
}
@Override
public void onServerProcess() {
virtualCore.setAppRequestListener(new MyAppRequestListener(VApp.this));
virtualCore.addVisibleOutsidePackage("com.tencent.mobileqq");
virtualCore.addVisibleOutsidePackage("com.tencent.mobileqqi");
virtualCore.addVisibleOutsidePackage("com.tencent.minihd.qq");
virtualCore.addVisibleOutsidePackage("com.tencent.qqlite");
virtualCore.addVisibleOutsidePackage("com.facebook.katana");
virtualCore.addVisibleOutsidePackage("com.whatsapp");
virtualCore.addVisibleOutsidePackage("com.tencent.mm");
virtualCore.addVisibleOutsidePackage("com.immomo.momo");
}
});
}
public static SharedPreferences getPreferences() {
return getApp().mPreferences;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/VCommends.java
================================================
package io.virtualapp;
/**
* @author Lody
*/
public class VCommends {
public static final String TAG_NEW_VERSION = "First launch new Version";
public static final String TAG_SHOW_ADD_APP_GUIDE = "Should show add app guide";
public static final int REQUEST_SELECT_APP = 5;
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";
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/BasePresenter.java
================================================
package io.virtualapp.abs;
/**
* @author Lody
*/
public interface BasePresenter {
void start();
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/BaseView.java
================================================
package io.virtualapp.abs;
import android.app.Activity;
import android.content.Context;
/**
* @author Lody
*/
public interface BaseView {
Activity getActivity();
Context getContext();
void setPresenter(T presenter);
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/Callback.java
================================================
package io.virtualapp.abs;
/**
* @author Lody
*/
public interface Callback {
void callback(T result);
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/Value.java
================================================
package io.virtualapp.abs;
/**
* @author Lody
*/
public class Value {
public T val;
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/nestedadapter/RecyclerViewAdapterWrapper.java
================================================
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;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/nestedadapter/SmartRecyclerAdapter.java
================================================
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);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/percent/PercentLinearLayout.java
================================================
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);
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/reflect/ReflectException.java
================================================
package io.virtualapp.abs.reflect;
/**
* @author Lody
*/
public class ReflectException extends RuntimeException {
private static final long serialVersionUID = 663038727503637969L;
public ReflectException(Throwable cause) {
super(cause);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/ui/BaseAdapterPlus.java
================================================
package io.virtualapp.abs.ui;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.SpinnerAdapter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public abstract class BaseAdapterPlus extends BaseAdapter implements SpinnerAdapter {
protected Context context;
private LayoutInflater mLayoutInflater;
protected final List mItems = new ArrayList();
public BaseAdapterPlus(Context context) {
this.context = context;
mLayoutInflater = LayoutInflater.from(context);
}
public Context getContext() {
return context;
}
public boolean add(T item) {
return add(-1, item, false);
}
public boolean add(int pos, T item, boolean onlyone) {
if (item != null) {
if (onlyone) {
if (exist(item)) {
return false;
}
}
if (pos >= 0) {
mItems.add(pos, item);
} else {
mItems.add(item);
}
return true;
}
return true;
}
public T remove(int pos) {
return mItems.remove(pos);
}
public List getItems() {
return mItems;
}
protected VW inflate(int resource, ViewGroup root) {
return (VW) mLayoutInflater.inflate(resource, root);
}
protected VW inflate(int resource, ViewGroup root, boolean attachToRoot) {
return (VW) mLayoutInflater.inflate(resource, root, attachToRoot);
}
public void clear() {
mItems.clear();
}
public void set(Collection items) {
clear();
addAll(items);
}
public void addAll(Collection items) {
if (items != null) {
mItems.addAll(items);
}
}
public int findItem(T item) {
return mItems.indexOf(item);
}
public boolean exist(T item) {
if (item == null) return false;
return mItems.contains(item);
}
@Override
public final int getCount() {
return mItems.size();
}
public final T getDataItem(int position) {
return mItems.get(position);
}
@Override
public final T getItem(int position) {
if (position >= 0 && position < getCount()) {
return mItems.get(position);
}
return null;
}
public final T getItemById(long id) {
return getItem((int) id);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public final View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = createView(position, parent);
}
T t = getItem(position);
attach(convertView, t, position);
return convertView;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = createView(position, parent);
}
T t = getItem(position);
attach(convertView, t, position);
return convertView;
}
protected abstract View createView(int position, ViewGroup parent);
protected abstract void attach(View view, T item, int position);
public static class BaseViewHolder {
protected View view;
protected Context context;
public BaseViewHolder(View view) {
this.view = view;
this.context = view.getContext();
}
protected T $(int id) {
return (T) view.findViewById(id);
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/ui/VActivity.java
================================================
package io.virtualapp.abs.ui;
import android.app.Activity;
import android.content.Context;
import android.support.annotation.IdRes;
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;
/**
* @author Lody
*/
public class VActivity extends AppCompatActivity {
/**
* Implement of {@link BaseView#getActivity()}
*/
public Activity getActivity() {
return this;
}
/**
* Implement of {@link BaseView#getContext()} ()}
*/
public Context getContext() {
return this;
}
protected AndroidDeferredManager defer() {
return VUiKit.defer();
}
public Fragment findFragmentById(@IdRes int id) {
return getSupportFragmentManager().findFragmentById(id);
}
public void replaceFragment(@IdRes int id, Fragment fragment) {
getSupportFragmentManager().beginTransaction().replace(id, fragment).commit();
}
@Override
protected void onStart() {
super.onStart();
FlurryAgent.onStartSession(this);
}
@Override
protected void onStop() {
super.onStop();
FlurryAgent.onEndSession(this);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/ui/VFragment.java
================================================
package io.virtualapp.abs.ui;
import org.jdeferred.android.AndroidDeferredManager;
import android.app.Activity;
import android.support.v4.app.Fragment;
import io.virtualapp.abs.BasePresenter;
/**
* @author Lody
*/
public class VFragment extends Fragment {
protected T mPresenter;
public T getPresenter() {
return mPresenter;
}
public void setPresenter(T presenter) {
this.mPresenter = presenter;
}
protected AndroidDeferredManager defer() {
return VUiKit.defer();
}
public void finishActivity() {
Activity activity = getActivity();
if (activity != null) {
activity.finish();
}
}
public void destroy() {
finishActivity();
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/abs/ui/VUiKit.java
================================================
package io.virtualapp.abs.ui;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.TypedValue;
import org.jdeferred.android.AndroidDeferredManager;
/**
* @author Lody
*
* A set of tools for UI.
*/
public class VUiKit {
private static final AndroidDeferredManager gDM = new AndroidDeferredManager();
private static final Handler gUiHandler = new Handler(Looper.getMainLooper());
public static AndroidDeferredManager defer() {
return gDM;
}
public static int dpToPx(Context context, int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
context.getResources().getDisplayMetrics());
}
public static void post(Runnable r) {
gUiHandler.post(r);
}
public static void postDelayed(long delay, Runnable r) {
gUiHandler.postDelayed(r, delay);
}
public static void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/delegate/MyAppRequestListener.java
================================================
package io.virtualapp.delegate;
import android.content.Context;
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;
/**
* @author Lody
*/
public class MyAppRequestListener implements VirtualCore.AppRequestListener {
private final Context context;
public MyAppRequestListener(Context context) {
this.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();
}
}
@Override
public void onRequestUninstall(String pkg) {
Toast.makeText(context, "Uninstall: " + pkg, Toast.LENGTH_SHORT).show();
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/delegate/MyComponentDelegate.java
================================================
package io.virtualapp.delegate;
import android.app.Activity;
import android.app.Application;
import android.content.Intent;
import com.lody.virtual.client.hook.delegate.ComponentDelegate;
import com.lody.virtual.helper.utils.Reflect;
import java.io.File;
public class MyComponentDelegate implements ComponentDelegate {
@Override
public void beforeApplicationCreate(Application application) {
}
@Override
public void afterApplicationCreate(Application application) {
}
@Override
public void beforeActivityCreate(Activity activity) {
}
@Override
public void beforeActivityResume(Activity activity) {
}
@Override
public void beforeActivityPause(Activity activity) {
}
@Override
public void beforeActivityDestroy(Activity activity) {
}
@Override
public void afterActivityCreate(Activity activity) {
}
@Override
public void afterActivityResume(Activity activity) {
}
@Override
public void afterActivityPause(Activity activity) {
}
@Override
public void afterActivityDestroy(Activity activity) {
}
@Override
public void onSendBroadcast(Intent intent) {
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/delegate/MyPhoneInfoDelegate.java
================================================
package io.virtualapp.delegate;
import com.lody.virtual.client.hook.delegate.PhoneInfoDelegate;
/**
* Fake the Device ID.
*/
public class MyPhoneInfoDelegate implements PhoneInfoDelegate {
@Override
public String getDeviceId(String oldDeviceId, int userId) {
return oldDeviceId;
}
@Override
public String getBluetoothAddress(String oldAddress, int userId) {
return oldAddress;
}
@Override
public String getMacAddress(String oldAddress, int userId) {
return oldAddress;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/delegate/MyTaskDescriptionDelegate.java
================================================
package io.virtualapp.delegate;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.os.Build;
import com.lody.virtual.client.hook.delegate.TaskDescriptionDelegate;
import com.lody.virtual.os.VUserManager;
/**
* Patch the task description with the (Virtual) user name
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyTaskDescriptionDelegate 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 (!oldLabel.startsWith(labelPrefix)) {
// Is it really necessary?
return new ActivityManager.TaskDescription(labelPrefix + oldTaskDescription.getLabel(), oldTaskDescription.getIcon(), oldTaskDescription.getPrimaryColor());
} else {
return oldTaskDescription;
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/effects/ExplosionAnimator.java
================================================
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;
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/effects/ExplosionField.java
================================================
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 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);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/FlurryROMCollector.java
================================================
package io.virtualapp.home;
import android.hardware.Camera;
import android.os.Build;
import android.util.Log;
import com.flurry.android.FlurryAgent;
import com.flurry.android.FlurryEventRecordStatus;
import com.lody.virtual.client.natives.NativeMethods;
import com.lody.virtual.helper.utils.Reflect;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author Lody
*/
public class FlurryROMCollector {
private static final String TAG = FlurryROMCollector.class.getSimpleName();
public static void startCollect() {
Log.d(TAG, "start collect...");
NativeMethods.init();
if (NativeMethods.gCameraNativeSetup == null) {
reportCameraNativeSetup();
}
Log.d(TAG, "end collect...");
}
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);
break;
}
}
}
private static Map createLogContent(String tag, String value) {
Map content = new HashMap<>(3);
addRomInfo(content);
content.put(tag, value);
return content;
}
private static void addRomInfo(Map content) {
content.put("device", Build.DEVICE);
content.put("brand", Build.BRAND);
content.put("manufacturer", Build.MANUFACTURER);
content.put("display", Build.DISPLAY);
content.put("model", Build.MODEL);
content.put("protect", Build.PRODUCT);
content.put("sdk_version", "API-" + Build.VERSION.SDK_INT);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/HomeActivity.java
================================================
package io.virtualapp.home;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
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.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.ContextThemeWrapper;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.lody.virtual.GmsSupport;
import com.lody.virtual.client.stub.ChooseTypeAndAccountActivity;
import com.lody.virtual.os.VUserInfo;
import com.lody.virtual.os.VUserManager;
import java.lang.reflect.Method;
import java.util.ArrayList;
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.location.VirtualLocationSettings;
import io.virtualapp.home.models.AddAppButton;
import io.virtualapp.home.models.AppData;
import io.virtualapp.home.models.AppInfoLite;
import io.virtualapp.home.models.EmptyAppData;
import io.virtualapp.home.models.MultiplePackageAppData;
import io.virtualapp.home.models.PackageAppData;
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 View mMenuView;
private PopupMenu mPopupMenu;
private View mBottomArea;
private View mCreateShortcutBox;
private TextView mCreateShortcutTextView;
private View mDeleteAppBox;
private TextView mDeleteAppTextView;
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();
initMenu();
new HomePresenterImpl(this).start();
}
private void initMenu() {
mPopupMenu = new PopupMenu(new ContextThemeWrapper(this, R.style.Theme_AppCompat_Light), mMenuView);
Menu menu = mPopupMenu.getMenu();
setIconEnable(menu, true);
menu.add("Accounts").setIcon(R.drawable.ic_account).setOnMenuItemClickListener(item -> {
List users = VUserManager.get().getUsers();
List names = new ArrayList<>(users.size());
for (VUserInfo info : users) {
names.add(info.name);
}
CharSequence[] items = new CharSequence[names.size()];
for (int i = 0; i < names.size(); i++) {
items[i] = names.get(i);
}
new AlertDialog.Builder(this)
.setTitle("Please select an user")
.setItems(items, (dialog, which) -> {
VUserInfo info = users.get(which);
Intent intent = new Intent(this, ChooseTypeAndAccountActivity.class);
intent.putExtra(ChooseTypeAndAccountActivity.KEY_USER_ID, info.id);
startActivity(intent);
}).show();
return false;
});
menu.add("Virtual Storage").setIcon(R.drawable.ic_vs).setOnMenuItemClickListener(item -> {
Toast.makeText(this, "The coming", Toast.LENGTH_SHORT).show();
return false;
});
menu.add("Notification").setIcon(R.drawable.ic_notification).setOnMenuItemClickListener(item -> {
Toast.makeText(this, "The coming", Toast.LENGTH_SHORT).show();
return false;
});
menu.add("Virtual Location").setIcon(R.drawable.ic_notification).setOnMenuItemClickListener(item -> {
startActivity(new Intent(this, VirtualLocationSettings.class));
return true;
});
menu.add("Settings").setIcon(R.drawable.ic_settings).setOnMenuItemClickListener(item -> {
Toast.makeText(this, "The coming", Toast.LENGTH_SHORT).show();
return false;
});
mMenuView.setOnClickListener(v -> mPopupMenu.show());
}
private static void setIconEnable(Menu menu, boolean enable) {
try {
@SuppressLint("PrivateApi")
Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", boolean.class);
m.setAccessible(true);
m.invoke(menu, enable);
} catch (Exception e) {
e.printStackTrace();
}
}
private void bindViews() {
mLoadingView = (TwoGearsView) findViewById(R.id.pb_loading_app);
mLauncherView = (RecyclerView) findViewById(R.id.home_launcher);
mMenuView = findViewById(R.id.home_menu);
mBottomArea = findViewById(R.id.bottom_area);
mCreateShortcutBox = findViewById(R.id.create_shortcut_area);
mCreateShortcutTextView = (TextView) findViewById(R.id.create_shortcut_text);
mDeleteAppBox = findViewById(R.id.delete_app_area);
mDeleteAppTextView = (TextView) findViewById(R.id.delete_app_text);
}
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()) {
if (data instanceof AddAppButton) {
onAddAppButtonClick();
}
mLaunchpadAdapter.notifyItemChanged(pos);
mPresenter.launchApp(data);
}
});
}
private void onAddAppButtonClick() {
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 || model instanceof MultiplePackageAppData) {
mPresenter.createShortcut(model);
}
}
@Override
public void setPresenter(HomeContract.HomePresenter presenter) {
mPresenter = presenter;
}
@Override
public void showBottomAction() {
mBottomArea.setTranslationY(mBottomArea.getHeight());
mBottomArea.setVisibility(View.VISIBLE);
mBottomArea.animate().translationY(0).setDuration(500L).start();
}
@Override
public void hideBottomAction() {
mBottomArea.setTranslationY(0);
ObjectAnimator transAnim = ObjectAnimator.ofFloat(mBottomArea, "translationY", 0, mBottomArea.getHeight());
transAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
mBottomArea.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animator) {
mBottomArea.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
transAnim.setDuration(500L);
transAnim.start();
}
@Override
public void showLoading() {
mLoadingView.setVisibility(View.VISIBLE);
mLoadingView.startAnim();
}
@Override
public void hideLoading() {
mLoadingView.setVisibility(View.GONE);
mLoadingView.stopAnim();
}
@Override
public void loadFinish(List list) {
list.add(new AddAppButton(this));
mLaunchpadAdapter.setList(list);
hideLoading();
}
@Override
public void loadError(Throwable err) {
err.printStackTrace();
hideLoading();
}
@Override
public void showGuide() {
}
@Override
public void addAppToLauncher(AppData model) {
List 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 askInstallGms() {
new AlertDialog.Builder(this)
.setTitle("Hi")
.setMessage("We found that your device has been installed the Google service, whether you need to install them?")
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
defer().when(() -> {
GmsSupport.installGApps(0);
}).done((res) -> {
mPresenter.dataChanged();
});
})
.setNegativeButton(android.R.string.cancel, (dialog, which) ->
Toast.makeText(HomeActivity.this, "You can also find it in the Settings~", Toast.LENGTH_LONG).show())
.setCancelable(false)
.show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && data != null) {
List 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 (mBottomArea.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 (mBottomArea.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);
mBottomArea.getLocationInWindow(location);
int baseLine = location[1] - mBottomArea.getHeight();
if (y >= baseLine) {
mDeleteAppBox.getLocationInWindow(location);
int deleteAppAreaStartX = location[0];
if (x < deleteAppAreaStartX) {
upAtCreateShortcutArea = true;
upAtDeleteAppArea = false;
mCreateShortcutTextView.setTextColor(Color.parseColor("#0099cc"));
mDeleteAppTextView.setTextColor(Color.WHITE);
} else {
upAtDeleteAppArea = true;
upAtCreateShortcutArea = false;
mDeleteAppTextView.setTextColor(Color.parseColor("#0099cc"));
mCreateShortcutTextView.setTextColor(Color.WHITE);
}
} else {
upAtCreateShortcutArea = false;
upAtDeleteAppArea = false;
mDeleteAppTextView.setTextColor(Color.WHITE);
mCreateShortcutTextView.setTextColor(Color.WHITE);
}
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/HomeContract.java
================================================
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 {
void showBottomAction();
void hideBottomAction();
void showLoading();
void hideLoading();
void loadFinish(List appModels);
void loadError(Throwable err);
void showGuide();
void addAppToLauncher(AppData model);
void removeAppToLauncher(AppData model);
void refreshLauncherItem(AppData model);
void askInstallGms();
}
/* package */ interface HomePresenter extends BasePresenter {
void launchApp(AppData data);
void dataChanged();
void addApp(AppInfoLite info);
void deleteApp(AppData data);
void createShortcut(AppData data);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/HomePresenterImpl.java
================================================
package io.virtualapp.home;
import android.app.Activity;
import android.graphics.Bitmap;
import com.lody.virtual.GmsSupport;
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;
private AppData mTempAppData;
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);
}
if (!Once.beenDone(VCommends.TAG_ASK_INSTALL_GMS) && GmsSupport.isOutsideGoogleFrameworkExist()) {
mView.askInstallGms();
Once.markDone(VCommends.TAG_ASK_INSTALL_GMS);
}
}
@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);
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/ListAppActivity.java
================================================
package io.virtualapp.home;
import android.Manifest;
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.widget.Toolbar;
import android.view.MenuItem;
import io.virtualapp.R;
import io.virtualapp.VCommends;
import io.virtualapp.abs.ui.VActivity;
import io.virtualapp.home.adapters.AppPagerAdapter;
/**
* @author Lody
*/
public class ListAppActivity extends VActivity {
private Toolbar mToolBar;
private TabLayout mTabLayout;
private ViewPager mViewPager;
public static void gotoListApp(Activity activity) {
Intent intent = new Intent(activity, ListAppActivity.class);
activity.startActivityForResult(intent, VCommends.REQUEST_SELECT_APP);
}
@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);
}
}
}
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) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
for (int result : grantResults) {
if (result == PackageManager.PERMISSION_GRANTED) {
mViewPager.setAdapter(new AppPagerAdapter(getSupportFragmentManager()));
break;
}
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/ListAppContract.java
================================================
package io.virtualapp.home;
import java.util.List;
import io.virtualapp.abs.BasePresenter;
import io.virtualapp.abs.BaseView;
import io.virtualapp.home.models.AppInfo;
/**
* @author Lody
* @version 1.0
*/
/*package*/ class ListAppContract {
interface ListAppView extends BaseView {
void startLoading();
void loadFinish(List infoList);
}
interface ListAppPresenter extends BasePresenter {
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/ListAppFragment.java
================================================
package io.virtualapp.home;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import io.virtualapp.R;
import io.virtualapp.VCommends;
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.widgets.DragSelectRecyclerView;
/**
* @author Lody
*/
public class ListAppFragment extends VFragment implements ListAppContract.ListAppView {
private static final String KEY_SELECT_FROM = "key_select_from";
private DragSelectRecyclerView mRecyclerView;
private ProgressBar mProgressBar;
private Button mInstallButton;
private CloneAppListAdapter mAdapter;
public static ListAppFragment newInstance(File selectFrom) {
Bundle args = new Bundle();
if (selectFrom != null)
args.putString(KEY_SELECT_FROM, selectFrom.getPath());
ListAppFragment fragment = new ListAppFragment();
fragment.setArguments(args);
return fragment;
}
private File getSelectFrom() {
Bundle bundle = getArguments();
if (bundle != null) {
String selectFrom = bundle.getString(KEY_SELECT_FROM);
if (selectFrom != null) {
return new File(selectFrom);
}
}
return null;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_list_app, null);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mAdapter.saveInstanceState(outState);
}
@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());
mRecyclerView.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new CloneAppListAdapter.ItemEventListener() {
@Override
public void onItemClick(AppInfo info, int position) {
int count = mAdapter.getSelectedCount();
if (!mAdapter.isIndexSelected(position)) {
if (count >= 9) {
Toast.makeText(getContext(), R.string.install_too_much_once_time, Toast.LENGTH_SHORT).show();
return;
}
}
mAdapter.toggleSelected(position);
}
@Override
public boolean isSelectable(int position) {
return mAdapter.isIndexSelected(position) || mAdapter.getSelectedCount() < 9;
}
});
mAdapter.setSelectionListener(count -> {
mInstallButton.setEnabled(count > 0);
mInstallButton.setText(String.format(Locale.ENGLISH, getResources().getString(R.string.install_d), count));
});
mInstallButton.setOnClickListener(v -> {
Integer[] selectedIndices = mAdapter.getSelectedIndices();
ArrayList dataList = new ArrayList(selectedIndices.length);
for (int index : selectedIndices) {
AppInfo info = mAdapter.getItem(index);
dataList.add(new AppInfoLite(info.packageName, info.path, info.fastOpen));
}
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();
}
@Override
public void startLoading() {
mProgressBar.setVisibility(View.VISIBLE);
mRecyclerView.setVisibility(View.GONE);
}
@Override
public void loadFinish(List infoList) {
mAdapter.setList(infoList);
mRecyclerView.setDragSelectActive(false, 0);
mAdapter.setSelected(0, false);
mProgressBar.setVisibility(View.GONE);
mRecyclerView.setVisibility(View.VISIBLE);
}
@Override
public void setPresenter(ListAppContract.ListAppPresenter presenter) {
this.mPresenter = presenter;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/ListAppPresenterImpl.java
================================================
package io.virtualapp.home;
import android.app.Activity;
import android.content.Intent;
import java.io.File;
import io.virtualapp.VCommends;
import io.virtualapp.home.repo.AppDataSource;
import io.virtualapp.home.models.PackageAppData;
import io.virtualapp.home.repo.AppRepository;
/**
* @author Lody
*/
class ListAppPresenterImpl implements ListAppContract.ListAppPresenter {
private Activity mActivity;
private ListAppContract.ListAppView mView;
private AppDataSource mRepository;
private File from;
ListAppPresenterImpl(Activity activity, ListAppContract.ListAppView view, File fromWhere) {
mActivity = activity;
mView = view;
mRepository = new AppRepository(activity);
mView.setPresenter(this);
this.from = fromWhere;
}
@Override
public void start() {
mView.setPresenter(this);
mView.startLoading();
if (from == null)
mRepository.getInstalledApps(mActivity).done(mView::loadFinish);
else
mRepository.getStorageApps(mActivity, from).done(mView::loadFinish);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/LoadingActivity.java
================================================
package io.virtualapp.home;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
import android.widget.ImageView;
import android.widget.TextView;
import com.lody.virtual.client.core.VirtualCore;
import com.lody.virtual.client.ipc.VActivityManager;
import java.util.Locale;
import io.virtualapp.R;
import io.virtualapp.abs.ui.VActivity;
import io.virtualapp.abs.ui.VUiKit;
import io.virtualapp.home.models.PackageAppData;
import io.virtualapp.home.repo.PackageAppDataStorage;
import io.virtualapp.widgets.EatBeansView;
/**
* @author Lody
*/
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 PackageAppData appModel;
private EatBeansView loadingView;
public static void 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.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
loadingPageIntent.putExtra(KEY_INTENT, intent);
loadingPageIntent.putExtra(KEY_USER, userId);
context.startActivity(loadingPageIntent);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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);
appModel = PackageAppDataStorage.get().acquire(pkg);
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);
if (intent == null) {
return;
}
VirtualCore.get().setUiCallback(intent, mUiCallback);
VUiKit.defer().when(() -> {
if (!appModel.fastOpen) {
try {
VirtualCore.get().preOpt(appModel.packageName);
} catch (Exception e) {
e.printStackTrace();
}
}
VActivityManager.get().startActivity(intent, userId);
});
}
private final VirtualCore.UiCallback mUiCallback = new VirtualCore.UiCallback() {
@Override
public void onAppOpened(String packageName, int userId) throws RemoteException {
finish();
}
};
@Override
protected void onResume() {
super.onResume();
loadingView.startAnim();
}
@Override
protected void onPause() {
super.onPause();
loadingView.stopAnim();
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/adapters/AppLocationAdapter.java
================================================
package io.virtualapp.home.adapters;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import io.virtualapp.R;
import io.virtualapp.abs.ui.BaseAdapterPlus;
import io.virtualapp.home.models.LocationData;
public class AppLocationAdapter extends BaseAdapterPlus {
public AppLocationAdapter(Context context) {
super(context);
}
@Override
protected View createView(int position, ViewGroup parent) {
View view = inflate(R.layout.item_location_app, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
view.setTag(viewHolder);
return view;
}
@Override
protected void attach(View view, LocationData item, int position) {
ViewHolder viewHolder = (ViewHolder) view.getTag();
viewHolder.icon.setImageDrawable(item.icon);
viewHolder.label.setText(item.name);
if (item.location != null && item.mode != 0) {
viewHolder.location.setText(item.location.latitude + "," + item.location.longitude);
} else {
viewHolder.location.setText("real location");
}
}
static class ViewHolder extends BaseAdapterPlus.BaseViewHolder {
public ViewHolder(View view) {
super(view);
icon = $(R.id.item_app_icon);
label = $(R.id.item_app_name);
location = $(R.id.item_location);
}
final ImageView icon;
final TextView label, location;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/adapters/AppPagerAdapter.java
================================================
package io.virtualapp.home.adapters;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import com.lody.virtual.helper.utils.Reflect;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import io.virtualapp.R;
import io.virtualapp.VApp;
import io.virtualapp.home.ListAppFragment;
/**
* @author Lody
*/
public class AppPagerAdapter extends FragmentPagerAdapter {
private List titles = new ArrayList<>();
private List dirs = new ArrayList<>();
public AppPagerAdapter(FragmentManager fm) {
super(fm);
titles.add(VApp.getApp().getResources().getString(R.string.clone_apps));
dirs.add(null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Context ctx = VApp.getApp();
StorageManager storage = (StorageManager) ctx.getSystemService(Context.STORAGE_SERVICE);
for (StorageVolume volume : storage.getStorageVolumes()) {
//Why the fuck are getPathFile and getUserLabel hidden?!
//StorageVolume is kinda useless without those...
File dir = Reflect.on(volume).call("getPathFile").get();
String label = Reflect.on(volume).call("getUserLabel").get();
if (dir.listFiles() != null) {
titles.add(label);
dirs.add(dir);
}
}
} else {
// Fallback: only support the default storage sources
File storageFir = Environment.getExternalStorageDirectory();
if (storageFir != null && storageFir.isDirectory()) {
titles.add(VApp.getApp().getResources().getString(R.string.external_storage));
dirs.add(storageFir);
}
}
}
@Override
public Fragment getItem(int position) {
return ListAppFragment.newInstance(dirs.get(position));
}
@Override
public int getCount() {
return titles.size();
}
@Override
public CharSequence getPageTitle(int position) {
return titles.get(position);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/adapters/CloneAppListAdapter.java
================================================
package io.virtualapp.home.adapters;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import io.virtualapp.R;
import io.virtualapp.abs.ui.VUiKit;
import io.virtualapp.home.models.AppInfo;
import io.virtualapp.widgets.DragSelectRecyclerViewAdapter;
import io.virtualapp.widgets.LabelView;
/**
* @author Lody
*/
public class CloneAppListAdapter extends DragSelectRecyclerViewAdapter {
private static final int TYPE_FOOTER = -2;
private final View mFooterView;
private LayoutInflater mInflater;
private List mAppList;
private ItemEventListener mItemEventListener;
public CloneAppListAdapter(Context context) {
this.mInflater = LayoutInflater.from(context);
mFooterView = new View(context);
StaggeredGridLayoutManager.LayoutParams params = new StaggeredGridLayoutManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, VUiKit.dpToPx(context, 60)
);
params.setFullSpan(true);
mFooterView.setLayoutParams(params);
}
public void setOnItemClickListener(ItemEventListener mItemEventListener) {
this.mItemEventListener = mItemEventListener;
}
public List getList() {
return mAppList;
}
public void setList(List models) {
this.mAppList = models;
notifyDataSetChanged();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_FOOTER) {
return new ViewHolder(mFooterView);
}
return new ViewHolder(mInflater.inflate(R.layout.item_clone_app, null));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (getItemViewType(position) == TYPE_FOOTER) {
return;
}
super.onBindViewHolder(holder, position);
AppInfo info = mAppList.get(position);
holder.iconView.setImageDrawable(info.icon);
holder.nameView.setText(info.name);
if (isIndexSelected(position)) {
holder.iconView.setAlpha(1f);
holder.appCheckView.setImageResource(R.drawable.ic_check);
} else {
holder.iconView.setAlpha(0.65f);
holder.appCheckView.setImageResource(R.drawable.ic_no_check);
}
if (info.cloneCount > 0) {
holder.labelView.setVisibility(View.VISIBLE);
holder.labelView.setText(info.cloneCount + 1 + "");
} else {
holder.labelView.setVisibility(View.INVISIBLE);
}
holder.itemView.setOnClickListener(v -> {
mItemEventListener.onItemClick(info, position);
});
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
@Override
protected boolean isIndexSelectable(int index) {
return mItemEventListener.isSelectable(index);
}
@Override
public int getItemCount() {
return mAppList == null ? 1 : mAppList.size() + 1;
}
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) {
return TYPE_FOOTER;
}
return super.getItemViewType(position);
}
public AppInfo getItem(int index) {
return mAppList.get(index);
}
public interface ItemEventListener {
void onItemClick(AppInfo appData, int position);
boolean isSelectable(int position);
}
class ViewHolder extends RecyclerView.ViewHolder {
private ImageView iconView;
private TextView nameView;
private ImageView appCheckView;
private LabelView labelView;
ViewHolder(View itemView) {
super(itemView);
if (itemView != mFooterView) {
iconView = (ImageView) itemView.findViewById(R.id.item_app_icon);
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);
}
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/adapters/LaunchpadAdapter.java
================================================
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 {
private LayoutInflater mInflater;
private List mList;
private SparseIntArray mColorArray = new SparseIntArray();
private OnAppClickListener mAppClickListener;
public LaunchpadAdapter(Context context) {
mInflater = LayoutInflater.from(context);
}
public void add(AppData model) {
int insertPos = mList.size() - 1;
mList.add(insertPos, model);
notifyItemInserted(insertPos);
}
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 getList() {
return mList;
}
public void setList(List 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);
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/adapters/decorations/ItemOffsetDecoration.java
================================================
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);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/location/MarkerActivity.java
================================================
package io.virtualapp.home.location;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.lody.virtual.helper.utils.VLog;
import com.lody.virtual.remote.vloc.VLocation;
import com.tencent.lbssearch.TencentSearch;
import com.tencent.lbssearch.httpresponse.BaseObject;
import com.tencent.lbssearch.httpresponse.HttpResponseListener;
import com.tencent.lbssearch.object.Location;
import com.tencent.lbssearch.object.param.Geo2AddressParam;
import com.tencent.lbssearch.object.result.Geo2AddressResultObject;
import com.tencent.map.geolocation.TencentLocation;
import com.tencent.map.geolocation.TencentLocationListener;
import com.tencent.map.geolocation.TencentLocationManager;
import com.tencent.map.geolocation.TencentLocationRequest;
import com.tencent.mapsdk.raster.model.BitmapDescriptorFactory;
import com.tencent.mapsdk.raster.model.CameraPosition;
import com.tencent.mapsdk.raster.model.LatLng;
import com.tencent.mapsdk.raster.model.MarkerOptions;
import com.tencent.tencentmap.mapsdk.map.CameraUpdateFactory;
import com.tencent.tencentmap.mapsdk.map.MapView;
import com.tencent.tencentmap.mapsdk.map.TencentMap;
import io.virtualapp.abs.ui.VActivity;
import io.virtualapp.R;
public class MarkerActivity extends VActivity implements TencentMap.OnMapClickListener, TencentLocationListener {
private TencentMap mMap;
private MapView mapView;
private LatLng mLatLng = new LatLng(39.9182645956, 116.3970032689);
private TextView pathText;
private TencentSearch geocoderSearch;
private String mAddress;
private boolean isNoPoint = true;
private VLocation mVLocation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_marker);
setResult(Activity.RESULT_CANCELED);
Toolbar toolbar = bind(R.id.task_top_toolbar);
setSupportActionBar(toolbar);
//地址显示,暂时不用
pathText = bind(R.id.address);
pathText.setVisibility(View.VISIBLE);
enableBackHome();
mapView = (MapView) findViewById(R.id.map);
mapView.onCreate(savedInstanceState); // 此方法必须重写
mMap = mapView.getMap();
mMap.setOnMapClickListener(this);
geocoderSearch = new TencentSearch(this);
//
Intent data = getIntent();
if (data != null) {
mVLocation = data.getParcelableExtra(EXTRA_LOCATION);
if (mVLocation != null && mVLocation.latitude != 0 && mVLocation.longitude != 0) {
mLatLng = new LatLng(mVLocation.latitude, mVLocation.longitude);
isNoPoint = false;
}
}
if (isNoPoint) {
startLocation();
} else {
onMapClick(mLatLng);
}
}
@SuppressWarnings("unchecked")
protected T bind(int id) {
return (T) findViewById(id);
}
public void enableBackHome() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
private void startLocation() {
Toast.makeText(this, "start location", Toast.LENGTH_SHORT).show();
TencentLocationRequest request = TencentLocationRequest.create()
.setRequestLevel(TencentLocationRequest.REQUEST_LEVEL_GEO)
.setAllowGPS(true);
int error = TencentLocationManager.getInstance(this)
.requestLocationUpdates(request, this);
if (error != 0) {
VLog.w("TMap", "startLocation:error=" + error);
}
}
@Override
public void onLocationChanged(TencentLocation location, int error, String msg) {
if (location != null) {
TencentLocationManager.getInstance(this).removeUpdates(this);
onMapClick(new LatLng(location.getLatitude(), location.getLongitude()));
} else {
String errText = "定位失败," + error + ": " + msg;
VLog.e("TMap", errText);
}
}
@Override
public void onStatusUpdate(String s, int i, String s1) {
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.marktet_map, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
break;
case R.id.action_clear:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Question");
builder.setMessage("Clear virtual location");
builder.setNegativeButton(android.R.string.ok, (d, s) -> {
if (mMap != null) {
mMap.clearAllOverlays();
}
setResultOk(null);
finish();
d.dismiss();
});
builder.setNeutralButton(android.R.string.cancel, (d, s) -> {
d.dismiss();
});
builder.show();
break;
case R.id.action_ok:
if (mLatLng != null) {
/**
* TODO edit info
* @see com.lody.virtual.remote.vloc.VLocation#altitude
* @see com.lody.virtual.remote.vloc.VLocation#accuracy
* @see com.lody.virtual.remote.vloc.VLocation#speed
* @see com.lody.virtual.remote.vloc.VLocation#bearing
*/
if (mVLocation == null) {
mVLocation = new VLocation();
mVLocation.accuracy = 50;
}
mVLocation.latitude = mLatLng.getLatitude();
mVLocation.longitude = mLatLng.getLongitude();
setResultOk(mVLocation);
finish();
}
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onMapClick(LatLng latLng) {
mLatLng = latLng;
MarkerOptions markerOption = new MarkerOptions()
.icon(BitmapDescriptorFactory.defaultMarker())
.position(mLatLng)
.draggable(true);
mMap.clearAllOverlays();
mMap.addMarker(markerOption);
int level = Math.min(mMap.getZoomLevel(), mMap.getMaxZoomLevel() / 3 * 2);
mMap.moveCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(latLng, level)));
//查询地理位置
ProgressDialog dialog = ProgressDialog.show(this, null, "get address of location");
Geo2AddressParam param = new Geo2AddressParam()
.location(new Location()
.lat((float) latLng.getLatitude())
.lng((float) latLng.getLongitude()));
geocoderSearch.geo2address(param, new HttpResponseListener() {
@Override
public void onSuccess(int i, BaseObject object) {
Geo2AddressResultObject oj = (Geo2AddressResultObject) object;
if (oj.result != null) {
pathText.setText(oj.result.address);
mAddress = oj.result.address;
}
dialog.dismiss();
}
@Override
public void onFailure(int i, String s, Throwable throwable) {
dialog.dismiss();
pathText.setText("error:" + s);
}
});
}
/**
* 方法必须重写
*/
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
/**
* 方法必须重写
*/
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
/**
* 方法必须重写
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
/**
* 方法必须重写
*/
@Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
private void setResultOk(VLocation location) {
Intent intent = new Intent();
intent.putExtra(EXTRA_LOCATION, location);
setResult(Activity.RESULT_OK, intent);
}
public static final String EXTRA_LOCATION = "virtual_location";
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/location/VirtualLocationSettings.java
================================================
package io.virtualapp.home.location;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import com.lody.virtual.client.core.VirtualCore;
import com.lody.virtual.client.ipc.VirtualLocationManager;
import com.lody.virtual.helper.utils.VLog;
import com.lody.virtual.remote.InstalledAppInfo;
import com.lody.virtual.remote.vloc.VLocation;
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.home.adapters.AppLocationAdapter;
import io.virtualapp.home.models.LocationData;
import io.virtualapp.home.repo.AppRepository;
import static io.virtualapp.home.location.MarkerActivity.EXTRA_LOCATION;
public class VirtualLocationSettings extends VActivity implements AdapterView.OnItemClickListener {
private static final int REQUSET_CODE = 1001;
private AppRepository mRepo;
private ListView mListView;
private AppLocationAdapter mAppLocationAdapter;
private LocationData mSelectData;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_location_settings);
mListView = (ListView) findViewById(R.id.appdata_list);
mRepo = new AppRepository(this);
mAppLocationAdapter = new AppLocationAdapter(this);
mListView.setAdapter(mAppLocationAdapter);
mListView.setOnItemClickListener(this);
loadData();
}
private void readLocation(LocationData locationData) {
locationData.mode = VirtualLocationManager.get().getMode(locationData.userId, locationData.packageName);
locationData.location = VirtualLocationManager.get().getLocation(locationData.userId, locationData.packageName);
}
private void saveLocation(LocationData locationData) {
if(locationData.location == null||locationData.location.isEmpty()){
VirtualLocationManager.get().setMode(locationData.userId, locationData.packageName, 0);
}else if(locationData.mode != 2){
VirtualLocationManager.get().setMode(locationData.userId, locationData.packageName, 2);
}
VirtualLocationManager.get().setLocation(locationData.userId, locationData.packageName, locationData.location);
}
private void loadData() {
ProgressDialog dialog = ProgressDialog.show(this, null, "loading");
VUiKit.defer().when(() -> {
List infos = VirtualCore.get().getInstalledApps(0);
List models = new ArrayList<>();
for (InstalledAppInfo info : infos) {
if (!VirtualCore.get().isPackageLaunchable(info.packageName)) {
continue;
}
int[] userIds = info.getInstalledUsers();
for (int userId : userIds) {
LocationData data = new LocationData(this, info, userId);
readLocation(data);
models.add(data);
}
}
return models;
}).done((list) -> {
dialog.dismiss();
mAppLocationAdapter.set(list);
mAppLocationAdapter.notifyDataSetChanged();
}).fail((e) -> {
dialog.dismiss();
});
}
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
mSelectData = mAppLocationAdapter.getItem(position);
Intent intent = new Intent(this, MarkerActivity.class);
if (mSelectData.location != null) {
intent.putExtra(EXTRA_LOCATION, mSelectData.location);
}
startActivityForResult(intent, REQUSET_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUSET_CODE) {
if (resultCode == RESULT_OK) {
VLocation location = data.getParcelableExtra(EXTRA_LOCATION);
if (mSelectData != null) {
mSelectData.location = location;
VLog.i("kk", "set" + mSelectData);
saveLocation(mSelectData);
mSelectData = null;
loadData();
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/models/AddAppButton.java
================================================
package io.virtualapp.home.models;
import android.content.Context;
import android.graphics.drawable.Drawable;
import io.virtualapp.R;
/**
* @author Lody
*/
public class AddAppButton implements AppData {
private String name;
private Drawable icon;
public AddAppButton(Context context) {
name = context.getResources().getString(R.string.add_app);
icon = context.getResources().getDrawable(R.drawable.ic_add_circle);
}
@Override
public boolean isLoading() {
return false;
}
@Override
public boolean isFirstOpen() {
return false;
}
@Override
public Drawable getIcon() {
return icon;
}
@Override
public String getName() {
return name;
}
@Override
public boolean canReorder() {
return false;
}
@Override
public boolean canLaunch() {
return false;
}
@Override
public boolean canDelete() {
return false;
}
@Override
public boolean canCreateShortcut() {
return false;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/models/AppData.java
================================================
package io.virtualapp.home.models;
import android.graphics.drawable.Drawable;
/**
* @author Lody
*/
public interface AppData {
boolean isLoading();
boolean isFirstOpen();
Drawable getIcon();
String getName();
boolean canReorder();
boolean canLaunch();
boolean canDelete();
boolean canCreateShortcut();
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/models/AppInfo.java
================================================
package io.virtualapp.home.models;
import android.graphics.drawable.Drawable;
/**
* @author Lody
*/
public class AppInfo {
public String packageName;
public String path;
public boolean fastOpen;
public Drawable icon;
public CharSequence name;
public int cloneCount;
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/models/AppInfoLite.java
================================================
package io.virtualapp.home.models;
import android.os.Parcel;
import android.os.Parcelable;
/**
* @author Lody
*/
public class AppInfoLite implements Parcelable {
public static final Creator CREATOR = new Creator() {
@Override
public AppInfoLite createFromParcel(Parcel source) {
return new AppInfoLite(source);
}
@Override
public AppInfoLite[] newArray(int size) {
return new AppInfoLite[size];
}
};
public String packageName;
public String path;
public boolean fastOpen;
public AppInfoLite(String packageName, String path, boolean fastOpen) {
this.packageName = packageName;
this.path = path;
this.fastOpen = fastOpen;
}
protected AppInfoLite(Parcel in) {
this.packageName = in.readString();
this.path = in.readString();
this.fastOpen = in.readByte() != 0;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.packageName);
dest.writeString(this.path);
dest.writeByte(this.fastOpen ? (byte) 1 : (byte) 0);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/models/EmptyAppData.java
================================================
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;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/models/LocationData.java
================================================
package io.virtualapp.home.models;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import com.lody.virtual.remote.InstalledAppInfo;
import com.lody.virtual.remote.vloc.VLocation;
/**
* @see android.location.Location
*/
public class LocationData {
public String packageName;
public int userId;
public String name;
public Drawable icon;
public int mode;
public VLocation location;
public LocationData() {
}
public LocationData(Context context, InstalledAppInfo installedAppInfo, int userId) {
this.packageName = installedAppInfo.packageName;
this.userId = userId;
loadData(context, installedAppInfo.getApplicationInfo(installedAppInfo.getInstalledUsers()[0]));
}
private void loadData(Context context, ApplicationInfo appInfo) {
if (appInfo == null) {
return;
}
PackageManager pm = context.getPackageManager();
try {
CharSequence sequence = appInfo.loadLabel(pm);
if (sequence != null) {
name = sequence.toString();
}
icon = appInfo.loadIcon(pm);
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "LocationData{" +
"packageName='" + packageName + '\'' +
", userId=" + userId +
", location=" + location +
'}';
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/models/MultiplePackageAppData.java
================================================
package io.virtualapp.home.models;
import android.graphics.drawable.Drawable;
import com.lody.virtual.client.core.VirtualCore;
import com.lody.virtual.remote.InstalledAppInfo;
/**
* @author Lody
*/
public class MultiplePackageAppData implements AppData {
public InstalledAppInfo appInfo;
public int userId;
public boolean isFirstOpen;
public boolean isLoading;
public Drawable icon;
public String name;
public MultiplePackageAppData(PackageAppData target, int userId) {
this.userId = userId;
this.appInfo = VirtualCore.get().getInstalledAppInfo(target.packageName, 0);
this.isFirstOpen = !appInfo.isLaunched(userId);
if (target.icon != null) {
Drawable.ConstantState state = target.icon.getConstantState();
if (state != null) {
icon = state.newDrawable();
}
}
name = target.name;
}
@Override
public boolean isLoading() {
return isLoading;
}
@Override
public boolean isFirstOpen() {
return isFirstOpen;
}
@Override
public Drawable getIcon() {
return icon;
}
@Override
public String getName() {
return name;
}
@Override
public boolean canReorder() {
return true;
}
@Override
public boolean canLaunch() {
return true;
}
@Override
public boolean canDelete() {
return true;
}
@Override
public boolean canCreateShortcut() {
return true;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/models/PackageAppData.java
================================================
package io.virtualapp.home.models;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import com.lody.virtual.remote.InstalledAppInfo;
/**
* @author Lody
*/
public class PackageAppData implements AppData {
public String packageName;
public String name;
public Drawable icon;
public boolean fastOpen;
public boolean isFirstOpen;
public boolean isLoading;
public PackageAppData(Context context, InstalledAppInfo installedAppInfo) {
this.packageName = installedAppInfo.packageName;
this.isFirstOpen = !installedAppInfo.isLaunched(0);
loadData(context, installedAppInfo.getApplicationInfo(installedAppInfo.getInstalledUsers()[0]));
}
private void loadData(Context context, ApplicationInfo appInfo) {
if (appInfo == null) {
return;
}
PackageManager pm = context.getPackageManager();
try {
CharSequence sequence = appInfo.loadLabel(pm);
if (sequence != null) {
name = sequence.toString();
}
icon = appInfo.loadIcon(pm);
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
public boolean isLoading() {
return isLoading;
}
@Override
public boolean isFirstOpen() {
return isFirstOpen;
}
@Override
public Drawable getIcon() {
return icon;
}
@Override
public String getName() {
return name;
}
@Override
public boolean canReorder() {
return true;
}
@Override
public boolean canLaunch() {
return true;
}
@Override
public boolean canDelete() {
return true;
}
@Override
public boolean canCreateShortcut() {
return true;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/platform/PlatformInfo.java
================================================
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 platformPkgs = new HashSet<>();
public PlatformInfo(String... pkgs) {
Collections.addAll(platformPkgs, pkgs);
}
public abstract boolean relyOnPlatform(PackageInfo info);
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/platform/WechatPlatformInfo.java
================================================
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;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/repo/AppDataSource.java
================================================
package io.virtualapp.home.repo;
import android.content.Context;
import com.lody.virtual.remote.InstallResult;
import org.jdeferred.Promise;
import java.io.File;
import java.util.List;
import io.virtualapp.home.models.AppData;
import io.virtualapp.home.models.AppInfo;
import io.virtualapp.home.models.AppInfoLite;
/**
* @author Lody
* @version 1.0
*/
public interface AppDataSource {
/**
* @return All the Applications we Virtual.
*/
Promise, Throwable, Void> getVirtualApps();
/**
* @param context Context
* @return All the Applications we Installed.
*/
Promise, Throwable, Void> getInstalledApps(Context context);
Promise, Throwable, Void> getStorageApps(Context context, File rootDir);
InstallResult addVirtualApp(AppInfoLite info);
boolean removeVirtualApp(String packageName, int userId);
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/repo/AppRepository.java
================================================
package io.virtualapp.home.repo;
import android.content.Context;
import android.content.pm.ApplicationInfo;
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.remote.InstallResult;
import com.lody.virtual.remote.InstalledAppInfo;
import org.jdeferred.Promise;
import java.io.File;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import io.virtualapp.abs.ui.VUiKit;
import io.virtualapp.home.models.AppData;
import io.virtualapp.home.models.AppInfo;
import io.virtualapp.home.models.AppInfoLite;
import io.virtualapp.home.models.MultiplePackageAppData;
import io.virtualapp.home.models.PackageAppData;
/**
* @author Lody
*/
public class AppRepository implements AppDataSource {
private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA);
private static final List SCAN_PATH_LIST = Arrays.asList(
".",
"wandoujia/app",
"tencent/tassistant/apk",
"BaiduAsa9103056",
"360Download",
"pp/downloader",
"pp/downloader/apk",
"pp/downloader/silent/apk");
private Context mContext;
public AppRepository(Context context) {
mContext = context;
}
private static boolean isSystemApplication(PackageInfo packageInfo) {
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
&& !GmsSupport.isGmsFamilyPackage(packageInfo.packageName);
}
@Override
public Promise, Throwable, Void> getVirtualApps() {
return VUiKit.defer().when(() -> {
List infos = VirtualCore.get().getInstalledApps(0);
List models = new ArrayList<>();
for (InstalledAppInfo info : infos) {
if (!VirtualCore.get().isPackageLaunchable(info.packageName)) {
continue;
}
PackageAppData data = new PackageAppData(mContext, info);
if (VirtualCore.get().isAppInstalledAsUser(0, info.packageName)) {
models.add(data);
}
int[] userIds = info.getInstalledUsers();
for (int userId : userIds) {
if (userId != 0) {
models.add(new MultiplePackageAppData(data, userId));
}
}
}
return models;
});
}
@Override
public Promise, Throwable, Void> getInstalledApps(Context context) {
return VUiKit.defer().when(() -> convertPackageInfoToAppData(context, context.getPackageManager().getInstalledPackages(0), true));
}
@Override
public Promise, Throwable, Void> getStorageApps(Context context, File rootDir) {
return VUiKit.defer().when(() -> convertPackageInfoToAppData(context, findAndParseAPKs(context, rootDir, SCAN_PATH_LIST), false));
}
private List findAndParseAPKs(Context context, File rootDir, List paths) {
List packageList = new ArrayList<>();
if (paths == null)
return packageList;
for (String path : paths) {
File[] dirFiles = new File(rootDir, path).listFiles();
if (dirFiles == null)
continue;
for (File f : dirFiles) {
if (!f.getName().toLowerCase().endsWith(".apk"))
continue;
PackageInfo pkgInfo = null;
try {
pkgInfo = context.getPackageManager().getPackageArchiveInfo(f.getAbsolutePath(), 0);
pkgInfo.applicationInfo.sourceDir = f.getAbsolutePath();
pkgInfo.applicationInfo.publicSourceDir = f.getAbsolutePath();
} catch (Exception e) {
// Ignore
}
if (pkgInfo != null)
packageList.add(pkgInfo);
}
}
return packageList;
}
private List convertPackageInfoToAppData(Context context, List pkgList, boolean fastOpen) {
PackageManager pm = context.getPackageManager();
List list = new ArrayList<>(pkgList.size());
String hostPkg = VirtualCore.get().getHostPkg();
for (PackageInfo pkg : pkgList) {
// ignore the host package
if (hostPkg.equals(pkg.packageName)) {
continue;
}
// ignore the System package
if (isSystemApplication(pkg)) {
continue;
}
ApplicationInfo ai = pkg.applicationInfo;
String path = ai.publicSourceDir != null ? ai.publicSourceDir : ai.sourceDir;
if (path == null) {
continue;
}
AppInfo info = new AppInfo();
info.packageName = pkg.packageName;
info.fastOpen = fastOpen;
info.path = path;
info.icon = ai.loadIcon(pm);
info.name = ai.loadLabel(pm);
InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(pkg.packageName, 0);
if (installedAppInfo != null) {
info.cloneCount = installedAppInfo.getInstalledUsers().length;
}
list.add(info);
}
return list;
}
@Override
public InstallResult addVirtualApp(AppInfoLite info) {
int flags = InstallStrategy.COMPARE_VERSION | InstallStrategy.SKIP_DEX_OPT;
if (info.fastOpen) {
flags |= InstallStrategy.DEPEND_SYSTEM_IF_EXIST;
}
return VirtualCore.get().installPackage(info.path, flags);
}
@Override
public boolean removeVirtualApp(String packageName, int userId) {
return VirtualCore.get().uninstallPackageAsUser(packageName, userId);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/home/repo/PackageAppDataStorage.java
================================================
package io.virtualapp.home.repo;
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.abs.Callback;
import io.virtualapp.abs.ui.VUiKit;
import io.virtualapp.home.models.PackageAppData;
/**
* @author Lody
*
* Cache the loaded PackageAppData.
*/
public class PackageAppDataStorage {
private static final PackageAppDataStorage STORAGE = new PackageAppDataStorage();
private final Map packageDataMap = new HashMap<>();
public static PackageAppDataStorage get() {
return STORAGE;
}
public PackageAppData acquire(String packageName) {
PackageAppData data;
synchronized (packageDataMap) {
data = packageDataMap.get(packageName);
if (data == null) {
data = loadAppData(packageName);
}
}
return data;
}
public void acquire(String packageName, Callback callback) {
VUiKit.defer()
.when(() -> acquire(packageName))
.done(callback::callback);
}
private PackageAppData loadAppData(String packageName) {
InstalledAppInfo setting = VirtualCore.get().getInstalledAppInfo(packageName, 0);
if (setting != null) {
PackageAppData data = new PackageAppData(VApp.getApp(), setting);
synchronized (packageDataMap) {
packageDataMap.put(packageName, data);
}
return data;
}
return null;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/splash/SplashActivity.java
================================================
package io.virtualapp.splash;
import android.os.Bundle;
import android.view.WindowManager;
import com.lody.virtual.client.core.VirtualCore;
import io.virtualapp.R;
import io.virtualapp.VCommends;
import io.virtualapp.abs.ui.VActivity;
import io.virtualapp.abs.ui.VUiKit;
import io.virtualapp.home.FlurryROMCollector;
import io.virtualapp.home.HomeActivity;
import jonathanfinerty.once.Once;
public class SplashActivity extends VActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
@SuppressWarnings("unused")
boolean enterGuide = !Once.beenDone(Once.THIS_APP_INSTALL, VCommends.TAG_NEW_VERSION);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
VUiKit.defer().when(() -> {
if (!Once.beenDone("collect_flurry")) {
FlurryROMCollector.startCollect();
Once.markDone("collect_flurry");
}
long time = System.currentTimeMillis();
doActionInThread();
time = System.currentTimeMillis() - time;
long delta = 3000L - time;
if (delta > 0) {
VUiKit.sleep(delta);
}
}).done((res) -> {
HomeActivity.goHome(this);
finish();
});
}
private void doActionInThread() {
if (!VirtualCore.get().isEngineLaunched()) {
VirtualCore.get().waitForEngine();
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/vs/VSManagerActivity.java
================================================
package io.virtualapp.vs;
import io.virtualapp.abs.ui.VActivity;
/**
* @author Lody
*
*
*
*/
public class VSManagerActivity extends VActivity {
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/BallGridBeatIndicator.java
================================================
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 onCreateAnimators() {
ArrayList 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;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/BallPulseIndicator.java
================================================
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 onCreateAnimators() {
ArrayList 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;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/BaseView.java
================================================
package io.virtualapp.widgets;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
public abstract class BaseView extends View {
public ValueAnimator valueAnimator;
public BaseView(Context context) {
this(context, null);
}
public BaseView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
InitPaint();
}
public void startAnim() {
stopAnim();
startViewAnim(0f, 1f, 500);
}
public void startAnim(int time) {
stopAnim();
startViewAnim(0f, 1f, time);
}
public void stopAnim() {
if (valueAnimator != null) {
clearAnimation();
valueAnimator.setRepeatCount(0);
valueAnimator.cancel();
valueAnimator.end();
if (OnStopAnim() == 0) {
valueAnimator.setRepeatCount(0);
valueAnimator.cancel();
valueAnimator.end();
}
}
}
private ValueAnimator startViewAnim(float startF, final float endF, long time) {
valueAnimator = ValueAnimator.ofFloat(startF, endF);
valueAnimator.setDuration(time);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(SetAnimRepeatCount());
if (ValueAnimator.RESTART == SetAnimRepeatMode()) {
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
} else if (ValueAnimator.REVERSE == SetAnimRepeatMode()) {
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
}
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
OnAnimationUpdate(valueAnimator);
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
}
@Override
public void onAnimationRepeat(Animator animation) {
super.onAnimationRepeat(animation);
OnAnimationRepeat(animation);
}
});
if (!valueAnimator.isRunning()) {
AnimIsRunning();
valueAnimator.start();
}
return valueAnimator;
}
protected abstract void InitPaint();
protected abstract void OnAnimationUpdate(ValueAnimator valueAnimator);
protected abstract void OnAnimationRepeat(Animator animation);
protected abstract int OnStopAnim();
protected abstract int SetAnimRepeatMode();
protected abstract int SetAnimRepeatCount();
protected abstract void AnimIsRunning();
public float getFontlength(Paint paint, String str) {
Rect rect = new Rect();
paint.getTextBounds(str, 0, str.length(), rect);
return rect.width();
}
public float getFontHeight(Paint paint, String str) {
Rect rect = new Rect();
paint.getTextBounds(str, 0, str.length(), rect);
return rect.height();
}
public float getFontHeight(Paint paint) {
Paint.FontMetrics fm = paint.getFontMetrics();
return fm.descent - fm.ascent;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/CardStackAdapter.java
================================================
package io.virtualapp.widgets;
import java.util.ArrayList;
import java.util.List;
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.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import io.virtualapp.R;
/**
* This class acts as an adapter for the {@link CardStackLayout} view. This
* adapter is intentionally made an abstract class with following abstract
* methods -
*
*
* {@link #getCount()} - Decides the number of views present in the view
*
* {@link #createView(int, ViewGroup)} - Creates the view for all positions in
* range [0, {@link #getCount()})
*
* Contains the logic for touch events in {@link #onTouch(View, MotionEvent)}
*/
public abstract class CardStackAdapter implements View.OnTouchListener, View.OnClickListener {
public static final int ANIM_DURATION = 600;
public static final int DECELERATION_FACTOR = 2;
public static final int INVALID_CARD_POSITION = -1;
private final int mScreenHeight;
private final int dp30;
// Settings for the adapter from layout
private float mCardGapBottom;
private float mCardGap;
private int mParallaxScale;
private boolean mParallaxEnabled;
private boolean mShowInitAnimation;
private int fullCardHeight;
private View[] mCardViews;
private float dp8;
private CardStackLayout mParent;
private boolean mScreenTouchable = false;
private float mTouchFirstY = -1;
private float mTouchPrevY = -1;
private float mTouchDistance = 0;
private int mSelectedCardPosition = INVALID_CARD_POSITION;
private float scaleFactorForElasticEffect;
private int mParentPaddingTop = 0;
private int mCardPaddingInternal = 0;
public CardStackAdapter(Context context) {
Resources resources = context.getResources();
DisplayMetrics dm = Resources.getSystem().getDisplayMetrics();
mScreenHeight = dm.heightPixels;
dp30 = (int) resources.getDimension(R.dimen.dp30);
scaleFactorForElasticEffect = (int) resources.getDimension(R.dimen.dp8);
dp8 = (int) resources.getDimension(R.dimen.dp8);
}
protected float getCardGapBottom() {
return mCardGapBottom;
}
/**
* Defines and initializes the view to be shown in the
* {@link CardStackLayout} Provides two parameters to the sub-class namely -
*
* @param position
* @param container
* @return View corresponding to the position and parent container
*/
public abstract View createView(int position, ViewGroup container);
/**
* Defines the number of cards that are present in the
* {@link CardStackLayout}
*
* @return cardCount - Number of views in the related
* {@link CardStackLayout}
*/
public abstract int getCount();
/**
* Returns true if no animation is in progress currently. Can be used to
* disable any events if they are not allowed during an animation. Returns
* false if an animation is in progress.
*
* @return - true if animation in progress, false otherwise
*/
public boolean isScreenTouchable() {
return mScreenTouchable;
}
private void setScreenTouchable(boolean screenTouchable) {
this.mScreenTouchable = screenTouchable;
}
void addView(final int position) {
View root = createView(position, mParent);
root.setOnTouchListener(this);
root.setTag(R.id.cardstack_internal_position_tag, position);
root.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mCardPaddingInternal = root.getPaddingTop();
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, fullCardHeight);
root.setLayoutParams(lp);
if (mShowInitAnimation) {
root.setY(getCardFinalY(position));
setScreenTouchable(false);
} else {
root.setY(getCardOriginalY(position) - mParentPaddingTop);
setScreenTouchable(true);
}
mCardViews[position] = root;
mParent.addView(root);
}
protected float getCardFinalY(int position) {
return mScreenHeight - dp30 - ((getCount() - position) * mCardGapBottom) - mCardPaddingInternal;
}
protected float getCardOriginalY(int position) {
return mParentPaddingTop + mCardGap * position;
}
/**
* Resets all cards in {@link CardStackLayout} to their initial positions
*
* @param r
* Execute r.run() once the reset animation is done
*/
public void resetCards(Runnable r) {
List animations = new ArrayList<>(getCount());
for (int i = 0; i < getCount(); i++) {
final View child = mCardViews[i];
animations.add(ObjectAnimator.ofFloat(child, View.Y, (int) child.getY(), getCardOriginalY(i)));
}
startAnimations(animations, r, true);
}
/**
* Plays together all animations passed in as parameter. Once animation is
* completed, r.run() is executed. If parameter isReset is set to true,
* {@link #mSelectedCardPosition} is set to {@link #INVALID_CARD_POSITION}
*
* @param animations
* @param r
* @param isReset
*/
private void startAnimations(List animations, final Runnable r, final boolean isReset) {
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animations);
animatorSet.setDuration(ANIM_DURATION);
animatorSet.setInterpolator(new DecelerateInterpolator(DECELERATION_FACTOR));
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (r != null)
r.run();
setScreenTouchable(true);
if (isReset)
mSelectedCardPosition = INVALID_CARD_POSITION;
}
});
animatorSet.start();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (!isScreenTouchable()) {
return false;
}
float y = event.getRawY();
int positionOfCardToMove = (int) v.getTag(R.id.cardstack_internal_position_tag);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN :
if (mTouchFirstY != -1) {
return false;
}
mTouchPrevY = mTouchFirstY = y;
mTouchDistance = 0;
break;
case MotionEvent.ACTION_MOVE :
if (mSelectedCardPosition == INVALID_CARD_POSITION)
moveCards(positionOfCardToMove, y - mTouchFirstY);
mTouchDistance += Math.abs(y - mTouchPrevY);
break;
case MotionEvent.ACTION_CANCEL :
case MotionEvent.ACTION_UP :
if (mTouchDistance < dp8 && Math.abs(y - mTouchFirstY) < dp8
&& mSelectedCardPosition == INVALID_CARD_POSITION) {
onClick(v);
} else {
resetCards();
}
mTouchPrevY = mTouchFirstY = -1;
mTouchDistance = 0;
return false;
}
return true;
}
@Override
public void onClick(final View v) {
if (!isScreenTouchable()) {
return;
}
setScreenTouchable(false);
if (mSelectedCardPosition == INVALID_CARD_POSITION) {
mSelectedCardPosition = (int) v.getTag(R.id.cardstack_internal_position_tag);
List animations = new ArrayList<>(getCount());
for (int i = 0; i < getCount(); i++) {
View child = mCardViews[i];
animations.add(getAnimatorForView(child, i, mSelectedCardPosition));
}
startAnimations(animations, () -> {
setScreenTouchable(true);
if (mParent.getOnCardSelectedListener() != null) {
mParent.getOnCardSelectedListener().onCardSelected(v, mSelectedCardPosition);
}
}, false);
}
}
/**
* This method can be overridden to have different animations for each card
* when a click event happens on any card view. This method will be called
* for every
*
* @param view
* The view for which this method needs to return an animator
* @param selectedCardPosition
* Position of the card that was clicked
* @param currentCardPosition
* Position of the current card
* @return animator which has to be applied on the current card
*/
protected Animator getAnimatorForView(View view, int currentCardPosition, int selectedCardPosition) {
if (currentCardPosition != selectedCardPosition) {
return ObjectAnimator.ofFloat(view, View.Y, (int) view.getY(), getCardFinalY(currentCardPosition));
} else {
return ObjectAnimator.ofFloat(view, View.Y, (int) view.getY(),
getCardOriginalY(0) + (currentCardPosition * mCardGapBottom));
}
}
private void moveCards(int positionOfCardToMove, float diff) {
if (diff < 0 || positionOfCardToMove < 0 || positionOfCardToMove >= getCount())
return;
for (int i = positionOfCardToMove; i < getCount(); i++) {
final View child = mCardViews[i];
float diffCard = diff / scaleFactorForElasticEffect;
if (mParallaxEnabled) {
if (mParallaxScale > 0) {
diffCard = diffCard * (mParallaxScale / 3) * (getCount() + 1 - i);
} else {
int scale = mParallaxScale * -1;
diffCard = diffCard * (i * (scale / 3) + 1);
}
} else
diffCard = diffCard * (getCount() * 2 + 1);
child.setY(getCardOriginalY(i) + diffCard);
}
}
/**
* Provides an API to {@link CardStackLayout} to set the parameters provided
* to it in its XML
*
* @param cardStackLayout
* Parent of all cards
*/
void setAdapterParams(CardStackLayout cardStackLayout) {
mParent = cardStackLayout;
mCardViews = new View[getCount()];
mCardGapBottom = cardStackLayout.getCardGapBottom();
mCardGap = cardStackLayout.getCardGap();
mParallaxScale = cardStackLayout.getParallaxScale();
mParallaxEnabled = cardStackLayout.isParallaxEnabled();
if (mParallaxEnabled && mParallaxScale == 0)
mParallaxEnabled = false;
mShowInitAnimation = cardStackLayout.isShowInitAnimation();
mParentPaddingTop = cardStackLayout.getPaddingTop();
fullCardHeight = (int) (mScreenHeight - dp30 - dp8 - getCount() * mCardGapBottom);
}
/**
* Resets all cards in {@link CardStackLayout} to their initial positions
*/
public void resetCards() {
resetCards(null);
}
/**
* Returns false if all the cards are in their initial position i.e. no card
* is selected
*
* Returns true if the {@link CardStackLayout} has a card selected and all
* other cards are at the bottom of the screen.
*
* @return true if any card is selected, false otherwise
*/
public boolean isCardSelected() {
return mSelectedCardPosition != INVALID_CARD_POSITION;
}
/**
* Returns the position of selected card. If no card is selected, returns
* {@link #INVALID_CARD_POSITION}
*/
public int getSelectedCardPosition() {
return mSelectedCardPosition;
}
/**
* Since there is no view recycling in {@link CardStackLayout}, we maintain
* an instance of every view that is set for every position. This method
* returns a view at the requested position.
*
* @param position
* Position of card in {@link CardStackLayout}
* @return View at requested position
*/
public View getCardView(int position) {
if (mCardViews == null)
return null;
return mCardViews[position];
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/CardStackLayout.java
================================================
package io.virtualapp.widgets;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import io.virtualapp.R;
/**
* Displays a list of cards as a stack on the screen.
*
* XML attributes
*
* See {@link R.styleable#CardStackLayout CardStackLayout Attributes}
*
* {@link R.styleable#CardStackLayout_showInitAnimation}
* {@link R.styleable#CardStackLayout_card_gap}
* {@link R.styleable#CardStackLayout_card_gap_bottom}
* {@link R.styleable#CardStackLayout_parallax_enabled}
* {@link R.styleable#CardStackLayout_parallax_scale}
*/
public class CardStackLayout extends FrameLayout {
public static final boolean PARALLAX_ENABLED_DEFAULT = false;
public static final boolean SHOW_INIT_ANIMATION_DEFAULT = true;
private float mCardGapBottom;
private float mCardGap;
private boolean mShowInitAnimation;
private boolean mParallaxEnabled;
private int mParallaxScale;
private OnCardSelected mOnCardSelectedListener = null;
private CardStackAdapter mAdapter = null;
public CardStackLayout(Context context) {
super(context);
resetDefaults();
}
public CardStackLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CardStackLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
handleArgs(context, attrs, defStyleAttr, 0);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CardStackLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
handleArgs(context, attrs, defStyleAttr, defStyleRes);
}
/**
* package restricted
*/
OnCardSelected getOnCardSelectedListener() {
return mOnCardSelectedListener;
}
/**
* Listen on card selection events for {@link CardStackLayout}. Sends
* clicked view and it's corresponding position in the callback.
*
* @param onCardSelectedListener
* listener
*/
public void setOnCardSelectedListener(OnCardSelected onCardSelectedListener) {
this.mOnCardSelectedListener = onCardSelectedListener;
}
private void resetDefaults() {
mOnCardSelectedListener = null;
}
private void handleArgs(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
resetDefaults();
final TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CardStackLayout, defStyleAttr,
defStyleRes);
mParallaxEnabled = a.getBoolean(R.styleable.CardStackLayout_parallax_enabled, PARALLAX_ENABLED_DEFAULT);
mShowInitAnimation = a.getBoolean(R.styleable.CardStackLayout_showInitAnimation, SHOW_INIT_ANIMATION_DEFAULT);
mParallaxScale = a.getInteger(R.styleable.CardStackLayout_parallax_scale,
getResources().getInteger(R.integer.parallax_scale_default));
mCardGap = a.getDimension(R.styleable.CardStackLayout_card_gap, getResources().getDimension(R.dimen.card_gap));
mCardGapBottom = a.getDimension(R.styleable.CardStackLayout_card_gap_bottom,
getResources().getDimension(R.dimen.card_gap_bottom));
a.recycle();
}
/**
* @return adapter of type {@link CardStackAdapter} that is set for this
* view.
*/
public CardStackAdapter getAdapter() {
return mAdapter;
}
/**
* Set the adapter for this {@link CardStackLayout}
*
* @param adapter
* Should extend {@link CardStackAdapter}
*/
public void setAdapter(CardStackAdapter adapter) {
this.mAdapter = adapter;
mAdapter.setAdapterParams(this);
for (int i = 0; i < mAdapter.getCount(); i++) {
mAdapter.addView(i);
}
if (mShowInitAnimation) {
postDelayed(this::restoreCards, 500);
}
}
/**
* @return currently set parallax scale value.
*/
public int getParallaxScale() {
return mParallaxScale;
}
/**
* Sets the value of parallax scale. Parallax scale is the factor which
* decides how much distance a card will scroll when the user drags it down.
*/
public void setParallaxScale(int mParallaxScale) {
this.mParallaxScale = mParallaxScale;
}
public boolean isParallaxEnabled() {
return mParallaxEnabled;
}
public void setParallaxEnabled(boolean mParallaxEnabled) {
this.mParallaxEnabled = mParallaxEnabled;
}
public boolean isShowInitAnimation() {
return mShowInitAnimation;
}
public void setShowInitAnimation(boolean mShowInitAnimation) {
this.mShowInitAnimation = mShowInitAnimation;
}
/**
* @return the gap (in pixels) between two consecutive cards
*/
public float getCardGap() {
return mCardGap;
}
/**
* Set the gap (in pixels) between two consecutive cards
*/
public void setCardGap(float mCardGap) {
this.mCardGap = mCardGap;
}
/**
* @return gap between the two consecutive cards when collapsed to the
* bottom of the screen
*/
public float getCardGapBottom() {
return mCardGapBottom;
}
public void setCardGapBottom(float mCardGapBottom) {
this.mCardGapBottom = mCardGapBottom;
}
/**
* @return true if a card is selected, false otherwise
*/
public boolean isCardSelected() {
return mAdapter.isCardSelected();
}
/**
* Removes the adapter that was previously set using
* {@link #setAdapter(CardStackAdapter)}
*/
public void removeAdapter() {
if (getChildCount() > 0)
removeAllViews();
mAdapter = null;
mOnCardSelectedListener = null;
}
/**
* Animates the cards to their initial position in the layout.
*/
public void restoreCards() {
mAdapter.resetCards();
}
/**
* Intimates the implementing class about the selection of a card
*/
public interface OnCardSelected {
void onCardSelected(View v, int position);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/CircularAnim.java
================================================
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();
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/DragSelectRecyclerView.java
================================================
package io.virtualapp.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import io.virtualapp.R;
/**
* @author Aidan Follestad (afollestad)
*/
public class DragSelectRecyclerView extends RecyclerView {
private static final boolean LOGGING = false;
private static final int AUTO_SCROLL_DELAY = 25;
private int mLastDraggedIndex = -1;
private DragSelectRecyclerViewAdapter> mAdapter;
private int mInitialSelection;
private boolean mDragSelectActive;
private int mMinReached;
private int mMaxReached;
private int mHotspotHeight;
private int mHotspotOffsetTop;
private int mHotspotOffsetBottom;
private int mHotspotTopBoundStart;
private int mHotspotTopBoundEnd;
private int mHotspotBottomBoundStart;
private int mHotspotBottomBoundEnd;
private int mAutoScrollVelocity;
private FingerListener mFingerListener;
private boolean mInTopHotspot;
private boolean mInBottomHotspot;
private Handler mAutoScrollHandler;
private Runnable mAutoScrollRunnable = new Runnable() {
@Override
public void run() {
if (mAutoScrollHandler == null)
return;
if (mInTopHotspot) {
scrollBy(0, -mAutoScrollVelocity);
mAutoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY);
} else if (mInBottomHotspot) {
scrollBy(0, mAutoScrollVelocity);
mAutoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY);
}
}
};
private RectF mTopBoundRect;
private RectF mBottomBoundRect;
private Paint mDebugPaint;
private boolean mDebugEnabled = false;
public DragSelectRecyclerView(Context context) {
super(context);
init(context, null);
}
public DragSelectRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public DragSelectRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private static void LOG(String message, Object... args) {
//noinspection PointlessBooleanExpression
if (!LOGGING) return;
if (args != null) {
Log.d("DragSelectRecyclerView", String.format(message, args));
} else {
Log.d("DragSelectRecyclerView", message);
}
}
private void init(Context context, AttributeSet attrs) {
mAutoScrollHandler = new Handler();
final int defaultHotspotHeight = context.getResources().getDimensionPixelSize(R.dimen.dsrv_defaultHotspotHeight);
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DragSelectRecyclerView, 0, 0);
try {
boolean autoScrollEnabled = a.getBoolean(R.styleable.DragSelectRecyclerView_dsrv_autoScrollEnabled, true);
if (!autoScrollEnabled) {
mHotspotHeight = -1;
mHotspotOffsetTop = -1;
mHotspotOffsetBottom = -1;
LOG("Auto-scroll disabled");
} else {
mHotspotHeight = a.getDimensionPixelSize(
R.styleable.DragSelectRecyclerView_dsrv_autoScrollHotspotHeight, defaultHotspotHeight);
mHotspotOffsetTop = a.getDimensionPixelSize(
R.styleable.DragSelectRecyclerView_dsrv_autoScrollHotspot_offsetTop, 0);
mHotspotOffsetBottom = a.getDimensionPixelSize(
R.styleable.DragSelectRecyclerView_dsrv_autoScrollHotspot_offsetBottom, 0);
LOG("Hotspot height = %d", mHotspotHeight);
}
} finally {
a.recycle();
}
} else {
mHotspotHeight = defaultHotspotHeight;
LOG("Hotspot height = %d", mHotspotHeight);
}
}
public void setFingerListener(@Nullable FingerListener listener) {
this.mFingerListener = listener;
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, heightSpec);
if (mHotspotHeight > -1) {
mHotspotTopBoundStart = mHotspotOffsetTop;
mHotspotTopBoundEnd = mHotspotOffsetTop + mHotspotHeight;
mHotspotBottomBoundStart = (getMeasuredHeight() - mHotspotHeight) - mHotspotOffsetBottom;
mHotspotBottomBoundEnd = getMeasuredHeight() - mHotspotOffsetBottom;
LOG("RecyclerView height = %d", getMeasuredHeight());
LOG("Hotspot top bound = %d to %d", mHotspotTopBoundStart, mHotspotTopBoundStart);
LOG("Hotspot bottom bound = %d to %d", mHotspotBottomBoundStart, mHotspotBottomBoundEnd);
}
}
public boolean setDragSelectActive(boolean active, int initialSelection) {
if (active && mDragSelectActive) {
LOG("Drag selection is already active.");
return false;
}
mLastDraggedIndex = -1;
mMinReached = -1;
mMaxReached = -1;
if (!mAdapter.isIndexSelectable(initialSelection)) {
mDragSelectActive = false;
mInitialSelection = -1;
mLastDraggedIndex = -1;
LOG("Index %d is not selectable.", initialSelection);
return false;
}
mAdapter.setSelected(initialSelection, true);
mDragSelectActive = active;
mInitialSelection = initialSelection;
mLastDraggedIndex = initialSelection;
if (mFingerListener != null)
mFingerListener.onDragSelectFingerAction(true);
LOG("Drag selection initialized, starting at index %d.", initialSelection);
return true;
}
/**
* Use {@link #setAdapter(DragSelectRecyclerViewAdapter)} instead.
*/
@Override
@Deprecated
public void setAdapter(Adapter adapter) {
if (!(adapter instanceof DragSelectRecyclerViewAdapter>))
throw new IllegalArgumentException("Adapter must be a DragSelectRecyclerViewAdapter.");
setAdapter((DragSelectRecyclerViewAdapter>) adapter);
}
public void setAdapter(DragSelectRecyclerViewAdapter> adapter) {
super.setAdapter(adapter);
mAdapter = adapter;
}
private int getItemPosition(MotionEvent e) {
final View v = findChildViewUnder(e.getX(), e.getY());
if (v == null) return NO_POSITION;
if (v.getTag() == null || !(v.getTag() instanceof ViewHolder))
throw new IllegalStateException("Make sure your adapter makes a call to super.onBindViewHolder(), and doesn't override itemView tags.");
final ViewHolder holder = (ViewHolder) v.getTag();
return holder.getAdapterPosition();
}
public final void enableDebug() {
mDebugEnabled = true;
invalidate();
}
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
if (mDebugEnabled) {
if (mDebugPaint == null) {
mDebugPaint = new Paint();
mDebugPaint.setColor(Color.BLACK);
mDebugPaint.setAntiAlias(true);
mDebugPaint.setStyle(Paint.Style.FILL);
mTopBoundRect = new RectF(0, mHotspotTopBoundStart, getMeasuredWidth(), mHotspotTopBoundEnd);
mBottomBoundRect = new RectF(0, mHotspotBottomBoundStart, getMeasuredWidth(), mHotspotBottomBoundEnd);
}
c.drawRect(mTopBoundRect, mDebugPaint);
c.drawRect(mBottomBoundRect, mDebugPaint);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
if (mAdapter.getItemCount() == 0)
return super.dispatchTouchEvent(e);
if (mDragSelectActive) {
if (e.getAction() == MotionEvent.ACTION_UP) {
mDragSelectActive = false;
mInTopHotspot = false;
mInBottomHotspot = false;
mAutoScrollHandler.removeCallbacks(mAutoScrollRunnable);
if (mFingerListener != null)
mFingerListener.onDragSelectFingerAction(false);
return true;
} else if (e.getAction() == MotionEvent.ACTION_MOVE) {
// Check for auto-scroll hotspot
if (mHotspotHeight > -1) {
if (e.getY() >= mHotspotTopBoundStart && e.getY() <= mHotspotTopBoundEnd) {
mInBottomHotspot = false;
if (!mInTopHotspot) {
mInTopHotspot = true;
LOG("Now in TOP hotspot");
mAutoScrollHandler.removeCallbacks(mAutoScrollRunnable);
mAutoScrollHandler.postDelayed(mAutoScrollRunnable, AUTO_SCROLL_DELAY);
}
final float simulatedFactor = mHotspotTopBoundEnd - mHotspotTopBoundStart;
final float simulatedY = e.getY() - mHotspotTopBoundStart;
mAutoScrollVelocity = (int) (simulatedFactor - simulatedY) / 2;
LOG("Auto scroll velocity = %d", mAutoScrollVelocity);
} else if (e.getY() >= mHotspotBottomBoundStart && e.getY() <= mHotspotBottomBoundEnd) {
mInTopHotspot = false;
if (!mInBottomHotspot) {
mInBottomHotspot = true;
LOG("Now in BOTTOM hotspot");
mAutoScrollHandler.removeCallbacks(mAutoScrollRunnable);
mAutoScrollHandler.postDelayed(mAutoScrollRunnable, AUTO_SCROLL_DELAY);
}
final float simulatedY = e.getY() + mHotspotBottomBoundEnd;
final float simulatedFactor = mHotspotBottomBoundStart + mHotspotBottomBoundEnd;
mAutoScrollVelocity = (int) (simulatedY - simulatedFactor) / 2;
LOG("Auto scroll velocity = %d", mAutoScrollVelocity);
} else if (mInTopHotspot || mInBottomHotspot) {
LOG("Left the hotspot");
mAutoScrollHandler.removeCallbacks(mAutoScrollRunnable);
mInTopHotspot = false;
mInBottomHotspot = false;
}
}
// Drag selection logic
// NOTE: DISABLE IT
// if (itemPosition != NO_POSITION && mLastDraggedIndex != itemPosition) {
// mLastDraggedIndex = itemPosition;
// if (mMinReached == -1) mMinReached = mLastDraggedIndex;
// if (mMaxReached == -1) mMaxReached = mLastDraggedIndex;
// if (mLastDraggedIndex > mMaxReached)
// mMaxReached = mLastDraggedIndex;
// if (mLastDraggedIndex < mMinReached)
// mMinReached = mLastDraggedIndex;
// if (mAdapter != null)
// mAdapter.selectRange(mInitialSelection, mLastDraggedIndex, mMinReached, mMaxReached);
// if (mInitialSelection == mLastDraggedIndex) {
// mMinReached = mLastDraggedIndex;
// mMaxReached = mLastDraggedIndex;
// }
// }
return true;
}
}
return super.dispatchTouchEvent(e);
}
public interface FingerListener {
void onDragSelectFingerAction(boolean fingerDown);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/DragSelectRecyclerViewAdapter.java
================================================
package io.virtualapp.widgets;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;
/**
* @author Aidan Follestad (afollestad)
*/
public abstract class DragSelectRecyclerViewAdapter extends RecyclerView.Adapter {
private ArrayList mSelectedIndices;
private SelectionListener mSelectionListener;
private int mLastCount = -1;
private int mMaxSelectionCount = -1;
protected DragSelectRecyclerViewAdapter() {
mSelectedIndices = new ArrayList<>();
}
private void fireSelectionListener() {
if (mLastCount == mSelectedIndices.size())
return;
mLastCount = mSelectedIndices.size();
if (mSelectionListener != null)
mSelectionListener.onDragSelectionChanged(mLastCount);
}
public void setMaxSelectionCount(int maxSelectionCount) {
this.mMaxSelectionCount = maxSelectionCount;
}
public void setSelectionListener(SelectionListener selectionListener) {
this.mSelectionListener = selectionListener;
}
public void saveInstanceState(Bundle out) {
saveInstanceState("selected_indices", out);
}
public void saveInstanceState(String key, Bundle out) {
out.putSerializable(key, mSelectedIndices);
}
public void restoreInstanceState(Bundle in) {
restoreInstanceState("selected_indices", in);
}
public void restoreInstanceState(String key, Bundle in) {
if (in != null && in.containsKey(key)) {
//noinspection unchecked
mSelectedIndices = (ArrayList) in.getSerializable(key);
if (mSelectedIndices == null) mSelectedIndices = new ArrayList<>();
else fireSelectionListener();
}
}
public final void setSelected(int index, boolean selected) {
if (!isIndexSelectable(index))
selected = false;
if (selected) {
if (!mSelectedIndices.contains(index) &&
(mMaxSelectionCount == -1 ||
mSelectedIndices.size() < mMaxSelectionCount)) {
mSelectedIndices.add(index);
notifyItemChanged(index);
}
} else if (mSelectedIndices.contains(index)) {
mSelectedIndices.remove((Integer) index);
notifyItemChanged(index);
}
fireSelectionListener();
}
public final boolean toggleSelected(int index) {
boolean selectedNow = false;
if (isIndexSelectable(index)) {
if (mSelectedIndices.contains(index)) {
mSelectedIndices.remove((Integer) index);
} else if (mMaxSelectionCount == -1 ||
mSelectedIndices.size() < mMaxSelectionCount) {
mSelectedIndices.add(index);
selectedNow = true;
}
notifyItemChanged(index);
}
fireSelectionListener();
return selectedNow;
}
protected boolean isIndexSelectable(int index) {
return true;
}
@CallSuper
@Override
public void onBindViewHolder(VH holder, int position) {
holder.itemView.setTag(holder);
}
public final void selectRange(int from, int to, int min, int max) {
if (from == to) {
// Finger is back on the initial item, unselect everything else
for (int i = min; i <= max; i++) {
if (i == from) continue;
setSelected(i, false);
}
fireSelectionListener();
return;
}
if (to < from) {
// When selecting from one to previous items
for (int i = to; i <= from; i++)
setSelected(i, true);
if (min > -1 && min < to) {
// Unselect items that were selected during this drag but no longer are
for (int i = min; i < to; i++) {
if (i == from) continue;
setSelected(i, false);
}
}
if (max > -1) {
for (int i = from + 1; i <= max; i++)
setSelected(i, false);
}
} else {
// When selecting from one to next items
for (int i = from; i <= to; i++)
setSelected(i, true);
if (max > -1 && max > to) {
// Unselect items that were selected during this drag but no longer are
for (int i = to + 1; i <= max; i++) {
if (i == from) continue;
setSelected(i, false);
}
}
if (min > -1) {
for (int i = min; i < from; i++)
setSelected(i, false);
}
}
fireSelectionListener();
}
public final void selectAll() {
int max = getItemCount();
mSelectedIndices.clear();
for (int i = 0; i < max; i++) {
if (isIndexSelectable(i)) {
mSelectedIndices.add(i);
}
}
notifyDataSetChanged();
fireSelectionListener();
}
public final void clearSelected() {
mSelectedIndices.clear();
notifyDataSetChanged();
fireSelectionListener();
}
public final int getSelectedCount() {
return mSelectedIndices.size();
}
public final Integer[] getSelectedIndices() {
return mSelectedIndices.toArray(new Integer[mSelectedIndices.size()]);
}
public final boolean isIndexSelected(int index) {
return mSelectedIndices.contains(index);
}
public interface SelectionListener {
void onDragSelectionChanged(int count);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/EatBeansView.java
================================================
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.graphics.RectF;
import android.util.AttributeSet;
public class EatBeansView extends BaseView {
int eatSpeed = 5;
private Paint mPaint, mPaintEye;
private float mWidth = 0f;
private float mHigh = 0f;
private float mPadding = 5f;
private float eatErWidth = 60f;
private float eatErPositionX = 0f;
private float beansWidth = 10f;
private float mAngle = 34;
private float eatErStartAngle = mAngle;
private float eatErEndAngle = 360 - 2 * eatErStartAngle;
public EatBeansView(Context context) {
super(context);
}
public EatBeansView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EatBeansView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHigh = getMeasuredHeight();
}
@Override
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
, true, mPaint);
canvas.drawCircle(mPadding + eatErPositionX + eatErWidth / 2,
mHigh / 2 - eatErWidth / 4,
beansWidth / 2, mPaintEye);
int beansCount = (int) ((mWidth - mPadding * 2 - eatErWidth) / beansWidth / 2);
for (int i = 0; i < beansCount; i++) {
float x = beansCount * i + beansWidth / 2 + mPadding + eatErWidth;
if (x > eatRightX) {
canvas.drawCircle(x,
mHigh / 2, beansWidth / 2, mPaint);
}
}
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.WHITE);
mPaintEye = new Paint();
mPaintEye.setAntiAlias(true);
mPaintEye.setStyle(Paint.Style.FILL);
mPaintEye.setColor(Color.BLACK);
}
public void setViewColor(int color) {
mPaint.setColor(color);
postInvalidate();
}
public void setEyeColor(int color) {
mPaintEye.setColor(color);
postInvalidate();
}
@Override
protected void InitPaint() {
initPaint();
}
@Override
protected void OnAnimationUpdate(ValueAnimator valueAnimator) {
float mAnimatedValue = (float) valueAnimator.getAnimatedValue();
eatErPositionX = (mWidth - 2 * mPadding - eatErWidth) * mAnimatedValue;
eatErStartAngle = mAngle * (1 - (mAnimatedValue * eatSpeed - (int) (mAnimatedValue * eatSpeed)));
eatErEndAngle = 360 - eatErStartAngle * 2;
invalidate();
}
@Override
protected void OnAnimationRepeat(Animator animation) {
}
@Override
protected int OnStopAnim() {
eatErPositionX = 0;
postInvalidate();
return 1;
}
@Override
protected int SetAnimRepeatMode() {
return ValueAnimator.RESTART;
}
@Override
protected void AnimIsRunning() {
}
@Override
protected int SetAnimRepeatCount() {
return ValueAnimator.INFINITE;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/Indicator.java
================================================
package io.virtualapp.widgets;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import java.util.ArrayList;
import java.util.HashMap;
public abstract class Indicator extends Drawable implements Animatable {
private static final Rect ZERO_BOUNDS_RECT = new Rect();
protected Rect drawBounds = ZERO_BOUNDS_RECT;
private HashMap mUpdateListeners = new HashMap<>();
private ArrayList mAnimators;
private int alpha = 255;
private boolean mHasAnimators;
private Paint mPaint = new Paint();
public Indicator() {
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
public int getColor() {
return mPaint.getColor();
}
public void setColor(int color) {
mPaint.setColor(color);
}
@Override
public int getAlpha() {
return alpha;
}
@Override
public void setAlpha(int alpha) {
this.alpha = alpha;
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public void draw(Canvas canvas) {
draw(canvas, mPaint);
}
public abstract void draw(Canvas canvas, Paint paint);
public abstract ArrayList onCreateAnimators();
@Override
public void start() {
ensureAnimators();
if (mAnimators == null) {
return;
}
// If the animators has not ended, do nothing.
if (isStarted()) {
return;
}
startAnimators();
invalidateSelf();
}
private void startAnimators() {
for (int i = 0; i < mAnimators.size(); i++) {
ValueAnimator animator = mAnimators.get(i);
//when the animator restart , add the updateListener again because they
// was removed by animator stop .
ValueAnimator.AnimatorUpdateListener updateListener = mUpdateListeners.get(animator);
if (updateListener != null) {
animator.addUpdateListener(updateListener);
}
animator.start();
}
}
private void stopAnimators() {
if (mAnimators != null) {
for (ValueAnimator animator : mAnimators) {
if (animator != null && animator.isStarted()) {
animator.removeAllUpdateListeners();
animator.end();
}
}
}
}
private void ensureAnimators() {
if (!mHasAnimators) {
mAnimators = onCreateAnimators();
mHasAnimators = true;
}
}
@Override
public void stop() {
stopAnimators();
}
private boolean isStarted() {
for (ValueAnimator animator : mAnimators) {
return animator.isStarted();
}
return false;
}
@Override
public boolean isRunning() {
for (ValueAnimator animator : mAnimators) {
return animator.isRunning();
}
return false;
}
/**
* Your should use this to add AnimatorUpdateListener when
* create animator , otherwise , animator doesn't work when
* the animation restart .
*
* @param updateListener
*/
public void addUpdateListener(ValueAnimator animator, ValueAnimator.AnimatorUpdateListener updateListener) {
mUpdateListeners.put(animator, updateListener);
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
setDrawBounds(bounds);
}
public void setDrawBounds(int left, int top, int right, int bottom) {
this.drawBounds = new Rect(left, top, right, bottom);
}
public void postInvalidate() {
invalidateSelf();
}
public Rect getDrawBounds() {
return drawBounds;
}
public void setDrawBounds(Rect drawBounds) {
setDrawBounds(drawBounds.left, drawBounds.top, drawBounds.right, drawBounds.bottom);
}
public int getWidth() {
return drawBounds.width();
}
public int getHeight() {
return drawBounds.height();
}
public int centerX() {
return drawBounds.centerX();
}
public int centerY() {
return drawBounds.centerY();
}
public float exactCenterX() {
return drawBounds.exactCenterX();
}
public float exactCenterY() {
return drawBounds.exactCenterY();
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/LabelView.java
================================================
package io.virtualapp.widgets;
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.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import io.virtualapp.R;
public class LabelView extends View {
private static final int DEFAULT_DEGREES = 45;
private String mTextContent;
private int mTextColor;
private float mTextSize;
private boolean mTextBold;
private boolean mFillTriangle;
private boolean mTextAllCaps;
private int mBackgroundColor;
private float mMinSize;
private float mPadding;
private int mGravity;
private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Path mPath = new Path();
public LabelView(Context context) {
this(context, null);
}
public LabelView(Context context, AttributeSet attrs) {
super(context, attrs);
obtainAttributes(context, attrs);
mTextPaint.setTextAlign(Paint.Align.CENTER);
}
private void obtainAttributes(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LabelView);
mTextContent = ta.getString(R.styleable.LabelView_lv_text);
mTextColor = ta.getColor(R.styleable.LabelView_lv_text_color, Color.parseColor("#ffffff"));
mTextSize = ta.getDimension(R.styleable.LabelView_lv_text_size, sp2px(11));
mTextBold = ta.getBoolean(R.styleable.LabelView_lv_text_bold, true);
mTextAllCaps = ta.getBoolean(R.styleable.LabelView_lv_text_all_caps, true);
mFillTriangle = ta.getBoolean(R.styleable.LabelView_lv_fill_triangle, false);
mBackgroundColor = ta.getColor(R.styleable.LabelView_lv_background_color, Color.parseColor("#FF4081"));
mMinSize = ta.getDimension(R.styleable.LabelView_lv_min_size, mFillTriangle ? dp2px(35) : dp2px(50));
mPadding = ta.getDimension(R.styleable.LabelView_lv_padding, dp2px(3.5f));
mGravity = ta.getInt(R.styleable.LabelView_lv_gravity, Gravity.TOP | Gravity.LEFT);
ta.recycle();
}
public String getText() {
return mTextContent;
}
public void setText(String text) {
mTextContent = text;
invalidate();
}
public int getTextColor() {
return mTextColor;
}
public void setTextColor(int textColor) {
mTextColor = textColor;
invalidate();
}
public float getTextSize() {
return mTextSize;
}
public void setTextSize(float textSize) {
mTextSize = sp2px(textSize);
invalidate();
}
public boolean isTextBold() {
return mTextBold;
}
public void setTextBold(boolean textBold) {
mTextBold = textBold;
invalidate();
}
public boolean isFillTriangle() {
return mFillTriangle;
}
public void setFillTriangle(boolean fillTriangle) {
mFillTriangle = fillTriangle;
invalidate();
}
public boolean isTextAllCaps() {
return mTextAllCaps;
}
public void setTextAllCaps(boolean textAllCaps) {
mTextAllCaps = textAllCaps;
invalidate();
}
public int getBgColor() {
return mBackgroundColor;
}
public void setBgColor(int backgroundColor) {
mBackgroundColor = backgroundColor;
invalidate();
}
public float getMinSize() {
return mMinSize;
}
public void setMinSize(float minSize) {
mMinSize = dp2px(minSize);
invalidate();
}
public float getPadding() {
return mPadding;
}
public void setPadding(float padding) {
mPadding = dp2px(padding);
invalidate();
}
public int getGravity() {
return mGravity;
}
/**
* Gravity.TOP | Gravity.LEFT
* Gravity.TOP | Gravity.RIGHT
* Gravity.BOTTOM | Gravity.LEFT
* Gravity.BOTTOM | Gravity.RIGHT
*/
public void setGravity(int gravity) {
mGravity = gravity;
}
@Override
protected void onDraw(Canvas canvas) {
int size = getHeight();
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
mTextPaint.setFakeBoldText(mTextBold);
mBackgroundPaint.setColor(mBackgroundColor);
float textHeight = mTextPaint.descent() - mTextPaint.ascent();
if (mFillTriangle) {
if (mGravity == (Gravity.TOP | Gravity.LEFT)) {
mPath.reset();
mPath.moveTo(0, 0);
mPath.lineTo(0, size);
mPath.lineTo(size, 0);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawTextWhenFill(size, -DEFAULT_DEGREES, canvas, true);
} else if (mGravity == (Gravity.TOP | Gravity.RIGHT)) {
mPath.reset();
mPath.moveTo(size, 0);
mPath.lineTo(0, 0);
mPath.lineTo(size, size);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawTextWhenFill(size, DEFAULT_DEGREES, canvas, true);
} else if (mGravity == (Gravity.BOTTOM | Gravity.LEFT)) {
mPath.reset();
mPath.moveTo(0, size);
mPath.lineTo(0, 0);
mPath.lineTo(size, size);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawTextWhenFill(size, DEFAULT_DEGREES, canvas, false);
} else if (mGravity == (Gravity.BOTTOM | Gravity.RIGHT)) {
mPath.reset();
mPath.moveTo(size, size);
mPath.lineTo(0, size);
mPath.lineTo(size, 0);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawTextWhenFill(size, -DEFAULT_DEGREES, canvas, false);
}
} else {
double delta = (textHeight + mPadding * 2) * Math.sqrt(2);
if (mGravity == (Gravity.TOP | Gravity.LEFT)) {
mPath.reset();
mPath.moveTo(0, (float) (size - delta));
mPath.lineTo(0, size);
mPath.lineTo(size, 0);
mPath.lineTo((float) (size - delta), 0);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawText(size, -DEFAULT_DEGREES, canvas, textHeight, true);
} else if (mGravity == (Gravity.TOP | Gravity.RIGHT)) {
mPath.reset();
mPath.moveTo(0, 0);
mPath.lineTo((float) delta, 0);
mPath.lineTo(size, (float) (size - delta));
mPath.lineTo(size, size);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawText(size, DEFAULT_DEGREES, canvas, textHeight, true);
} else if (mGravity == (Gravity.BOTTOM | Gravity.LEFT)) {
mPath.reset();
mPath.moveTo(0, 0);
mPath.lineTo(0, (float) delta);
mPath.lineTo((float) (size - delta), size);
mPath.lineTo(size, size);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawText(size, DEFAULT_DEGREES, canvas, textHeight, false);
} else if (mGravity == (Gravity.BOTTOM | Gravity.RIGHT)) {
mPath.reset();
mPath.moveTo(0, size);
mPath.lineTo((float) delta, size);
mPath.lineTo(size, (float) delta);
mPath.lineTo(size, 0);
mPath.close();
canvas.drawPath(mPath, mBackgroundPaint);
drawText(size, -DEFAULT_DEGREES, canvas, textHeight, false);
}
}
}
private void drawText(int size, float degrees, Canvas canvas, float textHeight, boolean isTop) {
canvas.save();
canvas.rotate(degrees, size / 2f, size / 2f);
float delta = isTop ? -(textHeight + mPadding * 2) / 2 : (textHeight + mPadding * 2) / 2;
float textBaseY = size / 2 - (mTextPaint.descent() + mTextPaint.ascent()) / 2 + delta;
canvas.drawText(mTextAllCaps ? mTextContent.toUpperCase() : mTextContent,
getPaddingLeft() + (size - getPaddingLeft() - getPaddingRight()) / 2, textBaseY, mTextPaint);
canvas.restore();
}
private void drawTextWhenFill(int size, float degrees, Canvas canvas, boolean isTop) {
canvas.save();
canvas.rotate(degrees, size / 2f, size / 2f);
float delta = isTop ? -size / 4 : size / 4;
float textBaseY = size / 2 - (mTextPaint.descent() + mTextPaint.ascent()) / 2 + delta;
canvas.drawText(mTextAllCaps ? mTextContent.toUpperCase() : mTextContent,
getPaddingLeft() + (size - getPaddingLeft() - getPaddingRight()) / 2, textBaseY, mTextPaint);
canvas.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredWidth = measureWidth(widthMeasureSpec);
setMeasuredDimension(measuredWidth, measuredWidth);
}
/**
* 确定View宽度大小
*/
private int measureWidth(int widthMeasureSpec) {
int result;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {//大小确定直接使用
result = specSize;
} else {
int padding = getPaddingLeft() + getPaddingRight();
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
float textWidth = mTextPaint.measureText(mTextContent + "");
result = (int) ((padding + (int) textWidth) * Math.sqrt(2));
//如果父视图的测量要求为AT_MOST,即限定了一个最大值,则再从系统建议值和自己计算值中去一个较小值
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
result = Math.max((int) mMinSize, result);
}
return result;
}
protected int dp2px(float dp) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
protected int sp2px(float sp) {
final float scale = getResources().getDisplayMetrics().scaledDensity;
return (int) (sp * scale + 0.5f);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/LauncherIconView.java
================================================
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);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/LoadingIndicatorView.java
================================================
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);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/MarqueeTextView.java
================================================
package io.virtualapp.widgets;
import android.content.Context;
import android.graphics.Canvas;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
public class MarqueeTextView extends AppCompatTextView {
private boolean isStop = false;
public MarqueeTextView(Context context) {
super(context);
}
public MarqueeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public boolean isFocused() {
if (this.isStop) {
return super.isFocused();
}
return true;
}
public void stopScroll() {
this.isStop = true;
}
public void start() {
this.isStop = false;
}
protected void onDetachedFromWindow() {
stopScroll();
super.onDetachedFromWindow();
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/MaterialRippleLayout.java
================================================
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 radiusProperty
= new Property(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 circleAlphaProperty
= new Property(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 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
*
* 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();
}
}
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/RippleButton.java
================================================
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);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/ShadowProperty.java
================================================
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;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/ShadowViewDrawable.java
================================================
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;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/Shimmer.java
================================================
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 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();
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/ShimmerViewBase.java
================================================
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);
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/ShimmerViewHelper.java
================================================
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);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/TwoGearsView.java
================================================
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);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/ViewHelper.java
================================================
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);
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/BaseTextView.java
================================================
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");
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/FitTextHelper.java
================================================
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 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;
}
}
================================================
FILE: VirtualApp/app/src/main/java/io/virtualapp/widgets/fittext/FitTextView.java
================================================
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);
}
}
================================================
FILE: VirtualApp/app/src/main/res/drawable/blue_circle.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/drawable/fab_bg.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/drawable/home_bg.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/drawable/icon_bg.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/drawable/sel_clone_app_btn.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/drawable/sel_guide_btn.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/drawable/shape_clone_app_btn.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/drawable/shape_clone_app_btn_pressed.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/activity_clone_app.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/activity_home.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/activity_install.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/activity_loading.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/activity_location_settings.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/activity_marker.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/activity_splash.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/activity_users.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/content_toolbar.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/fragment_list_app.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/item_app.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/item_clone_app.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/item_launcher_app.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/item_location_app.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/layout/item_user.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/menu/marktet_map.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/menu/user_menu.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/values/attrs.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/values/colors.xml
================================================
#607191#2A364C#607191#607191#F0DEB7#2C3B4E#314155#324257#2a3646#33cccc#ff9640#67e667#df38b1#ff4040@color/holo_blue_dark@color/holo_yellow_dark@color/holo_green_dark@color/holo_purple_dark@color/holo_red_dark#00000000#55000000#00FFFFFF
================================================
FILE: VirtualApp/app/src/main/res/values/dimens.xml
================================================
16dp16dp60dp5dp30dp8dp10dp80dp5dp16dp24dp0.5dp60dp56dp40dp
================================================
FILE: VirtualApp/app/src/main/res/values/fitTextView.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/values/ids.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/values/integers.xml
================================================
-2
================================================
FILE: VirtualApp/app/src/main/res/values/strings.xml
================================================
VirtualAppPlease wait…DesktopAdd AppOpening the app…DeleteCreate shortcutNew UserEnableSaveSave success!ManufacturerBrandDeviceFake Device InfoWifi StatusDevice InfoAboutClone AppsExternal StorageInstall (%d)No more then 9 apps can be chosen at a time!
================================================
FILE: VirtualApp/app/src/main/res/values/styles.xml
================================================
================================================
FILE: VirtualApp/app/src/main/res/values-zh-rCN/strings.xml
================================================
VirtualApp桌面添加App正在打开App,请稍等…删除创建快捷方式新的用户开启保存保存成功!制造商请稍后…品牌机型伪造设备信息Wifi状态配置设备信息关于克隆App外置存储安装 (%d)不能一次性安装超过9个App!
================================================
FILE: VirtualApp/app/src/main/res/values-zh-rTW/strings.xml
================================================
VirtualApp桌面新增App正在打開App,請稍候…刪除建立捷徑新的用戶開啟儲存儲存成功!製造商請稍候…品牌機型偽造裝置資訊Wifi狀態配置裝置資訊關於克隆App外部空間安裝 (%d)不能一次性安裝超過 9 個程式!
================================================
FILE: VirtualApp/build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.android.tools.build:gradle-experimental:0.11.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
maven {
url "https://jitpack.io"
}
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: VirtualApp/gradle/wrapper/gradle-wrapper.properties
================================================
#Sun Nov 19 13:36:42 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
================================================
FILE: VirtualApp/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useDeprecatedNdk=true
================================================
FILE: VirtualApp/gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: VirtualApp/gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: VirtualApp/lib/.gitignore
================================================
/build
.externalNativeBuild/
obj/
================================================
FILE: VirtualApp/lib/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
defaultConfig {
minSdkVersion 14
targetSdkVersion 22
versionCode 1
versionName "1.0"
externalNativeBuild {
ndkBuild {
abiFilters "armeabi-v7a", "x86"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
ndkBuild {
path file("src/main/jni/Android.mk")
}
}
lintOptions {
//IJobService need NewApi
warning 'NewApi','OnClick'
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
}
================================================
FILE: VirtualApp/lib/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/lody/Desktop/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: VirtualApp/lib/src/main/AndroidManifest.xml
================================================
================================================
FILE: VirtualApp/lib/src/main/aidl/android/accounts/IAccountAuthenticator.aidl
================================================
/*
* 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.
*/
package android.accounts;
import android.accounts.IAccountAuthenticatorResponse;
import android.accounts.Account;
import android.os.Bundle;
/**
* Service that allows the interaction with an authentication server.
* @hide
*/
interface IAccountAuthenticator {
/**
* prompts the user for account information and adds the result to the IAccountManager
*/
void addAccount(in IAccountAuthenticatorResponse response, String accountType,
String authTokenType, in String[] requiredFeatures, in Bundle options);
/**
* prompts the user for the credentials of the account
*/
void confirmCredentials(in IAccountAuthenticatorResponse response, in Account account,
in Bundle options);
/**
* gets the password by either prompting the user or querying the IAccountManager
*/
void getAuthToken(in IAccountAuthenticatorResponse response, in Account account,
String authTokenType, in Bundle options);
/**
* Gets the user-visible label of the given authtoken type.
*/
void getAuthTokenLabel(in IAccountAuthenticatorResponse response, String authTokenType);
/**
* prompts the user for a new password and writes it to the IAccountManager
*/
void updateCredentials(in IAccountAuthenticatorResponse response, in Account account,
String authTokenType, in Bundle options);
/**
* launches an activity that lets the user edit and set the properties for an authenticator
*/
void editProperties(in IAccountAuthenticatorResponse response, String accountType);
/**
* returns a Bundle where the boolean value BOOLEAN_RESULT_KEY is set if the account has the
* specified features
*/
void hasFeatures(in IAccountAuthenticatorResponse response, in Account account,
in String[] features);
/**
* Gets whether or not the account is allowed to be removed.
*/
void getAccountRemovalAllowed(in IAccountAuthenticatorResponse response, in Account account);
/**
* Returns a Bundle containing the required credentials to copy the account across users.
*/
void getAccountCredentialsForCloning(in IAccountAuthenticatorResponse response,
in Account account);
/**
* Uses the Bundle containing credentials from another instance of the authenticator to create
* a copy of the account on this user.
*/
void addAccountFromCredentials(in IAccountAuthenticatorResponse response, in Account account,
in Bundle accountCredentials);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/accounts/IAccountAuthenticatorResponse.aidl
================================================
/*
* 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.
*/
package android.accounts;
import android.os.Bundle;
/**
* The interface used to return responses from an {@link IAccountAuthenticator}
*/
interface IAccountAuthenticatorResponse {
void onResult(in Bundle value);
void onRequestContinued();
void onError(int errorCode, String errorMessage);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/accounts/IAccountManagerResponse.aidl
================================================
package android.accounts;
import android.os.Bundle;
/**
* The interface used to return responses for asynchronous calls to the {@link IAccountManager}
*/
interface IAccountManagerResponse {
void onResult(in Bundle value);
void onError(int errorCode, String errorMessage);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/app/IActivityManager/ContentProviderHolder.aidl
================================================
// ContentProviderHolder.aidl
package android.app.IActivityManager;
parcelable ContentProviderHolder;
================================================
FILE: VirtualApp/lib/src/main/aidl/android/app/IServiceConnection.aidl
================================================
/* //device/java/android/android/app/IServiceConnection.aidl
**
** Copyright 2007, 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.content.ComponentName;
/** @hide */
interface IServiceConnection {
void connected(in ComponentName name, IBinder service);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/app/IStopUserCallback.aidl
================================================
/*
** Copyright 2012, 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;
/**
* Callback to find out when we have finished stopping a user.
* {@hide}
*/
interface IStopUserCallback
{
void userStopped(int userId);
void userStopAborted(int userId);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/app/job/IJobCallback.aidl
================================================
/**
* Copyright 2014, 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.job;
/**
* The server side of the JobScheduler IPC protocols. The app-side implementation
* invokes on this interface to indicate completion of the (asynchronous) instructions
* issued by the server.
*
* In all cases, the 'who' parameter is the caller's service binder, used to track
* which Job Service instance is reporting.
*
*/
interface IJobCallback {
/**
* Immediate callback to the system after sending a start signal, used to quickly detect ANR.
*
* @param jobId Unique integer used to identify this job.
* @param ongoing True to indicate that the client is processing the job. False if the job is
* complete
*/
void acknowledgeStartMessage(int jobId, boolean ongoing);
/**
* Immediate callback to the system after sending a stop signal, used to quickly detect ANR.
*
* @param jobId Unique integer used to identify this job.
* @param reschedule Whether or not to reschedule this job.
*/
void acknowledgeStopMessage(int jobId, boolean reschedule);
/*
* Tell the job manager that the client is done with its execution, so that it can go on to
* the next one and stop attributing wakelock time to us etc.
*
* @param jobId Unique integer used to identify this job.
* @param reschedule Whether or not to reschedule this job.
*/
void jobFinished(int jobId, boolean reschedule);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/app/job/IJobService.aidl
================================================
package android.app.job;
import android.app.job.JobParameters;
/**
* Interface that the framework uses to communicate with application code that implements a
* JobService. End user code does not implement this interface directly; instead, the app's
* service implementation will extend android.app.job.JobService.
*/
interface IJobService {
/** Begin execution of application's job. */
void startJob(in JobParameters jobParams);
/** Stop execution of application's job. */
void stopJob(in JobParameters jobParams);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/content/IIntentReceiver.aidl
================================================
/*
* 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;
import android.content.Intent;
import android.os.Bundle;
/**
* System private API for dispatching intent broadcasts. This is given to the
* activity manager as part of registering for an intent broadcasts, and is
* called when it receives intents.
*
*/
interface IIntentReceiver {
void performReceive(in Intent intent, int resultCode, String data,
in Bundle extras, boolean ordered, boolean sticky, int sendingUser);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/content/ISyncAdapter.aidl
================================================
/*
* 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);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/content/ISyncContext.aidl
================================================
/*
* 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);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/content/ISyncStatusObserver.aidl
================================================
package android.content;
interface ISyncStatusObserver {
void onStatusChanged(int which);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/content/pm/IPackageDataObserver.aidl
================================================
package android.content.pm;
/**
* API for package data change related callbacks from the Package Manager.
* Some usage scenarios include deletion of cache directory, generate
* statistics related to code, data, cache usage(TODO)
*/
interface IPackageDataObserver {
void onRemoveCompleted(in String packageName, boolean succeeded);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/content/pm/IPackageDeleteObserver2.aidl
================================================
/*
* Copyright (C) 2014 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.pm;
import android.content.Intent;
interface IPackageDeleteObserver2 {
void onUserActionRequired(in Intent intent);
void onPackageDeleted(String packageName, int returnCode, String msg);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/content/pm/IPackageInstallObserver.aidl
================================================
package android.content.pm;
/**
* API for installation callbacks from the Package Manager.
*/
interface IPackageInstallObserver {
void packageInstalled(in String packageName, int returnCode);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/content/pm/IPackageInstallObserver2.aidl
================================================
/*
* Copyright (C) 2014 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.pm;
import android.content.Intent;
import android.os.Bundle;
/**
* API for installation callbacks from the Package Manager. In certain result cases
* additional information will be provided.
*/
interface IPackageInstallObserver2 {
void onUserActionRequired(in Intent intent);
/**
* The install operation has completed. {@code returnCode} holds a numeric code
* indicating success or failure. In certain cases the {@code extras} Bundle will
* contain additional details:
*
*
*
*
INSTALL_FAILED_DUPLICATE_PERMISSION
*
Two strings are provided in the extras bundle: EXTRA_EXISTING_PERMISSION
* is the name of the permission that the app is attempting to define, and
* EXTRA_EXISTING_PACKAGE is the package name of the app which has already
* defined the permission.
*
*
*/
void onPackageInstalled(String basePackageName, int returnCode, String msg, in Bundle extras);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/content/pm/IPackageInstallerCallback.aidl
================================================
package android.content.pm;
interface IPackageInstallerCallback {
void onSessionCreated(int sessionId);
void onSessionBadgingChanged(int sessionId);
void onSessionActiveChanged(int sessionId, boolean active);
void onSessionProgressChanged(int sessionId, float progress);
void onSessionFinished(int sessionId, boolean success);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/content/pm/IPackageInstallerSession.aidl
================================================
package android.content.pm;
import android.content.pm.IPackageInstallObserver2;
import android.content.IntentSender;
import android.os.ParcelFileDescriptor;
interface IPackageInstallerSession {
void setClientProgress(float progress);
void addClientProgress(float progress);
String[] getNames();
ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes);
ParcelFileDescriptor openRead(String name);
void removeSplit(String splitName);
void close();
void commit(in IntentSender statusReceiver);
void abandon();
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/location/ILocationListener.aidl
================================================
// 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);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/net/IConnectivityManager.aidl
================================================
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);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/android/net/wifi/IWifiScanner.aidl
================================================
package android.net.wifi;
import android.os.Messenger;
import android.os.Bundle;
interface IWifiScanner
{
Messenger getMessenger();
Bundle getAvailableChannels(int band);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/client/IVClient.aidl
================================================
// IVClient.aidl
package com.lody.virtual.client;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import com.lody.virtual.remote.PendingResultData;
interface IVClient {
void scheduleReceiver(in String processName,in ComponentName component, in Intent intent, in PendingResultData resultData);
void scheduleNewIntent(in String creator, in IBinder token, in Intent intent);
void finishActivity(in IBinder token);
IBinder createProxyService(in ComponentName component, in IBinder binder);
IBinder acquireProviderClient(in ProviderInfo info);
IBinder getAppThread();
IBinder getToken();
String getDebugInfo();
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/os/VUserInfo.aidl
================================================
// VUserInfo.aidl
package com.lody.virtual.os;
parcelable VUserInfo;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/AppTaskInfo.aidl
================================================
// AppTaskInfo.aidl
package com.lody.virtual.remote;
parcelable AppTaskInfo;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/BadgerInfo.aidl
================================================
// BadgerInfo.aidl
package com.lody.virtual.remote;
parcelable BadgerInfo;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/InstallResult.aidl
================================================
// InstallResult.aidl
package com.lody.virtual.remote;
parcelable InstallResult;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/InstalledAppInfo.aidl
================================================
// AppSetting.aidl
package com.lody.virtual.remote;
parcelable InstalledAppInfo;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/PendingIntentData.aidl
================================================
// PendingIntentData.aidl
package com.lody.virtual.remote;
parcelable PendingIntentData;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/PendingResultData.aidl
================================================
// PendingResultData.aidl
package com.lody.virtual.remote;
parcelable PendingResultData;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/Problem.aidl
================================================
// Problem.aidl
package com.lody.virtual.remote;
parcelable Problem;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/ReceiverInfo.aidl
================================================
// ReceiverInfo.aidl
package com.lody.virtual.remote;
parcelable ReceiverInfo;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/VDeviceInfo.aidl
================================================
// VDeviceInfo.aidl
package com.lody.virtual.remote;
parcelable VDeviceInfo;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/VParceledListSlice.aidl
================================================
// VParceledListSlice.aidl
package com.lody.virtual.remote;
parcelable VParceledListSlice;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VCell.aidl
================================================
// VCell.aidl
package com.lody.virtual.remote.vloc;
parcelable VCell;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VLocation.aidl
================================================
// VLocation.aidl
package com.lody.virtual.remote.vloc;
parcelable VLocation;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/remote/vloc/VWifi.aidl
================================================
// VWifi.aidl
package com.lody.virtual.remote.vloc;
parcelable VWifi;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IBinderDelegateService.aidl
================================================
// IBinderDelegateService.aidl
package com.lody.virtual.server;
import android.content.ComponentName;
interface IBinderDelegateService {
ComponentName getComponent();
IBinder getService();
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IPackageInstaller.aidl
================================================
package com.lody.virtual.server;
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageInstallerCallback;
import android.content.pm.IPackageInstallerSession;
import android.content.IntentSender;
import android.graphics.Bitmap;
import com.lody.virtual.remote.VParceledListSlice;
import com.lody.virtual.server.pm.installer.SessionParams;
import com.lody.virtual.server.pm.installer.SessionInfo;
interface IPackageInstaller {
int createSession(in SessionParams params, String installerPackageName, int userId);
void updateSessionAppIcon(int sessionId, in Bitmap appIcon);
void updateSessionAppLabel(int sessionId, String appLabel);
void abandonSession(int sessionId);
IPackageInstallerSession openSession(int sessionId);
SessionInfo getSessionInfo(int sessionId);
VParceledListSlice getAllSessions(int userId);
VParceledListSlice getMySessions(String installerPackageName, int userId);
void registerCallback(IPackageInstallerCallback callback, int userId);
void unregisterCallback(IPackageInstallerCallback callback);
void uninstall(String packageName, String callerPackageName, int flags,
in IntentSender statusReceiver, int userId);
void setPermissionsResult(int sessionId, boolean accepted);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/IPackageInstallerSession.aidl
================================================
package com.lody.virtual.server;
import android.content.IntentSender;
import android.os.ParcelFileDescriptor;
interface IPackageInstallerSession {
void setClientProgress(float progress);
void addClientProgress(float progress);
String[] getNames();
ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes);
ParcelFileDescriptor openRead(String name);
void close();
void commit(in IntentSender statusReceiver);
void abandon();
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/interfaces/IAppRequestListener.aidl
================================================
// IAppRequestListener.aidl
package com.lody.virtual.server.interfaces;
interface IAppRequestListener {
void onRequestInstall(in String path);
void onRequestUninstall(in String pkg);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/interfaces/IIntentFilterObserver.aidl
================================================
// IIntentFilterObserver.aidl
package com.lody.virtual.server.interfaces;
// Declare any non-default types here with import statements
interface IIntentFilterObserver {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
Intent filter(in Intent intent);
void setCallBack(IBinder callBack);
IBinder getCallBack();
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/interfaces/IPackageObserver.aidl
================================================
// IPackageObserver.aidl
package com.lody.virtual.server.interfaces;
interface IPackageObserver {
void onPackageInstalled(in String packageName);
void onPackageUninstalled(in String packageName);
void onPackageInstalledAsUser(in int userId, in String packageName);
void onPackageUninstalledAsUser(in int userId, in String packageName);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/interfaces/IProcessObserver.aidl
================================================
// IProcessObserver.aidl
package com.lody.virtual.server.interfaces;
interface IProcessObserver {
void onProcessCreated(in String pkg, in String processName);
void onProcessDied(in String pkg, in String processName);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/interfaces/IServiceFetcher.aidl
================================================
// IServiceFetcher.aidl
package com.lody.virtual.server.interfaces;
interface IServiceFetcher {
IBinder getService(String name);
void addService(String name,in IBinder service);
void removeService(String name);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/interfaces/IUiCallback.aidl
================================================
// IUiCallback.aidl
package com.lody.virtual.server.interfaces;
interface IUiCallback {
void onAppOpened(in String packageName, in int userId);
}
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/pm/installer/SessionInfo.aidl
================================================
// SessionInfo.aidl
package com.lody.virtual.server.pm.installer;
parcelable SessionInfo;
================================================
FILE: VirtualApp/lib/src/main/aidl/com/lody/virtual/server/pm/installer/SessionParams.aidl
================================================
// SessionParams.aidl
package com.lody.virtual.server.pm.installer;
parcelable SessionParams;
================================================
FILE: VirtualApp/lib/src/main/java/android/content/SyncStatusInfo.java
================================================
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 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();
for (int i=0; i(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 CREATOR = new Creator() {
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);
}
}
}
}
================================================
FILE: VirtualApp/lib/src/main/java/android/content/pm/PackageParser.java
================================================
package android.content.pm;
import android.content.ComponentName;
import android.content.IntentFilter;
import android.os.Bundle;
import java.util.ArrayList;
/**
* @author Lody
*/
public class PackageParser {
public static final int PARSE_IS_SYSTEM = 1;
public static class IntentInfo extends IntentFilter {
public boolean hasDefault;
public int labelRes;
public CharSequence nonLocalizedLabel;
public int icon;
public int logo;
public int banner;
}
public static class Component {
public Package owner;
public ArrayList intents;
public String className;
public Bundle metaData;
public ComponentName getComponentName() {
return null;
}
}
public final static class Activity extends Component {
public ActivityInfo info;
}
public class Package {
public final ArrayList activities = new ArrayList(0);
public final ArrayList receivers = new ArrayList(0);
public final ArrayList providers = new ArrayList(0);
public final ArrayList services = new ArrayList(0);
public final ArrayList instrumentation = new ArrayList(0);
public final ArrayList permissions = new ArrayList(0);
public final ArrayList permissionGroups = new ArrayList(0);
public final ArrayList requestedPermissions = new ArrayList();
public Signature[] mSignatures;
public Bundle mAppMetaData;
public Object mExtras;
public String packageName;
public int mPreferredOrder;
public String mSharedUserId;
public ArrayList usesLibraries;
public int mVersionCode;
public ApplicationInfo applicationInfo;
public String mVersionName;
// Applications hardware preferences
public ArrayList configPreferences = null;
// Applications requested features
public ArrayList reqFeatures = null;
public int mSharedUserLabel;
}
public final class Service extends Component {
public ServiceInfo info;
}
public final class Provider extends Component {
public ProviderInfo info;
}
public final class Instrumentation extends Component {
public InstrumentationInfo info;
}
public final class Permission extends Component {
public PermissionInfo info;
}
public final class PermissionGroup extends Component {
public PermissionGroupInfo info;
}
public class ActivityIntentInfo extends IntentInfo {
public Activity activity;
}
public class ServiceIntentInfo extends IntentInfo {
public Service service;
}
public class ProviderIntentInfo extends IntentInfo {
public Provider provider;
}
}
================================================
FILE: VirtualApp/lib/src/main/java/android/location/LocationRequest.java
================================================
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 CREATOR = new Creator() {
@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) {
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/Build.java
================================================
package com.lody.virtual;
/**
*
* Version info of VirtualApp project.
*
* @author Lody
*
*/
public class Build {
public static final String VERSION_NAME = "Build-823-01";
public static final int VERSION_CODE = 8230001;
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/DelegateApplication64Bit.java
================================================
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
*
*
* 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 files,
ArrayList 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 suppressedExceptions = new ArrayList<>();
ArrayList dexFiles = new ArrayList<>();
dexFiles.add(new File(ai.publicSourceDir));
ArrayList 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);
}
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/GmsSupport.java
================================================
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 java.util.Arrays;
import java.util.List;
/**
* @author Lody
*/
public class GmsSupport {
private static final List 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 GOOGLE_SERVICE = Arrays.asList(
"com.google.android.gsf",
"com.google.android.gms",
"com.google.android.gsf.login",
"com.google.android.backuptransport",
"com.google.android.backup",
"com.google.android.configupdater",
"com.google.android.syncadapters.contacts",
"com.google.android.feedback",
"com.google.android.onetimeinitializer",
"com.google.android.partnersetup",
"com.google.android.setupwizard",
"com.google.android.syncadapters.calendar"
);
public static boolean isGmsFamilyPackage(String packageName) {
return packageName.equals("com.android.vending")
|| packageName.equals("com.google.android.gms");
}
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 list, int userId) {
VirtualCore core = VirtualCore.get();
for (String packageName : list) {
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;
}
if (userId == 0) {
core.installPackage(info.sourceDir, InstallStrategy.DEPEND_SYSTEM_IF_EXIST);
} else {
core.installPackageAsUser(userId, packageName);
}
}
}
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);
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/NativeEngine.java
================================================
package com.lody.virtual.client;
import android.os.Binder;
import android.os.Build;
import android.os.Process;
import com.lody.virtual.client.core.VirtualCore;
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.VLog;
import com.lody.virtual.os.VUserHandle;
import com.lody.virtual.remote.InstalledAppInfo;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* VirtualApp Native Project
*/
public class NativeEngine {
private static final String TAG = NativeEngine.class.getSimpleName();
private static Map sDexOverrideMap;
private static boolean sFlag = false;
static {
try {
System.loadLibrary("va++");
} catch (Throwable e) {
VLog.e(TAG, VLog.getStackTraceString(e));
}
}
static {
NativeMethods.init();
}
public static void startDexOverride() {
List installedAppInfos = VirtualCore.get().getInstalledApps(0);
sDexOverrideMap = new HashMap<>(installedAppInfos.size());
for (InstalledAppInfo info : installedAppInfos) {
try {
sDexOverrideMap.put(new File(info.apkPath).getCanonicalPath(), info);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static String getRedirectedPath(String origPath) {
try {
return nativeGetRedirectedPath(origPath);
} catch (Throwable e) {
VLog.e(TAG, VLog.getStackTraceString(e));
}
return origPath;
}
public static String resverseRedirectedPath(String origPath) {
try {
return nativeReverseRedirectedPath(origPath);
} catch (Throwable e) {
VLog.e(TAG, VLog.getStackTraceString(e));
}
return origPath;
}
public static void redirectDirectory(String origPath, String newPath) {
if (!origPath.endsWith("/")) {
origPath = origPath + "/";
}
if (!newPath.endsWith("/")) {
newPath = newPath + "/";
}
try {
nativeIORedirect(origPath, newPath);
} catch (Throwable e) {
VLog.e(TAG, VLog.getStackTraceString(e));
}
}
public static void redirectFile(String origPath, String newPath) {
if (origPath.endsWith("/")) {
origPath = origPath.substring(0, origPath.length() - 1);
}
if (newPath.endsWith("/")) {
newPath = newPath.substring(0, newPath.length() - 1);
}
try {
nativeIORedirect(origPath, newPath);
} catch (Throwable e) {
VLog.e(TAG, VLog.getStackTraceString(e));
}
}
public static void whitelist(String path) {
try {
nativeIOWhitelist(path);
} catch (Throwable e) {
VLog.e(TAG, VLog.getStackTraceString(e));
}
}
public static void forbid(String path) {
if (!path.endsWith("/")) {
path = path + "/";
}
try {
nativeIOForbid(path);
} catch (Throwable e) {
VLog.e(TAG, VLog.getStackTraceString(e));
}
}
public static void enableIORedirect() {
try {
String soPath = String.format("/data/data/%s/lib/libva++.so", VirtualCore.get().getHostPkg());
if (!new File(soPath).exists()) {
throw new RuntimeException("Unable to find the so.");
}
nativeEnableIORedirect(soPath, Build.VERSION.SDK_INT, BuildCompat.getPreviewSDKInt());
} catch (Throwable e) {
VLog.e(TAG, VLog.getStackTraceString(e));
}
}
static void launchEngine() {
if (sFlag) {
return;
}
Method[] methods = {NativeMethods.gOpenDexFileNative, NativeMethods.gCameraNativeSetup, NativeMethods.gAudioRecordNativeCheckPermission};
try {
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) {
VLog.e(TAG, "killProcess: pid = %d, signal = %d.", pid, signal);
if (pid == android.os.Process.myPid()) {
VLog.e(TAG, VLog.getStackTraceString(new Throwable()));
}
}
public static int onGetCallingUid(int originUid) {
int callingPid = Binder.getCallingPid();
if (callingPid == Process.myPid()) {
return VClientImpl.get().getBaseVUid();
}
if (callingPid == VirtualCore.get().getSystemPid()) {
return Process.SYSTEM_UID;
}
int vuid = VActivityManager.get().getUidByPid(callingPid);
if (vuid != -1) {
return VUserHandle.getAppId(vuid);
}
VLog.d(TAG, "Unknown uid: " + callingPid);
return VClientImpl.get().getBaseVUid();
}
public static void onOpenDexFileNative(String[] params) {
String dexOrJarPath = params[0];
String outputPath = params[1];
VLog.d(TAG, "DexOrJarPath = %s, OutputPath = %s.", dexOrJarPath, outputPath);
try {
String canonical = new File(dexOrJarPath).getCanonicalPath();
InstalledAppInfo info = sDexOverrideMap.get(canonical);
if (info != null && !info.dependSystem) {
outputPath = info.getOdexFile().getPath();
params[1] = outputPath;
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static native void nativeLaunchEngine(Object[] method, String hostPackageName, boolean isArt, int apiLevel, int cameraMethodType);
private static native void nativeMark();
private static native String nativeReverseRedirectedPath(String redirectedPath);
private static native String nativeGetRedirectedPath(String orgPath);
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 nativeEnableIORedirect(String selfSoPath, int apiLevel, int previewApiLevel);
public static int onGetUid(int uid) {
return VClientImpl.get().getBaseVUid();
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/VClientImpl.java
================================================
package com.lody.virtual.client;
import android.annotation.SuppressLint;
import android.app.Application;
import android.app.Instrumentation;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
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.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
import android.util.Log;
import com.lody.virtual.client.core.CrashHandler;
import com.lody.virtual.client.core.InvocationStubManager;
import com.lody.virtual.client.core.VirtualCore;
import com.lody.virtual.client.env.SpecialComponentList;
import com.lody.virtual.client.env.VirtualRuntime;
import com.lody.virtual.client.fixer.ContextFixer;
import com.lody.virtual.client.hook.delegate.AppInstrumentation;
import com.lody.virtual.client.hook.providers.ProviderHook;
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.VASettings;
import com.lody.virtual.helper.compat.BuildCompat;
import com.lody.virtual.helper.compat.StorageManagerCompat;
import com.lody.virtual.helper.utils.VLog;
import com.lody.virtual.os.VEnvironment;
import com.lody.virtual.os.VUserHandle;
import com.lody.virtual.remote.InstalledAppInfo;
import com.lody.virtual.remote.PendingResultData;
import com.lody.virtual.remote.VDeviceInfo;
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 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;
import mirror.com.android.internal.content.ReferrerIntent;
import mirror.dalvik.system.VMRuntime;
import mirror.java.lang.ThreadGroupN;
import static com.lody.virtual.os.VUserHandle.getUserId;
/**
* @author Lody
*/
public final class VClientImpl extends IVClient.Stub {
private static final int NEW_INTENT = 11;
private static final int RECEIVER = 12;
private static final String TAG = VClientImpl.class.getSimpleName();
@SuppressLint("StaticFieldLeak")
private static final VClientImpl gClient = new VClientImpl();
private final H mH = new H();
private ConditionVariable mTempLock;
private Instrumentation mInstrumentation = AppInstrumentation.getDefault();
private IBinder token;
private int vuid;
private VDeviceInfo deviceInfo;
private AppBindData mBoundApplication;
private Application mInitialApplication;
private CrashHandler crashHandler;
public static VClientImpl get() {
return gClient;
}
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 : VPackageManager.get().getNameForUid(getVUid());
}
public ApplicationInfo getCurrentApplicationInfo() {
return mBoundApplication != null ? mBoundApplication.appInfo : null;
}
public CrashHandler getCrashHandler() {
return crashHandler;
}
public void setCrashHandler(CrashHandler crashHandler) {
this.crashHandler = crashHandler;
}
public int getVUid() {
return vuid;
}
public int getBaseVUid() {
return VUserHandle.getAppId(vuid);
}
public ClassLoader getClassLoader(ApplicationInfo appInfo) {
Context context = createPackageContext(appInfo.packageName);
return context.getClassLoader();
}
private void sendMessage(int what, Object obj) {
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
mH.sendMessage(msg);
}
@Override
public IBinder getAppThread() {
return ActivityThread.getApplicationThread.call(VirtualCore.mainThread());
}
@Override
public IBinder getToken() {
return token;
}
public void initProcess(IBinder token, int vuid) {
this.token = token;
this.vuid = vuid;
}
private void handleNewIntent(NewIntentData data) {
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
intent = ReferrerIntent.ctor.newInstance(data.intent, data.creator);
} else {
intent = data.intent;
}
if (ActivityThread.performNewIntents != null) {
ActivityThread.performNewIntents.call(
VirtualCore.mainThread(),
data.token,
Collections.singletonList(intent)
);
} else {
ActivityThreadNMR1.performNewIntents.call(
VirtualCore.mainThread(),
data.token,
Collections.singletonList(intent),
true);
}
}
public void bindApplication(final String packageName, final String processName) {
if (Looper.getMainLooper() == Looper.myLooper()) {
bindApplicationNoCheck(packageName, processName, new ConditionVariable());
} else {
final ConditionVariable lock = new ConditionVariable();
VirtualRuntime.getUIHandler().post(new Runnable() {
@Override
public void run() {
bindApplicationNoCheck(packageName, processName, lock);
lock.open();
}
});
lock.block();
}
}
private void bindApplicationNoCheck(String packageName, String processName, ConditionVariable lock) {
VDeviceInfo deviceInfo = getDeviceInfo();
if (processName == null) {
processName = packageName;
}
mTempLock = lock;
try {
setupUncaughtHandler();
} catch (Throwable e) {
e.printStackTrace();
}
try {
fixInstalledProviders();
} 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
);
AppBindData data = new AppBindData();
InstalledAppInfo info = VirtualCore.get().getInstalledAppInfo(packageName, 0);
if (info == null) {
new Exception("App not exist!").printStackTrace();
Process.killProcess(0);
System.exit(0);
}
data.appInfo = VPackageManager.get().getApplicationInfo(packageName, 0, getUserId(vuid));
data.processName = processName;
data.providers = VPackageManager.get().queryContentProviders(processName, getVUid(), PackageManager.GET_META_DATA);
Log.i(TAG, "Binding application " + data.appInfo.packageName + " (" + data.processName + ")");
mBoundApplication = data;
VirtualRuntime.setupRuntime(data.processName, data.appInfo);
int targetSdkVersion = data.appInfo.targetSdkVersion;
if (targetSdkVersion < Build.VERSION_CODES.GINGERBREAD) {
StrictMode.ThreadPolicy newPolicy = new StrictMode.ThreadPolicy.Builder(StrictMode.getThreadPolicy()).permitNetwork().build();
StrictMode.setThreadPolicy(newPolicy);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
mirror.android.os.Message.updateCheckRecycle.call(targetSdkVersion);
}
if (VASettings.ENABLE_IO_REDIRECT) {
startIOUniformer();
}
NativeEngine.launchEngine();
Object mainThread = VirtualCore.mainThread();
NativeEngine.startDexOverride();
Context context = createPackageContext(data.appInfo.packageName);
System.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
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);
}
} else {
if (ThreadedRenderer.setupDiskCache != null) {
ThreadedRenderer.setupDiskCache.call(codeCacheDir);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (RenderScriptCacheDir.setupDiskCache != null) {
RenderScriptCacheDir.setupDiskCache.call(codeCacheDir);
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (RenderScript.setupDiskCache != null) {
RenderScript.setupDiskCache.call(codeCacheDir);
}
}
Object boundApp = fixBoundApp(mBoundApplication);
mBoundApplication.info = ContextImpl.mPackageInfo.get(context);
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);
}
mInitialApplication = LoadedApk.makeApplication.call(data.info, false, null);
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);
}
if (lock != null) {
lock.open();
mTempLock = null;
}
VirtualCore.get().getComponentDelegate().beforeApplicationCreate(mInitialApplication);
try {
mInstrumentation.callApplicationOnCreate(mInitialApplication);
InvocationStubManager.getInstance().checkEnv(HCallbackStub.class);
if (conflict) {
InvocationStubManager.getInstance().checkEnv(AppInstrumentation.class);
}
Application createdApp = ActivityThread.mInitialApplication.get(mainThread);
if (createdApp != null) {
mInitialApplication = createdApp;
}
} catch (Exception e) {
if (!mInstrumentation.onException(mInitialApplication, e)) {
throw new RuntimeException(
"Unable to create 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() {
ThreadGroup root = Thread.currentThread().getThreadGroup();
while (root.getParent() != null) {
root = root.getParent();
}
ThreadGroup newRoot = new RootThreadGroup(root);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
final List groups = mirror.java.lang.ThreadGroup.groups.get(root);
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (groups) {
List newGroups = new ArrayList<>(groups);
newGroups.remove(newRoot);
mirror.java.lang.ThreadGroup.groups.set(newRoot, newGroups);
groups.clear();
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);
}
}
} else {
final ThreadGroup[] groups = ThreadGroupN.groups.get(root);
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (groups) {
ThreadGroup[] newGroups = groups.clone();
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);
}
}
}
@SuppressLint("SdCardPath")
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 = 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);
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 mountPoints = getMountPoints();
for (String mountPoint : mountPoints) {
NativeEngine.redirectDirectory(mountPoint, vsPath);
}
}
}
NativeEngine.enableIORedirect();
}
@SuppressLint("SdCardPath")
private HashSet getMountPoints() {
HashSet mountPoints = new HashSet<>(3);
mountPoints.add("/mnt/sdcard/");
mountPoints.add("/sdcard/");
String[] points = StorageManagerCompat.getAllPoints(VirtualCore.get().getContext());
if (points != null) {
Collections.addAll(mountPoints, points);
}
return mountPoints;
}
private Context createPackageContext(String packageName) {
try {
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();
}
private Object fixBoundApp(AppBindData data) {
Object thread = VirtualCore.mainThread();
Object boundApp = mirror.android.app.ActivityThread.mBoundApplication.get(thread);
mirror.android.app.ActivityThread.AppBindData.appInfo.set(boundApp, data.appInfo);
mirror.android.app.ActivityThread.AppBindData.processName.set(boundApp, data.processName);
mirror.android.app.ActivityThread.AppBindData.instrumentationName.set(
boundApp,
new ComponentName(data.appInfo.packageName, Instrumentation.class.getName())
);
ActivityThread.AppBindData.providers.set(boundApp, data.providers);
return boundApp;
}
private void installContentProviders(Context app, List providers) {
long origId = Binder.clearCallingIdentity();
Object mainThread = VirtualCore.mainThread();
try {
for (ProviderInfo cpi : providers) {
try {
ActivityThread.installProvider(mainThread, app, cpi, null);
} catch (Throwable e) {
e.printStackTrace();
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@Override
public IBinder acquireProviderClient(ProviderInfo info) {
if (mTempLock != null) {
mTempLock.block();
}
if (!isBound()) {
VClientImpl.get().bindApplication(info.packageName, info.processName);
}
IInterface provider = null;
String[] authorities = info.authority.split(";");
String authority = authorities.length == 0 ? info.authority : authorities[0];
ContentResolver resolver = VirtualCore.get().getContext().getContentResolver();
ContentProviderClient client = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
client = resolver.acquireUnstableContentProviderClient(authority);
} else {
client = resolver.acquireContentProviderClient(authority);
}
} catch (Throwable e) {
e.printStackTrace();
}
if (client != null) {
provider = mirror.android.content.ContentProviderClient.mContentProvider.get(client);
client.release();
}
return provider != null ? provider.asBinder() : null;
}
private void fixInstalledProviders() {
clearSettingProvider();
Map clientMap = ActivityThread.mProviderMap.get(VirtualCore.mainThread());
for (Object clientRecord : clientMap.values()) {
if (BuildCompat.isOreo()) {
IInterface provider = ActivityThread.ProviderClientRecordJB.mProvider.get(clientRecord);
Object holder = ActivityThread.ProviderClientRecordJB.mHolder.get(clientRecord);
if (holder == null) {
continue;
}
ProviderInfo info = ContentProviderHolderOreo.info.get(holder);
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);
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
IInterface provider = ActivityThread.ProviderClientRecordJB.mProvider.get(clientRecord);
Object holder = ActivityThread.ProviderClientRecordJB.mHolder.get(clientRecord);
if (holder == null) {
continue;
}
ProviderInfo info = IActivityManager.ContentProviderHolder.info.get(holder);
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);
}
} else {
String authority = ActivityThread.ProviderClientRecord.mName.get(clientRecord);
IInterface provider = ActivityThread.ProviderClientRecord.mProvider.get(clientRecord);
if (provider != null && !authority.startsWith(VASettings.STUB_CP_AUTHORITY)) {
provider = ProviderHook.createProxy(true, authority, provider);
ActivityThread.ProviderClientRecord.mProvider.set(clientRecord, provider);
}
}
}
}
private void clearSettingProvider() {
Object cache;
cache = Settings.System.sNameValueCache.get();
if (cache != null) {
clearContentProvider(cache);
}
cache = Settings.Secure.sNameValueCache.get();
if (cache != null) {
clearContentProvider(cache);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && Settings.Global.TYPE != null) {
cache = Settings.Global.sNameValueCache.get();
if (cache != null) {
clearContentProvider(cache);
}
}
}
private static void clearContentProvider(Object cache) {
if (BuildCompat.isOreo()) {
Object holder = Settings.NameValueCacheOreo.mProviderHolder.get(cache);
if (holder != null) {
Settings.ContentProviderHolder.mContentProvider.set(holder, null);
}
} else {
Settings.NameValueCache.mContentProvider.set(cache, null);
}
}
@Override
public void finishActivity(IBinder token) {
VActivityManager.get().finishActivity(token);
}
@Override
public void scheduleNewIntent(String creator, IBinder token, Intent intent) {
NewIntentData data = new NewIntentData();
data.creator = creator;
data.token = token;
data.intent = intent;
sendMessage(NEW_INTENT, data);
}
@Override
public void scheduleReceiver(String processName, ComponentName component, Intent intent, PendingResultData resultData) {
ReceiverData receiverData = new ReceiverData();
receiverData.resultData = resultData;
receiverData.intent = intent;
receiverData.component = component;
receiverData.processName = processName;
sendMessage(RECEIVER, receiverData);
}
private void handleReceiver(ReceiverData data) {
BroadcastReceiver.PendingResult result = data.resultData.build();
try {
if (!isBound()) {
bindApplication(data.component.getPackageName(), data.processName);
}
Context context = mInitialApplication.getBaseContext();
Context receiverContext = ContextImpl.getReceiverRestrictedContext.call(context);
String className = data.component.getClassName();
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);
}
VActivityManager.get().broadcastFinish(data.resultData);
}
@Override
public IBinder createProxyService(ComponentName component, IBinder binder) {
return ProxyServiceFactory.getProxyService(getCurrentApplication(), component, binder);
}
@Override
public String getDebugInfo() {
return "process : " + VirtualRuntime.getProcessName() + "\n" +
"initialPkg : " + VirtualRuntime.getInitialPackageName() + "\n" +
"vuid : " + vuid;
}
private static class RootThreadGroup extends ThreadGroup {
RootThreadGroup(ThreadGroup parent) {
super(parent, "VA-Root");
}
@Override
public void uncaughtException(Thread t, Throwable e) {
CrashHandler handler = VClientImpl.gClient.crashHandler;
if (handler != null) {
handler.handleUncaughtException(t, e);
} else {
VLog.e("uncaught", e);
System.exit(0);
}
}
}
private final class NewIntentData {
String creator;
IBinder token;
Intent intent;
}
private final class AppBindData {
String processName;
ApplicationInfo appInfo;
List providers;
Object info;
}
private final class ReceiverData {
PendingResultData resultData;
Intent intent;
ComponentName component;
String processName;
}
private class H extends Handler {
private H() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case NEW_INTENT: {
handleNewIntent((NewIntentData) msg.obj);
}
break;
case RECEIVER: {
handleReceiver((ReceiverData) msg.obj);
}
}
}
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BadgerManager.java
================================================
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 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;
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BroadcastBadger1.java
================================================
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";
}
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/BroadcastBadger2.java
================================================
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";
}
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/badger/IBadger.java
================================================
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);
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/core/CrashHandler.java
================================================
package com.lody.virtual.client.core;
/**
* @author Lody
*/
public interface CrashHandler {
void handleUncaughtException(Thread t, Throwable e);
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InstallStrategy.java
================================================
package com.lody.virtual.client.core;
/**
* @author Lody
*
*
*/
public interface InstallStrategy {
int TERMINATE_IF_EXIST = 0x01 << 1;
int UPDATE_IF_EXIST = 0x01 << 2;
int COMPARE_VERSION = 0X01 << 3;
int IGNORE_NEW_VERSION = 0x01 << 4;
int DEPEND_SYSTEM_IF_EXIST = 0x01 << 5;
int SKIP_DEX_OPT = 0x01 << 6;
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/core/InvocationStubManager.java
================================================
package com.lody.virtual.client.core;
import android.os.Build;
import com.lody.virtual.client.hook.base.MethodInvocationProxy;
import com.lody.virtual.client.hook.base.MethodInvocationStub;
import com.lody.virtual.client.hook.delegate.AppInstrumentation;
import com.lody.virtual.client.hook.proxies.account.AccountManagerStub;
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.appops.AppOpsManagerStub;
import com.lody.virtual.client.hook.proxies.appwidget.AppWidgetManagerStub;
import com.lody.virtual.client.hook.proxies.audio.AudioManagerStub;
import com.lody.virtual.client.hook.proxies.backup.BackupManagerStub;
import com.lody.virtual.client.hook.proxies.bluetooth.BluetoothStub;
import com.lody.virtual.client.hook.proxies.clipboard.ClipBoardStub;
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;
import com.lody.virtual.client.hook.proxies.isms.ISmsStub;
import com.lody.virtual.client.hook.proxies.isub.ISubStub;
import com.lody.virtual.client.hook.proxies.job.JobServiceStub;
import com.lody.virtual.client.hook.proxies.libcore.LibCoreStub;
import com.lody.virtual.client.hook.proxies.location.LocationManagerStub;
import com.lody.virtual.client.hook.proxies.media.router.MediaRouterServiceStub;
import com.lody.virtual.client.hook.proxies.media.session.SessionManagerStub;
import com.lody.virtual.client.hook.proxies.mount.MountServiceStub;
import com.lody.virtual.client.hook.proxies.network.NetworkManagementStub;
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.PackageManagerStub;
import com.lody.virtual.client.hook.proxies.power.PowerManagerStub;
import com.lody.virtual.client.hook.proxies.restriction.RestrictionStub;
import com.lody.virtual.client.hook.proxies.search.SearchManagerStub;
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;
import com.lody.virtual.client.interfaces.IInjector;
import java.util.HashMap;
import java.util.Map;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
/**
* @author Lody
*
*/
public final class InvocationStubManager {
private static InvocationStubManager sInstance = new InvocationStubManager();
private static boolean sInit;
private Map, IInjector> mInjectors = new HashMap<>(13);
private InvocationStubManager() {
}
public static InvocationStubManager getInstance() {
return sInstance;
}
void injectAll() throws Throwable {
for (IInjector injector : mInjectors.values()) {
injector.inject();
}
// XXX: Lazy inject the Instrumentation,
addInjector(AppInstrumentation.getDefault());
}
/**
* @return if the InvocationStubManager has been initialized.
*/
public boolean isInit() {
return sInit;
}
public void init() throws Throwable {
if (isInit()) {
throw new IllegalStateException("InvocationStubManager Has been initialized.");
}
injectInternal();
sInit = true;
}
private void injectInternal() throws Throwable {
if (VirtualCore.get().isMainProcess()) {
return;
}
if (VirtualCore.get().isServerProcess()) {
addInjector(new ActivityManagerStub());
addInjector(new PackageManagerStub());
return;
}
if (VirtualCore.get().isVAppProcess()) {
addInjector(new LibCoreStub());
addInjector(new ActivityManagerStub());
addInjector(new PackageManagerStub());
addInjector(HCallbackStub.getDefault());
addInjector(new ISmsStub());
addInjector(new ISubStub());
addInjector(new DropBoxManagerStub());
addInjector(new NotificationManagerStub());
addInjector(new LocationManagerStub());
addInjector(new WindowManagerStub());
addInjector(new ClipBoardStub());
addInjector(new MountServiceStub());
addInjector(new BackupManagerStub());
addInjector(new TelephonyStub());
addInjector(new TelephonyRegistryStub());
addInjector(new PhoneSubInfoStub());
addInjector(new PowerManagerStub());
addInjector(new AppWidgetManagerStub());
addInjector(new AccountManagerStub());
addInjector(new AudioManagerStub());
addInjector(new SearchManagerStub());
addInjector(new ContentServiceStub());
addInjector(new ConnectivityStub());
if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR2) {
addInjector(new VibratorStub());
addInjector(new WifiManagerStub());
addInjector(new BluetoothStub());
addInjector(new ContextHubServiceStub());
}
if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) {
addInjector(new UserManagerStub());
}
if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) {
addInjector(new DisplayStub());
}
if (Build.VERSION.SDK_INT >= LOLLIPOP) {
addInjector(new PersistentDataBlockServiceStub());
addInjector(new InputMethodManagerStub());
addInjector(new MmsStub());
addInjector(new SessionManagerStub());
addInjector(new JobServiceStub());
addInjector(new RestrictionStub());
}
if (Build.VERSION.SDK_INT >= KITKAT) {
addInjector(new AlarmManagerStub());
addInjector(new AppOpsManagerStub());
addInjector(new MediaRouterServiceStub());
}
if (Build.VERSION.SDK_INT >= LOLLIPOP_MR1) {
addInjector(new GraphicsStatsStub());
addInjector(new UsageStatsManagerStub());
}
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());
}
}
}
private void addInjector(IInjector IInjector) {
mInjectors.put(IInjector.getClass(), IInjector);
}
public T findInjector(Class clazz) {
// noinspection unchecked
return (T) mInjectors.get(clazz);
}
public void checkEnv(Class clazz) {
IInjector IInjector = findInjector(clazz);
if (IInjector != null && IInjector.isEnvBad()) {
try {
IInjector.inject();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
public H getInvocationStub(Class injectorClass) {
T injector = findInjector(injectorClass);
if (injector != null && injector instanceof MethodInvocationProxy) {
// noinspection unchecked
return (H) ((MethodInvocationProxy) injector).getInvocationStub();
}
return null;
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/core/VirtualCore.java
================================================
package com.lody.virtual.client.core;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
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 com.lody.virtual.R;
import com.lody.virtual.client.VClientImpl;
import com.lody.virtual.client.env.Constants;
import com.lody.virtual.client.env.VirtualRuntime;
import com.lody.virtual.client.fixer.ContextFixer;
import com.lody.virtual.client.hook.delegate.ComponentDelegate;
import com.lody.virtual.client.hook.delegate.PhoneInfoDelegate;
import com.lody.virtual.client.hook.delegate.TaskDescriptionDelegate;
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.VASettings;
import com.lody.virtual.helper.compat.BundleCompat;
import com.lody.virtual.helper.ipcbus.IPCBus;
import com.lody.virtual.helper.ipcbus.IPCSingleton;
import com.lody.virtual.helper.ipcbus.IServerCache;
import com.lody.virtual.helper.utils.BitmapUtils;
import com.lody.virtual.os.VUserHandle;
import com.lody.virtual.remote.InstallResult;
import com.lody.virtual.remote.InstalledAppInfo;
import com.lody.virtual.server.interfaces.IAppManager;
import com.lody.virtual.server.ServiceCache;
import com.lody.virtual.server.interfaces.IAppRequestListener;
import com.lody.virtual.server.interfaces.IPackageObserver;
import com.lody.virtual.server.interfaces.IUiCallback;
import java.io.IOException;
import java.util.List;
import dalvik.system.DexFile;
import mirror.android.app.ActivityThread;
/**
* @author Lody
* @version 3.5
*/
public final class VirtualCore {
public static final int GET_HIDDEN_APP = 0x00000001;
@SuppressLint("StaticFieldLeak")
private static VirtualCore gCore = new VirtualCore();
private final int myUid = Process.myUid();
/**
* Client Package Manager
*/
private PackageManager unHookPackageManager;
/**
* Host package name
*/
private String hostPkgName;
/**
* ActivityThread instance
*/
private Object mainThread;
private Context context;
/**
* Main ProcessName
*/
private String mainProcessName;
/**
* Real Process Name
*/
private String processName;
private ProcessType processType;
private IPCSingleton singleton = new IPCSingleton<>(IAppManager.class);
private boolean isStartUp;
private PackageInfo hostPkgInfo;
private int systemPid;
private ConditionVariable initLock = new ConditionVariable();
private PhoneInfoDelegate phoneInfoDelegate;
private ComponentDelegate componentDelegate;
private TaskDescriptionDelegate taskDescriptionDelegate;
private VirtualCore() {
}
public static VirtualCore get() {
return gCore;
}
public static PackageManager getPM() {
return get().getPackageManager();
}
public static Object mainThread() {
return get().mainThread;
}
public ConditionVariable getInitLock() {
return initLock;
}
public int myUid() {
return myUid;
}
public int myUserId() {
return VUserHandle.getUserId(myUid);
}
public ComponentDelegate getComponentDelegate() {
return componentDelegate == null ? ComponentDelegate.EMPTY : componentDelegate;
}
public void setComponentDelegate(ComponentDelegate delegate) {
this.componentDelegate = delegate;
}
public PhoneInfoDelegate getPhoneInfoDelegate() {
return phoneInfoDelegate;
}
public void setPhoneInfoDelegate(PhoneInfoDelegate phoneInfoDelegate) {
this.phoneInfoDelegate = phoneInfoDelegate;
}
public void setCrashHandler(CrashHandler handler) {
VClientImpl.get().setCrashHandler(handler);
}
public TaskDescriptionDelegate getTaskDescriptionDelegate() {
return taskDescriptionDelegate;
}
public void setTaskDescriptionDelegate(TaskDescriptionDelegate taskDescriptionDelegate) {
this.taskDescriptionDelegate = taskDescriptionDelegate;
}
public int[] getGids() {
return hostPkgInfo.gids;
}
public Context getContext() {
return context;
}
public PackageManager getPackageManager() {
return context.getPackageManager();
}
public String getHostPkg() {
return hostPkgName;
}
public PackageManager getUnHookPackageManager() {
return unHookPackageManager;
}
public void startup(Context context) throws Throwable {
if (!isStartUp) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("VirtualCore.startup() must called in main thread.");
}
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();
unHookPackageManager = context.getPackageManager();
hostPkgInfo = unHookPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS);
IPCBus.initialize(new IServerCache() {
@Override
public void join(String serverName, IBinder binder) {
ServiceCache.addService(serverName, binder);
}
@Override
public IBinder query(String serverName) {
return ServiceManagerNative.getService(serverName);
}
});
detectProcessType();
InvocationStubManager invocationStubManager = InvocationStubManager.getInstance();
invocationStubManager.init();
invocationStubManager.injectAll();
ContextFixer.fixContext(context);
isStartUp = true;
if (initLock != null) {
initLock.open();
initLock = null;
}
}
}
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");
}
switch (processType) {
case Main:
initializer.onMainProcess();
break;
case VAppClient:
initializer.onVirtualProcess();
break;
case Server:
initializer.onServerProcess();
break;
case CHILD:
initializer.onChildProcess();
break;
}
}
private void detectProcessType() {
// Host package name
hostPkgName = context.getApplicationInfo().packageName;
// Main process name
mainProcessName = context.getApplicationInfo().processName;
// Current process name
processName = ActivityThread.getProcessName.call(mainThread);
if (processName.equals(mainProcessName)) {
processType = ProcessType.Main;
} else if (processName.endsWith(Constants.SERVER_PROCESS_NAME)) {
processType = ProcessType.Server;
} else if (VActivityManager.get().isAppProcess(processName)) {
processType = ProcessType.VAppClient;
} else {
processType = ProcessType.CHILD;
}
if (isVAppProcess()) {
systemPid = VActivityManager.get().getSystemPid();
}
}
private IAppManager getService() {
return singleton.get();
}
/**
* @return If the current process is used to VA.
*/
public boolean isVAppProcess() {
return ProcessType.VAppClient == processType;
}
/**
* @return If the current process is the main.
*/
public boolean isMainProcess() {
return ProcessType.Main == processType;
}
/**
* @return If the current process is the child.
*/
public boolean isChildProcess() {
return ProcessType.CHILD == processType;
}
/**
* @return If the current process is the server.
*/
public boolean isServerProcess() {
return ProcessType.Server == processType;
}
/**
* @return the actual process name
*/
public String getProcessName() {
return processName;
}
/**
* @return the Main process name
*/
public String getMainProcessName() {
return mainProcessName;
}
/**
* Optimize the Dalvik-Cache for the specified package.
*
* @param pkg package name
* @throws IOException
*/
@Deprecated
public void preOpt(String pkg) throws IOException {
InstalledAppInfo info = getInstalledAppInfo(pkg, 0);
if (info != null && !info.dependSystem) {
DexFile.loadDex(info.apkPath, info.getOdexFile().getPath(), 0).close();
}
}
/**
* Is the specified app running in foreground / background?
*
* @param packageName package name
* @param userId user id
* @return if the specified app running in foreground / background.
*/
public boolean isAppRunning(String packageName, int userId) {
return VActivityManager.get().isAppRunning(packageName, userId);
}
public InstallResult installPackage(String apkPath, int flags) {
try {
return getService().installPackage(apkPath, flags);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public void addVisibleOutsidePackage(String pkg) {
try {
getService().addVisibleOutsidePackage(pkg);
} catch (RemoteException e) {
VirtualRuntime.crash(e);
}
}
public void removeVisibleOutsidePackage(String pkg) {
try {
getService().removeVisibleOutsidePackage(pkg);
} catch (RemoteException e) {
VirtualRuntime.crash(e);
}
}
public boolean isOutsidePackageVisible(String pkg) {
try {
return getService().isOutsidePackageVisible(pkg);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public boolean isAppInstalled(String pkg) {
try {
return getService().isAppInstalled(pkg);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public boolean isPackageLaunchable(String packageName) {
InstalledAppInfo info = getInstalledAppInfo(packageName, 0);
return info != null
&& getLaunchIntent(packageName, info.getInstalledUsers()[0]) != null;
}
public Intent getLaunchIntent(String packageName, int userId) {
VPackageManager pm = VPackageManager.get();
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
intentToResolve.addCategory(Intent.CATEGORY_INFO);
intentToResolve.setPackage(packageName);
List ris = pm.queryIntentActivities(intentToResolve, intentToResolve.resolveType(context), 0, userId);
// Otherwise, try to find a main launcher activity.
if (ris == null || ris.size() <= 0) {
// reuse the intent instance
intentToResolve.removeCategory(Intent.CATEGORY_INFO);
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
intentToResolve.setPackage(packageName);
ris = pm.queryIntentActivities(intentToResolve, intentToResolve.resolveType(context), 0, userId);
}
if (ris == null || ris.size() <= 0) {
return null;
}
Intent intent = new Intent(intentToResolve);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(ris.get(0).activityInfo.packageName,
ris.get(0).activityInfo.name);
return intent;
}
public boolean createShortcut(int userId, String packageName, OnEmitShortcutListener listener) {
return createShortcut(userId, packageName, null, listener);
}
public boolean createShortcut(int userId, String packageName, Intent splash, OnEmitShortcutListener listener) {
InstalledAppInfo setting = getInstalledAppInfo(packageName, 0);
if (setting == null) {
return false;
}
ApplicationInfo appInfo = setting.getApplicationInfo(userId);
PackageManager pm = context.getPackageManager();
String name;
Bitmap icon;
try {
CharSequence sequence = appInfo.loadLabel(pm);
name = sequence.toString();
icon = BitmapUtils.drawableToBitmap(appInfo.loadIcon(pm));
} catch (Throwable e) {
return false;
}
if (listener != null) {
String newName = listener.getName(name);
if (newName != null) {
name = newName;
}
Bitmap newIcon = listener.getIcon(icon);
if (newIcon != null) {
icon = newIcon;
}
}
Intent targetIntent = getLaunchIntent(packageName, userId);
if (targetIntent == null) {
return false;
}
Intent shortcutIntent = new Intent();
shortcutIntent.setClassName(getHostPkg(), Constants.SHORTCUT_PROXY_ACTIVITY_NAME);
shortcutIntent.addCategory(Intent.CATEGORY_DEFAULT);
if (splash != null) {
shortcutIntent.putExtra("_VA_|_splash_", splash.toUri(0));
}
shortcutIntent.putExtra("_VA_|_intent_", targetIntent);
shortcutIntent.putExtra("_VA_|_uri_", targetIntent.toUri(0));
shortcutIntent.putExtra("_VA_|_user_id_", userId);
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);
return true;
}
public boolean removeShortcut(int userId, String packageName, Intent splash, OnEmitShortcutListener listener) {
InstalledAppInfo setting = getInstalledAppInfo(packageName, 0);
if (setting == null) {
return false;
}
ApplicationInfo appInfo = setting.getApplicationInfo(userId);
PackageManager pm = context.getPackageManager();
String name;
try {
CharSequence sequence = appInfo.loadLabel(pm);
name = sequence.toString();
} catch (Throwable e) {
return false;
}
if (listener != null) {
String newName = listener.getName(name);
if (newName != null) {
name = newName;
}
}
Intent targetIntent = getLaunchIntent(packageName, userId);
if (targetIntent == null) {
return false;
}
Intent shortcutIntent = new Intent();
shortcutIntent.setClassName(getHostPkg(), Constants.SHORTCUT_PROXY_ACTIVITY_NAME);
shortcutIntent.addCategory(Intent.CATEGORY_DEFAULT);
if (splash != null) {
shortcutIntent.putExtra("_VA_|_splash_", splash.toUri(0));
}
shortcutIntent.putExtra("_VA_|_intent_", targetIntent);
shortcutIntent.putExtra("_VA_|_uri_", targetIntent.toUri(0));
shortcutIntent.putExtra("_VA_|_user_id_", VUserHandle.myUserId());
Intent addIntent = new Intent();
addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
addIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
context.sendBroadcast(addIntent);
return true;
}
public abstract static class UiCallback extends IUiCallback.Stub {
}
public void setUiCallback(Intent intent, IUiCallback callback) {
if (callback != null) {
Bundle bundle = new Bundle();
BundleCompat.putBinder(bundle, "_VA_|_ui_callback_", callback.asBinder());
intent.putExtra("_VA_|_sender_", bundle);
}
}
public InstalledAppInfo getInstalledAppInfo(String pkg, int flags) {
try {
return getService().getInstalledAppInfo(pkg, flags);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public int getInstalledAppCount() {
try {
return getService().getInstalledAppCount();
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public boolean isStartup() {
return isStartUp;
}
public boolean uninstallPackageAsUser(String pkgName, int userId) {
try {
return getService().uninstallPackageAsUser(pkgName, userId);
} catch (RemoteException e) {
// Ignore
}
return false;
}
public boolean uninstallPackage(String pkgName) {
try {
return getService().uninstallPackage(pkgName);
} catch (RemoteException e) {
// Ignore
}
return false;
}
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();
mirror.android.content.res.AssetManager.addAssetPath.call(assets, installedAppInfo.apkPath);
Resources hostRes = context.getResources();
return new Resources(assets, hostRes.getDisplayMetrics(), hostRes.getConfiguration());
}
throw new Resources.NotFoundException(pkg);
}
public synchronized ActivityInfo resolveActivityInfo(Intent intent, int userId) {
ActivityInfo activityInfo = null;
if (intent.getComponent() == null) {
ResolveInfo resolveInfo = VPackageManager.get().resolveIntent(intent, intent.getType(), 0, userId);
if (resolveInfo != null && resolveInfo.activityInfo != null) {
activityInfo = resolveInfo.activityInfo;
intent.setClassName(activityInfo.packageName, activityInfo.name);
}
} else {
activityInfo = resolveActivityInfo(intent.getComponent(), userId);
}
if (activityInfo != null) {
if (activityInfo.targetActivity != null) {
ComponentName componentName = new ComponentName(activityInfo.packageName, activityInfo.targetActivity);
activityInfo = VPackageManager.get().getActivityInfo(componentName, 0, userId);
intent.setComponent(componentName);
}
}
return activityInfo;
}
public ActivityInfo resolveActivityInfo(ComponentName componentName, int userId) {
return VPackageManager.get().getActivityInfo(componentName, 0, userId);
}
public ServiceInfo resolveServiceInfo(Intent intent, int userId) {
ServiceInfo serviceInfo = null;
ResolveInfo resolveInfo = VPackageManager.get().resolveService(intent, intent.getType(), 0, userId);
if (resolveInfo != null) {
serviceInfo = resolveInfo.serviceInfo;
}
return serviceInfo;
}
public void killApp(String pkg, int userId) {
VActivityManager.get().killAppByPkg(pkg, userId);
}
public void killAllApps() {
VActivityManager.get().killAllApps();
}
public List getInstalledApps(int flags) {
try {
return getService().getInstalledApps(flags);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public List getInstalledAppsAsUser(int userId, int flags) {
try {
return getService().getInstalledAppsAsUser(userId, flags);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public void clearAppRequestListener() {
try {
getService().clearAppRequestListener();
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void scanApps() {
try {
getService().scanApps();
} catch (RemoteException e) {
// Ignore
}
}
public IAppRequestListener getAppRequestListener() {
try {
return getService().getAppRequestListener();
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public void setAppRequestListener(final AppRequestListener listener) {
IAppRequestListener inner = new IAppRequestListener.Stub() {
@Override
public void onRequestInstall(final String path) {
VirtualRuntime.getUIHandler().post(new Runnable() {
@Override
public void run() {
listener.onRequestInstall(path);
}
});
}
@Override
public void onRequestUninstall(final String pkg) {
VirtualRuntime.getUIHandler().post(new Runnable() {
@Override
public void run() {
listener.onRequestUninstall(pkg);
}
});
}
};
try {
getService().setAppRequestListener(inner);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public boolean isPackageLaunched(int userId, String packageName) {
try {
return getService().isPackageLaunched(userId, packageName);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public void setPackageHidden(int userId, String packageName, boolean hidden) {
try {
getService().setPackageHidden(userId, packageName, hidden);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public boolean installPackageAsUser(int userId, String packageName) {
try {
return getService().installPackageAsUser(userId, packageName);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public boolean isAppInstalledAsUser(int userId, String packageName) {
try {
return getService().isAppInstalledAsUser(userId, packageName);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public int[] getPackageInstalledUsers(String packageName) {
try {
return getService().getPackageInstalledUsers(packageName);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
public abstract static class PackageObserver extends IPackageObserver.Stub {
}
public void registerObserver(IPackageObserver observer) {
try {
getService().registerObserver(observer);
} catch (RemoteException e) {
VirtualRuntime.crash(e);
}
}
public void unregisterObserver(IPackageObserver observer) {
try {
getService().unregisterObserver(observer);
} catch (RemoteException e) {
VirtualRuntime.crash(e);
}
}
public boolean isOutsideInstalled(String packageName) {
try {
return unHookPackageManager.getApplicationInfo(packageName, 0) != null;
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
return false;
}
public int getSystemPid() {
return systemPid;
}
/**
* Process type
*/
private enum ProcessType {
/**
* Server process
*/
Server,
/**
* Virtual app process
*/
VAppClient,
/**
* Main process
*/
Main,
/**
* Child process
*/
CHILD
}
public interface AppRequestListener {
void onRequestInstall(String path);
void onRequestUninstall(String pkg);
}
public interface OnEmitShortcutListener {
Bitmap getIcon(Bitmap originIcon);
String getName(String originName);
}
public static abstract class VirtualInitializer {
public void onMainProcess() {
}
public void onVirtualProcess() {
}
public void onServerProcess() {
}
public void onChildProcess() {
}
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/env/Constants.java
================================================
package com.lody.virtual.client.env;
import android.app.PendingIntent;
import android.content.Intent;
import com.lody.virtual.client.stub.ShortcutHandleActivity;
/**
* @author Lody
*
*/
public class Constants {
public static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
/**
* If an apk declared the "fake-signature" attribute on its Application TAG,
* we will use its signature instead of the real signature.
*
* For more detail, please see :
* https://github.com/microg/android_packages_apps_GmsCore/blob/master/
* patches/android_frameworks_base-M.patch.
*/
public static final String FEATURE_FAKE_SIGNATURE = "fake-signature";
public static final String ACTION_PACKAGE_ADDED = "virtual." + Intent.ACTION_PACKAGE_ADDED;
public static final String ACTION_PACKAGE_REMOVED = "virtual." + Intent.ACTION_PACKAGE_REMOVED;
public static final String ACTION_PACKAGE_CHANGED = "virtual." + Intent.ACTION_PACKAGE_CHANGED;
public static final String ACTION_USER_ADDED = "virtual." + "android.intent.action.USER_ADDED";
public static final String ACTION_USER_REMOVED = "virtual." + "android.intent.action.USER_REMOVED";
public static final String ACTION_USER_INFO_CHANGED = "virtual." + "android.intent.action.USER_CHANGED";
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";
/**
* Server process name of VA
*/
public static String SERVER_PROCESS_NAME = ":x";
/**
* The activity who handle the shortcut.
*/
public static String SHORTCUT_PROXY_ACTIVITY_NAME = ShortcutHandleActivity.class.getName();
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/env/DeadServerException.java
================================================
package com.lody.virtual.client.env;
/**
* @author Lody
*/
public class DeadServerException extends RuntimeException {
public DeadServerException() {
}
public DeadServerException(String message) {
super(message);
}
public DeadServerException(String message, Throwable cause) {
super(message, cause);
}
public DeadServerException(Throwable cause) {
super(cause);
}
public DeadServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/env/GPSStateline.java
================================================
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;
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/env/SpecialComponentList.java
================================================
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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import mirror.android.webkit.IWebViewUpdateService;
import mirror.android.webkit.WebViewFactory;
/**
* @author Lody
*/
public final class SpecialComponentList {
private static final List ACTION_BLACK_LIST = new ArrayList(1);
private static final Map PROTECTED_ACTION_MAP = new HashMap<>(5);
private static final HashSet WHITE_PERMISSION = new HashSet<>(3);
private static final HashSet INSTRUMENTATION_CONFLICTING = new HashSet<>(2);
private static final HashSet SPEC_SYSTEM_APP_LIST = new HashSet<>(3);
private static final Set SYSTEM_BROADCAST_ACTION = new HashSet<>(7);
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);
SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_TIME_TICK);
SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_TIME_CHANGED);
SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_TIMEZONE_CHANGED);
SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_BATTERY_CHANGED);
SYSTEM_BROADCAST_ACTION.add(Intent.ACTION_BATTERY_LOW);
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");
SYSTEM_BROADCAST_ACTION.add("android.net.wifi.SCAN_RESULTS");
SYSTEM_BROADCAST_ACTION.add("android.net.wifi.WIFI_STATE_CHANGED");
SYSTEM_BROADCAST_ACTION.add("android.net.conn.CONNECTIVITY_CHANGE");
SYSTEM_BROADCAST_ACTION.add("android.intent.action.ANY_DATA_STATE");
SYSTEM_BROADCAST_ACTION.add("android.intent.action.SIM_STATE_CHANGED");
SYSTEM_BROADCAST_ACTION.add("android.location.PROVIDERS_CHANGED");
SYSTEM_BROADCAST_ACTION.add("android.location.MODE_CHANGED");
ACTION_BLACK_LIST.add("android.appwidget.action.APPWIDGET_UPDATE");
WHITE_PERMISSION.add("com.google.android.gms.settings.SECURITY_SETTINGS");
WHITE_PERMISSION.add("com.google.android.apps.plus.PRIVACY_SETTINGS");
WHITE_PERMISSION.add(Manifest.permission.ACCOUNT_MANAGER);
PROTECTED_ACTION_MAP.put(Intent.ACTION_PACKAGE_ADDED, Constants.ACTION_PACKAGE_ADDED);
PROTECTED_ACTION_MAP.put(Intent.ACTION_PACKAGE_REMOVED, Constants.ACTION_PACKAGE_REMOVED);
PROTECTED_ACTION_MAP.put(Intent.ACTION_PACKAGE_CHANGED, Constants.ACTION_PACKAGE_CHANGED);
PROTECTED_ACTION_MAP.put("android.intent.action.USER_ADDED", Constants.ACTION_USER_ADDED);
PROTECTED_ACTION_MAP.put("android.intent.action.USER_REMOVED", Constants.ACTION_USER_REMOVED);
INSTRUMENTATION_CONFLICTING.add("com.qihoo.magic");
INSTRUMENTATION_CONFLICTING.add("com.qihoo.magic_mutiple");
INSTRUMENTATION_CONFLICTING.add("com.facebook.katana");
SPEC_SYSTEM_APP_LIST.add("android");
SPEC_SYSTEM_APP_LIST.add("com.google.android.webview");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
String webViewPkgN = IWebViewUpdateService.getCurrentWebViewPackageName.call(WebViewFactory.getUpdateService.call());
if (webViewPkgN != null) {
SPEC_SYSTEM_APP_LIST.add(webViewPkgN);
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
public static boolean isSpecSystemPackage(String pkg) {
return SPEC_SYSTEM_APP_LIST.contains(pkg);
}
public static boolean isConflictingInstrumentation(String packageName) {
return INSTRUMENTATION_CONFLICTING.contains(packageName);
}
/**
* Check if the action in the BlackList.
*
* @param action Action
*/
public static boolean isActionInBlackList(String action) {
return ACTION_BLACK_LIST.contains(action);
}
/**
* Add an action to the BlackList.
*
* @param action action
*/
public static void addBlackAction(String action) {
ACTION_BLACK_LIST.add(action);
}
public static void protectIntentFilter(IntentFilter filter) {
if (filter != null) {
List actions = mirror.android.content.IntentFilter.mActions.get(filter);
ListIterator iterator = actions.listIterator();
while (iterator.hasNext()) {
String action = iterator.next();
if (SpecialComponentList.isActionInBlackList(action)) {
iterator.remove();
continue;
}
if (SYSTEM_BROADCAST_ACTION.contains(action)) {
continue;
}
String newAction = SpecialComponentList.protectAction(action);
if (newAction != null) {
iterator.set(newAction);
}
}
}
}
public static void protectIntent(Intent intent) {
String protectAction = protectAction(intent.getAction());
if (protectAction != null) {
intent.setAction(protectAction);
}
}
public static void unprotectIntent(Intent intent) {
String unprotectAction = unprotectAction(intent.getAction());
if (unprotectAction != null) {
intent.setAction(unprotectAction);
}
}
public static String protectAction(String originAction) {
if (originAction == null) {
return null;
}
if (originAction.startsWith("_VA_")) {
return originAction;
}
String newAction = PROTECTED_ACTION_MAP.get(originAction);
if (newAction == null) {
newAction = PROTECT_ACTION_PREFIX + originAction;
}
return newAction;
}
public static String unprotectAction(String action) {
if (action == null) {
return null;
}
if (action.startsWith(PROTECT_ACTION_PREFIX)) {
return action.substring(PROTECT_ACTION_PREFIX.length());
}
for (Map.Entry next : PROTECTED_ACTION_MAP.entrySet()) {
String modifiedAction = next.getValue();
if (modifiedAction.equals(action)) {
return next.getKey();
}
}
return null;
}
public static boolean isWhitePermission(String permission) {
return WHITE_PERMISSION.contains(permission);
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/env/VirtualGPSSatalines.java
================================================
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 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;
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/env/VirtualRuntime.java
================================================
package com.lody.virtual.client.env;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import com.lody.virtual.client.core.VirtualCore;
import com.lody.virtual.helper.utils.VLog;
import mirror.android.ddm.DdmHandleAppName;
import mirror.android.ddm.DdmHandleAppNameJBMR1;
/**
* @author Lody
*
*
* Runtime Environment for App.
*/
public class VirtualRuntime {
private static final Handler sUIHandler = new Handler(Looper.getMainLooper());
private static String sInitialPackageName;
private static String sProcessName;
public static Handler getUIHandler() {
return sUIHandler;
}
public static String getProcessName() {
return sProcessName;
}
public static String getInitialPackageName() {
return sInitialPackageName;
}
public static void setupRuntime(String processName, ApplicationInfo appInfo) {
if (sProcessName != null) {
return;
}
sInitialPackageName = appInfo.packageName;
sProcessName = processName;
mirror.android.os.Process.setArgV0.call(processName);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
DdmHandleAppNameJBMR1.setAppName.call(processName, 0);
} else {
DdmHandleAppName.setAppName.call(processName);
}
}
public static T crash(RemoteException e) throws RuntimeException {
e.printStackTrace();
if (VirtualCore.get().isVAppProcess()) {
Process.killProcess(Process.myPid());
System.exit(0);
}
throw new DeadServerException(e);
}
public static boolean isArt() {
return System.getProperty("java.vm.version").startsWith("2");
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/fixer/ActivityFixer.java
================================================
package com.lody.virtual.client.fixer;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import mirror.com.android.internal.R_Hide;
/**
* @author Lody
*
*/
public final class ActivityFixer {
private ActivityFixer() {
}
public static void fixActivity(Activity activity) {
Context baseContext = activity.getBaseContext();
try {
TypedArray typedArray = activity.obtainStyledAttributes((R_Hide.styleable.Window.get()));
if (typedArray != null) {
boolean showWallpaper = typedArray.getBoolean(R_Hide.styleable.Window_windowShowWallpaper.get(),
false);
if (showWallpaper) {
activity.getWindow().setBackgroundDrawable(WallpaperManager.getInstance(activity).getDrawable());
}
typedArray.recycle();
}
} catch (Throwable e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Intent intent = activity.getIntent();
ApplicationInfo applicationInfo = baseContext.getApplicationInfo();
PackageManager pm = activity.getPackageManager();
if (intent != null && activity.isTaskRoot()) {
try {
String label = applicationInfo.loadLabel(pm) + "";
Bitmap icon = null;
Drawable drawable = applicationInfo.loadIcon(pm);
if (drawable instanceof BitmapDrawable) {
icon = ((BitmapDrawable) drawable).getBitmap();
}
activity.setTaskDescription(new ActivityManager.TaskDescription(label, icon));
} catch (Throwable e) {
e.printStackTrace();
}
}
}
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/fixer/ComponentFixer.java
================================================
package com.lody.virtual.client.fixer;
import android.content.pm.ComponentInfo;
import android.text.TextUtils;
import com.lody.virtual.server.pm.PackageSetting;
/**
* @author Lody
*/
public class ComponentFixer {
public static String fixComponentClassName(String pkgName, String className) {
if (className != null) {
if (className.charAt(0) == '.') {
return pkgName + className;
}
return className;
}
return null;
}
public static void fixComponentInfo(PackageSetting setting, ComponentInfo info, int userId) {
if (info != null) {
if (TextUtils.isEmpty(info.processName)) {
info.processName = info.packageName;
}
info.name = fixComponentClassName(info.packageName, info.name);
if (info.processName == null) {
info.processName = info.applicationInfo.processName;
}
}
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/fixer/ContextFixer.java
================================================
package com.lody.virtual.client.fixer;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.Build;
import android.os.DropBoxManager;
import com.lody.virtual.client.core.InvocationStubManager;
import com.lody.virtual.client.core.VirtualCore;
import com.lody.virtual.client.hook.base.BinderInvocationStub;
import com.lody.virtual.client.hook.proxies.dropbox.DropBoxManagerStub;
import com.lody.virtual.client.hook.proxies.graphics.GraphicsStatsStub;
import com.lody.virtual.helper.utils.Reflect;
import com.lody.virtual.helper.utils.ReflectException;
import mirror.android.app.ContextImpl;
import mirror.android.app.ContextImplKitkat;
import mirror.android.content.ContentResolverJBMR2;
/**
* @author Lody
*/
public class ContextFixer {
private static final String TAG = ContextFixer.class.getSimpleName();
/**
* Fuck AppOps
*
* @param context Context
*/
public static void fixContext(Context context) {
try {
context.getPackageName();
} catch (Throwable e) {
return;
}
InvocationStubManager.getInstance().checkEnv(GraphicsStatsStub.class);
int deep = 0;
while (context instanceof ContextWrapper) {
context = ((ContextWrapper) context).getBaseContext();
deep++;
if (deep >= 10) {
return;
}
}
ContextImpl.mPackageManager.set(context, null);
try {
context.getPackageManager();
} catch (Throwable e) {
e.printStackTrace();
}
if (!VirtualCore.get().isVAppProcess()) {
return;
}
DropBoxManager dm = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
BinderInvocationStub boxBinder = InvocationStubManager.getInstance().getInvocationStub(DropBoxManagerStub.class);
if (boxBinder != null) {
try {
Reflect.on(dm).set("mService", boxBinder.getProxyInterface());
} catch (ReflectException e) {
e.printStackTrace();
}
}
String hostPkg = VirtualCore.get().getHostPkg();
ContextImpl.mBasePackageName.set(context, hostPkg);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
ContextImplKitkat.mOpPackageName.set(context, hostPkg);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
ContentResolverJBMR2.mPackageName.set(context.getContentResolver(), hostPkg);
}
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/BinderInvocationProxy.java
================================================
package com.lody.virtual.client.hook.base;
import android.os.IBinder;
import android.os.IInterface;
import mirror.RefStaticMethod;
import mirror.android.os.ServiceManager;
/**
* @author Paulo Costa
*
* @see MethodInvocationProxy
*/
public abstract class BinderInvocationProxy extends MethodInvocationProxy {
protected String mServiceName;
public BinderInvocationProxy(IInterface stub, String serviceName) {
this(new BinderInvocationStub(stub), serviceName);
}
public BinderInvocationProxy(RefStaticMethod asInterfaceMethod, String serviceName) {
this(new BinderInvocationStub(asInterfaceMethod, ServiceManager.getService.call(serviceName)), serviceName);
}
public BinderInvocationProxy(Class> stubClass, String serviceName) {
this(new BinderInvocationStub(stubClass, ServiceManager.getService.call(serviceName)), serviceName);
}
public BinderInvocationProxy(BinderInvocationStub hookDelegate, String serviceName) {
super(hookDelegate);
this.mServiceName = serviceName;
}
@Override
public void inject() throws Throwable {
getInvocationStub().replaceService(mServiceName);
}
@Override
public boolean isEnvBad() {
IBinder binder = ServiceManager.getService.call(mServiceName);
return binder != null && getInvocationStub() != binder;
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/BinderInvocationStub.java
================================================
package com.lody.virtual.client.hook.base;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
import com.lody.virtual.client.core.VirtualCore;
import java.io.FileDescriptor;
import java.lang.reflect.Method;
import mirror.RefStaticMethod;
import mirror.android.os.ServiceManager;
/**
* @author Lody
*/
@SuppressWarnings("unchecked")
public class BinderInvocationStub extends MethodInvocationStub implements IBinder {
private static final String TAG = BinderInvocationStub.class.getSimpleName();
private IBinder mBaseBinder;
public BinderInvocationStub(RefStaticMethod asInterfaceMethod, IBinder binder) {
this(asInterface(asInterfaceMethod, binder));
}
public BinderInvocationStub(Class> stubClass, IBinder binder) {
this(asInterface(stubClass, binder));
}
public BinderInvocationStub(IInterface mBaseInterface) {
super(mBaseInterface);
mBaseBinder = getBaseInterface() != null ? getBaseInterface().asBinder() : null;
addMethodProxy(new AsBinder());
}
private static IInterface asInterface(RefStaticMethod asInterfaceMethod, IBinder binder) {
if (asInterfaceMethod == null || binder == null) {
return null;
}
return asInterfaceMethod.call(binder);
}
private static IInterface asInterface(Class> stubClass, IBinder binder) {
try {
if (stubClass == null || binder == null) {
return null;
}
Method asInterface = stubClass.getMethod("asInterface", IBinder.class);
return (IInterface) asInterface.invoke(null, binder);
} catch (Exception e) {
Log.d(TAG, "Could not create stub " + stubClass.getName() + ". Cause: " + e);
return null;
}
}
public void replaceService(String name) {
if (mBaseBinder != null) {
ServiceManager.sCache.get().put(name, this);
}
}
private final class AsBinder extends MethodProxy {
@Override
public String getMethodName() {
return "asBinder";
}
@Override
public Object call(Object who, Method method, Object... args) throws Throwable {
return BinderInvocationStub.this;
}
}
@Override
public String getInterfaceDescriptor() throws RemoteException {
return mBaseBinder.getInterfaceDescriptor();
}
public Context getContext() {
return VirtualCore.get().getContext();
}
@Override
public boolean pingBinder() {
return mBaseBinder.pingBinder();
}
@Override
public boolean isBinderAlive() {
return mBaseBinder.isBinderAlive();
}
@Override
public IInterface queryLocalInterface(String descriptor) {
return getProxyInterface();
}
@Override
public void dump(FileDescriptor fd, String[] args) throws RemoteException {
mBaseBinder.dump(fd, args);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
@Override
public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
mBaseBinder.dumpAsync(fd, args);
}
@Override
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
return mBaseBinder.transact(code, data, reply, flags);
}
@Override
public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException {
mBaseBinder.linkToDeath(recipient, flags);
}
@Override
public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
return mBaseBinder.unlinkToDeath(recipient, flags);
}
public IBinder getBaseBinder() {
return mBaseBinder;
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/Inject.java
================================================
package com.lody.virtual.client.hook.base;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Lody
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
Class> value();
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/LogInvocation.java
================================================
package com.lody.virtual.client.hook.base;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Add this annotation to a {@link MethodProxy} or a {@link MethodInvocationStub} to
* log all the calls and their arguments.
*
* Obviously, this is only useful for debugging.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface LogInvocation {
public Condition value() default Condition.ALWAYS;
static enum Condition {
/** Never logs anything */
NEVER {
public int getLogLevel(boolean isHooked, boolean isError) {
return -1;
}
},
/**
* Logs every call.
* Mostly useful for debugging.
*/
ALWAYS {
public int getLogLevel(boolean isHooked, boolean isError) {
return isError ? Log.WARN : Log.INFO;
}
},
/**
* Logs only calls that exited with error
* A reasonable tradeoff between noise and getting relevant information
*/
ON_ERROR {
public int getLogLevel(boolean isHooked, boolean isError) {
return isError ? Log.WARN : -1;
}
},
/**
* Log only calls that haven't been hooked
* It only makes sense on a MethodInvocationProxy, and is useful to pinpoint missing methods
*/
NOT_HOOKED {
public int getLogLevel(boolean isHooked, boolean isError) {
return isHooked ? -1 : isError ? Log.WARN : Log.INFO;
}
};
public abstract int getLogLevel(boolean isHooked, boolean isError);
};
};
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodBox.java
================================================
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 call() throws InvocationTargetException {
try {
return (T) method.invoke(who, args);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public T callSafe() {
try {
return (T) method.invoke(who, args);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodInvocationProxy.java
================================================
package com.lody.virtual.client.hook.base;
import android.content.Context;
import com.lody.virtual.client.core.InvocationStubManager;
import com.lody.virtual.client.core.VirtualCore;
import com.lody.virtual.client.interfaces.IInjector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
/**
* @author Lody
*
* This class is responsible with:
* - Instantiating a {@link MethodInvocationStub.HookInvocationHandler} on {@link #getInvocationStub()} ()}
* - Install a bunch of {@link MethodProxy}s, either with a @{@link Inject} annotation or manually
* calling {@link #addMethodProxy(MethodProxy)} from {@link #onBindMethods()}
* - Install the hooked object on the Runtime via {@link #inject()}
*
* All {@link MethodInvocationProxy}s (plus a couple of other @{@link IInjector}s are installed by
* {@link InvocationStubManager}
* @see Inject
*/
public abstract class MethodInvocationProxy implements IInjector {
protected T mInvocationStub;
public MethodInvocationProxy(T invocationStub) {
this.mInvocationStub = invocationStub;
onBindMethods();
afterHookApply(invocationStub);
LogInvocation loggingAnnotation = getClass().getAnnotation(LogInvocation.class);
if (loggingAnnotation != null) {
invocationStub.setInvocationLoggingCondition(loggingAnnotation.value());
}
}
protected void onBindMethods() {
if (mInvocationStub == null) {
return;
}
Class extends MethodInvocationProxy> clazz = getClass();
Inject inject = clazz.getAnnotation(Inject.class);
if (inject != null) {
Class> proxiesClass = inject.value();
Class>[] innerClasses = proxiesClass.getDeclaredClasses();
for (Class> innerClass : innerClasses) {
if (!Modifier.isAbstract(innerClass.getModifiers())
&& MethodProxy.class.isAssignableFrom(innerClass)
&& innerClass.getAnnotation(SkipInject.class) == null) {
addMethodProxy(innerClass);
}
}
}
}
private void addMethodProxy(Class> hookType) {
try {
Constructor> constructor = hookType.getDeclaredConstructors()[0];
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
MethodProxy methodProxy;
if (constructor.getParameterTypes().length == 0) {
methodProxy = (MethodProxy) constructor.newInstance();
} else {
methodProxy = (MethodProxy) constructor.newInstance(this);
}
mInvocationStub.addMethodProxy(methodProxy);
} catch (Throwable e) {
throw new RuntimeException("Unable to instance Hook : " + hookType + " : " + e.getMessage());
}
}
public MethodProxy addMethodProxy(MethodProxy methodProxy) {
return mInvocationStub.addMethodProxy(methodProxy);
}
protected void afterHookApply(T delegate) {
}
@Override
public abstract void inject() throws Throwable;
public Context getContext() {
return VirtualCore.get().getContext();
}
public T getInvocationStub() {
return mInvocationStub;
}
}
================================================
FILE: VirtualApp/lib/src/main/java/com/lody/virtual/client/hook/base/MethodInvocationStub.java
================================================
package com.lody.virtual.client.hook.base;
import android.text.TextUtils;
import android.util.Log;
import com.lody.virtual.client.hook.utils.MethodParameterUtils;
import com.lody.virtual.helper.utils.VLog;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @author Lody
*
* HookHandler uses Java's {@link Proxy} to create a wrapper for existing services.
*
* When any method is called on the wrapper, it checks if there is any {@link MethodProxy} registered
* and enabled for that method. If so, it calls the startUniformer instead of the wrapped implementation.
*