Amend last commit. Added new drawables and removed obsolete drawables and layouts.

This commit is contained in:
Karim Abou Zeid 2015-06-15 20:51:55 +02:00
commit 4e8c3694d4
37 changed files with 218 additions and 631 deletions

View file

@ -0,0 +1,70 @@
package com.kabouzeid.gramophone.helper;
import android.media.RemoteControlClient;
import android.os.Build;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
public class MediaSessionHelper {
public static void applyState(MediaSessionCompat session, PlaybackStateCompat playbackState) {
session.setPlaybackState(playbackState);
ensureTransportControls(session, playbackState);
}
private static void ensureTransportControls(MediaSessionCompat session, PlaybackStateCompat playbackState) {
long actions = playbackState.getActions();
Object rccObj = session.getRemoteControlClient();
if (actions != 0
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
&& rccObj != null) {
int transportControls = 0;
if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS;
}
if ((actions & PlaybackStateCompat.ACTION_REWIND) != 0) {
transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_REWIND;
}
if ((actions & PlaybackStateCompat.ACTION_PLAY) != 0) {
transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_PLAY;
}
if ((actions & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) {
transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE;
}
if ((actions & PlaybackStateCompat.ACTION_PAUSE) != 0) {
transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_PAUSE;
}
if ((actions & PlaybackStateCompat.ACTION_STOP) != 0) {
transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_STOP;
}
if ((actions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD;
}
if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_NEXT;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if ((actions & PlaybackStateCompat.ACTION_SEEK_TO) != 0) {
transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
}
if ((actions & PlaybackStateCompat.ACTION_SET_RATING) != 0) {
transportControls |= RemoteControlClient.FLAG_KEY_MEDIA_RATING;
}
}
((RemoteControlClient) rccObj).setTransportControlFlags(transportControls);
}
}
}

View file

