mirror of
https://gitlab.com/android_translation_layer/android_translation_layer.git
synced 2025-04-28 12:17:57 +03:00
add some documentation
This commit is contained in:
parent
5c6428baae
commit
d5d78d8621
4 changed files with 3455 additions and 0 deletions
|
@ -9,6 +9,8 @@ usage:
|
|||
`./launch_activity.sh <path_to_apk> <path_to_activity>`
|
||||
example: `./launch_activity.sh test_apks/org.happysanta.gd_29.apk org/happysanta/gd/GDActivity`
|
||||
|
||||
NOTE: you might need to copy some files out from the apk under `data/`, e.g the `assets` folder
|
||||
|
||||
when it doesn't work:
|
||||
if you are trying to launch a random app, chances are that we are missing implementations for some
|
||||
stuff that it needs, and we also don't have (sufficiently real looking) stubs for the stuff it says
|
||||
|
@ -23,6 +25,9 @@ widget such that it shows up as a Gtk Widget. for layout widgets, you can cheat
|
|||
LinearLayout or RelativeLayout, which are currently implemented in an extremely simplistic manner
|
||||
(it might not look correctly, but it should *work*)
|
||||
|
||||
for more specific instructions, see `doc/QuickHelp.md`
|
||||
for general description of the architecure, see `doc/Architecture.md`
|
||||
|
||||
screenshot:
|
||||
|
||||

