Commit 757f2860 authored by Danny Baumann's avatar Danny Baumann
Browse files

Use L's new notification and media session API.

Change-Id: Ic9e7facdfa7213439b168a915ac9f2e8afc0a252
parent 4547a6da
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012 Andrew Neal
Copyright (C) 2014 The CyanogenMod 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.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="128.0dip"
tools:ignore="ContentDescription" >
<!-- The height cannot be specified any other way. It must read "128.0dip" and cannot be referenced. I think it's a bug. -->
<ImageView
android:id="@+id/notification_expanded_base_image"
android:layout_width="@dimen/notification_expanded_height"
android:layout_height="@dimen/notification_expanded_height"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:scaleType="fitXY" />
<LinearLayout
android:id="@+id/notification_expanded_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@+id/notification_expanded_base_image"
android:dividerPadding="@dimen/notification_expanded_buttons_divider_padding"
android:gravity="center_vertical"
android:orientation="horizontal"
android:showDividers="middle" >
<ImageButton
android:id="@+id/notification_expanded_base_previous"
android:layout_width="0dp"
android:layout_height="@dimen/notification_expanded_button_height"
android:layout_weight="1"
android:background="?android:selectableItemBackground"
android:padding="@dimen/notification_expanded_button_padding"
android:scaleType="fitCenter"
android:src="@drawable/btn_playback_previous" />
<ImageButton
android:id="@+id/notification_expanded_base_play"
android:layout_width="0dp"
android:layout_height="@dimen/notification_expanded_button_height"
android:layout_weight="1"
android:background="?android:selectableItemBackground"
android:padding="@dimen/notification_expanded_button_padding"
android:scaleType="fitCenter"
android:src="@drawable/btn_playback_pause" />
<ImageButton
android:id="@+id/notification_expanded_base_next"
android:layout_width="0dp"
android:layout_height="@dimen/notification_expanded_button_height"
android:layout_weight="1"
android:background="?android:selectableItemBackground"
android:padding="@dimen/notification_expanded_button_padding"
android:scaleType="fitCenter"
android:src="@drawable/btn_playback_next" />
</LinearLayout>
<LinearLayout
android:layout_gravity="fill_horizontal"
android:layout_width="wrap_content"
android:layout_height="1dp"
android:layout_above="@+id/notification_expanded_buttons"
android:layout_alignParentRight="true"
android:layout_toRightOf="@+id/notification_expanded_base_image"
android:background="@color/widget_divider" />
<ImageButton
android:id="@+id/notification_expanded_base_collapse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:background="?android:selectableItemBackground"
android:padding="@dimen/notification_expanded_collapse_padding"
android:src="@drawable/btn_notification_collapse" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_gravity="center_vertical"
android:layout_toLeftOf="@+id/notification_expanded_base_collapse"
android:layout_toRightOf="@+id/notification_expanded_base_image"
android:orientation="vertical"
android:paddingLeft="@dimen/notification_info_container_padding_left"
android:paddingTop="@dimen/notification_expanded_content_padding_top" >
<TextView
android:id="@+id/notification_expanded_base_line_one"
style="@style/NotificationText"
android:textStyle="bold"
android:textColor="@color/widget_text"
android:textSize="@dimen/text_size_large"/>
<TextView
android:id="@+id/notification_expanded_base_line_two"
style="@style/NotificationText"
android:textColor="@color/widget_text"
android:fontFamily="sans-serif-light"
android:textSize="@dimen/text_size_small"/>
<TextView
android:id="@+id/notification_expanded_base_line_three"
style="@style/NotificationText"
android:textColor="@color/widget_text"
android:fontFamily="sans-serif-light"
android:textSize="@dimen/text_size_small"/>
</LinearLayout>
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2012 Andrew Neal
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:id="@+id/notification_base"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="ContentDescription" >
<ImageView
android:id="@+id/notification_base_image"
android:layout_width="@dimen/notification_big_icon_width"
android:layout_height="@dimen/notification_big_icon_height"
android:gravity="center" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="@dimen/notification_info_container_padding_left"
android:paddingBottom="@dimen/notification_info_container_padding_bottom" >
<TextView
android:id="@+id/notification_base_line_one"
style="@style/NotificationText"
android:textAppearance="@android:style/TextAppearance.StatusBar.EventContent.Title" />
<TextView
android:id="@+id/notification_base_line_two"
style="@style/NotificationText"
android:textAppearance="@android:style/TextAppearance.StatusBar.EventContent" />
</LinearLayout>
<ImageButton
android:id="@+id/notification_base_previous"
style="@style/NotificationAction.Previous" />
<ImageButton
android:id="@+id/notification_base_play"
style="@style/NotificationAction.Play" />
<ImageButton
android:id="@+id/notification_base_next"
style="@style/NotificationAction.Next" />
<ImageButton
android:id="@+id/notification_base_collapse"
style="@style/NotificationAction.Collapse" />
</LinearLayout>
\ No newline at end of file
......@@ -15,6 +15,7 @@ package com.cyanogenmod.eleven;
import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
......@@ -32,9 +33,11 @@ import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaMetadataRetriever;
import android.media.MediaMetadata;
import android.media.MediaPlayer;
import android.media.RemoteControlClient;
import android.media.audiofx.AudioEffect;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
......@@ -442,7 +445,7 @@ public class MusicPlaybackService extends Service {
/**
* Lock screen controls
*/
private RemoteControlClient mRemoteControlClient;
private MediaSession mSession;
private ComponentName mMediaButtonReceiverComponent;
......@@ -483,11 +486,6 @@ public class MusicPlaybackService extends Service {
*/
private ImageFetcher mImageFetcher;
/**
* Used to build the notification
*/
private NotificationHelper mNotificationHelper;
/**
* Recently listened database
*/
......@@ -567,9 +565,6 @@ public class MusicPlaybackService extends Service {
// gets a pointer to the playback state store
mPlaybackStateStore = MusicPlaybackState.getInstance(this);
// Initialize the notification helper
mNotificationHelper = new NotificationHelper(this);
// Initialize the image fetcher
mImageFetcher = ImageFetcher.getInstance(this);
// Initialize the image cache
......@@ -594,7 +589,7 @@ public class MusicPlaybackService extends Service {
mAudioManager.registerMediaButtonEventReceiver(mMediaButtonReceiverComponent);
// Use the remote control APIs to set the playback state
setUpRemoteControlClient();
setUpMediaSession();
// Initialize the preferences
mPreferences = getSharedPreferences("Service", 0);
......@@ -648,45 +643,40 @@ public class MusicPlaybackService extends Service {
notifyChange(META_CHANGED);
}
/**
* Initializes the remote control client
*/
private void setUpRemoteControlClient() {
final Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(mMediaButtonReceiverComponent);
mRemoteControlClient = new RemoteControlClient(
PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent,
PendingIntent.FLAG_UPDATE_CURRENT));
mAudioManager.registerRemoteControlClient(mRemoteControlClient);
// Flags for the media transport control that this client supports.
int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
| RemoteControlClient.FLAG_KEY_MEDIA_NEXT
| RemoteControlClient.FLAG_KEY_MEDIA_PLAY
| RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
| RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
| RemoteControlClient.FLAG_KEY_MEDIA_STOP;
if (ApolloUtils.hasJellyBeanMR2()) {
flags |= RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
mRemoteControlClient.setOnGetPlaybackPositionListener(
new RemoteControlClient.OnGetPlaybackPositionListener() {
@Override
public long onGetPlaybackPosition() {
return position();
}
});
mRemoteControlClient.setPlaybackPositionUpdateListener(
new RemoteControlClient.OnPlaybackPositionUpdateListener() {
@Override
public void onPlaybackPositionUpdate(long newPositionMs) {
seek(newPositionMs);
}
});
}
mRemoteControlClient.setTransportControlFlags(flags);
private void setUpMediaSession() {
mSession = new MediaSession(this, "Eleven");
mSession.setCallback(new MediaSession.Callback() {
@Override
public void onPause() {
pause();
mPausedByTransientLossOfFocus = false;
}
@Override
public void onPlay() {
play();
}
@Override
public void onSeekTo(long pos) {
seek(pos);
}
@Override
public void onSkipToNext() {
gotoNext(true);
}
@Override
public void onSkipToPrevious() {
prev(false);
}
@Override
public void onStop() {
pause();
mPausedByTransientLossOfFocus = false;
seek(0);
releaseServiceUiAndStop();
}
});
mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setActive(true);
}
/**
......@@ -712,7 +702,7 @@ public class MusicPlaybackService extends Service {
// Remove the audio focus listener and lock screen controls
mAudioManager.abandonAudioFocus(mAudioFocusListener);
mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
mSession.release();
// remove the media store observer
getContentResolver().unregisterContentObserver(mMediaStoreObserver);
......@@ -778,7 +768,7 @@ public class MusicPlaybackService extends Service {
}
if (D) Log.d(TAG, "Nothing is playing anymore, releasing notification");
mNotificationHelper.killNotification();
stopForeground(true);
mAudioManager.abandonAudioFocus(mAudioFocusListener);
if (!mServiceInUse) {
......@@ -827,10 +817,9 @@ public class MusicPlaybackService extends Service {
*/
private void updateNotification() {
if (!mAnyActivityInForeground && isPlaying()) {
mNotificationHelper.buildNotification(getAlbumName(), getArtistName(),
getTrackName(), getAlbumId(), getAlbumArt(true), isPlaying());
buildNotification();
} else if (mAnyActivityInForeground) {
mNotificationHelper.killNotification();
stopForeground(true);
}
}
......@@ -1376,7 +1365,7 @@ public class MusicPlaybackService extends Service {
if (D) Log.d(TAG, "notifyChange: what = " + what);
// Update the lockscreen controls
updateRemoteControlClient(what);
updateMediaSession(what);
if (what.equals(POSITION_CHANGED)) {
return;
......@@ -1424,7 +1413,7 @@ public class MusicPlaybackService extends Service {
}
if (what.equals(PLAYSTATE_CHANGED)) {
mNotificationHelper.updatePlayState(isPlaying());
buildNotification();
}
// Update the app-widgets
......@@ -1433,21 +1422,14 @@ public class MusicPlaybackService extends Service {
mAppWidgetLargeAlternate.notifyChange(this, what);
}
/**
* Updates the lockscreen controls.
*
* @param what The broadcast
*/
private void updateRemoteControlClient(final String what) {
private void updateMediaSession(final String what) {
int playState = mIsSupposedToBePlaying
? RemoteControlClient.PLAYSTATE_PLAYING
: RemoteControlClient.PLAYSTATE_PAUSED;
if (ApolloUtils.hasJellyBeanMR2()
&& (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED))) {
mRemoteControlClient.setPlaybackState(playState, position(), 1.0f);
} else if (what.equals(PLAYSTATE_CHANGED)) {
mRemoteControlClient.setPlaybackState(playState);
? PlaybackState.STATE_PLAYING
: PlaybackState.STATE_PAUSED;
if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) {
mSession.setPlaybackState(new PlaybackState.Builder()
.setState(playState, position(), 1.0f).build());
} else if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {
Bitmap albumArt = getAlbumArt(false);
if (albumArt != null) {
......@@ -1459,23 +1441,71 @@ public class MusicPlaybackService extends Service {
}
albumArt = albumArt.copy(config, false);
}
mRemoteControlClient
.editMetadata(true)
.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName())
.putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
getAlbumArtistName())
.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName())
.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName())
.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration())
.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, albumArt)
.apply();
if (ApolloUtils.hasJellyBeanMR2()) {
mRemoteControlClient.setPlaybackState(playState, position(), 1.0f);
}
mSession.setMetadata(new MediaMetadata.Builder()
.putString(MediaMetadata.METADATA_KEY_ARTIST, getArtistName())
.putString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST, getAlbumArtistName())
.putString(MediaMetadata.METADATA_KEY_ALBUM, getAlbumName())
.putString(MediaMetadata.METADATA_KEY_TITLE, getTrackName())
.putLong(MediaMetadata.METADATA_KEY_DURATION, duration())
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, albumArt)
.build());
mSession.setPlaybackState(new PlaybackState.Builder()
.setState(playState, position(), 1.0f).build());
}
}
private void buildNotification() {
final String albumName = getAlbumName();
final String artistName = getArtistName();
final boolean isPlaying = isPlaying();
String text = TextUtils.isEmpty(albumName)
? artistName : artistName + " - " + albumName;
int playButtonResId = isPlaying
? R.drawable.btn_playback_pause : R.drawable.btn_playback_play;
int playButtonTitleResId = isPlaying
? R.string.accessibility_pause : R.string.accessibility_play;
Notification.MediaStyle style = new Notification.MediaStyle()
.setMediaSession(mSession.getSessionToken())
.setShowActionsInCompactView(0, 1, 2);
Intent nowPlayingIntent = new Intent("com.cyanogenmod.eleven.AUDIO_PLAYER")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent clickIntent = PendingIntent.getActivity(this, 0, nowPlayingIntent, 0);
// TODO: Add back a beter small icon when we have time
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(getAlbumArt(false))
.setContentIntent(clickIntent)
.setContentTitle(getTrackName())
.setContentText(text)
.setStyle(style)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.addAction(R.drawable.btn_playback_previous,
getString(R.string.accessibility_prev),
retrievePlaybackAction(PREVIOUS_ACTION))
.addAction(playButtonResId, getString(playButtonTitleResId),
retrievePlaybackAction(TOGGLEPAUSE_ACTION))
.addAction(R.drawable.btn_playback_next,
getString(R.string.accessibility_next),
retrievePlaybackAction(NEXT_ACTION))
.build();
startForeground(hashCode(), notification);
}
private final PendingIntent retrievePlaybackAction(final String action) {
final ComponentName serviceName = new ComponentName(this, MusicPlaybackService.class);
Intent intent = new Intent(action);
intent.setComponent(serviceName);
return PendingIntent.getService(this, 0, intent, 0);
}
/**
* Saves the queue
*
......
/*
* Copyright (C) 2012 Andrew Neal
* Copyright (C) 2014 The CyanogenMod 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.
*/
package com.cyanogenmod.eleven;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.support.v4.app.NotificationCompat;
import android.widget.RemoteViews;
import com.cyanogenmod.eleven.utils.ApolloUtils;
/**
* Builds the notification for Apollo's service. Jelly Bean and higher uses the
* expanded notification by default.
*
* @author Andrew Neal (andrewdneal@gmail.com)
*/
@SuppressLint("NewApi")
public class NotificationHelper {
/**
* Notification ID
*/
private static final int ELEVEN_MUSIC_SERVICE = 1;
/**
* NotificationManager
*/
private final NotificationManager mNotificationManager;
/**
* Context
*/
private final MusicPlaybackService mService;
/**
* Custom notification layout
*/
private RemoteViews mNotificationTemplate;
/**
* The Notification
*/
private Notification mNotification = null;
/**
* API 16+ bigContentView
*/
private RemoteViews mExpandedView;
/**
* Constructor of <code>NotificationHelper</code>
*
* @param service The {@link Context} to use
*/
public NotificationHelper(final MusicPlaybackService service) {
mService = service;
mNotificationManager = (NotificationManager)service
.getSystemService(Context.NOTIFICATION_SERVICE);
}
/**
* Call this to build the {@link Notification}.
*/
public void buildNotification(final String albumName, final String artistName,
final String trackName, final Long albumId, final Bitmap albumArt,
final boolean isPlaying) {
// Default notfication layout
mNotificationTemplate = new RemoteViews(mService.getPackageName(),
R.layout.notification_template_base);
// Set up the content view
initCollapsedLayout(trackName, artistName, albumArt);
// Notification Builder
// TODO: Add back a beter small icon when we have time
mNotification = new NotificationCompat.Builder(mService)
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(getPendingIntent())
.setPriority(Notification.PRIORITY_DEFAULT)
.setContent(mNotificationTemplate)
.build();
// Control playback from the notification
initPlaybackActions(isPlaying);
if (ApolloUtils.hasJellyBean()) {
// Expanded notifiction style
mExpandedView = new RemoteViews(mService.getPackageName(),
R.layout.notification_template_expanded_base);
mNotification.bigContentView = mExpandedView;
// Control playback from the notification
initExpandedPlaybackActions(isPlaying);