• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MediaStreamProducer(Context context)58     public MediaStreamProducer(Context context) {
59         super(context);
60         mConverter = new MediaConverter(context);
61     }
62 
63     @Override
start()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
stop()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
onReceive(Context context, Intent intent)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 
onPlaybackStateChanged(PlaybackState state)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 
maybeUpdateStreamCard()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 
onMetadataChanged(String title, String subtitle, Bitmap albumArt, int color, String appName)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 
isDuplicatePlaybackState(PlaybackState state)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
onAlbumArtUpdated(Bitmap albumArt)199     public void onAlbumArtUpdated(Bitmap albumArt) {
200         mAlbumArt = albumArt;
201         maybeUpdateStreamCard();
202     }
203 
204     @Override
onNewAppConnected()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
removeMediaStreamCard()225     public void removeMediaStreamCard() {
226         removeCard(mCurrentMediaStreamCard);
227         mCurrentMediaStreamCard = null;
228     }
229 
isSameBitmap(Bitmap bmp1, Bitmap bmp2)230     private boolean isSameBitmap(Bitmap bmp1, Bitmap bmp2) {
231         return bmp1 == null
232                 ? bmp2 == null : (bmp1 == bmp2 && bmp1.getGenerationId() == bmp2.getGenerationId());
233     }
234 
isSameString(CharSequence str1, CharSequence str2)235     private boolean isSameString(CharSequence str1, CharSequence str2) {
236         return str1 == null ? str2 == null : str1.equals(str2);
237     }
238 }
239