1*90c8c64dSAndroid Build Coastguard Worker /* 2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2014 The Android Open Source Project 3*90c8c64dSAndroid Build Coastguard Worker * 4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*90c8c64dSAndroid Build Coastguard Worker * 8*90c8c64dSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*90c8c64dSAndroid Build Coastguard Worker * 10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License. 15*90c8c64dSAndroid Build Coastguard Worker */ 16*90c8c64dSAndroid Build Coastguard Worker 17*90c8c64dSAndroid Build Coastguard Worker package com.example.android.mediabrowserservice; 18*90c8c64dSAndroid Build Coastguard Worker 19*90c8c64dSAndroid Build Coastguard Worker import android.app.Notification; 20*90c8c64dSAndroid Build Coastguard Worker import android.app.NotificationManager; 21*90c8c64dSAndroid Build Coastguard Worker import android.app.PendingIntent; 22*90c8c64dSAndroid Build Coastguard Worker import android.content.BroadcastReceiver; 23*90c8c64dSAndroid Build Coastguard Worker import android.content.Context; 24*90c8c64dSAndroid Build Coastguard Worker import android.content.Intent; 25*90c8c64dSAndroid Build Coastguard Worker import android.content.IntentFilter; 26*90c8c64dSAndroid Build Coastguard Worker import android.graphics.Bitmap; 27*90c8c64dSAndroid Build Coastguard Worker import android.graphics.BitmapFactory; 28*90c8c64dSAndroid Build Coastguard Worker import android.graphics.Color; 29*90c8c64dSAndroid Build Coastguard Worker import android.media.MediaDescription; 30*90c8c64dSAndroid Build Coastguard Worker import android.media.MediaMetadata; 31*90c8c64dSAndroid Build Coastguard Worker import android.media.session.MediaController; 32*90c8c64dSAndroid Build Coastguard Worker import android.media.session.MediaSession; 33*90c8c64dSAndroid Build Coastguard Worker import android.media.session.PlaybackState; 34*90c8c64dSAndroid Build Coastguard Worker 35*90c8c64dSAndroid Build Coastguard Worker import com.example.android.mediabrowserservice.utils.LogHelper; 36*90c8c64dSAndroid Build Coastguard Worker import com.example.android.mediabrowserservice.utils.ResourceHelper; 37*90c8c64dSAndroid Build Coastguard Worker 38*90c8c64dSAndroid Build Coastguard Worker /** 39*90c8c64dSAndroid Build Coastguard Worker * Keeps track of a notification and updates it automatically for a given 40*90c8c64dSAndroid Build Coastguard Worker * MediaSession. Maintaining a visible notification (usually) guarantees that the music service 41*90c8c64dSAndroid Build Coastguard Worker * won't be killed during playback. 42*90c8c64dSAndroid Build Coastguard Worker */ 43*90c8c64dSAndroid Build Coastguard Worker public class MediaNotificationManager extends BroadcastReceiver { 44*90c8c64dSAndroid Build Coastguard Worker private static final String TAG = LogHelper.makeLogTag(MediaNotificationManager.class); 45*90c8c64dSAndroid Build Coastguard Worker 46*90c8c64dSAndroid Build Coastguard Worker private static final int NOTIFICATION_ID = 412; 47*90c8c64dSAndroid Build Coastguard Worker private static final int REQUEST_CODE = 100; 48*90c8c64dSAndroid Build Coastguard Worker 49*90c8c64dSAndroid Build Coastguard Worker public static final String ACTION_PAUSE = "com.example.android.mediabrowserservice.pause"; 50*90c8c64dSAndroid Build Coastguard Worker public static final String ACTION_PLAY = "com.example.android.mediabrowserservice.play"; 51*90c8c64dSAndroid Build Coastguard Worker public static final String ACTION_PREV = "com.example.android.mediabrowserservice.prev"; 52*90c8c64dSAndroid Build Coastguard Worker public static final String ACTION_NEXT = "com.example.android.mediabrowserservice.next"; 53*90c8c64dSAndroid Build Coastguard Worker 54*90c8c64dSAndroid Build Coastguard Worker private final MusicService mService; 55*90c8c64dSAndroid Build Coastguard Worker private MediaSession.Token mSessionToken; 56*90c8c64dSAndroid Build Coastguard Worker private MediaController mController; 57*90c8c64dSAndroid Build Coastguard Worker private MediaController.TransportControls mTransportControls; 58*90c8c64dSAndroid Build Coastguard Worker 59*90c8c64dSAndroid Build Coastguard Worker private PlaybackState mPlaybackState; 60*90c8c64dSAndroid Build Coastguard Worker private MediaMetadata mMetadata; 61*90c8c64dSAndroid Build Coastguard Worker 62*90c8c64dSAndroid Build Coastguard Worker private NotificationManager mNotificationManager; 63*90c8c64dSAndroid Build Coastguard Worker 64*90c8c64dSAndroid Build Coastguard Worker private PendingIntent mPauseIntent; 65*90c8c64dSAndroid Build Coastguard Worker private PendingIntent mPlayIntent; 66*90c8c64dSAndroid Build Coastguard Worker private PendingIntent mPreviousIntent; 67*90c8c64dSAndroid Build Coastguard Worker private PendingIntent mNextIntent; 68*90c8c64dSAndroid Build Coastguard Worker 69*90c8c64dSAndroid Build Coastguard Worker private int mNotificationColor; 70*90c8c64dSAndroid Build Coastguard Worker 71*90c8c64dSAndroid Build Coastguard Worker private boolean mStarted = false; 72*90c8c64dSAndroid Build Coastguard Worker MediaNotificationManager(MusicService service)73*90c8c64dSAndroid Build Coastguard Worker public MediaNotificationManager(MusicService service) { 74*90c8c64dSAndroid Build Coastguard Worker mService = service; 75*90c8c64dSAndroid Build Coastguard Worker updateSessionToken(); 76*90c8c64dSAndroid Build Coastguard Worker 77*90c8c64dSAndroid Build Coastguard Worker mNotificationColor = ResourceHelper.getThemeColor(mService, 78*90c8c64dSAndroid Build Coastguard Worker android.R.attr.colorPrimary, Color.DKGRAY); 79*90c8c64dSAndroid Build Coastguard Worker 80*90c8c64dSAndroid Build Coastguard Worker mNotificationManager = (NotificationManager) mService 81*90c8c64dSAndroid Build Coastguard Worker .getSystemService(Context.NOTIFICATION_SERVICE); 82*90c8c64dSAndroid Build Coastguard Worker 83*90c8c64dSAndroid Build Coastguard Worker String pkg = mService.getPackageName(); 84*90c8c64dSAndroid Build Coastguard Worker mPauseIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE, 85*90c8c64dSAndroid Build Coastguard Worker new Intent(ACTION_PAUSE).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT); 86*90c8c64dSAndroid Build Coastguard Worker mPlayIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE, 87*90c8c64dSAndroid Build Coastguard Worker new Intent(ACTION_PLAY).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT); 88*90c8c64dSAndroid Build Coastguard Worker mPreviousIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE, 89*90c8c64dSAndroid Build Coastguard Worker new Intent(ACTION_PREV).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT); 90*90c8c64dSAndroid Build Coastguard Worker mNextIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE, 91*90c8c64dSAndroid Build Coastguard Worker new Intent(ACTION_NEXT).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT); 92*90c8c64dSAndroid Build Coastguard Worker 93*90c8c64dSAndroid Build Coastguard Worker // Cancel all notifications to handle the case where the Service was killed and 94*90c8c64dSAndroid Build Coastguard Worker // restarted by the system. 95*90c8c64dSAndroid Build Coastguard Worker mNotificationManager.cancelAll(); 96*90c8c64dSAndroid Build Coastguard Worker } 97*90c8c64dSAndroid Build Coastguard Worker 98*90c8c64dSAndroid Build Coastguard Worker /** 99*90c8c64dSAndroid Build Coastguard Worker * Posts the notification and starts tracking the session to keep it 100*90c8c64dSAndroid Build Coastguard Worker * updated. The notification will automatically be removed if the session is 101*90c8c64dSAndroid Build Coastguard Worker * destroyed before {@link #stopNotification} is called. 102*90c8c64dSAndroid Build Coastguard Worker */ startNotification()103*90c8c64dSAndroid Build Coastguard Worker public void startNotification() { 104*90c8c64dSAndroid Build Coastguard Worker if (!mStarted) { 105*90c8c64dSAndroid Build Coastguard Worker mMetadata = mController.getMetadata(); 106*90c8c64dSAndroid Build Coastguard Worker mPlaybackState = mController.getPlaybackState(); 107*90c8c64dSAndroid Build Coastguard Worker 108*90c8c64dSAndroid Build Coastguard Worker // The notification must be updated after setting started to true 109*90c8c64dSAndroid Build Coastguard Worker Notification notification = createNotification(); 110*90c8c64dSAndroid Build Coastguard Worker if (notification != null) { 111*90c8c64dSAndroid Build Coastguard Worker mController.registerCallback(mCb); 112*90c8c64dSAndroid Build Coastguard Worker IntentFilter filter = new IntentFilter(); 113*90c8c64dSAndroid Build Coastguard Worker filter.addAction(ACTION_NEXT); 114*90c8c64dSAndroid Build Coastguard Worker filter.addAction(ACTION_PAUSE); 115*90c8c64dSAndroid Build Coastguard Worker filter.addAction(ACTION_PLAY); 116*90c8c64dSAndroid Build Coastguard Worker filter.addAction(ACTION_PREV); 117*90c8c64dSAndroid Build Coastguard Worker mService.registerReceiver(this, filter); 118*90c8c64dSAndroid Build Coastguard Worker 119*90c8c64dSAndroid Build Coastguard Worker mService.startForeground(NOTIFICATION_ID, notification); 120*90c8c64dSAndroid Build Coastguard Worker mStarted = true; 121*90c8c64dSAndroid Build Coastguard Worker } 122*90c8c64dSAndroid Build Coastguard Worker } 123*90c8c64dSAndroid Build Coastguard Worker } 124*90c8c64dSAndroid Build Coastguard Worker 125*90c8c64dSAndroid Build Coastguard Worker /** 126*90c8c64dSAndroid Build Coastguard Worker * Removes the notification and stops tracking the session. If the session 127*90c8c64dSAndroid Build Coastguard Worker * was destroyed this has no effect. 128*90c8c64dSAndroid Build Coastguard Worker */ stopNotification()129*90c8c64dSAndroid Build Coastguard Worker public void stopNotification() { 130*90c8c64dSAndroid Build Coastguard Worker if (mStarted) { 131*90c8c64dSAndroid Build Coastguard Worker mStarted = false; 132*90c8c64dSAndroid Build Coastguard Worker mController.unregisterCallback(mCb); 133*90c8c64dSAndroid Build Coastguard Worker try { 134*90c8c64dSAndroid Build Coastguard Worker mNotificationManager.cancel(NOTIFICATION_ID); 135*90c8c64dSAndroid Build Coastguard Worker mService.unregisterReceiver(this); 136*90c8c64dSAndroid Build Coastguard Worker } catch (IllegalArgumentException ex) { 137*90c8c64dSAndroid Build Coastguard Worker // ignore if the receiver is not registered. 138*90c8c64dSAndroid Build Coastguard Worker } 139*90c8c64dSAndroid Build Coastguard Worker mService.stopForeground(true); 140*90c8c64dSAndroid Build Coastguard Worker } 141*90c8c64dSAndroid Build Coastguard Worker } 142*90c8c64dSAndroid Build Coastguard Worker 143*90c8c64dSAndroid Build Coastguard Worker @Override onReceive(Context context, Intent intent)144*90c8c64dSAndroid Build Coastguard Worker public void onReceive(Context context, Intent intent) { 145*90c8c64dSAndroid Build Coastguard Worker final String action = intent.getAction(); 146*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "Received intent with action " + action); 147*90c8c64dSAndroid Build Coastguard Worker switch (action) { 148*90c8c64dSAndroid Build Coastguard Worker case ACTION_PAUSE: 149*90c8c64dSAndroid Build Coastguard Worker mTransportControls.pause(); 150*90c8c64dSAndroid Build Coastguard Worker break; 151*90c8c64dSAndroid Build Coastguard Worker case ACTION_PLAY: 152*90c8c64dSAndroid Build Coastguard Worker mTransportControls.play(); 153*90c8c64dSAndroid Build Coastguard Worker break; 154*90c8c64dSAndroid Build Coastguard Worker case ACTION_NEXT: 155*90c8c64dSAndroid Build Coastguard Worker mTransportControls.skipToNext(); 156*90c8c64dSAndroid Build Coastguard Worker break; 157*90c8c64dSAndroid Build Coastguard Worker case ACTION_PREV: 158*90c8c64dSAndroid Build Coastguard Worker mTransportControls.skipToPrevious(); 159*90c8c64dSAndroid Build Coastguard Worker break; 160*90c8c64dSAndroid Build Coastguard Worker default: 161*90c8c64dSAndroid Build Coastguard Worker LogHelper.w(TAG, "Unknown intent ignored. Action=", action); 162*90c8c64dSAndroid Build Coastguard Worker } 163*90c8c64dSAndroid Build Coastguard Worker } 164*90c8c64dSAndroid Build Coastguard Worker 165*90c8c64dSAndroid Build Coastguard Worker /** 166*90c8c64dSAndroid Build Coastguard Worker * Update the state based on a change on the session token. Called either when 167*90c8c64dSAndroid Build Coastguard Worker * we are running for the first time or when the media session owner has destroyed the session 168*90c8c64dSAndroid Build Coastguard Worker * (see {@link android.media.session.MediaController.Callback#onSessionDestroyed()}) 169*90c8c64dSAndroid Build Coastguard Worker */ updateSessionToken()170*90c8c64dSAndroid Build Coastguard Worker private void updateSessionToken() { 171*90c8c64dSAndroid Build Coastguard Worker MediaSession.Token freshToken = mService.getSessionToken(); 172*90c8c64dSAndroid Build Coastguard Worker if (mSessionToken == null || !mSessionToken.equals(freshToken)) { 173*90c8c64dSAndroid Build Coastguard Worker if (mController != null) { 174*90c8c64dSAndroid Build Coastguard Worker mController.unregisterCallback(mCb); 175*90c8c64dSAndroid Build Coastguard Worker } 176*90c8c64dSAndroid Build Coastguard Worker mSessionToken = freshToken; 177*90c8c64dSAndroid Build Coastguard Worker mController = new MediaController(mService, mSessionToken); 178*90c8c64dSAndroid Build Coastguard Worker mTransportControls = mController.getTransportControls(); 179*90c8c64dSAndroid Build Coastguard Worker if (mStarted) { 180*90c8c64dSAndroid Build Coastguard Worker mController.registerCallback(mCb); 181*90c8c64dSAndroid Build Coastguard Worker } 182*90c8c64dSAndroid Build Coastguard Worker } 183*90c8c64dSAndroid Build Coastguard Worker } 184*90c8c64dSAndroid Build Coastguard Worker createContentIntent()185*90c8c64dSAndroid Build Coastguard Worker private PendingIntent createContentIntent() { 186*90c8c64dSAndroid Build Coastguard Worker Intent openUI = new Intent(mService, MusicPlayerActivity.class); 187*90c8c64dSAndroid Build Coastguard Worker openUI.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 188*90c8c64dSAndroid Build Coastguard Worker return PendingIntent.getActivity(mService, REQUEST_CODE, openUI, 189*90c8c64dSAndroid Build Coastguard Worker PendingIntent.FLAG_CANCEL_CURRENT); 190*90c8c64dSAndroid Build Coastguard Worker } 191*90c8c64dSAndroid Build Coastguard Worker 192*90c8c64dSAndroid Build Coastguard Worker private final MediaController.Callback mCb = new MediaController.Callback() { 193*90c8c64dSAndroid Build Coastguard Worker @Override 194*90c8c64dSAndroid Build Coastguard Worker public void onPlaybackStateChanged(PlaybackState state) { 195*90c8c64dSAndroid Build Coastguard Worker mPlaybackState = state; 196*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "Received new playback state", state); 197*90c8c64dSAndroid Build Coastguard Worker if (state != null && (state.getState() == PlaybackState.STATE_STOPPED || 198*90c8c64dSAndroid Build Coastguard Worker state.getState() == PlaybackState.STATE_NONE)) { 199*90c8c64dSAndroid Build Coastguard Worker stopNotification(); 200*90c8c64dSAndroid Build Coastguard Worker } else { 201*90c8c64dSAndroid Build Coastguard Worker Notification notification = createNotification(); 202*90c8c64dSAndroid Build Coastguard Worker if (notification != null) { 203*90c8c64dSAndroid Build Coastguard Worker mNotificationManager.notify(NOTIFICATION_ID, notification); 204*90c8c64dSAndroid Build Coastguard Worker } 205*90c8c64dSAndroid Build Coastguard Worker } 206*90c8c64dSAndroid Build Coastguard Worker } 207*90c8c64dSAndroid Build Coastguard Worker 208*90c8c64dSAndroid Build Coastguard Worker @Override 209*90c8c64dSAndroid Build Coastguard Worker public void onMetadataChanged(MediaMetadata metadata) { 210*90c8c64dSAndroid Build Coastguard Worker mMetadata = metadata; 211*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "Received new metadata ", metadata); 212*90c8c64dSAndroid Build Coastguard Worker Notification notification = createNotification(); 213*90c8c64dSAndroid Build Coastguard Worker if (notification != null) { 214*90c8c64dSAndroid Build Coastguard Worker mNotificationManager.notify(NOTIFICATION_ID, notification); 215*90c8c64dSAndroid Build Coastguard Worker } 216*90c8c64dSAndroid Build Coastguard Worker } 217*90c8c64dSAndroid Build Coastguard Worker 218*90c8c64dSAndroid Build Coastguard Worker @Override 219*90c8c64dSAndroid Build Coastguard Worker public void onSessionDestroyed() { 220*90c8c64dSAndroid Build Coastguard Worker super.onSessionDestroyed(); 221*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "Session was destroyed, resetting to the new session token"); 222*90c8c64dSAndroid Build Coastguard Worker updateSessionToken(); 223*90c8c64dSAndroid Build Coastguard Worker } 224*90c8c64dSAndroid Build Coastguard Worker }; 225*90c8c64dSAndroid Build Coastguard Worker createNotification()226*90c8c64dSAndroid Build Coastguard Worker private Notification createNotification() { 227*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "updateNotificationMetadata. mMetadata=" + mMetadata); 228*90c8c64dSAndroid Build Coastguard Worker if (mMetadata == null || mPlaybackState == null) { 229*90c8c64dSAndroid Build Coastguard Worker return null; 230*90c8c64dSAndroid Build Coastguard Worker } 231*90c8c64dSAndroid Build Coastguard Worker 232*90c8c64dSAndroid Build Coastguard Worker Notification.Builder notificationBuilder = new Notification.Builder(mService); 233*90c8c64dSAndroid Build Coastguard Worker int playPauseButtonPosition = 0; 234*90c8c64dSAndroid Build Coastguard Worker 235*90c8c64dSAndroid Build Coastguard Worker // If skip to previous action is enabled 236*90c8c64dSAndroid Build Coastguard Worker if ((mPlaybackState.getActions() & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) { 237*90c8c64dSAndroid Build Coastguard Worker notificationBuilder.addAction(R.drawable.ic_skip_previous_white_24dp, 238*90c8c64dSAndroid Build Coastguard Worker mService.getString(R.string.label_previous), mPreviousIntent); 239*90c8c64dSAndroid Build Coastguard Worker 240*90c8c64dSAndroid Build Coastguard Worker // If there is a "skip to previous" button, the play/pause button will 241*90c8c64dSAndroid Build Coastguard Worker // be the second one. We need to keep track of it, because the MediaStyle notification 242*90c8c64dSAndroid Build Coastguard Worker // requires to specify the index of the buttons (actions) that should be visible 243*90c8c64dSAndroid Build Coastguard Worker // when in compact view. 244*90c8c64dSAndroid Build Coastguard Worker playPauseButtonPosition = 1; 245*90c8c64dSAndroid Build Coastguard Worker } 246*90c8c64dSAndroid Build Coastguard Worker 247*90c8c64dSAndroid Build Coastguard Worker addPlayPauseAction(notificationBuilder); 248*90c8c64dSAndroid Build Coastguard Worker 249*90c8c64dSAndroid Build Coastguard Worker // If skip to next action is enabled 250*90c8c64dSAndroid Build Coastguard Worker if ((mPlaybackState.getActions() & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) { 251*90c8c64dSAndroid Build Coastguard Worker notificationBuilder.addAction(R.drawable.ic_skip_next_white_24dp, 252*90c8c64dSAndroid Build Coastguard Worker mService.getString(R.string.label_next), mNextIntent); 253*90c8c64dSAndroid Build Coastguard Worker } 254*90c8c64dSAndroid Build Coastguard Worker 255*90c8c64dSAndroid Build Coastguard Worker MediaDescription description = mMetadata.getDescription(); 256*90c8c64dSAndroid Build Coastguard Worker 257*90c8c64dSAndroid Build Coastguard Worker String fetchArtUrl = null; 258*90c8c64dSAndroid Build Coastguard Worker Bitmap art = null; 259*90c8c64dSAndroid Build Coastguard Worker if (description.getIconUri() != null) { 260*90c8c64dSAndroid Build Coastguard Worker // This sample assumes the iconUri will be a valid URL formatted String, but 261*90c8c64dSAndroid Build Coastguard Worker // it can actually be any valid Android Uri formatted String. 262*90c8c64dSAndroid Build Coastguard Worker // async fetch the album art icon 263*90c8c64dSAndroid Build Coastguard Worker String artUrl = description.getIconUri().toString(); 264*90c8c64dSAndroid Build Coastguard Worker art = AlbumArtCache.getInstance().getBigImage(artUrl); 265*90c8c64dSAndroid Build Coastguard Worker if (art == null) { 266*90c8c64dSAndroid Build Coastguard Worker fetchArtUrl = artUrl; 267*90c8c64dSAndroid Build Coastguard Worker // use a placeholder art while the remote art is being downloaded 268*90c8c64dSAndroid Build Coastguard Worker art = BitmapFactory.decodeResource(mService.getResources(), 269*90c8c64dSAndroid Build Coastguard Worker R.drawable.ic_default_art); 270*90c8c64dSAndroid Build Coastguard Worker } 271*90c8c64dSAndroid Build Coastguard Worker } 272*90c8c64dSAndroid Build Coastguard Worker 273*90c8c64dSAndroid Build Coastguard Worker notificationBuilder 274*90c8c64dSAndroid Build Coastguard Worker .setStyle(new Notification.MediaStyle() 275*90c8c64dSAndroid Build Coastguard Worker .setShowActionsInCompactView( 276*90c8c64dSAndroid Build Coastguard Worker new int[]{playPauseButtonPosition}) // show only play/pause in compact view 277*90c8c64dSAndroid Build Coastguard Worker .setMediaSession(mSessionToken)) 278*90c8c64dSAndroid Build Coastguard Worker .setColor(mNotificationColor) 279*90c8c64dSAndroid Build Coastguard Worker .setSmallIcon(R.drawable.ic_notification) 280*90c8c64dSAndroid Build Coastguard Worker .setVisibility(Notification.VISIBILITY_PUBLIC) 281*90c8c64dSAndroid Build Coastguard Worker .setUsesChronometer(true) 282*90c8c64dSAndroid Build Coastguard Worker .setContentIntent(createContentIntent()) 283*90c8c64dSAndroid Build Coastguard Worker .setContentTitle(description.getTitle()) 284*90c8c64dSAndroid Build Coastguard Worker .setContentText(description.getSubtitle()) 285*90c8c64dSAndroid Build Coastguard Worker .setLargeIcon(art); 286*90c8c64dSAndroid Build Coastguard Worker 287*90c8c64dSAndroid Build Coastguard Worker setNotificationPlaybackState(notificationBuilder); 288*90c8c64dSAndroid Build Coastguard Worker if (fetchArtUrl != null) { 289*90c8c64dSAndroid Build Coastguard Worker fetchBitmapFromURLAsync(fetchArtUrl, notificationBuilder); 290*90c8c64dSAndroid Build Coastguard Worker } 291*90c8c64dSAndroid Build Coastguard Worker 292*90c8c64dSAndroid Build Coastguard Worker return notificationBuilder.build(); 293*90c8c64dSAndroid Build Coastguard Worker } 294*90c8c64dSAndroid Build Coastguard Worker addPlayPauseAction(Notification.Builder builder)295*90c8c64dSAndroid Build Coastguard Worker private void addPlayPauseAction(Notification.Builder builder) { 296*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "updatePlayPauseAction"); 297*90c8c64dSAndroid Build Coastguard Worker String label; 298*90c8c64dSAndroid Build Coastguard Worker int icon; 299*90c8c64dSAndroid Build Coastguard Worker PendingIntent intent; 300*90c8c64dSAndroid Build Coastguard Worker if (mPlaybackState.getState() == PlaybackState.STATE_PLAYING) { 301*90c8c64dSAndroid Build Coastguard Worker label = mService.getString(R.string.label_pause); 302*90c8c64dSAndroid Build Coastguard Worker icon = R.drawable.ic_pause_white_24dp; 303*90c8c64dSAndroid Build Coastguard Worker intent = mPauseIntent; 304*90c8c64dSAndroid Build Coastguard Worker } else { 305*90c8c64dSAndroid Build Coastguard Worker label = mService.getString(R.string.label_play); 306*90c8c64dSAndroid Build Coastguard Worker icon = R.drawable.ic_play_arrow_white_24dp; 307*90c8c64dSAndroid Build Coastguard Worker intent = mPlayIntent; 308*90c8c64dSAndroid Build Coastguard Worker } 309*90c8c64dSAndroid Build Coastguard Worker builder.addAction(new Notification.Action(icon, label, intent)); 310*90c8c64dSAndroid Build Coastguard Worker } 311*90c8c64dSAndroid Build Coastguard Worker setNotificationPlaybackState(Notification.Builder builder)312*90c8c64dSAndroid Build Coastguard Worker private void setNotificationPlaybackState(Notification.Builder builder) { 313*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "updateNotificationPlaybackState. mPlaybackState=" + mPlaybackState); 314*90c8c64dSAndroid Build Coastguard Worker if (mPlaybackState == null || !mStarted) { 315*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "updateNotificationPlaybackState. cancelling notification!"); 316*90c8c64dSAndroid Build Coastguard Worker mService.stopForeground(true); 317*90c8c64dSAndroid Build Coastguard Worker return; 318*90c8c64dSAndroid Build Coastguard Worker } 319*90c8c64dSAndroid Build Coastguard Worker if (mPlaybackState.getState() == PlaybackState.STATE_PLAYING 320*90c8c64dSAndroid Build Coastguard Worker && mPlaybackState.getPosition() >= 0) { 321*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "updateNotificationPlaybackState. updating playback position to ", 322*90c8c64dSAndroid Build Coastguard Worker (System.currentTimeMillis() - mPlaybackState.getPosition()) / 1000, " seconds"); 323*90c8c64dSAndroid Build Coastguard Worker builder 324*90c8c64dSAndroid Build Coastguard Worker .setWhen(System.currentTimeMillis() - mPlaybackState.getPosition()) 325*90c8c64dSAndroid Build Coastguard Worker .setShowWhen(true) 326*90c8c64dSAndroid Build Coastguard Worker .setUsesChronometer(true); 327*90c8c64dSAndroid Build Coastguard Worker } else { 328*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "updateNotificationPlaybackState. hiding playback position"); 329*90c8c64dSAndroid Build Coastguard Worker builder 330*90c8c64dSAndroid Build Coastguard Worker .setWhen(0) 331*90c8c64dSAndroid Build Coastguard Worker .setShowWhen(false) 332*90c8c64dSAndroid Build Coastguard Worker .setUsesChronometer(false); 333*90c8c64dSAndroid Build Coastguard Worker } 334*90c8c64dSAndroid Build Coastguard Worker 335*90c8c64dSAndroid Build Coastguard Worker // Make sure that the notification can be dismissed by the user when we are not playing: 336*90c8c64dSAndroid Build Coastguard Worker builder.setOngoing(mPlaybackState.getState() == PlaybackState.STATE_PLAYING); 337*90c8c64dSAndroid Build Coastguard Worker } 338*90c8c64dSAndroid Build Coastguard Worker fetchBitmapFromURLAsync(final String bitmapUrl, final Notification.Builder builder)339*90c8c64dSAndroid Build Coastguard Worker private void fetchBitmapFromURLAsync(final String bitmapUrl, 340*90c8c64dSAndroid Build Coastguard Worker final Notification.Builder builder) { 341*90c8c64dSAndroid Build Coastguard Worker AlbumArtCache.getInstance().fetch(bitmapUrl, new AlbumArtCache.FetchListener() { 342*90c8c64dSAndroid Build Coastguard Worker @Override 343*90c8c64dSAndroid Build Coastguard Worker public void onFetched(String artUrl, Bitmap bitmap, Bitmap icon) { 344*90c8c64dSAndroid Build Coastguard Worker if (mMetadata != null && mMetadata.getDescription() != null && 345*90c8c64dSAndroid Build Coastguard Worker artUrl.equals(mMetadata.getDescription().getIconUri().toString())) { 346*90c8c64dSAndroid Build Coastguard Worker // If the media is still the same, update the notification: 347*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "fetchBitmapFromURLAsync: set bitmap to ", artUrl); 348*90c8c64dSAndroid Build Coastguard Worker builder.setLargeIcon(bitmap); 349*90c8c64dSAndroid Build Coastguard Worker mNotificationManager.notify(NOTIFICATION_ID, builder.build()); 350*90c8c64dSAndroid Build Coastguard Worker } 351*90c8c64dSAndroid Build Coastguard Worker } 352*90c8c64dSAndroid Build Coastguard Worker }); 353*90c8c64dSAndroid Build Coastguard Worker } 354*90c8c64dSAndroid Build Coastguard Worker } 355