diff --git a/src/api-impl/android/app/Activity.java b/src/api-impl/android/app/Activity.java index 7c1a8002..cacceb39 100644 --- a/src/api-impl/android/app/Activity.java +++ b/src/api-impl/android/app/Activity.java @@ -50,11 +50,13 @@ public class Activity extends ContextThemeWrapper implements Window.Callback { * @return instance of main activity class * @throws Exception */ - private static Activity createMainActivity(String className, long native_window) throws Exception { + private static Activity createMainActivity(String className, long native_window, String uriString) throws Exception { + Uri uri = uriString != null ? Uri.parse(uriString) : null; if (className == null) { for (PackageParser.Activity activity: pkg.activities) { for (PackageParser.IntentInfo intent: activity.intents) { - if (intent.hasCategory("android.intent.category.LAUNCHER")) { + if ((uri == null && intent.hasCategory("android.intent.category.LAUNCHER")) || + (uri != null && intent.hasDataScheme(uri.getScheme()))) { className = activity.className; break; } @@ -65,10 +67,16 @@ public class Activity extends ContextThemeWrapper implements Window.Callback { } else { className = className.replace('/', '.'); } + if (className == null) { + System.err.println("Failed to find Activity to launch URI: " + uri); + System.exit(1); + } Class cls = Class.forName(className).asSubclass(Activity.class); Constructor constructor = cls.getConstructor(); Activity activity = constructor.newInstance(); activity.window.native_window = native_window; + if (uri != null) + activity.setIntent(new Intent("android.intent.action.VIEW", uri)); return activity; } @@ -467,7 +475,9 @@ public class Activity extends ContextThemeWrapper implements Window.Callback { finish(); } - public void setIntent(Intent newIntent) {} + public void setIntent(Intent newIntent) { + this.intent = newIntent; + } public void unregisterReceiver(BroadcastReceiver receiver) {} diff --git a/src/api-impl/android/app/Application.java b/src/api-impl/android/app/Application.java index 4ed7cad8..ebb9ea65 100644 --- a/src/api-impl/android/app/Application.java +++ b/src/api-impl/android/app/Application.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.content.res.Configuration; import android.content.Context; import android.content.ContextWrapper; +import android.content.pm.PackageParser; public class Application extends ContextWrapper { public long native_window; @@ -16,6 +17,21 @@ public class Application extends ContextWrapper { return getString(pkg.applicationInfo.labelRes); } + String get_supported_mime_types() { + String mimeTypes = ""; + for (PackageParser.Activity activity: pkg.activities) { + for (PackageParser.IntentInfo intent: activity.intents) { + for (int i = 0; i < intent.countDataSchemes(); i++) { + String scheme = intent.getDataScheme(i); + // ignore http and https, as there is no way to only handle specific hosts in a .desktop file + if (!"http".equals(scheme) && !"https".equals(scheme)) + mimeTypes += "x-scheme-handler/" + intent.getDataScheme(i) + ";"; + } + } + } + return "".equals(mimeTypes) ? null : mimeTypes; + } + public interface ActivityLifecycleCallbacks { void onActivityCreated(Activity activity, Bundle savedInstanceState); void onActivityStarted(Activity activity); diff --git a/src/api-impl/android/content/IntentFilter.java b/src/api-impl/android/content/IntentFilter.java index 118bc244..edc2704c 100644 --- a/src/api-impl/android/content/IntentFilter.java +++ b/src/api-impl/android/content/IntentFilter.java @@ -9,6 +9,7 @@ public class IntentFilter { private List actions = new ArrayList<>(); private Set categories = new HashSet<>(); + private List dataSchemes = new ArrayList<>(); public IntentFilter() {} public IntentFilter(String action) { @@ -42,4 +43,20 @@ public class IntentFilter { } public void setPriority(int priority) {} + + public void addDataScheme(String dataScheme) { + dataSchemes.add(dataScheme); + } + + public boolean hasDataScheme(String dataScheme) { + return dataSchemes.contains(dataScheme); + } + + public int countDataSchemes() { + return dataSchemes.size(); + } + + public String getDataScheme(int index) { + return dataSchemes.get(index); + } } diff --git a/src/api-impl/android/content/pm/PackageParser.java b/src/api-impl/android/content/pm/PackageParser.java index 4cb6c3f5..83fedadf 100644 --- a/src/api-impl/android/content/pm/PackageParser.java +++ b/src/api-impl/android/content/pm/PackageParser.java @@ -28,6 +28,7 @@ import android.os.Build; import android.os.Bundle; import android.os.PatternMatcher; import android.os.UserHandle; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Base64; import android.util.DisplayMetrics; @@ -2247,6 +2248,10 @@ public class PackageParser { XmlUtils.skipCurrentTag(parser); outInfo.addCategory(value); } else if (nodeName.equals("data")) { + String scheme = parser.getAttributeValue( + ANDROID_RESOURCES, "scheme"); + if (!TextUtils.isEmpty(scheme)) + outInfo.addDataScheme(scheme); XmlUtils.skipCurrentTag(parser); } else if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under : " diff --git a/src/main-executable/main.c b/src/main-executable/main.c index 35c2b62c..6db63ead 100644 --- a/src/main-executable/main.c +++ b/src/main-executable/main.c @@ -198,6 +198,8 @@ struct jni_callback_data { char **extra_string_keys; }; +static char *uri_option = NULL; + static void open(GtkApplication *app, GFile **files, gint nfiles, const gchar *hint, struct jni_callback_data *d) { // TODO: pass all files to classpath @@ -428,10 +430,12 @@ static void open(GtkApplication *app, GFile **files, gint nfiles, const gchar *h // construct main Activity activity_object = (*env)->CallStaticObjectMethod(env, handle_cache.activity.class, - _STATIC_METHOD(handle_cache.activity.class, "createMainActivity", "(Ljava/lang/String;J)Landroid/app/Activity;"), - _JSTRING(d->apk_main_activity_class), _INTPTR(window)); + _STATIC_METHOD(handle_cache.activity.class, "createMainActivity", "(Ljava/lang/String;JLjava/lang/String;)Landroid/app/Activity;"), + _JSTRING(d->apk_main_activity_class), _INTPTR(window), uri_option ? _JSTRING(uri_option) : NULL); if ((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); + if (uri_option) + g_free(uri_option); if (d->extra_string_keys) { GError *error = NULL; @@ -493,6 +497,12 @@ static void open(GtkApplication *app, GFile **files, gint nfiles, const gchar *h g_file_make_directory(g_file_get_parent(dest), NULL, NULL); g_file_copy(files[0], dest, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, NULL); + jmethodID get_supported_mime_types = _METHOD(handle_cache.application.class, "get_supported_mime_types", "()Ljava/lang/String;"); + jstring supported_mime_types_jstr = (*env)->CallObjectMethod(env, application_object, get_supported_mime_types); + const char *supported_mime_types = supported_mime_types_jstr ? _CSTRING(supported_mime_types_jstr) : NULL; + if ((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + GString *desktop_entry = g_string_new("[Desktop Entry]\n" "Type=Application\n" "Exec=env "); @@ -514,7 +524,9 @@ static void open(GtkApplication *app, GFile **files, gint nfiles, const gchar *h g_string_append_printf(desktop_entry, "-w %d ", d->window_width); if (d->window_height) g_string_append_printf(desktop_entry, "-h %d ", d->window_height); - g_string_append_printf(desktop_entry, "%s\n", g_file_get_path(dest)); + g_string_append_printf(desktop_entry, "%s --uri %%u\n", g_file_get_path(dest)); + if (supported_mime_types) + g_string_append_printf(desktop_entry, "MimeType=%s\n", supported_mime_types); struct dynamic_launcher_callback_data *cb_data = g_new(struct dynamic_launcher_callback_data, 1); cb_data->desktop_file_id = g_strdup_printf("%s.desktop", package_name); cb_data->desktop_entry = g_string_free(desktop_entry, FALSE); @@ -573,6 +585,13 @@ static void activate(GtkApplication *app, struct jni_callback_data *d) exit(1); } +static gboolean option_uri_cb(const gchar* option_name, const gchar* value, gpointer data, GError** error) +{ + printf("option_uri_cb: %s %s, %p, %p\n", option_name, value, data, error); + uri_option = g_strdup(value); + return TRUE; +} + void init_cmd_parameters(GApplication *app, struct jni_callback_data *d) { const GOptionEntry cmd_params[] = { @@ -583,6 +602,7 @@ void init_cmd_parameters(GApplication *app, struct jni_callback_data *d) { "install", 'i', 0, G_OPTION_ARG_NONE, &d->install, "install .desktop file for the given apk", NULL }, { "extra-jvm-option", 'X', 0, G_OPTION_ARG_STRING_ARRAY, &d->extra_jvm_options, "pass an additional option directly to art (e.g -X \"-verbose:jni\")", "\"OPTION\"" }, { "extra-string-key", 'e', 0, G_OPTION_ARG_STRING_ARRAY, &d->extra_string_keys, "pass a string extra (-e key=value)", "\"KEY=VALUE\"" }, + { "uri", 'u', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, option_uri_cb, "open the given URI inside the application", "URI" }, {NULL} };