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
implement Google Cloud Messaging using DBus activatable GActions
This needs https://gitlab.com/android_translation_layer/gcm_service running in the background. For D-Bus activation, a D-Bus service file needs to be manually installed under ~/.local/share/dbus-1/services. Tested with FCM-Toolbox app.
This commit is contained in:
@@ -175,3 +175,18 @@ JNIEXPORT void JNICALL Java_android_content_Context_nativeRegisterUnifiedPush(JN
|
||||
(*env)->ReleaseStringUTFChars(env, token_jstr, token);
|
||||
(*env)->ReleaseStringUTFChars(env, application_jstr, application);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_android_content_Context_nativeStartExternalService(JNIEnv *env, jclass this, jstring package_jstr, jobject intent)
|
||||
{
|
||||
GVariant *variant = intent_serialize(env, intent);
|
||||
const char *package = (*env)->GetStringUTFChars(env, package_jstr, NULL);
|
||||
char *object_path = g_strdup_printf("/%s", package);
|
||||
g_strdelimit(object_path, ".", '/');
|
||||
GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
|
||||
GActionGroup *action_group = G_ACTION_GROUP(g_dbus_action_group_get(connection, package, object_path));
|
||||
g_action_group_activate_action(action_group, "startService", variant);
|
||||
g_object_unref(action_group);
|
||||
g_object_unref(connection);
|
||||
g_free(object_path);
|
||||
(*env)->ReleaseStringUTFChars(env, package_jstr, package);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,14 @@ JNIEXPORT void JNICALL Java_android_content_Context_nativeExportUnifiedPush
|
||||
JNIEXPORT void JNICALL Java_android_content_Context_nativeRegisterUnifiedPush
|
||||
(JNIEnv *, jclass, jstring, jstring);
|
||||
|
||||
/*
|
||||
* Class: android_content_Context
|
||||
* Method: nativeStartExternalService
|
||||
* Signature: (Ljava/lang/String;Landroid/content/Intent;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_android_content_Context_nativeStartExternalService
|
||||
(JNIEnv *, jclass, jstring, jobject);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -122,6 +122,8 @@ void set_up_handle_cache(JNIEnv *env)
|
||||
handle_cache.intent.constructor = _METHOD(handle_cache.intent.class, "<init>", "()V");
|
||||
handle_cache.intent.putExtraCharSequence = _METHOD(handle_cache.intent.class, "putExtra", "(Ljava/lang/String;Ljava/lang/CharSequence;)Landroid/content/Intent;");
|
||||
handle_cache.intent.putExtraByteArray = _METHOD(handle_cache.intent.class, "putExtra", "(Ljava/lang/String;[B)Landroid/content/Intent;");
|
||||
handle_cache.intent.putExtraInt = _METHOD(handle_cache.intent.class, "putExtra", "(Ljava/lang/String;I)Landroid/content/Intent;");
|
||||
handle_cache.intent.putExtraLong = _METHOD(handle_cache.intent.class, "putExtra", "(Ljava/lang/String;J)Landroid/content/Intent;");
|
||||
handle_cache.intent.getDataString = _METHOD(handle_cache.intent.class, "getDataString", "()Ljava/lang/String;");
|
||||
handle_cache.intent.setClassName = _METHOD(handle_cache.intent.class, "setClassName", "(Landroid/content/Context;Ljava/lang/String;)Landroid/content/Intent;");
|
||||
|
||||
@@ -136,4 +138,11 @@ void set_up_handle_cache(JNIEnv *env)
|
||||
|
||||
handle_cache.uri.class = _REF((*env)->FindClass(env, "android/net/Uri"));
|
||||
handle_cache.uri.parse = _STATIC_METHOD(handle_cache.uri.class, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
|
||||
|
||||
handle_cache.bundle.class = _REF((*env)->FindClass(env, "android/os/Bundle"));
|
||||
handle_cache.bundle.get = _METHOD(handle_cache.bundle.class, "get", "(Ljava/lang/String;)Ljava/lang/Object;");
|
||||
handle_cache.bundle.keySet = _METHOD(handle_cache.bundle.class, "keySet", "()Ljava/util/Set;");
|
||||
|
||||
handle_cache.set.class = _REF((*env)->FindClass(env, "java/util/Set"));
|
||||
handle_cache.set.toArray = _METHOD(handle_cache.set.class, "toArray", "()[Ljava/lang/Object;");
|
||||
}
|
||||
|
||||
@@ -125,6 +125,8 @@ struct handle_cache {
|
||||
jmethodID constructor;
|
||||
jmethodID putExtraCharSequence;
|
||||
jmethodID putExtraByteArray;
|
||||
jmethodID putExtraInt;
|
||||
jmethodID putExtraLong;
|
||||
jmethodID getDataString;
|
||||
jmethodID setClassName;
|
||||
} intent;
|
||||
@@ -146,6 +148,15 @@ struct handle_cache {
|
||||
jclass class;
|
||||
jmethodID parse;
|
||||
} uri;
|
||||
struct {
|
||||
jclass class;
|
||||
jmethodID keySet;
|
||||
jmethodID get;
|
||||
} bundle;
|
||||
struct {
|
||||
jclass class;
|
||||
jmethodID toArray;
|
||||
} set;
|
||||
};
|
||||
|
||||
extern struct handle_cache handle_cache;
|
||||
|
||||
@@ -249,11 +249,37 @@ GVariant *intent_serialize(JNIEnv *env, jobject intent) {
|
||||
jobject component = _GET_OBJ_FIELD(intent, "component", "Landroid/content/ComponentName;");
|
||||
jstring className_jstr = component ? _GET_OBJ_FIELD(component, "mClass", "Ljava/lang/String;") : NULL;
|
||||
jstring data_jstr = (*env)->CallObjectMethod(env, intent, handle_cache.intent.getDataString);
|
||||
jstring sender_package_jstr = (*env)->CallObjectMethod(env, _GET_STATIC_OBJ_FIELD(handle_cache.context.class, "this_application", "Landroid/app/Application;"), handle_cache.context.get_package_name);
|
||||
|
||||
GVariantBuilder extras_builder;
|
||||
g_variant_builder_init(&extras_builder, G_VARIANT_TYPE_VARDICT);
|
||||
jobject extras = _GET_OBJ_FIELD(intent, "extras", "Landroid/os/Bundle;");
|
||||
jobject extras_key_set = (*env)->CallObjectMethod(env, extras, handle_cache.bundle.keySet);
|
||||
jobjectArray extras_keys = (*env)->CallObjectMethod(env, extras_key_set, handle_cache.set.toArray);
|
||||
jsize extras_keys_length = (*env)->GetArrayLength(env, extras_keys);
|
||||
for (jint i = 0; i < extras_keys_length; i++) {
|
||||
jstring key_jstr = (*env)->GetObjectArrayElement(env, extras_keys, i);
|
||||
jobject value_jobj = (*env)->CallObjectMethod(env, extras, handle_cache.bundle.get, key_jstr);
|
||||
if (!(*env)->IsSameObject(env, _CLASS(key_jstr), _CLASS(value_jobj))) {
|
||||
printf("skipping non-string extra: %s\n", (*env)->GetStringUTFChars(env, key_jstr, NULL));
|
||||
continue;
|
||||
}
|
||||
const char *key = (*env)->GetStringUTFChars(env, key_jstr, NULL);
|
||||
const char *value = (*env)->GetStringUTFChars(env, value_jobj, NULL);
|
||||
g_variant_builder_add(&extras_builder, "{sv}", key, g_variant_new_string(value));
|
||||
(*env)->ReleaseStringUTFChars(env, key_jstr, key);
|
||||
(*env)->ReleaseStringUTFChars(env, value_jobj, value);
|
||||
(*env)->DeleteLocalRef(env, key_jstr);
|
||||
(*env)->DeleteLocalRef(env, value_jobj);
|
||||
}
|
||||
|
||||
const char *action = action_jstr ? (*env)->GetStringUTFChars(env, action_jstr, NULL) : NULL;
|
||||
const char *className = className_jstr ? (*env)->GetStringUTFChars(env, className_jstr, NULL) : NULL;
|
||||
const char *data = data_jstr ? (*env)->GetStringUTFChars(env, data_jstr, NULL) : NULL;
|
||||
GVariant *variant = g_variant_new(INTENT_G_VARIANT_TYPE_STRING, action ?: "", className ?: "", data ?: "");
|
||||
const char *sender_package = sender_package_jstr ? (*env)->GetStringUTFChars(env, sender_package_jstr, NULL) : NULL;
|
||||
GVariant *variant = g_variant_new(INTENT_G_VARIANT_TYPE_STRING, action ?: "", className ?: "", data ?: "", &extras_builder, sender_package);
|
||||
if (sender_package_jstr)
|
||||
(*env)->ReleaseStringUTFChars(env, sender_package_jstr, sender_package);
|
||||
if (action_jstr)
|
||||
(*env)->ReleaseStringUTFChars(env, action_jstr, action);
|
||||
if (className_jstr)
|
||||
@@ -267,7 +293,8 @@ jobject intent_deserialize(JNIEnv *env, GVariant *variant) {
|
||||
const char *action;
|
||||
const char *className;
|
||||
const char *data;
|
||||
g_variant_get(variant, INTENT_G_VARIANT_TYPE_STRING, &action, &className, &data);
|
||||
GVariantIter *extras;
|
||||
g_variant_get(variant, INTENT_G_VARIANT_TYPE_STRING, &action, &className, &data, &extras, NULL);
|
||||
if (action && action[0] == '\0')
|
||||
action = NULL;
|
||||
if (className && className[0] == '\0')
|
||||
@@ -281,6 +308,24 @@ jobject intent_deserialize(JNIEnv *env, GVariant *variant) {
|
||||
(*env)->CallObjectMethod(env, intent, handle_cache.intent.setClassName, _GET_STATIC_OBJ_FIELD(handle_cache.context.class, "this_application", "Landroid/app/Application;"), _JSTRING(className));
|
||||
if (data)
|
||||
_SET_OBJ_FIELD(intent, "data", "Landroid/net/Uri;", (*env)->CallStaticObjectMethod(env, handle_cache.uri.class, handle_cache.uri.parse, _JSTRING(data)));
|
||||
const char *key;
|
||||
GVariant *value;
|
||||
while (g_variant_iter_loop(extras, "{sv}", &key, &value)) {
|
||||
if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) {
|
||||
(*env)->CallObjectMethod(env, intent, handle_cache.intent.putExtraCharSequence, _JSTRING(key), _JSTRING(g_variant_get_string(value, NULL)));
|
||||
} else if (g_variant_is_of_type(value, G_VARIANT_TYPE_INT32)) {
|
||||
(*env)->CallObjectMethod(env, intent, handle_cache.intent.putExtraInt, _JSTRING(key), g_variant_get_int32(value));
|
||||
} else if (g_variant_is_of_type(value, G_VARIANT_TYPE_INT64)) {
|
||||
(*env)->CallObjectMethod(env, intent, handle_cache.intent.putExtraLong, _JSTRING(key), g_variant_get_int64(value));
|
||||
} else if (g_variant_is_of_type(value, G_VARIANT_TYPE_BYTESTRING)) {
|
||||
gsize size;
|
||||
const int8_t *message = g_variant_get_fixed_array(value, &size, 1);
|
||||
jbyteArray bytesMessage = (*env)->NewByteArray(env, size);
|
||||
(*env)->SetByteArrayRegion(env, bytesMessage, 0, size, message);
|
||||
(*env)->CallObjectMethod(env, intent, handle_cache.intent.putExtraByteArray, _JSTRING(key), bytesMessage);
|
||||
}
|
||||
}
|
||||
g_variant_iter_free(extras);
|
||||
return intent;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ void atl_safe_gtk_widget_set_visible(GtkWidget *widget, gboolean visible);
|
||||
void atl_safe_gtk_widget_queue_allocate(GtkWidget *widget);
|
||||
void atl_safe_gtk_widget_queue_resize(GtkWidget *widget);
|
||||
|
||||
#define INTENT_G_VARIANT_TYPE_STRING "(sss)" // (action, className, data)
|
||||
#define INTENT_G_VARIANT_TYPE_STRING "(sssa{sv}s)" // (action, className, data, extras, sender_package)
|
||||
GVariant *intent_serialize(JNIEnv *env, jobject intent);
|
||||
jobject intent_deserialize(JNIEnv *env, GVariant *variant);
|
||||
const char *intent_actionname_from_type(int type);
|
||||
|
||||
@@ -60,6 +60,11 @@ public abstract class Service extends ContextWrapper {
|
||||
System.out.println("Service.stopSelf() called");
|
||||
}
|
||||
|
||||
public boolean stopSelfResult(int startId) {
|
||||
System.out.println("Service.stopSelfResult(" + startId + ") called");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void attachBaseContext(Context newBase) {
|
||||
System.out.println("Service.attachBaseContext(" + newBase + ") called");
|
||||
}
|
||||
|
||||
@@ -2,5 +2,20 @@ package android.content;
|
||||
|
||||
public abstract class BroadcastReceiver {
|
||||
|
||||
public abstract void onReceive(Context context, Intent intent);
|
||||
public static class PendingResult {
|
||||
|
||||
public void setResultCode(int resultCode) {}
|
||||
|
||||
public void finish() {}
|
||||
}
|
||||
|
||||
public abstract void onReceive(Context context, Intent intent);
|
||||
|
||||
public boolean isOrderedBroadcast() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public PendingResult goAsync() {
|
||||
return new PendingResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,11 @@ import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserManager;
|
||||
import android.os.Vibrator;
|
||||
import android.telephony.TelephonyManager;
|
||||
@@ -151,6 +154,7 @@ public class Context extends Object {
|
||||
private static native void nativeOpenFile(int fd);
|
||||
private static native void nativeExportUnifiedPush(String packageName);
|
||||
private static native void nativeRegisterUnifiedPush(String token, String application);
|
||||
private static native void nativeStartExternalService(String packageName, Intent service);
|
||||
|
||||
static Application createApplication(long native_window) throws Exception {
|
||||
Application application;
|
||||
@@ -467,15 +471,47 @@ public class Context extends Object {
|
||||
public ComponentName startService(Intent intent) {
|
||||
ComponentName component = intent.getComponent();
|
||||
if (component == null) {
|
||||
Slog.w(TAG, "startService: component is null for intent: " + intent);
|
||||
int priority = Integer.MIN_VALUE;
|
||||
for (PackageParser.Service service: pkg.services) {
|
||||
for (PackageParser.IntentInfo intentInfo: service.intents) {
|
||||
if (intentInfo.matchAction(intent.getAction()) && intentInfo.priority > priority) {
|
||||
component = new ComponentName(pkg.packageName, service.className);
|
||||
priority = intentInfo.priority;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intent.getAction() != null && intent.getAction().startsWith("com.google.android.c2dm")) {
|
||||
nativeStartExternalService("com.google.android.c2dm", intent);
|
||||
// Newer applications use a Messenger instead of a BroadcastReceiver for the return Intent.
|
||||
// To support new and old apps with a common interface, we wrap the Messenger in a BroadcastReceiver
|
||||
final Messenger messenger = (Messenger)intent.getParcelableExtra("google.messenger");
|
||||
if (messenger != null) {
|
||||
receiverMap.put(new IntentFilter("com.google.android.c2dm.intent.REGISTRATION"), new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent resultIntent) {
|
||||
try {
|
||||
messenger.send(Message.obtain(null, 0, resultIntent));
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (component == null) {
|
||||
Slog.w(TAG, "startService: no matching service found for intent: " + intent);
|
||||
return null;
|
||||
}
|
||||
final String className = component.getClassName();
|
||||
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Class<? extends Service> cls = Class.forName(component.getClassName()).asSubclass(Service.class);
|
||||
Class<? extends Service> cls = Class.forName(className).asSubclass(Service.class);
|
||||
if (!runningServices.containsKey(cls)) {
|
||||
Service service = cls.getConstructor().newInstance();
|
||||
service.attachBaseContext(new Context());
|
||||
|
||||
@@ -2220,6 +2220,8 @@ public class PackageParser {
|
||||
|
||||
outInfo.logo = sa.getResourceId(
|
||||
com.android.internal.R.styleable.AndroidManifestIntentFilter_logo, 0);
|
||||
outInfo.priority = sa.getInt(
|
||||
com.android.internal.R.styleable.AndroidManifestIntentFilter_priority, 0);
|
||||
sa.recycle();
|
||||
int outerDepth = parser.getDepth();
|
||||
int type;
|
||||
@@ -3881,6 +3883,7 @@ public class PackageParser {
|
||||
public int icon;
|
||||
public int logo;
|
||||
public int preferred;
|
||||
public int priority;
|
||||
}
|
||||
|
||||
public final static class ActivityIntentInfo extends IntentInfo {
|
||||
|
||||
Reference in New Issue
Block a user