Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (c) 2016, The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.car.stream.media;
     17 
     18 import android.content.BroadcastReceiver;
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.IntentFilter;
     22 import android.graphics.Bitmap;
     23 import android.media.session.PlaybackState;
     24 import android.util.Log;
     25 import android.view.KeyEvent;
     26 import com.android.car.stream.StreamCard;
     27 import com.android.car.stream.StreamProducer;
     28 
     29 /**
     30  * Produces {@link StreamCard} on media playback or metadata changes.
     31  */
     32 public class MediaStreamProducer extends StreamProducer
     33         implements MediaPlaybackMonitor.MediaPlaybackMonitorListener {
     34     private static final String TAG = "MediaStreamProducer";
     35 
     36     private MediaPlaybackMonitor mPlaybackMonitor;
     37     private MediaStateManager mMediaStateManager;
     38     private MediaKeyReceiver mMediaKeyReceiver;
     39     private MediaConverter mConverter;
     40 
     41     private StreamCard mCurrentMediaStreamCard;
     42 
     43     private boolean mHasReceivedPlaybackState;
     44     private boolean mHasReceivedMetadata;
     45 
     46     // Current playback state of the media session.
     47     private boolean mIsPlaying;
     48     private boolean mHasPause;
     49     private boolean mCanSkipToNext;
     50     private boolean mCanSkipToPrevious;
     51 
     52     private String mTitle;
     53     private String mSubtitle;
     54     private Bitmap mAlbumArt;
     55     private int mAppAccentColor;
     56     private String mAppName;
     57 
     58     public MediaStreamProducer(Context context) {
     59         super(context);
     60         mConverter = new MediaConverter(context);
     61     }
     62 
     63     @Override
     64     public void start() {
     65         super.start();
     66         mPlaybackMonitor = new MediaPlaybackMonitor(mContext,
     67                 MediaStreamProducer.this /* MediaPlaybackMonitorListener */);
     68         mPlaybackMonitor.start();
     69 
     70         mMediaKeyReceiver = new MediaKeyReceiver();
     71         mContext.registerReceiver(mMediaKeyReceiver,
     72                 new IntentFilter(Intent.ACTION_MEDIA_BUTTON));
     73 
     74         mMediaStateManager = new MediaStateManager(mContext);
     75         mMediaStateManager.addListener(mPlaybackMonitor);
     76         mMediaStateManager.start();
     77     }
     78 
     79     @Override
     80     public void stop() {
     81         mPlaybackMonitor.stop();
     82         mMediaStateManager.destroy();
     83 
     84         mPlaybackMonitor = null;
     85         mMediaStateManager = null;
     86 
     87         mContext.unregisterReceiver(mMediaKeyReceiver);
     88         mMediaKeyReceiver = null;
     89         super.stop();
     90     }
     91 
     92     private class MediaKeyReceiver extends BroadcastReceiver {
     93         @Override
     94         public void onReceive(Context context, Intent intent) {
     95             String intentAction = intent.getAction();
     96             if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
     97                 KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
     98                 if (event == null) {
     99                     return;
    100                 }
    101                 if (Log.isLoggable(TAG, Log.DEBUG)) {
    102                     Log.d(TAG, "Received media key " + event.getKeyCode());
    103                 }
    104                 mMediaStateManager.dispatchMediaButton(event);
    105             }
    106         }
    107     }
    108 
    109     public void onPlaybackStateChanged(PlaybackState state) {
    110         //Some media apps tend to spam playback state changes. Check if the playback state changes
    111         // are relevant. If it is the same, don't bother updating and posting to the stream.
    112         if (isDuplicatePlaybackState(state)) {
    113             return;
    114         }
    115 
    116         int playbackState = state.getState();
    117         mHasPause = ((state.getActions() & PlaybackState.ACTION_PAUSE) != 0);
    118         if (!mHasPause) {
    119             mHasPause = ((state.getActions() & PlaybackState.ACTION_PLAY_PAUSE) != 0);
    120         }
    121         mCanSkipToNext = ((state.getActions() & PlaybackState.ACTION_SKIP_TO_NEXT) != 0);
    122         mCanSkipToPrevious = ((state.getActions() & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
    123         if (playbackState == PlaybackState.STATE_PLAYING
    124                 || playbackState == PlaybackState.STATE_BUFFERING) {
    125             mIsPlaying = true;
    126         }  else {
    127             mIsPlaying = false;
    128         }
    129         mHasReceivedPlaybackState = true;
    130         maybeUpdateStreamCard();
    131     }
    132 
    133     private void maybeUpdateStreamCard() {
    134         if (mHasReceivedPlaybackState && mHasReceivedMetadata) {
    135             mCurrentMediaStreamCard = mConverter.convert(mTitle, mSubtitle, mAlbumArt,
    136                     mAppAccentColor, mAppName, mCanSkipToNext, mCanSkipToPrevious,
    137                     mHasPause, mIsPlaying);
    138             if (mCurrentMediaStreamCard == null) {
    139                 Log.w(TAG, "Media Card was not created");
    140                 return;
    141             }
    142 
    143             if (Log.isLoggable(TAG, Log.DEBUG)) {
    144                 Log.d(TAG, "Media Card posted");
    145             }
    146             postCard(mCurrentMediaStreamCard);
    147         }
    148     }
    149 
    150     public void onMetadataChanged(String title, String subtitle, Bitmap albumArt, int color,
    151             String appName) {
    152         //Some media apps tend to spam metadata state changes. Check if the playback state changes
    153         // are relevant. If it is the same, don't bother updating and posting to the stream.
    154         if (isSameString(title, mTitle)
    155                 && isSameString(subtitle, mSubtitle)
    156                 && isSameBitmap(albumArt, albumArt)
    157                 && color == mAppAccentColor
    158                 && isSameString(appName, mAppName)) {
    159             return;
    160         }
    161 
    162         if (Log.isLoggable(TAG, Log.DEBUG)) {
    163             Log.d(TAG, "Update notification.");
    164         }
    165 
    166         mTitle = title;
    167         mSubtitle = subtitle;
    168         mAlbumArt = albumArt;
    169         mAppAccentColor = color;
    170         mAppName = appName;
    171 
    172         mHasReceivedMetadata = true;
    173         maybeUpdateStreamCard();
    174     }
    175 
    176     private boolean isDuplicatePlaybackState(PlaybackState state) {
    177         if (!mHasReceivedPlaybackState) {
    178             return false;
    179         }
    180         int playbackState = state.getState();
    181 
    182         boolean hasPause
    183                 = ((state.getActions() & PlaybackState.ACTION_PAUSE) != 0);
    184         boolean canSkipToNext
    185                 = ((state.getActions() & PlaybackState.ACTION_SKIP_TO_NEXT) != 0);
    186         boolean canSkipToPrevious
    187                 = ((state.getActions() & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0);
    188 
    189         boolean isPlaying = playbackState == PlaybackState.STATE_PLAYING
    190                 || playbackState == PlaybackState.STATE_BUFFERING;
    191 
    192         return (hasPause == mHasPause
    193                 && canSkipToNext == mCanSkipToNext
    194                 && canSkipToPrevious == mCanSkipToPrevious
    195                 && isPlaying == mIsPlaying);
    196     }
    197 
    198     @Override
    199     public void onAlbumArtUpdated(Bitmap albumArt) {
    200         mAlbumArt = albumArt;
    201         maybeUpdateStreamCard();
    202     }
    203 
    204     @Override
    205     public void onNewAppConnected() {
    206         mHasReceivedMetadata = false;
    207         mHasReceivedPlaybackState = false;
    208         removeCard(mCurrentMediaStreamCard);
    209         mCurrentMediaStreamCard = null;
    210 
    211         // clear out all existing values
    212         mTitle = null;
    213         mSubtitle = null;
    214         mAlbumArt = null;
    215         mAppName = null;
    216         mAppAccentColor = 0;
    217         mCanSkipToNext = false;
    218         mCanSkipToPrevious = false;
    219         mHasPause = false;
    220         mIsPlaying = false;
    221         mIsPlaying = false;
    222     }
    223 
    224     @Override
    225     public void removeMediaStreamCard() {
    226         removeCard(mCurrentMediaStreamCard);
    227         mCurrentMediaStreamCard = null;
    228     }
    229 
    230     private boolean isSameBitmap(Bitmap bmp1, Bitmap bmp2) {
    231         return bmp1 == null
    232                 ? bmp2 == null : (bmp1 == bmp2 && bmp1.getGenerationId() == bmp2.getGenerationId());
    233     }
    234 
    235     private boolean isSameString(CharSequence str1, CharSequence str2) {
    236         return str1 == null ? str2 == null : str1.equals(str2);
    237     }
    238 }
    239