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.StrictMode;
import com.lody.virtual.IOHook;
import com.lody.virtual.client.core.PatchManager;
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.delegate.IORedirectDelegate;
import com.lody.virtual.client.hook.patchs.am.HCallbackHook;
import com.lody.virtual.client.hook.providers.ProviderHook;
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.stub.StubManifest;
import com.lody.virtual.helper.proto.PendingResultData;
import com.lody.virtual.helper.utils.VLog;
import com.lody.virtual.os.VUserHandle;
import com.lody.virtual.server.secondary.FakeIdentityBinder;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
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.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;
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 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() {
Binder appThread = ActivityThread.getApplicationThread.call(VirtualCore.mainThread());
return new FakeIdentityBinder(appThread) {
@Override
protected int getFakeUid() {
return Process.SYSTEM_UID;
}
};
}
@Override
public IBinder getToken() {
return token;
}
public void initProcess(IBinder token, int vuid) {
if (this.token != null) {
throw new IllegalStateException("Token is exist!");
}
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 (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
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();
data.appInfo = VPackageManager.get().getApplicationInfo(packageName, 0, getUserId(vuid));
data.processName = processName;
data.providers = VPackageManager.get().queryContentProviders(processName, getVUid(), PackageManager.GET_META_DATA);
mBoundApplication = data;
VirtualRuntime.setupRuntime(data.processName, data.appInfo);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public synchronized void start() {
Throwable t = new Exception();
t.printStackTrace();
if (VirtualCore.get().uncheckedExceptionDelegate != null)
VirtualCore.get().uncheckedExceptionDelegate.onShutdown(t);
super.start();
}
});
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();
}
IOHook.hookNative();
Object mainThread = VirtualCore.mainThread();
IOHook.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) {
PatchManager.getInstance().checkEnv(AppInstrumentation.class);
}
mInitialApplication = LoadedApk.makeApplication.call(data.info, false, null);
VLog.d(TAG, "app: %s", mInitialApplication);
mirror.android.app.ActivityThread.mInitialApplication.set(mainThread, mInitialApplication);
ContextFixer.fixContext(mInitialApplication);
List<ProviderInfo> providers = VPackageManager.get().queryContentProviders(data.processName, vuid, PackageManager.GET_META_DATA);
if (providers != null) {
installContentProviders(mInitialApplication, providers);
}
if (lock != null) {
lock.open();
mTempLock = null;
}
try {
mInstrumentation.callApplicationOnCreate(mInitialApplication);
PatchManager.getInstance().checkEnv(HCallbackHook.class);
if (conflict) {
PatchManager.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 == null ? "NULL" : 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) {
List<ThreadGroup> groups = mirror.java.lang.ThreadGroup.groups.get(root);
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 {
ThreadGroup[] groups = ThreadGroupN.groups.get(root);
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);
}
}
}
}
@SuppressLint("SdCardPath")
private void startIOUniformer() {
ApplicationInfo info = mBoundApplication.appInfo;
IOHook.redirect("/data/data/" + info.packageName + "/", info.dataDir + "/");
IOHook.redirect("/data/user/0/" + info.packageName + "/", info.dataDir + "/");
IOHook.redirect(info.dataDir + "/lib", info.nativeLibraryDir);
IORedirectDelegate delegate = VirtualCore.get().ioRedirectDelegate;
if (delegate != null) {
Map<String, String> ioRedirect = delegate.getIORedirect();
for (Map.Entry<String, String> entry : ioRedirect.entrySet())
IOHook.redirect(entry.getKey(), entry.getValue());
Map<String, String> reversedRedirect = delegate.getIOReversedRedirect();
for (Map.Entry<String, String> entry : reversedRedirect.entrySet())
IOHook.reversed(entry.getKey(), entry.getValue());
}
IOHook.hook();
}
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) {
throw new RuntimeException(e);
}
}
private Object fixBoundApp(AppBindData data) {
// TODO: Using Native VM Hook to fix the `Camera` and `AudioRecord`.
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()));
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 (!VClientImpl.get().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());
boolean highApi = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
for (Object clientRecord : clientMap.values()) {
if (highApi) {
IInterface provider = ActivityThread.ProviderClientRecordJB.mProvider.get(clientRecord);
Object holder = ActivityThread.ProviderClientRecordJB.mHolder.get(clientRecord);
ProviderInfo info = IActivityManager.ContentProviderHolder.info.get(holder);
if (holder != null && !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;
if (Settings.System.TYPE != null) {
cache = Settings.System.sNameValueCache.get();
if (cache != null) {
Settings.NameValueCache.mContentProvider.set(cache, null);
}
}
if (Settings.Secure.TYPE != null) {
cache = Settings.Secure.sNameValueCache.get();
if (cache != null) {
Settings.NameValueCache.mContentProvider.set(cache, null);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && Settings.Global.TYPE != null) {
cache = Settings.Global.sNameValueCache.get();
if (cache != null) {
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(ComponentName component, Intent intent, PendingResultData resultData) {
ReceiverData receiverData = new ReceiverData();
receiverData.resultData = resultData;
receiverData.intent = intent;
receiverData.component = component;
sendMessage(RECEIVER, receiverData);
}
private void handleReceiver(ReceiverData data) {
BroadcastReceiver.PendingResult result = data.resultData.build();
try {
Context context = createPackageContext(data.component.getPackageName());
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 {
public RootThreadGroup(ThreadGroup parent) {
super(parent, "VA-Root");
}
@Override
public void uncaughtException(Thread t, Throwable e) {
VLog.e("uncaught", e);
if (VirtualCore.get().uncheckedExceptionDelegate != null)
VirtualCore.get().uncheckedExceptionDelegate.onThreadGroupUncaughtException(t, 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;
}
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);
}
}
}
}
}