|
||||
|
|
92
doc/Architecture.md
Normal file
92
doc/Architecture.md
Normal file
|
@ -0,0 +1,92 @@
|
|||
#### directory structure
|
||||
|
||||
`dalvik/*dalvik` - helper scripts for running bytecode in the dalvik VM
|
||||
`dalvik/linux-${arch}` - pre-complied dalvik from https://gitlab.com/Mis012/dalvik_standalone
|
||||
|
||||
`arsc_parser/` - Java .arsc parser I found somewhere, with fixes (should eventually get replaced by C code)
|
||||
`data` - the equivalent of `/data/data/${app-package-name}/`; TODO - use `data/${app-package-name}` instead
|
||||
to allow for storing the data of multiple apps at the same time
|
||||
`data/lib/` - hardcoded location, libraries under which are assumed to be linked against bionic
|
||||
(and will therefore be loaded with a shim bionic linker)
|
||||
`doc` - documentation
|
||||
`jars` - when we want to link against dalvik core java libs, we need to keep their non-dex versions here
|
||||
`jni` - C code implementing things which it doesn't make sense to do in Java (ideally this would be most things)
|
||||
`libandroid-src` - C code implementing `libandroid.so` (this is needed by most JNI libs which come with android apps)
|
||||
`libnative` - compilation output for `.so` libraries
|
||||
`main-src/org/launch/main.java` - the entry point; TODO - at this point it just calls into the C entry point,
|
||||
so should probably move the entry point to C
|
||||
`main-src/` (rest) - Java code implementing the android APIs
|
||||
`test-apks` - all apks known to somewhat work, except paid proprietary ones
|
||||
|
||||
`./com.google.android.gms.apk`: microg; stopgap solution to run apps with GSF dependencies
|
||||
`./*.dex` - compiled Java code; `hax_arsc_parser.dex` corresponds to `hax_arsc_parser.dex/`, `main.dex`
|
||||
corresponds to `main-src/org/launch/main.java` and `hax.dex` corresponds to the rest of `main-src`
|
||||
|
||||
#### philosophy
|
||||
|
||||
companion infographic for this section:
|
||||
`doc/android_translation_architecture.svg`
|
||||
|
||||
We believe that the cleanest approach for supporting android apps on desktop
|
||||
Linux platforms is to make a chirurgical cut on the android platform stack
|
||||
as close to the apps themselves as possible, and sew on a new implementation
|
||||
of whatever we have cut off.
|
||||
|
||||
If you glance at the companion infographic, you can see that the place where
|
||||
we chose to make the cut is directly between the Apps and the Java APIs
|
||||
provided by the android frameworks. (additionally, for apps with native
|
||||
components, we also keep just the respective .so files from those apps
|
||||
and provide implementations/shims for the system libraries they are linked
|
||||
against)
|
||||
|
||||
#### current control flow (to be refined)
|
||||
|
||||
1. the user executes `launch_activity.sh`
|
||||
(contents:
|
||||
```sh
|
||||
LD_PRELOAD=libpthread_bio.so:libbsd.so.0 ./dalvik/dalvik -verbose:jni -cp hax_arsc_parser.dex:hax.dex:main.dex:${1}:com.google.android.gms.apk org/launch/main -l ${2}
|
||||
```
|
||||
)
|
||||
this line accomplishes the following things:
|
||||
- `LD_PRELOAD` makes sure that `libbsd` (which provides some functions that bionic would on android) is loaded, even though bionic-linked libraries don't know they want it
|
||||
- `LD_PRELOAD` also loads libpthread_bio.so, which is a shim for translating libpthread calls made by bionic-linked libraries to calls to glibc/musl libpthread
|
||||
- `./dalvik/dalvik` is a helper script for launching the dalvik vm with the right `LD_LIBRARY_PATH`, `bootclasspath`, etc
|
||||
- `hax_arsc_parser.dex` is dalvik bytecode implementing .arsc parsing duties (to be replaced by C code eventually)
|
||||
- `hax.dex` contains all the implementations of android framework functions
|
||||
- `main.dex` contains the Java entry point `public static void main(String[])`
|
||||
- `${1}` will be substituted by the path to the app's apk, making the bytecode within (and resources.arsc, which is currently the only other file read straight from the apk) available in classpath
|
||||
- `com.google.android.gms.apk` is the path to a microG apk, needed for apps with a dependency on GSF; this is specified after the app's apk so that the the app's apk is the first zip file in the classpath (needed for getting the right resources.arsc)
|
||||
- `org/launch/main` is the class which contains the Java entry point, this tells dalvik to look for it there
|
||||
- `-l ${2}` is cmdline argument list to be passed to the entry point function, which passes it to the C entry point
|
||||
|
||||
2. the Java entry point is executed by dalvik
|
||||
(contents:
|
||||
```Java
|
||||
public class main {
|
||||
public static void main(String[] args) {
|
||||
System.load("libnative/org_launch_main.so");
|
||||
real_main(args);
|
||||
}
|
||||
|
||||
public static native void real_main(String[] args);
|
||||
}
|
||||
M
|
||||
```
|
||||
);
|
||||
|
||||
3. execution jumps to the C entry point in `jni/org_launch_main.c`
|
||||
1. the function `JNIEXPORT void JNICALL Java_org_launch_main_real_1main(JNIEnv *env, jclass this_class, jobjectArray args)` fixes up argc/argv in preparation for removing the Java entry point in favor of a C entry point, and calls `static int main(int argc, char **argv, JNIEnv *env)`
|
||||
2. `static int main(int argc, char **argv, JNIEnv *env)` sets up a GtkApllication, cmdline handling, and calls `g_application_run`
|
||||
3. GtkApplication glue parses the cmdline and calls `static void activate(GtkApplication *app, struct jni_callback_data *d)`
|
||||
4. `static void activate(GtkApplication *app, struct jni_callback_data *d)`:
|
||||
1. sets up a JNI handle cache
|
||||
2. sets display size to be passed to apps according to optionally specified window size
|
||||
3. creates a Gtk Window with said size and shows it on screen
|
||||
4. calls the OnCreate method of the class specified with the `-l` option
|
||||
|
||||
4. the Activity specfified with `-l` calls various android APIs, which we hopefully implement;
|
||||
typically, it will set up some android widgets, which will appear in the Gtk windows as Gtk widgets
|
||||
|
||||
5. you, the user, interact with the Gtk widgets, which the app might have registered callbacks for;
|
||||
as long as our implementation of android APIs is good enough for what the app needs, you can use the app
|
||||
as you would on android
|
192
doc/QuickHelp.md
Normal file
192
doc/QuickHelp.md
Normal file
|
@ -0,0 +1,192 @@
|
|||
#### quick guide to implementing as-of-yet uinmplemented android APIs to make a new app work
|
||||
|
||||
If you try to run any random android app using the translation layer, you will most likely encounter errors
|
||||
telling you that some class/interface/function doesn't exist. If you wish to help improve the translation layer
|
||||
by adding support for the missing APIs (and if not you, who will), then read ahead :)
|
||||
|
||||
##### simplest case: stubbing
|
||||
|
||||
In a lot of cases, the functionality of the missing APIs is not really relevant to you. And even when it is,
|
||||
you would probably prefer to first get the app to launch without errors. With a lot of APIs, you can get away
|
||||
with writing stubs.
|
||||
|
||||
What is a stub? Well, the simplest stub would be an empty class, like this:
|
||||
`main-src/android/webkit/WebView.java:`
|
||||
```Java
|
||||
package android.webkit;
|
||||
|
||||
public class WebView {
|
||||
}
|
||||
```
|
||||
This should fix the "no such class" error, and let you get further in your attempt to simply launch the app.
|
||||
If the app uses a non-default constructor, you will need to provide that as well (empty is fine), and you will
|
||||
need to provide stub classes for any types used for paramters.
|
||||
|
||||
Since the class was needed, it's pretty likely that up next you will get a "no such method" error. The easiest
|
||||
case is a void method:
|
||||
```Java
|
||||
package android.webkit;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class WebView {
|
||||
public void doSomething(Context context) {
|
||||
}
|
||||
}
|
||||
```
|
||||
here, all that you need to take care of is that at `main-src/android/content/Context.java`, you have at minimum
|
||||
a stub class.
|
||||
|
||||
Unfortunately, in the WebView case, the method that an app was trying call wasn't returning `void`. If this is
|
||||
the case, your best case scenario is that you can return some sort of value that will make you progress further.
|
||||
For example, if the method is called DoWeHaveInternetConnection, it's pretty likely that upon decompiling
|
||||
the app, you will find that returing `false` from that function makes the app decide not to attempt to use
|
||||
Internet-related APIs (which you might not feel like implementing at the moment).
|
||||
Sadly, in our case, the return type is an Object. If that's the case, and the Object is of a type not yet
|
||||
implemented, you can try simply making a stub class for said object, and then returning `null`.
|
||||
|
||||
```Java
|
||||
package android.webkit;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
// the only reason we need to implement this is that some app developers are such scumbags that they try to use this for tracking purposes
|
||||
public class WebView {
|
||||
public WebView (Context context) {
|
||||
}
|
||||
|
||||
public WebSettings getSettings() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
this will obviously only work when the app checks for NULL, and decides to abandon whatever it was planning to
|
||||
do when NULL is returned. (and you don't mind that it does so)
|
||||
|
||||
When that's not an option, simply return a new instance of the stub class:
|
||||
```Java
|
||||
package android.webkit;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
// the only reason we need to implement this is that some app developers are such scumbags that they try to use this for tracking purposes
|
||||
public class WebView {
|
||||
public WebView (Context context) {
|
||||
}
|
||||
|
||||
public WebSettings getSettings() {
|
||||
return new WebSettings();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This might be enough, but quite often, the returned Object's methods are called by the app. If that's the case,
|
||||
simply create stub methods in that class same as we did above, and after a few iterations you should get to the
|
||||
end of the rabbit hole.
|
||||
|
||||
NOTE: in some cases, such as with enums and interfaces, you should be able to simply copy the APACHE-licensed
|
||||
android code. With interfaces, you might want to comment out any methods not needed for the app you are trying
|
||||
to get to work in order to cut down on the amount of stubbing you need to do.
|
||||
|
||||
##### intriguing case: widgets
|
||||
|
||||
There are two basic types of widgets (Views): containers (Layouts) and the rest.
|
||||
|
||||
To implement a container widget, simply copy an existing container widget implementation (e.g LinearLayout
|
||||
(`./main-src/android/widget/LinearLayout.java` and `./jni/widgets/android_widget_LinearLayout.c`)), and that's
|
||||
it! Now, chances are that you wanted something slightly different, but this will at least display the child
|
||||
widgets so that you can focus on implementing those.
|
||||
|
||||
When you get around to properly implementing the particularities of the specific container widget, it's
|
||||
basically the same process as below.
|
||||
|
||||
To implement any other widget, copy a widget that is closest to what you're looking for, and if Gtk has
|
||||
a better approximation for your widget, then change to that as the backing Gtk widget. If Gtk doesn't have
|
||||
anything close enough, you will need to implement your own widget. You might need to do that anyway, and wrap
|
||||
the close-enough Gtk widget, since subclassing is mostly not possible in Gtk.
|
||||
|
||||
###### case study: ImageView
|
||||
|
||||
`main-src/android/widget/ImageView.java`
|
||||
```Java
|
||||
package android.widget;
|
||||
```
|
||||
↑ most widgets are in this package, but not all of them are
|
||||
```Java
|
||||
import android.util.AttributeSet;
|
||||
import android.content.Context;
|
||||
|
||||
import android.view.View;
|
||||
```
|
||||
↑ any widget will need to import these
|
||||
```
|
||||
public class ImageView extends View {
|
||||
public ImageView(AttributeSet attrs) {
|
||||
super(attrs);
|
||||
|
||||
native_constructor(attrs);
|
||||
}
|
||||
|
||||
public ImageView(Context context) {
|
||||
super(context);
|
||||
|
||||
native_constructor(context);
|
||||
}
|
||||
|
||||
private native void native_constructor(AttributeSet attrs);
|
||||
private native void native_constructor(Context context);
|
||||
```
|
||||
↑ at least these will be needed for any widget
|
||||
```Java
|
||||
public /*native*/ void setImageResource(final int resid) {}
|
||||
public void setAdjustViewBounds(boolean adjustViewBounds) {}
|
||||
|
||||
public void setScaleType(ScaleType scaleType) {}
|
||||
|
||||
public enum ScaleType { ... }
|
||||
}
|
||||
```
|
||||
↑ you might need some stubs, don't fall into the trap of thinking that you need to immediately implement these
|
||||
|
||||
---
|
||||
|
||||
`jni/widgets/android_widget_ImageView.c`
|
||||
```C
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "../defines.h"
|
||||
#include "../util.h"
|
||||
|
||||
#include "WrapperWidget.h"
|
||||
|
||||
```
|
||||
↑ every widget will be under `jni/widgets/` and will have these includes
|
||||
```C
|
||||
#include "android_widget_ImageView.h"
|
||||
```
|
||||
↑ this is the jni-generated header file; you will need to add this to the part of the Makefile which moves widget header files to `jni/widgets/`
|
||||
↓ there should be two functions here, one for the `Context` costructor and one for the `AttributeSet` one; for start, you can keep them the same
|
||||
```C
|
||||
JNIEXPORT void JNICALL Java_android_widget_ImageView_native_1constructor__Landroid_content_Context_2(JNIEnv *env, jobject this, jobject context)
|
||||
{
|
||||
GtkWidget *wrapper = wrapper_widget_new();
|
||||
```
|
||||
↑ the wrapper widget is required, it's expected by generic functions operating on widgets; the purpose is to allow for things like background image
|
||||
handling independently of the
|
||||
```C
|
||||
GtkWidget *image = gtk_image_new_from_icon_name("FIXME"); // will not actually use gtk_image_new_from_icon_name when implementing this, but we want that nice "broken image" icon
|
||||
```
|
||||
↑ here we create the actual backing Gtk widget.
|
||||
```C
|
||||
wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), image);
|
||||
```
|
||||
↑ put the widget in the wrapper
|
||||
```C
|
||||
_SET_LONG_FIELD(this, "widget", _INTPTR(image));
|
||||
```
|
||||
↑ set the `widget` member of the View-derived class to the pointer to our widget; this lets us access this widget in the context of the View, which is precisely when we will need to access it
|
||||
```
|
||||
}
|
||||
|
||||
```
|
3166
doc/android_translation_architecture.svg
Normal file
3166
doc/android_translation_architecture.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 887 KiB |
Loading…
Add table
Add a link
Reference in a new issue