You've already forked android_translation_layer
mirror of
https://gitlab.com/android_translation_layer/android_translation_layer.git
synced 2025-10-27 11:48:10 -07:00
382 lines
12 KiB
Java
382 lines
12 KiB
Java
|
|
package android.app;
|
||
|
|
|
||
|
|
import android.content.Context;
|
||
|
|
import android.content.Intent;
|
||
|
|
import android.content.pm.ActivityInfo;
|
||
|
|
import android.content.pm.PackageParser;
|
||
|
|
import android.os.Bundle;
|
||
|
|
import android.os.Handler;
|
||
|
|
import android.os.IBinder;
|
||
|
|
import android.os.Looper;
|
||
|
|
import android.os.MessageQueue;
|
||
|
|
import android.util.Slog;
|
||
|
|
import android.view.KeyEvent;
|
||
|
|
|
||
|
|
import dalvik.system.DexClassLoader;
|
||
|
|
|
||
|
|
import java.lang.reflect.Constructor;
|
||
|
|
import java.lang.reflect.InvocationTargetException;
|
||
|
|
import java.util.ArrayList;
|
||
|
|
import java.util.Collection;
|
||
|
|
import java.util.Collections;
|
||
|
|
|
||
|
|
/* for hacky classloader patching */
|
||
|
|
import dalvik.system.DexFile;
|
||
|
|
import java.io.File;
|
||
|
|
import java.io.IOException;
|
||
|
|
import java.lang.reflect.Array;
|
||
|
|
import java.lang.reflect.Field;
|
||
|
|
|
||
|
|
public class Instrumentation {
|
||
|
|
private static final String TAG = "Instrumentation";
|
||
|
|
public static final String REPORT_KEY_IDENTIFIER = "id";
|
||
|
|
public static final String REPORT_KEY_STREAMRESULT = "stream";
|
||
|
|
|
||
|
|
private static Instrumentation create(String className, Intent arguments) throws Exception {
|
||
|
|
Thread.setUncaughtExceptionPreHandler(new ExceptionHandler());
|
||
|
|
try {
|
||
|
|
String target_package = null;
|
||
|
|
for (PackageParser.Instrumentation instrumentation : Context.pkg.instrumentation) {
|
||
|
|
if(className.equals(instrumentation.className)) {
|
||
|
|
target_package = instrumentation.info.targetPackage;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
System.out.println("targetPackage: " + target_package);
|
||
|
|
|
||
|
|
String target_path = android.os.Environment.getExternalStorageDirectory()+"/../_installed_apks_/"+target_package+".apk";
|
||
|
|
|
||
|
|
Context.this_application.getAssets().addAssetPath(target_path);
|
||
|
|
|
||
|
|
patchClassLoader(DexClassLoader.getSystemClassLoader(), new File(target_path));
|
||
|
|
|
||
|
|
Class<? extends Instrumentation> cls = Class.forName(className).asSubclass(Instrumentation.class);
|
||
|
|
Constructor<? extends Instrumentation> constructor = cls.getConstructor();
|
||
|
|
Instrumentation i = constructor.newInstance();
|
||
|
|
i.onCreate(arguments.getExtras());
|
||
|
|
|
||
|
|
return i;
|
||
|
|
} catch (Exception e) {
|
||
|
|
/* there is no global handler for exceptions on the main thread */
|
||
|
|
Thread.getUncaughtExceptionPreHandler().uncaughtException(Thread.currentThread(), e);
|
||
|
|
}
|
||
|
|
return null; // we will never get here
|
||
|
|
}
|
||
|
|
|
||
|
|
public Instrumentation() {
|
||
|
|
}
|
||
|
|
|
||
|
|
public void start() {
|
||
|
|
Thread t = new Thread(new Runnable() {
|
||
|
|
public void run() {
|
||
|
|
//Looper.prepare();
|
||
|
|
onStart();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
t.start();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void onCreate(Bundle arguments) {
|
||
|
|
}
|
||
|
|
|
||
|
|
public boolean onException(Object obj, Throwable e) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void onStart() {
|
||
|
|
}
|
||
|
|
|
||
|
|
public Context getContext() {
|
||
|
|
return new Context();
|
||
|
|
}
|
||
|
|
|
||
|
|
public Context getTargetContext() {
|
||
|
|
return new Context();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void setAutomaticPerformanceSnapshots() {
|
||
|
|
}
|
||
|
|
|
||
|
|
public void setInTouchMode(boolean inTouch) {
|
||
|
|
Slog.w(TAG, "FIXME: Instrumentation.setInTouchMode: " + inTouch);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void sendKeySync(KeyEvent event) {
|
||
|
|
validateNotAppThread();
|
||
|
|
/*long downTime = event.getDownTime();
|
||
|
|
long eventTime = event.getEventTime();
|
||
|
|
int source = event.getSource();
|
||
|
|
if (source == InputDevice.SOURCE_UNKNOWN) {
|
||
|
|
source = InputDevice.SOURCE_KEYBOARD;
|
||
|
|
}
|
||
|
|
if (eventTime == 0) {
|
||
|
|
eventTime = SystemClock.uptimeMillis();
|
||
|
|
}
|
||
|
|
if (downTime == 0) {
|
||
|
|
downTime = eventTime;
|
||
|
|
}
|
||
|
|
KeyEvent newEvent = new KeyEvent(event);
|
||
|
|
newEvent.setTime(downTime, eventTime);
|
||
|
|
newEvent.setSource(source);
|
||
|
|
newEvent.setFlags(event.getFlags() | KeyEvent.FLAG_FROM_SYSTEM);
|
||
|
|
setDisplayIfNeeded(newEvent);
|
||
|
|
InputManagerGlobal.getInstance().injectInputEvent(newEvent,
|
||
|
|
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);*/
|
||
|
|
Slog.w(TAG, "FIXME: Instrumentation.sendKeySync: " + event);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void sendKeyDownUpSync(int keyCode) {
|
||
|
|
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
||
|
|
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
||
|
|
}
|
||
|
|
|
||
|
|
static public Application newApplication(Class<?> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
|
||
|
|
return Context.this_application; // we don't (currently?) support multiple applications in a single process
|
||
|
|
}
|
||
|
|
|
||
|
|
public Activity newActivity(Class<?> clazz, Context context, IBinder token, Application application,
|
||
|
|
Intent intent, ActivityInfo info, CharSequence title, Activity parent,
|
||
|
|
String id, Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException {
|
||
|
|
Activity activity = (Activity)clazz.newInstance();
|
||
|
|
activity.getWindow().native_window = Context.this_application.native_window;
|
||
|
|
Slog.i(TAG, "activity.getWindow().native_window >"+activity.getWindow().native_window+"<");
|
||
|
|
return activity;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Copyright (C) 2006 The Android Open Source Project */
|
||
|
|
public void callActivityOnCreate(Activity activity, Bundle savedState) {
|
||
|
|
//prePerformCreate(activity);
|
||
|
|
runOnMainSync(new Runnable() {
|
||
|
|
public void run() {
|
||
|
|
activity.onCreate(savedState);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
//postPerformCreate(activity);
|
||
|
|
}
|
||
|
|
|
||
|
|
public Activity startActivitySync(Intent intent) {
|
||
|
|
return startActivitySync(intent, null);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* TODO - deduplicate with startActivityForResult? */
|
||
|
|
public Activity startActivitySync(Intent intent, Bundle options) {
|
||
|
|
Slog.i(TAG, "startActivitySync(" + intent + ", " + options + ") called");
|
||
|
|
if (intent.getComponent() != null) {
|
||
|
|
try {
|
||
|
|
Class<? extends Activity> cls = Class.forName(intent.getComponent().getClassName()).asSubclass(Activity.class);
|
||
|
|
Constructor<? extends Activity> constructor = cls.getConstructor();
|
||
|
|
final Activity activity = constructor.newInstance();
|
||
|
|
activity.intent = intent;
|
||
|
|
activity.getWindow().native_window = Context.this_application.native_window;
|
||
|
|
runOnMainSync(new Runnable() {
|
||
|
|
@Override
|
||
|
|
public void run() {
|
||
|
|
Activity.nativeStartActivity(activity);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return activity;
|
||
|
|
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||
|
|
/* not sure what to do here */
|
||
|
|
}
|
||
|
|
} /*else if (FILE_CHOOSER_ACTIONS.contains(intent.getAction())) { // not sure what to do here either
|
||
|
|
nativeFileChooser(FILE_CHOOSER_ACTIONS.indexOf(intent.getAction()), intent.getType(), intent.getStringExtra("android.intent.extra.TITLE"), requestCode);
|
||
|
|
} */else {
|
||
|
|
Slog.i(TAG, "startActivityForResult: intent was not handled.");
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void runOnMainSync(Runnable runner) {
|
||
|
|
validateNotAppThread();
|
||
|
|
SyncRunnable sr = new SyncRunnable(runner);
|
||
|
|
new Handler(Looper.getMainLooper()).post(sr);
|
||
|
|
sr.waitForComplete();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void sendStatus(int resultCode, Bundle results) {
|
||
|
|
if (results != null) {
|
||
|
|
for (String key : sorted(results.keySet())) {
|
||
|
|
System.out.println("INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void finish(int resultCode, Bundle results) {
|
||
|
|
boolean need_hack = false;
|
||
|
|
if (results != null) {
|
||
|
|
for (String key : sorted(results.keySet())) {
|
||
|
|
System.out.println("INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
|
||
|
|
/* HACK: no idea why this isn't recognized as an error otherwise */
|
||
|
|
if(((String)results.get(key)).contains("Test run aborted due to unexpected exception"))
|
||
|
|
need_hack = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if(need_hack) {
|
||
|
|
System.out.println("INSTRUMENTATION_STATUS: shortMsg=ugly hack: Test run aborted due to unexpected exception");
|
||
|
|
System.out.println("INSTRUMENTATION_STATUS_CODE: -1");
|
||
|
|
}
|
||
|
|
|
||
|
|
System.out.println("INSTRUMENTATION_CODE: " + resultCode);
|
||
|
|
System.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Copyright (C) 2006 The Android Open Source Project */
|
||
|
|
private static Collection<String> sorted(Collection<String> list) {
|
||
|
|
final ArrayList<String> copy = new ArrayList<>(list);
|
||
|
|
Collections.sort(copy);
|
||
|
|
return copy;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Copyright (C) 2006 The Android Open Source Project */
|
||
|
|
private static final class SyncRunnable implements Runnable {
|
||
|
|
private final Runnable mTarget;
|
||
|
|
private boolean mComplete;
|
||
|
|
public SyncRunnable(Runnable target) {
|
||
|
|
mTarget = target;
|
||
|
|
}
|
||
|
|
public void run() {
|
||
|
|
mTarget.run();
|
||
|
|
synchronized (this) {
|
||
|
|
mComplete = true;
|
||
|
|
notifyAll();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
public void waitForComplete() {
|
||
|
|
synchronized (this) {
|
||
|
|
while (!mComplete) {
|
||
|
|
try {
|
||
|
|
wait();
|
||
|
|
} catch (InterruptedException e) {}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Copyright (C) 2006 The Android Open Source Project */
|
||
|
|
private static final class EmptyRunnable implements Runnable {
|
||
|
|
public void run() {}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Copyright (C) 2006 The Android Open Source Project */
|
||
|
|
private static final class Idler implements MessageQueue.IdleHandler {
|
||
|
|
private final Runnable mCallback;
|
||
|
|
private boolean mIdle;
|
||
|
|
public Idler(Runnable callback) {
|
||
|
|
mCallback = callback;
|
||
|
|
mIdle = false;
|
||
|
|
}
|
||
|
|
public final boolean queueIdle() {
|
||
|
|
if (mCallback != null) {
|
||
|
|
mCallback.run();
|
||
|
|
}
|
||
|
|
synchronized (this) {
|
||
|
|
mIdle = true;
|
||
|
|
notifyAll();
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
public void waitForIdle() {
|
||
|
|
synchronized (this) {
|
||
|
|
while (!mIdle) {
|
||
|
|
try {
|
||
|
|
wait();
|
||
|
|
} catch (InterruptedException e) {
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Copyright (C) 2006 The Android Open Source Project */
|
||
|
|
public void waitForIdleSync() {
|
||
|
|
/*validateNotAppThread();
|
||
|
|
Idler idler = new Idler(null);
|
||
|
|
Looper.myLooper().myQueue().addIdleHandler(idler);
|
||
|
|
new Handler(Looper.myLooper()).post(new EmptyRunnable());
|
||
|
|
idler.waitForIdle();*/
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Copyright (C) 2006 The Android Open Source Project */
|
||
|
|
private final void validateNotAppThread() {
|
||
|
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||
|
|
throw new RuntimeException("This method can not be called from the main application thread");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static class ExceptionHandler implements Thread.UncaughtExceptionHandler {
|
||
|
|
public void uncaughtException(Thread t, Throwable e) {
|
||
|
|
//onException(null /*FIXME?*/, e);
|
||
|
|
System.out.print("INSTRUMENTATION_RESULT: shortMsg=");
|
||
|
|
e.printStackTrace();
|
||
|
|
System.out.println("INSTRUMENTATION_STATUS_CODE: -1");
|
||
|
|
System.exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -- a hacky method to patch in a classpath entry (there should be a better way *in theory*, but other approaches didn't work) -- */
|
||
|
|
private static Object getFieldObject(Class cls, Object obj, String field_name) {
|
||
|
|
try {
|
||
|
|
Field field = cls.getDeclaredField(field_name);
|
||
|
|
field.setAccessible(true);
|
||
|
|
Object ret = field.get(obj);
|
||
|
|
field.setAccessible(false);
|
||
|
|
return ret;
|
||
|
|
} catch(Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void setFieldObject(Class cls, Object obj, String field_name, Object value) {
|
||
|
|
try {
|
||
|
|
Field field = cls.getDeclaredField(field_name);
|
||
|
|
field.setAccessible(true);
|
||
|
|
field.set(obj, value);
|
||
|
|
field.setAccessible(false);
|
||
|
|
} catch(Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static Object createObject(Class cls, Class[] type_array, Object[] value_array) {
|
||
|
|
try {
|
||
|
|
Constructor ctor = cls.getDeclaredConstructor(type_array);
|
||
|
|
ctor.setAccessible(true);
|
||
|
|
Object ret = ctor.newInstance(value_array);
|
||
|
|
ctor.setAccessible(false);
|
||
|
|
return ret;
|
||
|
|
} catch(Exception e) {
|
||
|
|
e.printStackTrace();
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void patchClassLoader(ClassLoader cl, File apk_path) throws IOException {
|
||
|
|
// get cl.pathList
|
||
|
|
Object pathList = getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList");
|
||
|
|
// get pathList.dexElements
|
||
|
|
Object[] dexElements = (Object[]) getFieldObject(pathList.getClass(), pathList, "dexElements");
|
||
|
|
// Element type
|
||
|
|
Class<?> Element_class = dexElements.getClass().getComponentType();
|
||
|
|
// Create an array to replace the original array
|
||
|
|
Object[] DexElements_new = (Object[]) Array.newInstance(Element_class, dexElements.length + 1);
|
||
|
|
// use this constructor: ElementDexFile.class(DexFile dexFile, File file)
|
||
|
|
Class[] type_array = {DexFile.class, File.class};
|
||
|
|
Object[] value_array = {DexFile.loadDex(apk_path.getCanonicalPath(), null, 0), apk_path};
|
||
|
|
Object new_element = createObject(Element_class, type_array, value_array);
|
||
|
|
Object[] new_element_wrapper_array = new Object[] {new_element};
|
||
|
|
// Copy the original elements
|
||
|
|
System.arraycopy(dexElements, 0, DexElements_new, 0, dexElements.length);
|
||
|
|
// The element of the plugin is copied in
|
||
|
|
System.arraycopy(new_element_wrapper_array, 0, DexElements_new, dexElements.length, new_element_wrapper_array.length);
|
||
|
|
// replace
|
||
|
|
setFieldObject(pathList.getClass(), pathList, "dexElements", DexElements_new);
|
||
|
|
}
|
||
|
|
}
|