Java tutorial
/* * Copyright (C) 2014 Google Inc. All Rights Reserved. * * 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. * * Modified by Enno Gottschalk <mrmaffen@googlemail.com> in 2016 */ package org.tomahawk.tomahawk_android.utils; import org.apache.lucene.util.ArrayUtil; import org.tomahawk.libtomahawk.collection.Image; import org.tomahawk.tomahawk_android.R; import org.tomahawk.tomahawk_android.activities.TomahawkMainActivity; import org.tomahawk.tomahawk_android.services.PlaybackService; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.os.Build; import android.os.RemoteException; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.RatingCompat; import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.support.v7.app.NotificationCompat; import android.util.Log; import android.util.SparseArray; import java.util.ArrayList; import java.util.List; /** * Keeps track of a notification and updates it automatically for a given MediaSession. Maintaining * a visible notification (usually) guarantees that the music service won't be killed during * playback. */ public class MediaNotification { private static final String TAG = MediaNotification.class.getSimpleName(); private static final int NOTIFICATION_ID = 412; // public static final String ACTION_FAVORITE = "org.tomahawk.tomahawk_android.favorite"; // // public static final String ACTION_UNFAVORITE = "org.tomahawk.tomahawk_android.unfavorite"; public static final String ACTION_PAUSE = "org.tomahawk.tomahawk_android.pause"; public static final String ACTION_PLAY = "org.tomahawk.tomahawk_android.play"; public static final String ACTION_PREV = "org.tomahawk.tomahawk_android.prev"; public static final String ACTION_NEXT = "org.tomahawk.tomahawk_android.next"; private final PlaybackService mService; private MediaSessionCompat.Token mSessionToken; private MediaControllerCompat mController; private MediaControllerCompat.TransportControls mTransportControls; private final SparseArray<PendingIntent> mIntents = new SparseArray<>(); private PlaybackStateCompat mPlaybackState; private MediaMetadataCompat mMetadata; private NotificationCompat.Builder mNotificationBuilder; private NotificationManagerCompat mNotificationManager; private NotificationCompat.Action mPlayPauseAction; private NotificationCompat.Action mFavoriteAction; private int mNotificationColor; private boolean mStarted = false; private final MediaControllerCompat.Callback mCallback = new MediaControllerCompat.Callback() { @Override public void onPlaybackStateChanged(PlaybackStateCompat state) { mPlaybackState = state; Log.d(TAG, "Received new playback state"); if (mStarted) { updateNotificationPlaybackState(); mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build()); Log.d(TAG, "Updated notification to new playback state. mStarted: " + mStarted); } else { Log.d(TAG, "Couldn't update playback state because notification is stopped"); } } @Override public void onMetadataChanged(MediaMetadataCompat metadata) { mMetadata = metadata; Log.d(TAG, "Received new metadata "); if (mStarted) { updateNotificationMetadata(); } else { Log.d(TAG, "Couldn't update playback state because notification is stopped"); } } @Override public void onSessionDestroyed() { super.onSessionDestroyed(); Log.d(TAG, "Session was destroyed, resetting to the new session token"); try { updateSessionToken(); } catch (RemoteException e) { Log.e(TAG, "Could not connect to media controller: ", e); } } }; private BroadcastReceiver mActionReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); Log.d(TAG, "Received intent with action " + action); // if (ACTION_FAVORITE.equals(action)) { // mTransportControls.setRating(RatingCompat.newHeartRating(true)); // } else if (ACTION_UNFAVORITE.equals(action)) { // mTransportControls.setRating(RatingCompat.newHeartRating(false)); // } else if (ACTION_PAUSE.equals(action)) { mTransportControls.pause(); } else if (ACTION_PLAY.equals(action)) { mTransportControls.play(); } else if (ACTION_NEXT.equals(action)) { mTransportControls.skipToNext(); } else if (ACTION_PREV.equals(action)) { mTransportControls.skipToPrevious(); } } }; public MediaNotification(PlaybackService service) throws RemoteException { mService = service; updateSessionToken(); mNotificationColor = mService.getResources().getColor(R.color.notification_bg); mNotificationManager = NotificationManagerCompat.from(mService); stopNotification(); String pkg = mService.getPackageName(); // mIntents.put(R.drawable.ic_action_favorites_small, PendingIntent.getBroadcast(mService, 100, // new Intent(ACTION_FAVORITE).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT)); // mIntents.put(R.drawable.ic_action_favorites_small_underlined, // PendingIntent.getBroadcast(mService, 100, new Intent(ACTION_UNFAVORITE) // .setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT)); mIntents.put(R.drawable.ic_av_pause, PendingIntent.getBroadcast(mService, 100, new Intent(ACTION_PAUSE).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT)); mIntents.put(R.drawable.ic_av_play_arrow, PendingIntent.getBroadcast(mService, 100, new Intent(ACTION_PLAY).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT)); mIntents.put(R.drawable.ic_player_previous_light, PendingIntent.getBroadcast(mService, 100, new Intent(ACTION_PREV).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT)); mIntents.put(R.drawable.ic_player_next_light, PendingIntent.getBroadcast(mService, 100, new Intent(ACTION_NEXT).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 #stopNotification} is * called. */ public void startNotification() { mService.getCallbackHandler().post(new Runnable() { @Override public void run() { if (!mStarted) { Log.d(TAG, "Starting notification"); mController.registerCallback(mCallback, mService.getCallbackHandler()); IntentFilter filter = new IntentFilter(); //filter.addAction(ACTION_FAVORITE); //filter.addAction(ACTION_UNFAVORITE); filter.addAction(ACTION_NEXT); filter.addAction(ACTION_PAUSE); filter.addAction(ACTION_PLAY); filter.addAction(ACTION_PREV); mService.registerReceiver(mActionReceiver, filter); mMetadata = mController.getMetadata(); mPlaybackState = mController.getPlaybackState(); mStarted = true; // The notification must be updated after setting started to true updateNotificationMetadata(); } } }); } /** * Removes the notification and stops tracking the session. If the session was destroyed this * has no effect. */ public void stopNotification() { mService.getCallbackHandler().post(new Runnable() { @Override public void run() { mStarted = false; if (mController != null) { mController.unregisterCallback(mCallback); } try { mService.unregisterReceiver(mActionReceiver); } catch (IllegalArgumentException ex) { // ignore if the receiver is not registered. } mService.stopForeground(true); Log.d(TAG, "Stopped notification"); } }); } /** * Update the state based on a change on the session token. Called either when we are running * for the first time or when the media session owner has destroyed the session (see {@link * android.media.session.MediaController.Callback#onSessionDestroyed()}) */ private void updateSessionToken() throws RemoteException { MediaSessionCompat.Token freshToken = mService.getSessionToken(); if (mSessionToken == null && freshToken != null || mSessionToken != null && !mSessionToken.equals(freshToken)) { if (mController != null) { mController.unregisterCallback(mCallback); } mSessionToken = freshToken; if (mSessionToken != null) { mController = new MediaControllerCompat(mService, mSessionToken); mTransportControls = mController.getTransportControls(); if (mStarted) { mController.registerCallback(mCallback, mService.getCallbackHandler()); } } } } private void updateNotificationMetadata() { try { Log.d(TAG, "updateNotificationMetadata. mMetadata=" + mMetadata); if (mMetadata == null || mPlaybackState == null) { return; } mNotificationBuilder = new NotificationCompat.Builder(mService); List<Integer> showInCompact = new ArrayList<>(); // updateFavoriteAction(); // showInCompact.add(mNotificationBuilder.mActions.size()); // mNotificationBuilder.addAction(mFavoriteAction); // If skip to previous action is enabled if ((mPlaybackState.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) { NotificationCompat.Action action = new NotificationCompat.Action.Builder( R.drawable.ic_player_previous_light, mService.getString(R.string.playback_previous), mIntents.get(R.drawable.ic_player_previous_light)).build(); mNotificationBuilder.addAction(action); } updatePlayPauseAction(); showInCompact.add(mNotificationBuilder.mActions.size()); mNotificationBuilder.addAction(mPlayPauseAction); // If skip to next action is enabled if ((mPlaybackState.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) { NotificationCompat.Action action = new NotificationCompat.Action.Builder( R.drawable.ic_player_next_light, mService.getString(R.string.playback_next), mIntents.get(R.drawable.ic_player_next_light)).build(); showInCompact.add(mNotificationBuilder.mActions.size()); mNotificationBuilder.addAction(action); } MediaDescriptionCompat description = mMetadata.getDescription(); String playbackManagerId = mController.getExtras() .getString(PlaybackService.EXTRAS_KEY_PLAYBACKMANAGER); PlaybackManager playbackManager = PlaybackManager.getByKey(playbackManagerId); Image image = playbackManager.getCurrentQuery().getImage(); Bitmap art = null; if (image != null) { art = MediaImageHelper.get().getMediaImageCache().get(image); } if (art == null) { art = MediaImageHelper.get().getCachedPlaceHolder(); } mNotificationBuilder .setStyle(new NotificationCompat.MediaStyle() .setShowActionsInCompactView(ArrayUtil.toIntArray(showInCompact)) .setMediaSession(mSessionToken).setShowCancelButton(true) .setCancelButtonIntent(createCancelIntent())) .setColor(mNotificationColor).setSmallIcon(R.drawable.ic_notification) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC).setContentIntent(createContentIntent()) .setContentTitle(description.getTitle()).setContentText(description.getSubtitle()) .setTicker(description.getTitle() + " - " + description.getSubtitle()).setLargeIcon(art) .setOngoing(false); updateNotificationPlaybackState(); mService.startForeground(NOTIFICATION_ID, mNotificationBuilder.build()); Log.d(TAG, "updateNotificationMetadata. Notification shown"); } catch (Exception e) { } } private PendingIntent createContentIntent() { Intent intent = new Intent(mService, TomahawkMainActivity.class); intent.setAction(TomahawkMainActivity.SHOW_PLAYBACKFRAGMENT_ON_STARTUP); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); return PendingIntent.getActivity(mService, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } private PendingIntent createCancelIntent() { Intent intent = new Intent(mService, PlaybackService.class); intent.setAction(PlaybackService.ACTION_STOP_NOTIFICATION); return PendingIntent.getService(mService, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } // private void updateFavoriteAction() { // Log.d(TAG, "updateFavoriteAction"); // String favoriteLabel; // int favoriteIcon; // RatingCompat rating = mMetadata.getRating(MediaMetadataCompat.METADATA_KEY_USER_RATING); // if (rating != null && rating.hasHeart()) { // favoriteLabel = mService.getString(R.string.playback_unfavorite); // favoriteIcon = R.drawable.ic_action_favorites_small_underlined; // } else { // favoriteLabel = mService.getString(R.string.playback_favorite); // favoriteIcon = R.drawable.ic_action_favorites_small; // } // if (mFavoriteAction == null) { // mFavoriteAction = new NotificationCompat.Action.Builder(favoriteIcon, // favoriteLabel, mIntents.get(favoriteIcon)).build(); // } else { // mFavoriteAction.icon = favoriteIcon; // mFavoriteAction.title = favoriteLabel; // mFavoriteAction.actionIntent = mIntents.get(favoriteIcon); // } // } private void updatePlayPauseAction() { Log.d(TAG, "updatePlayPauseAction"); String playPauseLabel; int playPauseIcon; if (mPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING) { playPauseLabel = mService.getString(R.string.playback_pause); playPauseIcon = R.drawable.ic_av_pause; } else { playPauseLabel = mService.getString(R.string.playback_play); playPauseIcon = R.drawable.ic_av_play_arrow; } if (mPlayPauseAction == null) { mPlayPauseAction = new NotificationCompat.Action.Builder(playPauseIcon, playPauseLabel, mIntents.get(playPauseIcon)).build(); } else { mPlayPauseAction.icon = playPauseIcon; mPlayPauseAction.title = playPauseLabel; mPlayPauseAction.actionIntent = mIntents.get(playPauseIcon); } } private void updateNotificationPlaybackState() { Log.d(TAG, "updateNotificationPlaybackState. mPlaybackState=" + mPlaybackState); if (mPlaybackState == null || !mStarted) { Log.d(TAG, "updateNotificationPlaybackState. cancelling notification!"); mService.stopForeground(true); return; } if (mNotificationBuilder == null) { Log.d(TAG, "updateNotificationPlaybackState. there is no notificationBuilder. " + "Ignoring request to update state!"); return; } if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT && mPlaybackState.getPosition() >= 0 && mPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING) { Log.d(TAG, "updateNotificationPlaybackState. updating playback position to " + (System.currentTimeMillis() - mPlaybackState.getPosition()) / 1000 + " seconds"); mNotificationBuilder.setWhen(System.currentTimeMillis() - mPlaybackState.getPosition()) .setShowWhen(true).setUsesChronometer(true); } else { Log.d(TAG, "updateNotificationPlaybackState. hiding playback position"); mNotificationBuilder.setWhen(0).setShowWhen(false).setUsesChronometer(false); } updatePlayPauseAction(); // Make sure that the notification can be dismissed by the user when we are not playing: mNotificationBuilder.setOngoing(mPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING); if (mPlaybackState.getState() != PlaybackStateCompat.STATE_PLAYING) { mService.stopForeground(false); } } }