diff options
author | Daniel Sandler <dsandler@android.com> | 2014-06-05 02:54:13 -0400 |
---|---|---|
committer | RoboErik <epastern@google.com> | 2014-08-14 16:18:15 -0700 |
commit | 45f7ee8201efbda59b57b1fe637a1b9ffef25bb6 (patch) | |
tree | 3cae0465b42aeca12ed8ae7864592c3d40cbe01b /tests/OneMedia/src | |
parent | 1d2a1c917f46b6854e91f9867a20abb76ecb794d (diff) | |
download | frameworks_base-45f7ee8201efbda59b57b1fe637a1b9ffef25bb6.zip frameworks_base-45f7ee8201efbda59b57b1fe637a1b9ffef25bb6.tar.gz frameworks_base-45f7ee8201efbda59b57b1fe637a1b9ffef25bb6.tar.bz2 |
Add metadata and notifications to OneMedia
This wires up a notification and some basic metadata for testing
in OneMedia.
Change-Id: I0f2e922536c85caa63f66dae7deb55ffe94fe231
Diffstat (limited to 'tests/OneMedia/src')
7 files changed, 376 insertions, 9 deletions
diff --git a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl index d4df4c5..f53eac0 100644 --- a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl +++ b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl @@ -15,6 +15,7 @@ package com.android.onemedia; +import android.graphics.Bitmap; import android.media.session.MediaSession; import android.os.Bundle; @@ -26,4 +27,5 @@ interface IPlayerService { void registerCallback(in IPlayerCallback cb); void unregisterCallback(in IPlayerCallback cb); void sendRequest(String action, in Bundle params, in IRequestCallback cb); + void setIcon(in Bitmap icon); } diff --git a/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java b/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java new file mode 100644 index 0000000..a5bcda5 --- /dev/null +++ b/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java @@ -0,0 +1,234 @@ +package com.android.onemedia; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Bitmap; +import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.text.format.DateUtils; +import android.util.Log; +import android.util.SparseArray; + +import com.android.onemedia.playback.RequestUtils; + +/** + * Keeps track of a notification and updates it automatically for a given + * MediaSession. + */ +public class NotificationHelper extends BroadcastReceiver { + private static final String TAG = "NotificationHelper"; + + private static final int NOTIFICATION_ID = 433; // John Cage, 1952 + + private final Service mService; + private final MediaSession mSession; + private final MediaController mController; + private final MediaController.TransportControls mTransportControls; + private final SparseArray<PendingIntent> mIntents = new SparseArray<PendingIntent>(); + + private PlaybackState mPlaybackState; + private MediaMetadata mMetadata; + + private boolean mStarted = false; + + public NotificationHelper(Service service, MediaSession session) { + mService = service; + mSession = session; + mController = session.getController(); + mTransportControls = mController.getTransportControls(); + String pkg = mService.getPackageName(); + + mIntents.put(R.drawable.ic_pause, PendingIntent.getBroadcast(mService, 100, new Intent( + com.android.onemedia.playback.RequestUtils.ACTION_PAUSE).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + mIntents.put(R.drawable.ic_play_arrow, PendingIntent.getBroadcast(mService, 100, + new Intent(com.android.onemedia.playback.RequestUtils.ACTION_PLAY).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + mIntents.put(R.drawable.ic_skip_previous, PendingIntent.getBroadcast(mService, 100, + new Intent(com.android.onemedia.playback.RequestUtils.ACTION_PREV).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + mIntents.put(R.drawable.ic_skip_next, PendingIntent.getBroadcast(mService, 100, + new Intent(com.android.onemedia.playback.RequestUtils.ACTION_NEXT).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + mIntents.put(R.drawable.ic_fast_rewind, PendingIntent.getBroadcast(mService, 100, + new Intent(com.android.onemedia.playback.RequestUtils.ACTION_REW).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + mIntents.put(R.drawable.ic_fast_forward, PendingIntent.getBroadcast(mService, 100, + new Intent(com.android.onemedia.playback.RequestUtils.ACTION_FFWD).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + } + + /** + * Posts the notification and starts tracking the session to keep it + * updated. The notification will automatically be removed if the session is + * destroyed before {@link #onStop} is called. + */ + public void onStart() { + mController.addCallback(mCb); + IntentFilter filter = new IntentFilter(); + filter.addAction(RequestUtils.ACTION_FFWD); + filter.addAction(RequestUtils.ACTION_NEXT); + filter.addAction(RequestUtils.ACTION_PAUSE); + filter.addAction(RequestUtils.ACTION_PLAY); + filter.addAction(RequestUtils.ACTION_PREV); + filter.addAction(RequestUtils.ACTION_REW); + mService.registerReceiver(this, filter); + + mMetadata = mController.getMetadata(); + mPlaybackState = mController.getPlaybackState(); + + mStarted = true; + // The notification must be updated after setting started to true + updateNotification(); + } + + /** + * Removes the notification and stops tracking the session. If the session + * was destroyed this has no effect. + */ + public void onStop() { + mStarted = false; + mController.removeCallback(mCb); + mService.unregisterReceiver(this); + updateNotification(); + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + Log.d(TAG, "Received intent with action " + action); + if (RequestUtils.ACTION_PAUSE.equals(action)) { + mTransportControls.pause(); + } else if (RequestUtils.ACTION_PLAY.equals(action)) { + mTransportControls.play(); + } else if (RequestUtils.ACTION_NEXT.equals(action)) { + mTransportControls.skipToNext(); + } else if (RequestUtils.ACTION_PREV.equals(action)) { + mTransportControls.skipToPrevious(); + } else if (RequestUtils.ACTION_REW.equals(action)) { + mTransportControls.rewind(); + } else if (RequestUtils.ACTION_FFWD.equals(action)) { + mTransportControls.fastForward(); + } + + } + + private final MediaController.Callback mCb = new MediaController.Callback() { + @Override + public void onPlaybackStateChanged(PlaybackState state) { + mPlaybackState = state; + Log.d(TAG, "Received new playback state" + state); + updateNotification(); + } + + @Override + public void onMetadataChanged(MediaMetadata metadata) { + mMetadata = metadata; + Log.d(TAG, "Received new metadata " + metadata); + updateNotification(); + } + }; + + NotificationManager mNoMan = null; + + private void updateNotification() { + if (mNoMan == null) { + mNoMan = (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE); + } + if (mPlaybackState == null) { + mNoMan.cancel(NOTIFICATION_ID); + return; + } + if (!mStarted) { + mNoMan.cancel(NOTIFICATION_ID); + return; + } + + String status; + final int state = mPlaybackState.getState(); + switch (state) { + case PlaybackState.STATE_PLAYING: + status = "PLAYING: "; + break; + case PlaybackState.STATE_PAUSED: + status = "PAUSED: "; + break; + case PlaybackState.STATE_STOPPED: + status = "STOPPED: "; + break; + case PlaybackState.STATE_ERROR: + status = "ERROR: "; + break; + case PlaybackState.STATE_BUFFERING: + status = "BUFFERING: "; + break; + case PlaybackState.STATE_NONE: + default: + status = ""; + break; + } + CharSequence title, text; + Bitmap art; + if (mMetadata == null) { + title = status; + text = "Empty metadata!"; + art = null; + } else { + MediaMetadata.Description description = mMetadata.getDescription(); + title = description.getTitle(); + text = description.getSubtitle(); + art = description.getIcon(); + } + + String playPauseLabel = ""; + int playPauseIcon; + if (state == PlaybackState.STATE_PLAYING) { + playPauseLabel = "Pause"; + playPauseIcon = R.drawable.ic_pause; + } else { + playPauseLabel = "Play"; + playPauseIcon = R.drawable.ic_play_arrow; + } + + final long pos = mPlaybackState.getPosition(); + final long end = mMetadata == null ? 0 : mMetadata + .getLong(MediaMetadata.METADATA_KEY_DURATION); + Notification notification = new Notification.Builder(mService) + .setSmallIcon(android.R.drawable.stat_notify_chat) + .setContentTitle(title) + .setContentText(text) + .setShowWhen(false) + .setContentInfo(DateUtils.formatElapsedTime(pos)) + .setProgress((int) end, (int) pos, false) + .setLargeIcon(art) + .addAction(R.drawable.ic_skip_previous, "Previous", + mIntents.get(R.drawable.ic_skip_previous)) + .addAction(R.drawable.ic_fast_rewind, "Rewind", + mIntents.get(R.drawable.ic_fast_rewind)) + .addAction(playPauseIcon, playPauseLabel, + mIntents.get(playPauseIcon)) + .addAction(R.drawable.ic_fast_forward, "Fast Forward", + mIntents.get(R.drawable.ic_fast_forward)) + .addAction(R.drawable.ic_skip_next, "Next", + mIntents.get(R.drawable.ic_skip_next)) + .setStyle(new Notification.MediaStyle() + .setShowActionsInCompactView(2) + .setMediaSession(mSession.getSessionToken())) + .setColor(0xFFDB4437) + .build(); + + mService.startForeground(NOTIFICATION_ID, notification); + } + +} diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java index 894377b..2ff3e20 100644 --- a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java +++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java @@ -17,20 +17,35 @@ package com.android.onemedia; import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.media.MediaMetadata; import android.media.session.PlaybackState; +import android.net.Uri; import android.os.Bundle; +import android.provider.MediaStore; +import android.text.format.DateUtils; import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; +import android.widget.ImageView; import android.widget.TextView; +import java.io.IOException; + public class OnePlayerActivity extends Activity { private static final String TAG = "OnePlayerActivity"; + private static final int READ_REQUEST_CODE = 42; + protected PlayerController mPlayer; private Button mStartButton; @@ -41,8 +56,10 @@ public class OnePlayerActivity extends Activity { private EditText mContentText; private EditText mNextContentText; private CheckBox mHasVideo; + private ImageView mArtView; - private int mPlaybackState; + private PlaybackState mPlaybackState; + private Bitmap mAlbumArtBitmap; @Override protected void onCreate(Bundle savedInstanceState) { @@ -58,6 +75,10 @@ public class OnePlayerActivity extends Activity { mContentText = (EditText) findViewById(R.id.content); mNextContentText = (EditText) findViewById(R.id.next_content); mHasVideo = (CheckBox) findViewById(R.id.has_video); + mArtView = (ImageView) findViewById(R.id.art); + + final Button artPicker = (Button) findViewById(R.id.art_picker); + artPicker.setOnClickListener(mButtonListener); mStartButton.setOnClickListener(mButtonListener); mPlayButton.setOnClickListener(mButtonListener); @@ -86,6 +107,31 @@ public class OnePlayerActivity extends Activity { super.onPause(); } + @Override + public void onActivityResult(int requestCode, int resultCode, + Intent resultData) { + if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + Uri uri = null; + if (resultData != null) { + uri = resultData.getData(); + Log.i(TAG, "Uri: " + uri.toString()); + mAlbumArtBitmap = null; + try { + mAlbumArtBitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri); + } catch (IOException e) { + Log.v(TAG, "Couldn't load album art", e); + } + mArtView.setImageBitmap(mAlbumArtBitmap); + if (mAlbumArtBitmap != null) { + mArtView.setVisibility(View.VISIBLE); + } else { + mArtView.setVisibility(View.GONE); + } + mPlayer.setArt(mAlbumArtBitmap); + } + } + } + private void setControlsEnabled(boolean enabled) { mStartButton.setEnabled(enabled); mPlayButton.setEnabled(enabled); @@ -94,36 +140,46 @@ public class OnePlayerActivity extends Activity { private View.OnClickListener mButtonListener = new View.OnClickListener() { @Override public void onClick(View v) { + final int state = mPlaybackState.getState(); switch (v.getId()) { case R.id.play_button: - Log.d(TAG, "Play button pressed, in state " + mPlaybackState); - if (mPlaybackState == PlaybackState.STATE_PAUSED - || mPlaybackState == PlaybackState.STATE_STOPPED) { + Log.d(TAG, "Play button pressed, in state " + state); + if (state == PlaybackState.STATE_PAUSED + || state == PlaybackState.STATE_STOPPED) { mPlayer.play(); - } else if (mPlaybackState == PlaybackState.STATE_PLAYING) { + } else if (state == PlaybackState.STATE_PLAYING) { mPlayer.pause(); } break; case R.id.start_button: - Log.d(TAG, "Start button pressed, in state " + mPlaybackState); + Log.d(TAG, "Start button pressed, in state " + state); mPlayer.setContent(mContentText.getText().toString()); break; case R.id.route_button: mPlayer.showRoutePicker(); break; + case R.id.art_picker: + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("image/*"); + + startActivityForResult(intent, READ_REQUEST_CODE); + break; } } }; private PlayerController.Listener mListener = new PlayerController.Listener() { + public MediaMetadata mMetadata; + @Override public void onPlaybackStateChange(PlaybackState state) { - mPlaybackState = state.getState(); + mPlaybackState = state; boolean enablePlay = false; boolean enableControls = true; StringBuilder statusBuilder = new StringBuilder(); - switch (mPlaybackState) { + switch (mPlaybackState.getState()) { case PlaybackState.STATE_PLAYING: statusBuilder.append("playing"); mPlayButton.setText("Pause"); @@ -172,7 +228,7 @@ public class OnePlayerActivity extends Activity { @Override public void onMetadataChange(MediaMetadata metadata) { - Log.d(TAG, "Metadata update! Title: " + metadata); + mMetadata = metadata; } }; } diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java index c0799fc..c8d72ca 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java @@ -30,6 +30,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.graphics.Bitmap; import android.util.Log; import com.android.onemedia.playback.RequestUtils; @@ -52,6 +53,7 @@ public class PlayerController { private Handler mHandler = new Handler(); private boolean mResumed; + private Bitmap mArt; public PlayerController(Activity context, Intent serviceIntent) { mContext = context; @@ -89,6 +91,16 @@ public class PlayerController { unbindFromService(); } + public void setArt(Bitmap art) { + mArt = art; + if (mBinder != null) { + try { + mBinder.setIcon(art); + } catch (RemoteException e) { + } + } + } + public void play() { if (mTransportControls != null) { mTransportControls.play(); @@ -125,6 +137,16 @@ public class PlayerController { // TODO } + public MediaSession.Token getSessionToken() { + if (mBinder != null) { + try { + return mBinder.getSessionToken(); + } catch (RemoteException e) { + } + } + return null; + } + private void unbindFromService() { mContext.unbindService(mServiceConnection); } @@ -165,6 +187,9 @@ public class PlayerController { mContext.setMediaController(mController); mController.addCallback(mControllerCb, mHandler); mTransportControls = mController.getTransportControls(); + if (mArt != null) { + setArt(mArt); + } Log.d(TAG, "Ready to use PlayerService"); if (mListener != null) { @@ -194,6 +219,9 @@ public class PlayerController { return; } Log.d(TAG, "Received metadata change, " + metadata.getDescription()); + if (mListener != null) { + mListener.onMetadataChange(metadata); + } } } diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerService.java b/tests/OneMedia/src/com/android/onemedia/PlayerService.java index 58ee4a1..9967c99 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerService.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerService.java @@ -17,6 +17,7 @@ package com.android.onemedia; import android.app.Service; import android.content.Intent; +import android.graphics.Bitmap; import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.os.Bundle; @@ -34,6 +35,7 @@ public class PlayerService extends Service { private PlayerBinder mBinder; private PlayerSession mSession; + private NotificationHelper mNotifyHelper; private Intent mIntent; private boolean mStarted = false; @@ -47,6 +49,7 @@ public class PlayerService extends Service { mSession = onCreatePlayerController(); mSession.createSession(); mSession.setListener(mPlayerListener); + mNotifyHelper = new NotificationHelper(this, mSession.mSession); } } @@ -75,6 +78,7 @@ public class PlayerService extends Service { if (!mStarted) { Log.d(TAG, "Starting self"); startService(onCreateServiceIntent()); + mNotifyHelper.onStart(); mStarted = true; } } @@ -82,6 +86,7 @@ public class PlayerService extends Service { public void onPlaybackEnded() { if (mStarted) { Log.d(TAG, "Stopping self"); + mNotifyHelper.onStop(); stopSelf(); mStarted = false; } @@ -150,8 +155,17 @@ public class PlayerService extends Service { @Override public MediaSession.Token getSessionToken() throws RemoteException { + if (mSession == null) { + Log.e(TAG, "Error in PlayerService: mSession=null in getSessionToken()"); + return null; + } return mSession.getSessionToken(); } + + @Override + public void setIcon(Bitmap icon) { + mSession.setIcon(icon); + } } } diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java index 890d68d..9afcf24 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java @@ -17,6 +17,8 @@ package com.android.onemedia; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; +import android.media.MediaMetadata; import android.media.routing.MediaRouteSelector; import android.media.routing.MediaRouter; import android.media.routing.MediaRouter.ConnectionRequest; @@ -50,6 +52,7 @@ public class PlayerSession { protected Renderer mRenderer; protected MediaSession.Callback mCallback; protected Renderer.Listener mRenderListener; + protected MediaMetadata.Builder mMetadataBuilder; protected PlaybackState mPlaybackState; protected Listener mListener; @@ -66,6 +69,8 @@ public class PlayerSession { mPlaybackState = psBob.build(); mRenderer.registerListener(mRenderListener); + + initMetadata(); } public void createSession() { @@ -92,6 +97,7 @@ public class PlayerSession { | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); mSession.setMediaRouter(mRouter); mSession.setActive(true); + updateMetadata(); } public void onDestroy() { @@ -130,6 +136,19 @@ public class PlayerSession { mRenderer.setNextContent(request); } + public void setIcon(Bitmap icon) { + mMetadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, icon); + updateMetadata(); + } + + private void updateMetadata() { + // This is a mild abuse of metadata and shouldn't be duplicated in real + // code + if (mSession != null && mSession.isActive()) { + mSession.setMetadata(mMetadataBuilder.build()); + } + } + private void updateState(int newState) { float rate = newState == PlaybackState.STATE_PLAYING ? 1 : 0; long position = mRenderer == null ? -1 : mRenderer.getSeekPosition(); @@ -140,6 +159,14 @@ public class PlayerSession { mSession.setPlaybackState(mPlaybackState); } + private void initMetadata() { + mMetadataBuilder = new MediaMetadata.Builder(); + mMetadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, + "OneMedia display title"); + mMetadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, + "OneMedia display subtitle"); + } + public interface Listener { public void onPlayStateChanged(PlaybackState state); } diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java index 3778c5f..1688395 100644 --- a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java +++ b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java @@ -26,6 +26,12 @@ import java.util.Map; public class RequestUtils { public static final String ACTION_SET_CONTENT = "set_content"; public static final String ACTION_SET_NEXT_CONTENT = "set_next_content"; + public static final String ACTION_PAUSE = "com.android.onemedia.pause"; + public static final String ACTION_PLAY = "com.android.onemedia.play"; + public static final String ACTION_REW = "com.android.onemedia.rew"; + public static final String ACTION_FFWD = "com.android.onemedia.ffwd"; + public static final String ACTION_PREV = "com.android.onemedia.prev"; + public static final String ACTION_NEXT = "com.android.onemedia.next"; public static final String EXTRA_KEY_SOURCE = "source"; public static final String EXTRA_KEY_METADATA = "metadata"; |