@ -5,16 +5,15 @@ package com.kabouzeid.gramophone.helper;
*/ */
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.TaskStackBuilder; import android.app.TaskStackBuilder;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v7.app.NotificationCompat; import android.support.v7.app.NotificationCompat;
import android.view.View;
import android.widget.RemoteViews;
import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.model.Song;
@ -22,192 +21,101 @@ import com.kabouzeid.gramophone.service.MusicService;
import com.kabouzeid.gramophone.ui.activities.MusicControllerActivity; import com.kabouzeid.gramophone.ui.activities.MusicControllerActivity;
import com.kabouzeid.gramophone.util.MusicUtil; import com.kabouzeid.gramophone.util.MusicUtil;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.assist.ImageSize;
import com.nostra13.universalimageloader.core.assist.ViewScaleType;
import com.nostra13.universalimageloader.core.imageaware.NonViewAware;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
public class PlayingNotificationHelper { public class PlayingNotificationHelper {
public static final String TAG = PlayingNotificationHelper.class.getSimpleName(); public static final String TAG = PlayingNotificationHelper.class.getSimpleName();
public static final int NOTIFICATION_ID = 1337;
private final MusicService service; public static Notification buildNotification(final Context context, MediaSessionCompat.Token sessionToken, final Song song, final boolean isPlaying) {
private final NotificationManager notificationManager; NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle()
private Notification notification = null; .setMediaSession(sessionToken)
.setShowActionsInCompactView(0, 1, 2);
private RemoteViews notificationLayout; style.setShowCancelButton(true);
private RemoteViews notificationLayoutExpanded; style.setCancelButtonIntent(retrievePlaybackAction(context, 3));
private Song currentSong; Bitmap albumArt = ImageLoader.getInstance().loadImageSync(MusicUtil.getAlbumArtUri(song.albumId).toString());
private String currentAlbumArtUri; if (albumArt == null) {
albumArt = BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art);
}
public PlayingNotificationHelper(final MusicService service) { return new NotificationCompat.Builder(context)
this.service = service;
notificationManager = (NotificationManager) service
.getSystemService(Context.NOTIFICATION_SERVICE);
}
public void buildNotification(final Song song, final boolean isPlaying) {
currentSong = song;
notificationLayout = new RemoteViews(service.getPackageName(),
R.layout.notification_playing);
notificationLayoutExpanded = new RemoteViews(service.getPackageName(),
R.layout.notification_playing_expanded);
notification = new NotificationCompat.Builder(service)
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setContentIntent(getOpenMusicControllerPendingIntent()) .setLargeIcon(albumArt)
.setCategory(NotificationCompat.CATEGORY_PROGRESS) .setContentIntent(getOpenMusicControllerPendingIntent(context))
.setPriority(NotificationCompat.PRIORITY_MAX) .setContentTitle(song.title)
.setContentText(song.artistName)
.setSubText(song.albumName)
.setWhen(0)
.setShowWhen(false)
.setStyle(style)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContent(notificationLayout)
.setStyle(new NotificationCompat.MediaStyle()) .addAction(R.drawable.ic_skip_previous_white_36dp,
"",
retrievePlaybackAction(context, 2))
.addAction(isPlaying ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_arrow_white_36dp,
"",
retrievePlaybackAction(context, 0))
.addAction(R.drawable.ic_skip_next_white_36dp,
"",
retrievePlaybackAction(context, 1))
.setOnlyAlertOnce(true)
.build(); .build();
notification.bigContentView = notificationLayoutExpanded;
setUpCollapsedLayout();
setUpExpandedLayout();
loadAlbumArt();
setUpPlaybackActions(isPlaying);
setUpExpandedPlaybackActions(isPlaying);
service.startForeground(NOTIFICATION_ID, notification);
} }
private PendingIntent getOpenMusicControllerPendingIntent() { private static PendingIntent getOpenMusicControllerPendingIntent(final Context context) {
Intent result = new Intent(service, MusicControllerActivity.class); Intent result = new Intent(context, MusicControllerActivity.class);
TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(service); TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
taskStackBuilder.addParentStack(MusicControllerActivity.class); taskStackBuilder.addParentStack(MusicControllerActivity.class);
taskStackBuilder.addNextIntent(result); taskStackBuilder.addNextIntent(result);
return taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); return taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
} }
private void setUpExpandedPlaybackActions(boolean isPlaying) { private static PendingIntent retrievePlaybackAction(final Context context, final int which) {
notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_toggle_play_pause, String actionString = null;
retrievePlaybackActions(1));
notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_next,
retrievePlaybackActions(2));
notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_prev,
retrievePlaybackActions(3));
notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_quit,
retrievePlaybackActions(4));
notificationLayoutExpanded.setImageViewResource(R.id.button_toggle_play_pause,
isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp);
}
private void setUpPlaybackActions(boolean isPlaying) {
notificationLayout.setOnClickPendingIntent(R.id.button_toggle_play_pause,
retrievePlaybackActions(1));
notificationLayout.setOnClickPendingIntent(R.id.button_next,
retrievePlaybackActions(2));
notificationLayout.setOnClickPendingIntent(R.id.button_quit,
retrievePlaybackActions(4));
notificationLayout.setImageViewResource(R.id.button_toggle_play_pause,
isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp);
}
private PendingIntent retrievePlaybackActions(final int which) {
Intent action;
PendingIntent pendingIntent;
final ComponentName serviceName = new ComponentName(service, MusicService.class);
switch (which) { switch (which) {
case 1: case 0:
action = new Intent(MusicService.ACTION_TOGGLE_PLAYBACK); actionString = MusicService.ACTION_TOGGLE_PLAYBACK;
action.setComponent(serviceName);
pendingIntent = PendingIntent.getService(service, 1, action, 0);
return pendingIntent;
case 2:
action = new Intent(MusicService.ACTION_SKIP);
action.setComponent(serviceName);
pendingIntent = PendingIntent.getService(service, 2, action, 0);
return pendingIntent;
case 3:
action = new Intent(MusicService.ACTION_REWIND);
action.setComponent(serviceName);
pendingIntent = PendingIntent.getService(service, 3, action, 0);
return pendingIntent;
case 4:
action = new Intent(MusicService.ACTION_QUIT);
action.setComponent(serviceName);
pendingIntent = PendingIntent.getService(service, 4, action, 0);
return pendingIntent;
default:
break; break;
case 1:
actionString = MusicService.ACTION_SKIP;
break;
case 2:
actionString = MusicService.ACTION_REWIND;
break;
case 3:
actionString = MusicService.ACTION_QUIT;
break;
}
if (actionString != null) {
final ComponentName serviceName = new ComponentName(context, MusicService.class);
Intent actionIntent = new Intent(actionString);
actionIntent.setComponent(serviceName);
return PendingIntent.getService(context, 0, actionIntent, 0);
} }
return null; return null;
} }
private void setUpCollapsedLayout() { // private void loadAlbumArt() {
if (currentSong != null) { // currentAlbumArtUri = MusicUtil.getAlbumArtUri(currentSong.albumId).toString();
notificationLayout.setTextViewText(R.id.song_title, currentSong.title); // ImageLoader.getInstance().displayImage(currentAlbumArtUri, new NonViewAware(new ImageSize(-1, -1), ViewScaleType.CROP), new SimpleImageLoadingListener() {
notificationLayout.setTextViewText(R.id.song_artist, currentSong.artistName); // @Override
} // public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
} // if (currentAlbumArtUri.equals(imageUri))
// // copy() prevents the original bitmap in the memory cache from being recycled by the remote views
private void setUpExpandedLayout() { // setAlbumArt(loadedImage.copy(loadedImage.getConfig(), true));
if (currentSong != null) { // }
notificationLayoutExpanded.setTextViewText(R.id.song_title, currentSong.title); //
notificationLayoutExpanded.setTextViewText(R.id.song_artist, currentSong.artistName); // @Override
notificationLayoutExpanded.setTextViewText(R.id.album_title, currentSong.albumName); // public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
} // if (currentAlbumArtUri.equals(imageUri))
} // setAlbumArt(null);
// }
private void loadAlbumArt() { // });
currentAlbumArtUri = MusicUtil.getAlbumArtUri(currentSong.albumId).toString(); // }
ImageLoader.getInstance().displayImage(currentAlbumArtUri, new NonViewAware(new ImageSize(-1, -1), ViewScaleType.CROP), new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
if (currentAlbumArtUri.equals(imageUri))
// copy() prevents the original bitmap in the memory cache from being recycled by the remote views
setAlbumArt(loadedImage.copy(loadedImage.getConfig(), true));
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
if (currentAlbumArtUri.equals(imageUri))
setAlbumArt(null);
}
});
}
private void setAlbumArt(Bitmap albumArt) {
if (albumArt != null) {
notificationLayout.setImageViewBitmap(R.id.album_art, albumArt);
notificationLayoutExpanded.setImageViewBitmap(R.id.album_art, albumArt);
} else {
notificationLayout.setImageViewResource(R.id.album_art, R.drawable.default_album_art);
notificationLayoutExpanded.setImageViewResource(R.id.album_art, R.drawable.default_album_art);
}
notificationManager.notify(NOTIFICATION_ID, notification);
}
public void killNotification() {
service.stopForeground(true);
notification = null;
}
public void updatePlayState(final boolean isPlaying) {
if (notification == null || notificationManager == null) {
return;
}
if (notificationLayout != null) {
notificationLayout.setImageViewResource(R.id.button_toggle_play_pause,
isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp);
}
if (notificationLayoutExpanded != null) {
notificationLayoutExpanded.setImageViewResource(R.id.button_toggle_play_pause,
isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp);
}
notificationManager.notify(NOTIFICATION_ID, notification);
}
} }

