2022-05-06 14:54:42 +02:00
|
|
|
#### 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:
|
2022-10-12 17:23:19 +02:00
|
|
|
`src/api-impl/android/webkit/WebView.java:`
|
2022-05-06 14:54:42 +02:00
|
|
|
```Java
|
|
|
|
package android.webkit;
|
|
|
|
|
|
|
|
public class WebView {
|
|
|
|
}
|
|
|
|
```
|
2024-05-15 12:28:42 +02:00
|
|
|
|
2022-05-06 14:54:42 +02:00
|
|
|
This should fix the "no such class" error, and let you get further in your attempt to simply launch the app.
|
2024-05-15 12:28:42 +02:00
|
|
|
|
|
|
|
__NOTE__: you may also get a "no such class" error for a class inside the app, caused by that class being
|
|
|
|
a descendant of some android class that we're missing. Decompiling the app with e.g `jadx` should help
|
|
|
|
you figure out what class you need to stub out.
|
|
|
|
|
2022-05-06 14:54:42 +02:00
|
|
|
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.
|
|
|
|
|
2024-05-15 12:28:42 +02:00
|
|
|
Since the class was needed, it's pretty likely that up next you will get a "no such method" error.
|
|
|
|
(`java.lang.NoSuchMethodError: No [static] method [method_name]([parameters])[return type] in class L[class]`)
|
|
|
|
The easiest case is a void method:
|
2022-05-06 14:54:42 +02:00
|
|
|
```Java
|
|
|
|
package android.webkit;
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
|
|
|
public class WebView {
|
|
|
|
public void doSomething(Context context) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
2024-05-15 12:28:42 +02:00
|
|
|
Here, all that you need to take care of is that at `src/api-impl/android/content/Context.java`, you have at minimum
|
2022-05-06 14:54:42 +02:00
|
|
|
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.
|
2024-05-15 12:28:42 +02:00
|
|
|
For example, if a method is called DoWeHaveInternetConnection, it's pretty likely that upon decompiling
|
|
|
|
the app (e.g with `jadx`), you will find that returning `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).
|
2022-05-06 14:54:42 +02:00
|
|
|
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.
|
|
|
|
|
2024-05-15 12:28:42 +02:00
|
|
|
__NOTE__: exceptions like `java.lang.NoSuchMethodException` may sometimes not provide information about what class
|
|
|
|
the method is supposed to be in (not even with the "or it's superclasses" disclaimer). If that happens, you will
|
|
|
|
again need to decompile the app's code and look around the place mentioned in the stack trace for calls to a method
|
|
|
|
of the name mentioned.
|
|
|
|
|
|
|
|
__NOTE__: in some cases, such as with enums and interfaces, you should be able to simply copy the APACHE-licensed
|
2022-05-06 14:54:42 +02:00
|
|
|
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.
|
|
|
|
|
2024-05-15 12:28:42 +02:00
|
|
|
Random Layout widgets can also mostly be copied from AOSP, with minor changes and maybe some commenting out
|
|
|
|
of things that make the code not compile (if they turn out to have been important, can always fix them properly later)
|
|
|
|
|
|
|
|
__IMPORTANT__: run `clang-format --style="{BasedOnStyle: LLVM, IndentWidth: 8, UseTab: Always, AllowShortIfStatementsOnASingleLine: false, IndentCaseLabels: true, ColumnLimit: 0}"`
|
|
|
|
on any AOSP file that you are importing; manual code style changes are not required, but this simple automatic step is.
|
|
|
|
Make sure to run this before doing any changes to the code, since e.g commented out sections do not get reformatted
|
|
|
|
by `clang-format`.
|
|
|
|
|
|
|
|
If you added any classes, make sure to add them in `src/api-impl/meson.build` (sorted alphabetically).
|
|
|
|
|
2022-05-06 14:54:42 +02:00
|
|
|
##### intriguing case: widgets
|
|
|
|
|
|
|
|
There are two basic types of widgets (Views): containers (Layouts) and the rest.
|
|
|
|
|
2024-05-15 12:28:42 +02:00
|
|
|
Initially all container widgets were backed by Gtk container widgets. As this caused lots of behaviour diffeneces with AOSP,
|
2024-02-04 16:15:26 +01:00
|
|
|
we have instead implemented the API of ViewGroup, which is the super class of all container widgets. This allows to more or
|
|
|
|
less completely reuse specialized container widget implementation from AOSP source code.
|
2022-05-06 14:54:42 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2022-10-12 17:23:19 +02:00
|
|
|
`src/api-impl/android/widget/ImageView.java`
|
2022-05-06 14:54:42 +02:00
|
|
|
```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
|
2024-05-15 12:28:42 +02:00
|
|
|
```Java
|
2022-05-06 14:54:42 +02:00
|
|
|
public class ImageView extends View {
|
2024-05-15 12:28:42 +02:00
|
|
|
public ImageView(Context context, AttributeSet attrs) {
|
|
|
|
this(context, attrs, 0);
|
2022-05-06 14:54:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public ImageView(Context context) {
|
2024-05-15 12:28:42 +02:00
|
|
|
this(context, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public ImageView(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
|
|
super(context, attrs, defStyleAttr);
|
2022-05-06 14:54:42 +02:00
|
|
|
|
2024-05-15 12:28:42 +02:00
|
|
|
haveCustomMeasure = false;
|
|
|
|
```
|
|
|
|
↑ TODO: explain this
|
|
|
|
```Java
|
|
|
|
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ImageView, defStyleAttr, 0);
|
|
|
|
setImageDrawable(a.getDrawable(com.android.internal.R.styleable.ImageView_src));
|
|
|
|
setScaleType(scaletype_from_int[a.getInt(com.android.internal.R.styleable.ImageView_scaleType, 3 /*CENTER*/)]);
|
|
|
|
a.recycle();
|
|
|
|
```
|
|
|
|
↑ you will probably want to add this stuff at a later point, here we apply properties from layout xml files
|
|
|
|
```Java
|
2022-05-06 14:54:42 +02:00
|
|
|
}
|
|
|
|
|
2024-05-15 12:28:42 +02:00
|
|
|
@Override
|
|
|
|
protected native long native_constructor(Context context, AttributeSet attrs);
|
2022-05-06 14:54:42 +02:00
|
|
|
```
|
|
|
|
↑ 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 { ... }
|
|
|
|
}
|
|
|
|
```
|
2024-05-15 12:28:42 +02:00
|
|
|
↑ you might need some stubs, don't fall into the trap of thinking that you need to immediately implement everything
|
2022-05-06 14:54:42 +02:00
|
|
|
|
|
|
|
---
|
|
|
|
|
2022-10-12 17:23:19 +02:00
|
|
|
`src/api-impl-jni/widgets/android_widget_ImageView.c`
|
2022-05-06 14:54:42 +02:00
|
|
|
```C
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
|
|
|
|
#include "../defines.h"
|
|
|
|
#include "../util.h"
|
|
|
|
|
|
|
|
#include "WrapperWidget.h"
|
|
|
|
|
|
|
|
```
|
2022-10-12 17:23:19 +02:00
|
|
|
↑ every widget will be under `src/api-impl-jni/widgets/` and will have these includes
|
2022-05-06 14:54:42 +02:00
|
|
|
```C
|
2022-10-12 17:23:19 +02:00
|
|
|
#include "../generated_headers/android_widget_ImageView.h"
|
2022-05-06 14:54:42 +02:00
|
|
|
```
|
2022-10-12 17:23:19 +02:00
|
|
|
↑ this is the jni-generated header file (btw, t's name is what dicates the name of this .c file)
|
2024-05-15 12:28:42 +02:00
|
|
|
↓ native constructor
|
2022-05-06 14:54:42 +02:00
|
|
|
```C
|
2024-05-15 12:28:42 +02:00
|
|
|
JNIEXPORT jlong JNICALL Java_android_widget_ImageView_native_1constructor(JNIEnv *env, jobject this, jobject context, jobject attrs)
|
2022-05-06 14:54:42 +02:00
|
|
|
{
|
2024-05-15 12:28:42 +02:00
|
|
|
GtkWidget *wrapper = g_object_ref(wrapper_widget_new());
|
2022-05-06 14:54:42 +02:00
|
|
|
```
|
|
|
|
↑ the wrapper widget is required, it's expected by generic functions operating on widgets; the purpose is to allow for things like background image
|
2022-10-12 17:23:19 +02:00
|
|
|
handling for cases where we can't subclass the backing widget itself
|
2022-05-06 14:54:42 +02:00
|
|
|
```C
|
2024-05-15 12:28:42 +02:00
|
|
|
GtkWidget *image = gtk_picture_new();
|
2022-05-06 14:54:42 +02:00
|
|
|
```
|
|
|
|
↑ here we create the actual backing Gtk widget.
|
|
|
|
```C
|
|
|
|
wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), image);
|
2024-05-15 12:28:42 +02:00
|
|
|
wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this);
|
2022-05-06 14:54:42 +02:00
|
|
|
```
|
|
|
|
↑ put the widget in the wrapper
|
|
|
|
```C
|
2024-05-15 12:28:42 +02:00
|
|
|
return _INTPTR(image);
|
2022-05-06 14:54:42 +02:00
|
|
|
```
|
2024-05-15 12:28:42 +02:00
|
|
|
↑ the Java constructor will set the `widget` member of the View-derived class to the pointer to our widget that we return here;
|
|
|
|
this will then be passed to other native functions that will operate on the backing widget.
|
|
|
|
```C
|
2022-05-06 14:54:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
```
|