/* Copyright (C) 2023-2024 José Rebelo This file is part of Gadgetbridge. Gadgetbridge is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gadgetbridge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.util; import android.content.ComponentName; import android.content.Context; import android.media.AudioManager; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; public class MediaManager { private static final Logger LOG = LoggerFactory.getLogger(MediaManager.class); private final Context context; private MusicSpec bufferMusicSpec = null; private MusicStateSpec bufferMusicStateSpec = null; public MediaManager(final Context context) { this.context = context; } public MusicSpec getBufferMusicSpec() { return bufferMusicSpec; } public MusicStateSpec getBufferMusicStateSpec() { return bufferMusicStateSpec; } /** * Returns true if the spec changed, so the device should be updated. */ public boolean onSetMusicState(final MusicStateSpec stateSpec) { if (stateSpec != null && !stateSpec.equals(bufferMusicStateSpec)) { bufferMusicStateSpec = stateSpec; return true; } return false; } /** * Returns true if the spec changed, so the device should be updated. */ public boolean onSetMusicInfo(MusicSpec musicSpec) { if (musicSpec != null && !musicSpec.equals(bufferMusicSpec)) { bufferMusicSpec = musicSpec; if (bufferMusicStateSpec != null) { bufferMusicStateSpec.state = 0; bufferMusicStateSpec.position = 0; } return true; } return false; } public void refresh() { LOG.info("Refreshing media state"); final MediaSessionManager mediaSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE); try { final List controllers = mediaSessionManager.getActiveSessions( new ComponentName(context, NotificationListener.class) ); if (controllers.isEmpty()) { LOG.debug("No media controller available"); return; } final MediaController controller = controllers.get(0); bufferMusicSpec = extractMusicSpec(controller.getMetadata()); bufferMusicStateSpec = extractMusicStateSpec(controller.getPlaybackState()); } catch (final SecurityException e) { LOG.warn("No permission to get media sessions - did not grant notification access?", e); } catch (final Exception e) { LOG.error("Failed to get media info", e); } } @Nullable public static MusicSpec extractMusicSpec(final MediaMetadata d) { if (d == null) { return null; } final MusicSpec musicSpec = new MusicSpec(); try { if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST); if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM); if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE)) musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE); if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION)) musicSpec.duration = (int) d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000; if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) musicSpec.trackCount = (int) d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS); if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) musicSpec.trackNr = (int) d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER); } catch (final Exception e) { LOG.error("Failed to extract music spec", e); } return musicSpec; } @Nullable public static MusicStateSpec extractMusicStateSpec(final PlaybackState s) { if (s == null) { return null; } final MusicStateSpec stateSpec = new MusicStateSpec(); try { stateSpec.position = (int) (s.getPosition() / 1000); stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed()); stateSpec.repeat = MusicStateSpec.STATE_UNKNOWN; stateSpec.shuffle = MusicStateSpec.STATE_UNKNOWN; switch (s.getState()) { case PlaybackState.STATE_PLAYING: stateSpec.state = MusicStateSpec.STATE_PLAYING; break; case PlaybackState.STATE_STOPPED: stateSpec.state = MusicStateSpec.STATE_STOPPED; break; case PlaybackState.STATE_PAUSED: stateSpec.state = MusicStateSpec.STATE_PAUSED; break; default: stateSpec.state = MusicStateSpec.STATE_UNKNOWN; break; } } catch (final Exception e) { LOG.error("Failed to extract music state spec", e); } return stateSpec; } public int getPhoneVolume() { return getPhoneVolume(context); } public static int getPhoneVolume(final Context context) { final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); final int volumeLevel = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); final int volumeMax = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); return Math.round(100 * (volumeLevel / (float) volumeMax)); } }