View file

@ -1,5 +1,7 @@
package com.kabouzeid.gramophone.service; package com.kabouzeid.gramophone.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@ -10,10 +12,8 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.media.audiofx.AudioEffect; import android.media.audiofx.AudioEffect;
import android.media.session.PlaybackState;
import android.net.Uri; import android.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.os.Handler; import android.os.Handler;
@ -33,6 +33,7 @@ import android.widget.Toast;
import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.appwidget.MusicPlayerWidget; import com.kabouzeid.gramophone.appwidget.MusicPlayerWidget;
import com.kabouzeid.gramophone.helper.MediaSessionHelper;
import com.kabouzeid.gramophone.helper.PlayingNotificationHelper; import com.kabouzeid.gramophone.helper.PlayingNotificationHelper;
import com.kabouzeid.gramophone.helper.ShuffleHelper; import com.kabouzeid.gramophone.helper.ShuffleHelper;
import com.kabouzeid.gramophone.misc.AppKeys; import com.kabouzeid.gramophone.misc.AppKeys;
@ -56,6 +57,8 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
public static final String PHONOGRAPH_PACKAGE_NAME = "com.kabouzeid.gramophone"; public static final String PHONOGRAPH_PACKAGE_NAME = "com.kabouzeid.gramophone";
public static final String MUSIC_PACKAGE_NAME = "com.android.music"; public static final String MUSIC_PACKAGE_NAME = "com.android.music";
public static final int NOTIFICATION_ID = 1337;
public static final String ACTION_TOGGLE_PLAYBACK = "com.kabouzeid.gramophone.action.TOGGLE_PLAYBACK"; public static final String ACTION_TOGGLE_PLAYBACK = "com.kabouzeid.gramophone.action.TOGGLE_PLAYBACK";
public static final String ACTION_PLAY = "com.kabouzeid.gramophone.action.PLAY"; public static final String ACTION_PLAY = "com.kabouzeid.gramophone.action.PLAY";
public static final String ACTION_RESUME = "com.kabouzeid.gramophone.action.RESUME"; public static final String ACTION_RESUME = "com.kabouzeid.gramophone.action.RESUME";
@ -69,7 +72,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
public static final String PLAYSTATE_CHANGED = "com.kabouzeid.gramophone.playstatechanged"; public static final String PLAYSTATE_CHANGED = "com.kabouzeid.gramophone.playstatechanged";
public static final String REPEATMODE_CHANGED = "com.kabouzeid.gramophone.repeatmodechanged"; public static final String REPEATMODE_CHANGED = "com.kabouzeid.gramophone.repeatmodechanged";
public static final String SHUFFLEMODE_CHANGED = "com.kabouzeid.gramophone.shufflemodechanged"; public static final String SHUFFLEMODE_CHANGED = "com.kabouzeid.gramophone.shufflemodechanged";
public static final String POSITION_CHANGED = "com.kabouzeid.phonograph.positionchanged"; public static final String POSITION_IN_SONG_CHANGED = "com.kabouzeid.phonograph.positionchanged";
private static final int FOCUSCHANGE = 5; private static final int FOCUSCHANGE = 5;
private static final int DUCK = 6; private static final int DUCK = 6;
@ -97,14 +100,14 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
private boolean thingsRegistered; private boolean thingsRegistered;
private boolean saveQueuesAgain; private boolean saveQueuesAgain;
private boolean isSavingQueues; private boolean isSavingQueues;
private PlayingNotificationHelper playingNotificationHelper;
private AudioManager audioManager; private AudioManager audioManager;
private MediaSessionCompat mSession; private MediaSessionCompat mediaSession;
private PowerManager.WakeLock wakeLock; private PowerManager.WakeLock wakeLock;
private String currentAlbumArtUri; private String currentAlbumArtUri;
private MusicPlayerHandler playerHandler; private MusicPlayerHandler playerHandler;
private boolean fadingDown = false; private boolean fadingDown = false;
private HandlerThread handlerThread; private HandlerThread handlerThread;
private NotificationManager notificationManager;
private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() {
@Override @Override
@ -129,7 +132,6 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
isPlayerPrepared = false; isPlayerPrepared = false;
playingQueue = new ArrayList<>(); playingQueue = new ArrayList<>();
originalPlayingQueue = new ArrayList<>(); originalPlayingQueue = new ArrayList<>();
playingNotificationHelper = new PlayingNotificationHelper(this);
shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_SHUFFLE_MODE, 0); shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_SHUFFLE_MODE, 0);
repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_REPEAT_MODE, 0); repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_REPEAT_MODE, 0);
@ -143,18 +145,16 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
handlerThread.start(); handlerThread.start();
playerHandler = new MusicPlayerHandler(this, handlerThread.getLooper()); playerHandler = new MusicPlayerHandler(this, handlerThread.getLooper());
registerEverything(); notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
setUpMediaSession(); setUpMediaSession();
registerEverything();
} }
private void setUpMediaSession() { private void setUpMediaSession() {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaSession = new MediaSessionCompat(this, "Phonograph", new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class), getMediaButtonIntent());
mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); mediaSession.setCallback(new MediaSessionCompat.Callback() {
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
mSession = new MediaSessionCompat(this, "Phonograph", new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class), mediaPendingIntent);
mSession.setCallback(new MediaSessionCompat.Callback() {
@Override @Override
public void onPause() { public void onPause() {
pausePlaying(false); pausePlaying(false);
@ -186,7 +186,8 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
stopPlaying(); stopPlaying();
} }
}); });
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
} }
private void registerEverything() { private void registerEverything() {
@ -205,17 +206,10 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
return audioManager; return audioManager;
} }
private void initRemoteControlClient() { private PendingIntent getMediaButtonIntent() {
// Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
// mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class));
// PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); return PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
// remoteControlClient = new RemoteControlClient(mediaPendingIntent);
// remoteControlClient.setTransportControlFlags(
// RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
// RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
// RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
// RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS);
// getAudioManager().registerRemoteControlClient(remoteControlClient);
} }
@Override @Override
@ -292,7 +286,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
private void killEverythingAndReleaseResources() { private void killEverythingAndReleaseResources() {
stopPlaying(); stopPlaying();
playingNotificationHelper.killNotification(); stopForeground(true);
savePosition(); savePosition();
saveQueues(); saveQueues();
stopSelf(); stopSelf();
@ -306,8 +300,8 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
player.release(); player.release();
player = null; player = null;
} }
mSession.setActive(false); mediaSession.setActive(false);
mSession.release(); mediaSession.release();
notifyChange(PLAYSTATE_CHANGED); notifyChange(PLAYSTATE_CHANGED);
} }
@ -383,13 +377,8 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
player.setAudioStreamType(AudioManager.STREAM_MUSIC); player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setDataSource(getApplicationContext(), trackUri); player.setDataSource(getApplicationContext(), trackUri);
player.prepareAsync(); player.prepareAsync();
} catch (Exception e) { } catch (Exception ignored) {
player.reset(); // handled in onError()
player = null;
notifyChange(PLAYSTATE_CHANGED);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
return;
} }
currentSongId = playingQueue.get(getPosition()).id; currentSongId = playingQueue.get(getPosition()).id;
notifyChange(META_CHANGED); notifyChange(META_CHANGED);
@ -426,24 +415,26 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
final Song song = playingQueue.get(getPosition()); final Song song = playingQueue.get(getPosition());
int playState = isPlaying() int playState = isPlaying()
? PlaybackState.STATE_PLAYING ? PlaybackStateCompat.STATE_PLAYING
: PlaybackState.STATE_PAUSED; : PlaybackStateCompat.STATE_PAUSED;
if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) { if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_IN_SONG_CHANGED)) {
mSession.setPlaybackState(new PlaybackStateCompat.Builder() MediaSessionHelper.applyState(mediaSession, new PlaybackStateCompat.Builder()
.setState(playState, getSongProgressMillis(), 1.0f).build()); .setActions(getAvailablePlaybackStateActions())
.setState(playState, player != null ? getSongProgressMillis() : 0, 1.0f).build());
} else if (what.equals(META_CHANGED)) { } else if (what.equals(META_CHANGED)) {
mSession.setMetadata(new MediaMetadataCompat.Builder() mediaSession.setMetadata(new MediaMetadataCompat.Builder()
.putString(MediaMetadata.METADATA_KEY_ARTIST, song.artistName) .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artistName)
.putString(MediaMetadata.METADATA_KEY_ALBUM, song.albumName) .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName)
.putString(MediaMetadata.METADATA_KEY_TITLE, song.title) .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title)
.putLong(MediaMetadata.METADATA_KEY_DURATION, song.duration) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration)
.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1)
.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()) .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size())
.build()); .build());
mSession.setPlaybackState(new PlaybackStateCompat.Builder() MediaSessionHelper.applyState(mediaSession, new PlaybackStateCompat.Builder()
.setState(playState, getSongProgressMillis(), 1.0f).build()); .setActions(getAvailablePlaybackStateActions())
.setState(playState, player != null ? getSongProgressMillis() : 0, 1.0f).build());
} }
currentAlbumArtUri = MusicUtil.getAlbumArtUri(song.albumId).toString(); currentAlbumArtUri = MusicUtil.getAlbumArtUri(song.albumId).toString();
@ -474,14 +465,34 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
} }
private void updateMediaSessionBitmap(final Bitmap albumArt) { private void updateMediaSessionBitmap(final Bitmap albumArt) {
MediaMetadataCompat current = mSession.getController().getMetadata(); MediaMetadataCompat current = mediaSession.getController().getMetadata();
if (current == null) current = new MediaMetadataCompat.Builder().build(); if (current == null) current = new MediaMetadataCompat.Builder().build();
mSession.setMetadata(new MediaMetadataCompat.Builder(current) mediaSession.setMetadata(new MediaMetadataCompat.Builder(current)
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, albumArt) .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt)
.build()); .build());
} }
private long getAvailablePlaybackStateActions() {
if (getPlayingQueue() == null || getPlayingQueue().isEmpty()) {
return 0;
}
long actions = PlaybackStateCompat.ACTION_PLAY_PAUSE;
if (isPlaying()) {
actions |= PlaybackStateCompat.ACTION_PAUSE;
} else {
actions |= PlaybackStateCompat.ACTION_PLAY;
}
if (getPosition() > 0) {
actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
}
if (getPosition() < getPlayingQueue().size() - 1) {
actions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
}
return actions;
}
private void setUpMediaPlayerIfNeeded() { private void setUpMediaPlayerIfNeeded() {
if (player == null) { if (player == null) {
player = new MediaPlayer(); player = new MediaPlayer();
@ -496,7 +507,14 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
} }
private void updateNotification() { private void updateNotification() {
playingNotificationHelper.buildNotification(playingQueue.get(position), isPlaying()); final boolean isPlaying = isPlaying();
Notification notification = PlayingNotificationHelper.buildNotification(this, mediaSession.getSessionToken(), getPlayingQueue().get(getPosition()), isPlaying);
if (isPlaying)
startForeground(NOTIFICATION_ID, notification);
else {
notificationManager.notify(NOTIFICATION_ID, notification);
stopForeground(false);
}
} }
private void updateWidgets() { private void updateWidgets() {
@ -568,6 +586,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
player.reset(); player.reset();
player = null; player = null;
notifyChange(PLAYSTATE_CHANGED); notifyChange(PLAYSTATE_CHANGED);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
return false; return false;
} }
@ -723,7 +742,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
} }
public void resumePlaying(boolean forceNoFading) { public void resumePlaying(boolean forceNoFading) {
mSession.setActive(true); mediaSession.setActive(true);
if (!forceNoFading && PreferenceUtils.getInstance(this).fadePlayPauseAndInterruptions()) { if (!forceNoFading && PreferenceUtils.getInstance(this).fadePlayPauseAndInterruptions()) {
playerHandler.removeMessages(FADEDOWNANDPAUSE); playerHandler.removeMessages(FADEDOWNANDPAUSE);
playerHandler.sendEmptyMessage(FADEUPANDRESUME); playerHandler.sendEmptyMessage(FADEUPANDRESUME);
@ -805,6 +824,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
public void seekTo(int millis) { public void seekTo(int millis) {
player.seekTo(millis); player.seekTo(millis);
notifyChange(POSITION_IN_SONG_CHANGED);
} }
public boolean isPlayerPrepared() { public boolean isPlayerPrepared() {
@ -864,7 +884,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
private void notifyChange(final String what) { private void notifyChange(final String what) {
updateMediaSession(what); updateMediaSession(what);
if (what.equals(POSITION_CHANGED)) { if (what.equals(POSITION_IN_SONG_CHANGED)) {
return; return;
} }
@ -887,7 +907,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
if (what.equals(PLAYSTATE_CHANGED)) { if (what.equals(PLAYSTATE_CHANGED)) {
final boolean isPlaying = isPlaying(); final boolean isPlaying = isPlaying();
playingNotificationHelper.updatePlayState(isPlaying); updateNotification();
MusicPlayerWidget.updateWidgetsPlayState(this, isPlaying); MusicPlayerWidget.updateWidgetsPlayState(this, isPlaying);
} else if (what.equals(META_CHANGED)) { } else if (what.equals(META_CHANGED)) {
updateNotification(); updateNotification();
@ -962,7 +982,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
service.fadingDown = true; service.fadingDown = true;
service.notifyChange(PLAYSTATE_CHANGED); service.notifyChange(PLAYSTATE_CHANGED);
} }
currentPlayPauseFadeVolume -= .1f; currentPlayPauseFadeVolume -= .2f;
if (currentPlayPauseFadeVolume > 0f) { if (currentPlayPauseFadeVolume > 0f) {
sendEmptyMessageDelayed(FADEDOWNANDPAUSE, 10); sendEmptyMessageDelayed(FADEDOWNANDPAUSE, 10);
} else { } else {
@ -980,7 +1000,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
service.notifyChange(PLAYSTATE_CHANGED); service.notifyChange(PLAYSTATE_CHANGED);
} }
service.resume(); service.resume();
currentPlayPauseFadeVolume += .1f; currentPlayPauseFadeVolume += .2f;
if (currentPlayPauseFadeVolume < 1.0f) { if (currentPlayPauseFadeVolume < 1.0f) {
sendEmptyMessageDelayed(FADEUPANDRESUME, 10); sendEmptyMessageDelayed(FADEUPANDRESUME, 10);
} else { } else {

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

View file

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2014 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="64dp">
<ImageView
android:id="@+id/album_art"
android:layout_width="@dimen/notification_big_icon_width"
android:layout_height="@dimen/notification_big_icon_height"
android:layout_weight="0"
android:scaleType="centerCrop"
tools:ignore="ContentDescription" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill_vertical"
android:layout_weight="1"
android:gravity="center_vertical"
android:minHeight="@dimen/notification_big_icon_height"
android:orientation="vertical"
android:paddingBottom="@dimen/notification_info_container_padding_bottom"
android:paddingStart="@dimen/notification_info_container_padding_left">
<TextView
android:id="@+id/song_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/Theme.MaterialMusic.Notification.Title" />
<TextView
android:id="@+id/song_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/Theme.MaterialMusic.Notification" />
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:orientation="horizontal"
tools:ignore="ContentDescription">
<ImageButton
android:id="@+id/button_toggle_play_pause"
style="@style/NotificationButton"
android:paddingBottom="16dp"
android:paddingTop="16dp"
android:src="@drawable/ic_play_arrow_white_48dp" />
<ImageButton
android:id="@+id/button_next"
style="@style/NotificationButton"
android:paddingBottom="16dp"
android:paddingTop="16dp"
android:src="@drawable/ic_skip_next_white_48dp" />
<ImageButton
android:id="@+id/button_quit"
style="@style/NotificationButton"
android:padding="8dp"
android:src="@drawable/ic_close_white_24dp" />
</LinearLayout>
</LinearLayout>

View file

@ -1,114 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2014 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<!-- Layout to be used with only max 3 actions. It has a much larger picture at the left side-->
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/status_bar_latest_event_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="128dp"
tools:ignore="ContentDescription">
<ImageView
android:id="@+id/album_art"
android:layout_width="128dp"
android:layout_height="128dp"
android:scaleType="centerCrop" />
<ImageButton
android:id="@+id/button_quit"
style="@style/NotificationButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_gravity="center"
android:background="@drawable/notification_selector"
android:padding="8dp"
android:src="@drawable/ic_close_white_24dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@+id/button_quit"
android:layout_toEndOf="@+id/album_art"
android:gravity="center"
android:minHeight="@dimen/notification_big_icon_height"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingTop="12dp">
<TextView
android:id="@+id/song_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/Theme.MaterialMusic.Notification.Title" />
<TextView
android:id="@+id/song_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/Theme.MaterialMusic.Notification" />
<TextView
android:id="@+id/album_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/Theme.MaterialMusic.Notification" />
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_toEndOf="@+id/album_art"
android:orientation="horizontal">
<ImageButton
android:id="@+id/button_prev"
style="@style/NotificationButton"
android:padding="8dp"
android:src="@drawable/ic_skip_previous_white_48dp" />
<ImageButton
android:id="@+id/button_toggle_play_pause"
style="@style/NotificationButton"
android:padding="8dp"
android:src="@drawable/ic_play_arrow_white_48dp" />
<ImageButton
android:id="@+id/button_next"
style="@style/NotificationButton"
android:padding="8dp"
android:src="@drawable/ic_skip_next_white_48dp" />
</LinearLayout>
<ImageView
android:id="@+id/action_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_above="@+id/media_actions"
android:layout_toEndOf="@+id/album_art"
android:background="@drawable/notification_template_divider_media" />
</RelativeLayout>

View file

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2014 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="64dp"
android:orientation="horizontal"
tools:ignore="ContentDescription">
<ImageView
android:id="@+id/album_art"
android:layout_width="@dimen/notification_big_icon_width"
android:layout_height="@dimen/notification_big_icon_height"
android:layout_weight="0"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill_vertical"
android:layout_weight="1"
android:gravity="center_vertical"
android:minHeight="@dimen/notification_big_icon_height"
android:orientation="vertical"
android:paddingBottom="@dimen/notification_info_container_padding_bottom"
android:paddingLeft="@dimen/notification_info_container_padding_left"
android:paddingStart="@dimen/notification_info_container_padding_left">
<TextView
android:id="@+id/song_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/Theme.MaterialMusic.Notification.Title" />
<TextView
android:id="@+id/song_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/Theme.MaterialMusic.Notification" />
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:orientation="horizontal">
<ImageButton
android:id="@+id/button_toggle_play_pause"
style="@style/NotificationButton"
android:paddingBottom="16dp"
android:paddingTop="16dp"
android:src="@drawable/ic_play_arrow_white_48dp" />
<ImageButton
android:id="@+id/button_next"
style="@style/NotificationButton"
android:paddingBottom="16dp"
android:paddingTop="16dp"
android:src="@drawable/ic_skip_next_white_48dp" />
<ImageButton
android:id="@+id/button_quit"
style="@style/NotificationButton"
android:padding="8dp"
android:src="@drawable/ic_close_white_24dp" />
</LinearLayout>
</LinearLayout>

View file

@ -1,121 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2014 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<!-- Layout to be used with only max 3 actions. It has a much larger picture at the left side-->
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/status_bar_latest_event_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="128dp"
tools:ignore="ContentDescription">
<ImageView
android:id="@+id/album_art"
android:layout_width="@dimen/notification_albumart_size"
android:layout_height="@dimen/notification_albumart_size"
android:scaleType="centerCrop" />
<ImageButton
android:id="@+id/button_quit"
style="@style/NotificationButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_gravity="center"
android:background="?android:selectableItemBackground"
android:padding="8dp"
android:src="@drawable/ic_close_white_24dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/button_quit"
android:layout_toStartOf="@+id/button_quit"
android:layout_toRightOf="@+id/album_art"
android:layout_toEndOf="@+id/album_art"
android:gravity="center"
android:minHeight="@dimen/notification_big_icon_height"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingStart="16dp"
android:paddingTop="12dp">
<TextView
android:id="@+id/song_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/Theme.MaterialMusic.Notification.Title" />
<TextView
android:id="@+id/song_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/Theme.MaterialMusic.Notification" />
<TextView
android:id="@+id/album_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/Theme.MaterialMusic.Notification" />
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_toRightOf="@+id/album_art"
android:layout_toEndOf="@+id/album_art"
android:orientation="horizontal">
<ImageButton
android:id="@+id/button_prev"
style="@style/NotificationButton"
android:padding="8dp"
android:src="@drawable/ic_skip_previous_white_48dp" />
<ImageButton
android:id="@+id/button_toggle_play_pause"
style="@style/NotificationButton"
android:padding="8dp"
android:src="@drawable/ic_play_arrow_white_48dp" />
<ImageButton
android:id="@+id/button_next"
style="@style/NotificationButton"
android:padding="8dp"
android:src="@drawable/ic_skip_next_white_48dp" />
</LinearLayout>
<ImageView
android:id="@+id/action_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_above="@+id/media_actions"
android:layout_toRightOf="@+id/album_art"
android:layout_toEndOf="@+id/album_art"
android:background="@drawable/notification_template_divider_media" />
</RelativeLayout>