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.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.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.VPackageManager;
import com.lody.virtual.client.ipc.VirtualStorageManager;
import com.lody.virtual.client.stub.StubManifest;
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.taobao.android.dex.interpret.ARTUtils;
import java.io.File;
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.IActivityManager;
import mirror.android.app.LoadedApk;
import mirror.android.content.ContentProviderHolderOreo;
import mirror.android.providers.Settings;
import mirror.android.renderscript.RenderScriptCacheDir;
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 AppBindData mBoundApplication;
private Application mInitialApplication;
private CrashHandler crashHandler;
public static VClientImpl get() {
return gClient;
}
public boolean isBound() {
return mBoundApplication != null;
}
public Application getCurrentApplication() {
return mInitialApplication;
}
public String getCurrentPackage() {
return mBoundApplication != null ? mBoundApplication.appInfo.packageName : null;
}
public ApplicationInfo getCurrentApplicationInfo() {
return mInitialApplication != null ? mInitialApplication.getApplicationInfo() : 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) {
mTempLock = lock;
try {
setupUncaughtHandler();
} catch (Throwable e) {
e.printStackTrace();
}
try {
fixInstalledProviders();
} catch (Throwable e) {
e.printStackTrace();
}
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);
}
if (!info.dependSystem && info.artFlyMode) {
ARTUtils.init(VirtualCore.get().getContext());
ARTUtils.setIsDex2oatEnabled(false);
}
data.appInfo = VPackageManager.get().getApplicationInfo(packageName, 0, getUserId(vuid));
data.processName = processName;
data.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.N) {
if (mirror.android.os.StrictMode.sVmPolicyMask != null) {
mirror.android.os.StrictMode.sVmPolicyMask.set(0);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
mirror.android.os.Message.updateCheckRecycle.call(targetSdkVersion);
}
if (StubManifest.ENABLE_IO_REDIRECT) {
startIOUniformer();
}
NativeEngine.hookNative();
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);
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 (data.providers != null) {
installContentProviders(mInitialApplication, data.providers);
}
if (lock != null) {
lock.open();
mTempLock = null;
}
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();
}
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<ThreadGroup> groups = mirror.java.lang.ThreadGroup.groups.get(root);
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (groups) {
List<ThreadGroup> 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) {
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) {
ThreadGroupN.parent.set(group, newRoot);
}
ThreadGroupN.ngroups.set(root, 1);
}
}
}
@SuppressLint("SdCardPath")
private void startIOUniformer() {
ApplicationInfo info = mBoundApplication.appInfo;
int userId = VUserHandle.myUserId();
NativeEngine.redirectDirectory("/data/data/" + info.packageName, info.dataDir);
NativeEngine.redirectDirectory("/data/user/0/" + info.packageName, info.dataDir);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
NativeEngine.redirectDirectory("/data/user_de/0/" + info.packageName, info.dataDir);
}
String libPath = new File(VEnvironment.getDataAppPackageDirectory(info.packageName), "lib").getAbsolutePath();
String userLibPath = new File(VEnvironment.getUserSystemDirectory(userId), "lib").getAbsolutePath();
NativeEngine.redirectDirectory(userLibPath, libPath);
NativeEngine.redirectDirectory("/data/data/" + info.packageName + "/lib/", libPath);
NativeEngine.redirectDirectory("/data/user/0/" + info.packageName + "/lib/", libPath);
NativeEngine.readOnly(VEnvironment.getDataAppDirectory().getPath());
VirtualStorageManager vsManager = VirtualStorageManager.get();
String vsPath = vsManager.getVirtualStorage(info.packageName, userId);
boolean enable = vsManager.isVirtualStorageEnable(info.packageName, userId);
if (enable && vsPath != null) {
File vsDirectory = new File(vsPath);
if (vsDirectory.exists() || vsDirectory.mkdirs()) {
HashSet<String> mountPoints = getMountPoints();
for (String mountPoint : mountPoints) {
NativeEngine.redirectDirectory(mountPoint, vsPath);
}
}
}
NativeEngine.hook();
}
@SuppressLint("SdCardPath")
private HashSet<String> getMountPoints() {
HashSet<String> 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) {
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<ProviderInfo> providers) {
long origId = Binder.clearCallingIdentity();
Object mainThread = VirtualCore.mainThread();
try {
for (ProviderInfo cpi : providers) {
if (cpi.enabled) {
ActivityThread.installProvider(mainThread, app, cpi, null);
}
}
} 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(StubManifest.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(StubManifest.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(StubManifest.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());
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<ProviderInfo> 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);
}
}
}
}
}