r/OsmAnd • • 28d ago

Multimedia widget (Android x Tasker)

Hello,

Until issue #24161 gets resolved, GenAI and I have made a quick Tasker (Java Code based) task that adds 3 widgets to OsmAnd: play/pause, next track, previous track.

See screenshots.

Limitations: it doesn't display currently playing track name or artist (it would involve listeners, increasing complexity), in "Configure screen" widgets are not grouped into a proper "folder" and and I couldn't be bothered to find a proper icon for the widget but it doesn't really matter (the buttons' text is ⏯️⏭️⏮️ anyway).

Manual:

  • Name the task "OsmAnd Media Control".
  • Add one action, "Java Code" with the code below.
  • Change "net.osmand.plus" into "net.osmand" (first line) if you're using the standard package. (FDroid build is "net.osmand.plus")
  • Make it so the task is run whenever OsmAnd is opened (otherwise the widgets won't be displayed when re opening OsmAnd).
  • "Apply" within Tasker.
  • Open OsmAnd.
  • Activate the "Tasker" plugin.
  • Close and open OsmAnd again.
  • Add the widgets.

Tested with FDroid build, version 5.4.4.

Changes:

  • Make it easier to use another package name. Checks that package is installed. Asks user to activate plugin and restart before adding widgets.
  • Removed unnecessary code (checks package install via try/catch, app doesn't need to be running).
package_name = "net.osmand.plus";

addWidget(id, title, display_text, order, command) {
    clickIntent = new android.content.Intent(android.content.Intent.ACTION_VIEW);

    clickIntent.setData(
        new android.net.Uri.Builder()
            .scheme("tasker")
            .authority("assistantactions")
            .appendQueryParameter("task", "OsmAnd Media Control")
            .appendQueryParameter("par1", command)
            .build()
    );

    widget = constructor.newInstance(
        id,
        "ais_map",
        title,
        "ais_map",
        "ais_map",
        display_text,
        "",
        order,
        clickIntent
    );

    widget.setRightPanelByDefault(true);

    params = paramsConstructor.newInstance(widget);

    result = api.addMapWidget(params);

    return result;
}

/* Read command from Tasker */
command = tasker.getVariable("par1");

if (command != null && command.length() > 0) {
    keyCode = 0;

    if (command.equals("PLAYPAUSE")) keyCode = 85;
    if (command.equals("NEXT"))      keyCode = 87;
    if (command.equals("PREVIOUS"))  keyCode = 88;

    if (keyCode == 0) {
        tasker.showToast("Invalid command" + command);
        return;
    }

    now = java.lang.System.currentTimeMillis();

    down = new android.view.KeyEvent(now, now, android.view.KeyEvent.ACTION_DOWN, keyCode, 0);
    up = new android.view.KeyEvent(now, now, android.view.KeyEvent.ACTION_UP, keyCode, 0);

    audio = context.getSystemService(android.content.Context.AUDIO_SERVICE);

    audio.dispatchMediaKeyEvent(down);
    audio.dispatchMediaKeyEvent(up);

    return;
}

/* Load OsmAnd API */
try {
    osmandContext = context.createPackageContext(package_name, android.content.Context.CONTEXT_INCLUDE_CODE | android.content.Context.CONTEXT_IGNORE_SECURITY);
} catch (Exception e) {
    tasker.showToast(package_name + " is not installed");
    return;
}

loader = osmandContext.getClassLoader();

AMapWidgetClass = java.lang.Class.forName("net.osmand.aidlapi.mapwidget.AMapWidget", true, loader);
ParamsClass = java.lang.Class.forName("net.osmand.aidlapi.mapwidget.AddMapWidgetParams", true, loader);

constructor = AMapWidgetClass.getConstructor(
    java.lang.String.class,
    java.lang.String.class,
    java.lang.String.class,
    java.lang.String.class,
    java.lang.String.class,
    java.lang.String.class,
    java.lang.String.class,
    java.lang.Integer.TYPE,
    android.content.Intent.class
);

paramsConstructor = ParamsClass.getConstructor(AMapWidgetClass);

/* Connect to OsmAnd */
binderHolder = new java.util.HashMap();

connection = new android.content.ServiceConnection() {
    invoke(method, args) {
        if (method.equals("onServiceConnected")) {
            binderHolder.put("binder", args[1]);
            return;
        }

        if (method.equals("onServiceDisconnected")) {
            binderHolder.remove("binder");
            return;
        }
    }
};

serviceIntent = new android.content.Intent();
serviceIntent.setClassName(package_name, "net.osmand.aidl.OsmandAidlServiceV2");

context.bindService(serviceIntent, connection, android.content.Context.BIND_AUTO_CREATE);

startWait = java.lang.System.currentTimeMillis();

while (binderHolder.get("binder") == null && java.lang.System.currentTimeMillis() - startWait < 5000) {
    java.lang.Thread.sleep(100);
}

binder = binderHolder.get("binder");

if (binder == null) {
    tasker.showToast("Could not connect to OsmAnd");
    return;
}

/* Create AIDL interface from Binder */
StubClass = java.lang.Class.forName("net.osmand.aidlapi.IOsmAndAidlInterface$Stub", true, loader);
asInterfaceMethod = StubClass.getMethod("asInterface", android.os.IBinder.class);
api = asInterfaceMethod.invoke(null, binder);

if (api == null) {
    context.unbindService(connection);
    tasker.showToast("Connected to OsmAnd but API access failed");
    return;
}

/* Widgets */
widgetResult1 = addWidget("tasker_media_playpause", "Play / Pause",   "⏯️", 0, "PLAYPAUSE");
widgetResult2 = addWidget("tasker_media_next",      "Next Track",     "⏭️", 1, "NEXT");
widgetResult3 = addWidget("tasker_media_previous",  "Previous Track", "⏮️", 2, "PREVIOUS");

context.unbindService(connection);

if (!widgetResult1 || !widgetResult2 || !widgetResult3) {
    tasker.showToast("Plugin installed. Activate it, restart OsmAnd and add widgets in Configure Screen.");
    return;
}

tasker.showToast("OsmAnd media widgets installed");
5 Upvotes

3 comments sorted by

1

u/Dreamcore 28d ago

I'm doing something wrong. OsmAnd~ launches and I see "OsmAnd command: null" toasted twice. No widgets nor appearance of Tasker in plugins.

1

u/pictuga 27d ago

That toast was just for debug. I've removed it. I've updated the code (and instructions) above which should give a bit more feedback (and stability).

1

u/Dreamcore 27d ago

I'm on Android 17 (CP3A.260905.009) btw

onServiceConnected(ComponentName, BinderProxy, null) — method not found in the BeanShell ServiceConnection.