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
implementing MediaSession using MPRIS
NotificationManager will now ignore MediaStyle notifications
This commit is contained in:
@@ -47,6 +47,10 @@ linux_dmabuf = wl_mod.scan_xml(xml)
|
|||||||
xml = wl_mod.find_protocol('viewporter')
|
xml = wl_mod.find_protocol('viewporter')
|
||||||
viewporter = wl_mod.scan_xml(xml)
|
viewporter = wl_mod.scan_xml(xml)
|
||||||
|
|
||||||
|
mpris = gnome.gdbus_codegen('mpris-dbus',
|
||||||
|
'src/api-impl-jni/media/org.mpris.MediaPlayer2.xml',
|
||||||
|
interface_prefix: 'org.mpris')
|
||||||
|
|
||||||
# libandroid
|
# libandroid
|
||||||
libandroid_so = shared_library('android', [
|
libandroid_so = shared_library('android', [
|
||||||
'src/libandroid/asset_manager.c',
|
'src/libandroid/asset_manager.c',
|
||||||
@@ -105,6 +109,7 @@ libtranslationlayer_so = shared_library('translation_layer_main', [
|
|||||||
'src/api-impl-jni/graphics/android_graphics_drawable_DrawableContainer.c',
|
'src/api-impl-jni/graphics/android_graphics_drawable_DrawableContainer.c',
|
||||||
'src/api-impl-jni/location/android_location_LocationManager.c',
|
'src/api-impl-jni/location/android_location_LocationManager.c',
|
||||||
'src/api-impl-jni/media/android_media_MediaCodec.c',
|
'src/api-impl-jni/media/android_media_MediaCodec.c',
|
||||||
|
'src/api-impl-jni/media/android_media_session_MediaSession.c',
|
||||||
'src/api-impl-jni/net/android_net_ConnectivityManager.c',
|
'src/api-impl-jni/net/android_net_ConnectivityManager.c',
|
||||||
'src/api-impl-jni/sensors/android_hardware_SensorManager.c',
|
'src/api-impl-jni/sensors/android_hardware_SensorManager.c',
|
||||||
'src/api-impl-jni/util.c',
|
'src/api-impl-jni/util.c',
|
||||||
@@ -131,6 +136,7 @@ libtranslationlayer_so = shared_library('translation_layer_main', [
|
|||||||
'src/sk_area/sk_area.c',
|
'src/sk_area/sk_area.c',
|
||||||
linux_dmabuf,
|
linux_dmabuf,
|
||||||
viewporter,
|
viewporter,
|
||||||
|
mpris,
|
||||||
] + marshal_files,
|
] + marshal_files,
|
||||||
include_directories: ['src/sk_area/'],
|
include_directories: ['src/sk_area/'],
|
||||||
install: true,
|
install: true,
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||||
|
#include <jni.h>
|
||||||
|
/* Header for class android_media_session_MediaSession */
|
||||||
|
|
||||||
|
#ifndef _Included_android_media_session_MediaSession
|
||||||
|
#define _Included_android_media_session_MediaSession
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
/*
|
||||||
|
* Class: android_media_session_MediaSession
|
||||||
|
* Method: nativeSetState
|
||||||
|
* Signature: (IJJJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
*/
|
||||||
|
JNIEXPORT void JNICALL Java_android_media_session_MediaSession_nativeSetState
|
||||||
|
(JNIEnv *, jobject, jint, jlong, jlong, jlong, jstring, jstring, jstring);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: android_media_session_MediaSession
|
||||||
|
* Method: nativeSetCallback
|
||||||
|
* Signature: (Landroid/media/session/MediaSession/Callback;)V
|
||||||
|
*/
|
||||||
|
JNIEXPORT void JNICALL Java_android_media_session_MediaSession_nativeSetCallback
|
||||||
|
(JNIEnv *, jobject, jobject);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
117
src/api-impl-jni/media/android_media_session_MediaSession.c
Normal file
117
src/api-impl-jni/media/android_media_session_MediaSession.c
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#include "../util.h"
|
||||||
|
#include "../defines.h"
|
||||||
|
|
||||||
|
#include "mpris-dbus.h"
|
||||||
|
|
||||||
|
#include "../generated_headers/android_media_session_MediaSession.h"
|
||||||
|
#include "../generated_headers/android_os_SystemClock.h"
|
||||||
|
|
||||||
|
#define MPRIS_BUS_NAME_PREFIX "org.mpris.MediaPlayer2."
|
||||||
|
#define MPRIS_OBJECT_NAME "/org/mpris/MediaPlayer2"
|
||||||
|
|
||||||
|
MediaPlayer2Player *mpris_player = NULL;
|
||||||
|
static jobject callback = NULL;
|
||||||
|
static jlong last_position = 0; // playback_position - SystemClock.elapsedRealtime in ms
|
||||||
|
|
||||||
|
static void on_bus_acquired(GDBusConnection *connection, const char *name, gpointer user_data)
|
||||||
|
{
|
||||||
|
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON (mpris_player),
|
||||||
|
connection, MPRIS_OBJECT_NAME, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean on_media_player_handle_action(MediaPlayer2Player *mpris_player, GDBusMethodInvocation *invocation, char *method)
|
||||||
|
{
|
||||||
|
if (callback) {
|
||||||
|
JNIEnv *env = get_jni_env();
|
||||||
|
(*env)->CallVoidMethod(env, callback, _METHOD(_CLASS(callback), method, "()V"));
|
||||||
|
}
|
||||||
|
g_dbus_method_invocation_return_value(invocation, g_variant_new("()"));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean on_media_player_handle_play_pause(MediaPlayer2Player *mpris_player, GDBusMethodInvocation *invocation, gpointer user_data)
|
||||||
|
{
|
||||||
|
gboolean is_playing = !strcmp("Playing", media_player2_player_get_playback_status(mpris_player));
|
||||||
|
return on_media_player_handle_action(mpris_player, invocation, is_playing ? "onPause" : "onPlay");
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean on_media_player_handle_seek(MediaPlayer2Player *mpris_player, GDBusMethodInvocation *invocation, gint64 offset_us, gpointer user_data)
|
||||||
|
{
|
||||||
|
if (callback) {
|
||||||
|
JNIEnv *env = get_jni_env();
|
||||||
|
last_position += offset_us / 1000;
|
||||||
|
(*env)->CallVoidMethod(env, callback, _METHOD(_CLASS(callback), "onSeekTo", "(J)V"), last_position + Java_android_os_SystemClock_elapsedRealtime(env, NULL));
|
||||||
|
}
|
||||||
|
media_player2_player_complete_seek(mpris_player, invocation);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean on_media_player_handle_set_position(MediaPlayer2Player *mpris_player, GDBusMethodInvocation *invocation, GVariant *trackid, gint64 pos_us, gpointer user_data)
|
||||||
|
{
|
||||||
|
if (callback) {
|
||||||
|
JNIEnv *env = get_jni_env();
|
||||||
|
(*env)->CallVoidMethod(env, callback, _METHOD(_CLASS(callback), "onSeekTo", "(J)V"), pos_us / 1000);
|
||||||
|
}
|
||||||
|
media_player2_player_complete_set_position(mpris_player, invocation);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ACTION_PAUSE (1 << 1)
|
||||||
|
#define ACTION_PLAY (1 << 2)
|
||||||
|
#define ACTION_SKIP_TO_PREVIOUS (1 << 4)
|
||||||
|
#define ACTION_SKIP_TO_NEXT (1 << 5)
|
||||||
|
#define ACTION_SEEK_TO (1 << 8)
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_android_media_session_MediaSession_nativeSetState(JNIEnv *env, jobject this, jint state,
|
||||||
|
jlong actions, jlong position, jlong update_time, jstring title_str, jstring artist_str, jstring art_url_str)
|
||||||
|
{
|
||||||
|
const char *playback_states[] = {"None", "Stopped", "Paused", "Playing"};
|
||||||
|
if (!mpris_player) {
|
||||||
|
mpris_player = media_player2_player_skeleton_new();
|
||||||
|
g_object_connect(mpris_player,
|
||||||
|
"signal::handle-play", on_media_player_handle_action, "onPlay",
|
||||||
|
"signal::handle-pause", on_media_player_handle_action, "onPause",
|
||||||
|
"signal::handle-next", on_media_player_handle_action, "onSkipToNext",
|
||||||
|
"signal::handle-previous", on_media_player_handle_action, "onSkipToPrevious",
|
||||||
|
"signal::handle-play-pause", on_media_player_handle_play_pause, NULL,
|
||||||
|
"signal::handle-seek", on_media_player_handle_seek, NULL,
|
||||||
|
"signal::handle-set-position", on_media_player_handle_set_position, NULL,
|
||||||
|
NULL);
|
||||||
|
g_bus_own_name(G_BUS_TYPE_SESSION, MPRIS_BUS_NAME_PREFIX "ATL", G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||||
|
on_bus_acquired, NULL, NULL, mpris_player, NULL);
|
||||||
|
}
|
||||||
|
media_player2_player_set_playback_status(mpris_player, playback_states[state < 4 ? state : 0]);
|
||||||
|
media_player2_player_set_position(mpris_player, position * 1000);
|
||||||
|
last_position = position - update_time;
|
||||||
|
media_player2_player_set_can_control(mpris_player, !!(actions));
|
||||||
|
media_player2_player_set_can_play(mpris_player, !!(actions & ACTION_PLAY));
|
||||||
|
media_player2_player_set_can_pause(mpris_player, !!(actions & ACTION_PAUSE));
|
||||||
|
media_player2_player_set_can_seek(mpris_player, !!(actions & ACTION_SEEK_TO));
|
||||||
|
media_player2_player_set_can_go_next(mpris_player, !!(actions & ACTION_SKIP_TO_NEXT));
|
||||||
|
media_player2_player_set_can_go_previous(mpris_player, !!(actions & ACTION_SKIP_TO_PREVIOUS));
|
||||||
|
|
||||||
|
GVariantDict dict;
|
||||||
|
g_variant_dict_init(&dict, NULL);
|
||||||
|
g_variant_dict_insert(&dict, "mpris:trackid", "s", MPRIS_OBJECT_NAME "/Track/0");
|
||||||
|
if (art_url_str) {
|
||||||
|
const char *art_url = (*env)->GetStringUTFChars(env, art_url_str, NULL);
|
||||||
|
g_variant_dict_insert(&dict, "mpris:artUrl", "s", art_url);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, art_url_str, art_url);
|
||||||
|
}
|
||||||
|
if (title_str) {
|
||||||
|
const char *title = (*env)->GetStringUTFChars(env, title_str, NULL);
|
||||||
|
g_variant_dict_insert(&dict, "xesam:title", "s", title);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, title_str, title);
|
||||||
|
}
|
||||||
|
if (artist_str) {
|
||||||
|
const char *artist = (*env)->GetStringUTFChars(env, artist_str, NULL);
|
||||||
|
g_variant_dict_insert(&dict, "xesam:artist", "s", artist);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, artist_str, artist);
|
||||||
|
}
|
||||||
|
media_player2_player_set_metadata(mpris_player, g_variant_dict_end(&dict));
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_android_media_session_MediaSession_nativeSetCallback(JNIEnv *env, jobject this, jobject new_callback)
|
||||||
|
{
|
||||||
|
callback = _REF(new_callback);
|
||||||
|
}
|
||||||
25
src/api-impl-jni/media/org.mpris.MediaPlayer2.xml
Normal file
25
src/api-impl-jni/media/org.mpris.MediaPlayer2.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<node>
|
||||||
|
<interface name="org.mpris.MediaPlayer2.Player">
|
||||||
|
<method name="Play"/>
|
||||||
|
<method name="Pause"/>
|
||||||
|
<method name="PlayPause"/>
|
||||||
|
<method name="Next"/>
|
||||||
|
<method name="Previous"/>
|
||||||
|
<method name="Seek">
|
||||||
|
<arg name="Offset" direction="in" type="x"/>
|
||||||
|
</method>
|
||||||
|
<method name="SetPosition">
|
||||||
|
<arg name="TrackId" direction="in" type="o"/>
|
||||||
|
<arg name="Position" direction="in" type="x"/>
|
||||||
|
</method>
|
||||||
|
<property name="CanControl" type="b" access="read"/>
|
||||||
|
<property name="CanPlay" type="b" access="read"/>
|
||||||
|
<property name="CanPause" type="b" access="read"/>
|
||||||
|
<property name="CanSeek" type="b" access="read"/>
|
||||||
|
<property name="CanGoNext" type="b" access="read"/>
|
||||||
|
<property name="CanGoPrevious" type="b" access="read"/>
|
||||||
|
<property name="Metadata" type="a{sv}" access="read"/>
|
||||||
|
<property name="PlaybackStatus" type="s" access="read"/>
|
||||||
|
<property name="Position" type="x" access="read"/>
|
||||||
|
</interface>
|
||||||
|
</node>
|
||||||
@@ -54,6 +54,7 @@ public class Notification implements Parcelable {
|
|||||||
PendingIntent intent;
|
PendingIntent intent;
|
||||||
String iconPath;
|
String iconPath;
|
||||||
boolean ongoing;
|
boolean ongoing;
|
||||||
|
Style style;
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Notification [" + title + ", " + text + ", " + actions + "]";
|
return "Notification [" + title + ", " + text + ", " + actions + "]";
|
||||||
@@ -151,6 +152,7 @@ public class Notification implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Builder setStyle(Style style) {
|
public Builder setStyle(Style style) {
|
||||||
|
notification.style = style;
|
||||||
if (style instanceof MediaStyle) {
|
if (style instanceof MediaStyle) {
|
||||||
notification.ongoing = true;
|
notification.ongoing = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package android.app;
|
package android.app;
|
||||||
|
|
||||||
|
import android.app.Notification.MediaStyle;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -10,6 +11,10 @@ public class NotificationManager {
|
|||||||
public void cancelAll() {}
|
public void cancelAll() {}
|
||||||
|
|
||||||
public void notify(String tag, int id, Notification notification) {
|
public void notify(String tag, int id, Notification notification) {
|
||||||
|
if (notification.style instanceof MediaStyle) {
|
||||||
|
return; // MPRIS is handled by MediaSession implementation
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println("notify(" + tag + ", " + id + ", " + notification + ") called");
|
System.out.println("notify(" + tag + ", " + id + ", " + notification + ") called");
|
||||||
long builder = nativeInitBuilder();
|
long builder = nativeInitBuilder();
|
||||||
for (Notification.Action action : notification.actions) {
|
for (Notification.Action action : notification.actions) {
|
||||||
|
|||||||
@@ -6,24 +6,39 @@ import android.os.Bundle;
|
|||||||
|
|
||||||
public class MediaDescription {
|
public class MediaDescription {
|
||||||
|
|
||||||
|
public Uri iconUri;
|
||||||
|
public CharSequence title;
|
||||||
|
public CharSequence subtitle;
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
|
MediaDescription description = new MediaDescription();
|
||||||
|
|
||||||
public Builder setMediaId(String mediaId) {return this;}
|
public Builder setMediaId(String mediaId) {return this;}
|
||||||
|
|
||||||
public Builder setTitle(CharSequence title) {return this;}
|
public Builder setTitle(CharSequence title) {
|
||||||
|
description.title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setSubtitle(CharSequence subtitle) {return this;}
|
public Builder setSubtitle(CharSequence subtitle) {
|
||||||
|
description.subtitle = subtitle;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setDescription(CharSequence description) {return this;}
|
public Builder setDescription(CharSequence description) {return this;}
|
||||||
|
|
||||||
public Builder setIconBitmap(Bitmap iconBitmap) {return this;}
|
public Builder setIconBitmap(Bitmap iconBitmap) {return this;}
|
||||||
|
|
||||||
public Builder setIconUri(Uri iconUri) {return this;}
|
public Builder setIconUri(Uri iconUri) {
|
||||||
|
description.iconUri = iconUri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setExtras(Bundle extras) {return this;}
|
public Builder setExtras(Bundle extras) {return this;}
|
||||||
|
|
||||||
public MediaDescription build() {
|
public MediaDescription build() {
|
||||||
return new MediaDescription();
|
return description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,20 @@ import android.os.Handler;
|
|||||||
|
|
||||||
public class MediaSession {
|
public class MediaSession {
|
||||||
|
|
||||||
|
private List<QueueItem> queue;
|
||||||
|
|
||||||
public static final class Token {}
|
public static final class Token {}
|
||||||
|
|
||||||
public static abstract class Callback {}
|
public static abstract class Callback {}
|
||||||
|
|
||||||
public static class QueueItem {
|
public static class QueueItem {
|
||||||
public QueueItem(MediaDescription description, long id) {}
|
long id;
|
||||||
|
MediaDescription description;
|
||||||
|
|
||||||
|
public QueueItem(MediaDescription description, long id) {
|
||||||
|
this.description = description;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaSession(Context context, String tag) {}
|
public MediaSession(Context context, String tag) {}
|
||||||
@@ -26,19 +34,41 @@ public class MediaSession {
|
|||||||
|
|
||||||
public void setFlags(int flags) {}
|
public void setFlags(int flags) {}
|
||||||
|
|
||||||
public void setCallback(Callback callback, Handler handler) {}
|
public void setCallback(Callback callback, Handler handler) {
|
||||||
|
nativeSetCallback(callback);
|
||||||
|
}
|
||||||
|
|
||||||
public void setCallback(Callback callback) {}
|
public void setCallback(Callback callback) {
|
||||||
|
nativeSetCallback(callback);
|
||||||
|
}
|
||||||
|
|
||||||
public void setMediaButtonReceiver(PendingIntent pendingIntent) {}
|
public void setMediaButtonReceiver(PendingIntent pendingIntent) {}
|
||||||
|
|
||||||
public void setActive(boolean active) {}
|
public void setActive(boolean active) {}
|
||||||
|
|
||||||
public void setPlaybackState(PlaybackState state) {}
|
public void setPlaybackState(PlaybackState state) {
|
||||||
|
String title = null;
|
||||||
|
String subTitle = null;
|
||||||
|
String artUrl = null;
|
||||||
|
if (queue != null) for (QueueItem item : queue) {
|
||||||
|
if (item.id == state.activeQueueItemId) {
|
||||||
|
title = item.description.title.toString();
|
||||||
|
subTitle = item.description.subtitle.toString();
|
||||||
|
artUrl = item.description.iconUri.toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nativeSetState(state.state, state.actions, state.position, state.updateTime, title, subTitle, artUrl);
|
||||||
|
}
|
||||||
|
|
||||||
public void setMetadata(MediaMetadata metadata) {}
|
public void setMetadata(MediaMetadata metadata) {}
|
||||||
|
|
||||||
public void setQueue(List<QueueItem> queue) {}
|
public void setQueue(List<QueueItem> queue) {
|
||||||
|
this.queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
public void release() {}
|
public void release() {}
|
||||||
|
|
||||||
|
protected native void nativeSetState(int state, long actions, long position, long updateTime, String title, String subTitle, String artUrl);
|
||||||
|
protected native void nativeSetCallback(Callback callback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,49 @@ package android.media.session;
|
|||||||
|
|
||||||
public class PlaybackState {
|
public class PlaybackState {
|
||||||
|
|
||||||
public class Builder {
|
public int state;
|
||||||
|
public long position;
|
||||||
|
public float playbackSpeed;
|
||||||
|
public long updateTime;
|
||||||
|
public long actions;
|
||||||
|
public long activeQueueItemId;
|
||||||
|
|
||||||
public Builder setState(int state, long position, float playbackSpeed, long updateTime) {return this;}
|
public static class Builder {
|
||||||
|
|
||||||
|
PlaybackState state;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
state = new PlaybackState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(PlaybackState from) {
|
||||||
|
state = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setState(int state, long position, float playbackSpeed, long updateTime) {
|
||||||
|
this.state.state = state;
|
||||||
|
this.state.position = position;
|
||||||
|
this.state.playbackSpeed = playbackSpeed;
|
||||||
|
this.state.updateTime = updateTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setBufferedPosition(long bufferedPosition) {return this;}
|
public Builder setBufferedPosition(long bufferedPosition) {return this;}
|
||||||
|
|
||||||
public Builder setActions(long actions) {return this;}
|
public Builder setActions(long actions) {
|
||||||
|
state.actions = actions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setErrorMessage(CharSequence errorMessage) {return this;}
|
public Builder setErrorMessage(CharSequence errorMessage) {return this;}
|
||||||
|
|
||||||
public Builder setActiveQueueItemId(long activeQueueItemId) {return this;}
|
public Builder setActiveQueueItemId(long activeQueueItemId) {
|
||||||
|
state.activeQueueItemId = activeQueueItemId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public PlaybackState build() {
|
public PlaybackState build() {
|
||||||
return new PlaybackState();
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user