blob: 8808f14118e9a40f7ed7ec67889bfb085237c6fd [file] [log] [blame]
/*
* Copyright (c) 2016, 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.
*/
package com.android.car.stream.media;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.media.session.PlaybackState;
import android.util.Log;
import android.view.KeyEvent;
import com.android.car.stream.StreamCard;
import com.android.car.stream.StreamProducer;
/**
* Produces {@link StreamCard} on media playback or metadata changes.
*/
public class MediaStreamProducer extends StreamProducer
implements MediaPlaybackMonitor.MediaPlaybackMonitorListener {
private static final String TAG = "MediaStreamProducer";
private MediaPlaybackMonitor mPlaybackMonitor;
private MediaStateManager mMediaStateManager;
private MediaKeyReceiver mMediaKeyReceiver;
private MediaConverter mConverter;
private StreamCard mCurrentMediaStreamCard;
private boolean mHasReceivedPlaybackState;
private boolean mHasReceivedMetadata;
// Current playback state of the media session.
private boolean mIsPlaying;
private boolean mHasPause;
private boolean mCanSkipToNext;
private boolean mCanSkipToPrevious;
private String mTitle;
private String mSubtitle;
private Bitmap mAlbumArt;
private int mAppAccentColor;
private String mAppName;
public MediaStreamProducer(Context context) {
super(context);
mConverter = new MediaConverter(context);
}
@Override
public void start() {
super.start();
mPlaybackMonitor = new MediaPlaybackMonitor(mContext,
MediaStreamProducer.this /* MediaPlaybackMonitorListener */);
mPlaybackMonitor.start();
mMediaKeyReceiver = new MediaKeyReceiver();
mContext.registerReceiver(mMediaKeyReceiver,
new IntentFilter(Intent.ACTION_MEDIA_BUTTON));
mMediaStateManager = new MediaStateManager(mContext);
mMediaStateManager.addListener(mPlaybackMonitor);
mMediaStateManager.start();
}
@Override
public void stop() {
mPlaybackMonitor.stop();
mMediaStateManager.destroy();
mPlaybackMonitor = null;
mMediaStateManager = null;
mContext.unregisterReceiver(mMediaKeyReceiver);
mMediaKeyReceiver = null;
super.stop();
}
private class MediaKeyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String intentAction = intent.getAction();
if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event == null) {
return;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Received media key " + event.getKeyCode());
}
mMediaStateManager.dispatchMediaButton(event);
}
}
}
public void onPlaybackStateChanged(PlaybackState state) {
//Some media apps tend to spam playback state changes. Check if the playback state changes
// are relevant. If it is the same, don't bother updating and posting to the stream.
if (isDuplicatePlaybackState(state)) {
return;
}
int playbackState = state.getState();
mHasPause = ((state.getActions() & PlaybackState.ACTION_PAUSE) != 0);
if (!mHasPause) {
mHasPause = ((state.getActions() & PlaybackState.ACTION_PLAY_PAUSE) != 0);
}
mCanSkipToNext = ((state.getActions() & PlaybackState.ACTION_SKIP_TO_NEXT) != 0);
mCanSkipToPrevious = ((state.getActions() & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
if (playbackState == PlaybackState.STATE_PLAYING
|| playbackState == PlaybackState.STATE_BUFFERING) {
mIsPlaying = true;
} else {
mIsPlaying = false;
}
mHasReceivedPlaybackState = true;
maybeUpdateStreamCard();
}
private void maybeUpdateStreamCard() {
if (mHasReceivedPlaybackState && mHasReceivedMetadata) {
mCurrentMediaStreamCard = mConverter.convert(mTitle, mSubtitle, mAlbumArt,
mAppAccentColor, mAppName, mCanSkipToNext, mCanSkipToPrevious,
mHasPause, mIsPlaying);
if (mCurrentMediaStreamCard == null) {
Log.w(TAG, "Media Card was not created");
return;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Media Card posted");
}
postCard(mCurrentMediaStreamCard);
}
}
public void onMetadataChanged(String title, String subtitle, Bitmap albumArt, int color,
String appName) {
//Some media apps tend to spam metadata state changes. Check if the playback state changes
// are relevant. If it is the same, don't bother updating and posting to the stream.
if (isSameString(title, mTitle)
&& isSameString(subtitle, mSubtitle)
&& isSameBitmap(albumArt, albumArt)
&& color == mAppAccentColor
&& isSameString(appName, mAppName)) {
return;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Update notification.");
}
mTitle = title;
mSubtitle = subtitle;
mAlbumArt = albumArt;
mAppAccentColor = color;
mAppName = appName;
mHasReceivedMetadata = true;
maybeUpdateStreamCard();
}
private boolean isDuplicatePlaybackState(PlaybackState state) {
if (!mHasReceivedPlaybackState) {
return false;
}
int playbackState = state.getState();
boolean hasPause
= ((state.getActions() & PlaybackState.ACTION_PAUSE) != 0);
boolean canSkipToNext
= ((state.getActions() & PlaybackState.ACTION_SKIP_TO_NEXT) != 0);
boolean canSkipToPrevious
= ((state.getActions() & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
boolean isPlaying = playbackState == PlaybackState.STATE_PLAYING
|| playbackState == PlaybackState.STATE_BUFFERING;
return (hasPause == mHasPause
&& canSkipToNext == mCanSkipToNext
&& canSkipToPrevious == mCanSkipToPrevious
&& isPlaying == mIsPlaying);
}
@Override
public void onAlbumArtUpdated(Bitmap albumArt) {
mAlbumArt = albumArt;
maybeUpdateStreamCard();
}
@Override
public void onNewAppConnected() {
mHasReceivedMetadata = false;
mHasReceivedPlaybackState = false;
removeCard(mCurrentMediaStreamCard);
mCurrentMediaStreamCard = null;
// clear out all existing values
mTitle = null;
mSubtitle = null;
mAlbumArt = null;
mAppName = null;
mAppAccentColor = 0;
mCanSkipToNext = false;
mCanSkipToPrevious = false;
mHasPause = false;
mIsPlaying = false;
mIsPlaying = false;
}
@Override
public void removeMediaStreamCard() {
removeCard(mCurrentMediaStreamCard);
mCurrentMediaStreamCard = null;
}
private boolean isSameBitmap(Bitmap bmp1, Bitmap bmp2) {
return bmp1 == null
? bmp2 == null : (bmp1 == bmp2 && bmp1.getGenerationId() == bmp2.getGenerationId());
}
private boolean isSameString(CharSequence str1, CharSequence str2) {
return str1 == null ? str2 == null : str1.equals(str2);
}
}