org.opensilk.music.playback.NotificationHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.opensilk.music.playback.NotificationHelper.java

Source

/*
 * Copyright (C) 2012 Andrew Neal 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 org.opensilk.music.playback;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.session.MediaSession;
import android.os.Handler;
import android.support.v4.app.NotificationCompat;
import android.widget.RemoteViews;

import org.opensilk.common.core.dagger2.ForApplication;
import org.opensilk.common.core.util.VersionUtils;
import org.opensilk.music.artwork.ArtworkType;
import org.opensilk.music.artwork.UtilsArt;
import org.opensilk.music.artwork.service.ArtworkProviderHelper;
import org.opensilk.music.model.ArtInfo;
import org.opensilk.music.model.Track;
import org.opensilk.music.playback.service.PlaybackService;

import java.lang.ref.WeakReference;

import javax.inject.Inject;
import javax.inject.Singleton;

import rx.Scheduler;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
import timber.log.Timber;

/**
 * Builds the notification for Apollo's service. Jelly Bean and higher uses the
 * expanded notification by default.
 *
 * @author Andrew Neal (andrewdneal@gmail.com)
 */
public class NotificationHelper {

    /**
     * Notification ID
     */
    private static final int APOLLO_MUSIC_SERVICE = 1;

    /**
     * NotificationManager
     */
    private final NotificationManager mNotificationManager;

    /**
     * Context
     */
    private final Context mContext;

    private final ArtworkProviderHelper mArtworkHelper;

    /**
     * Custom notification layout
     */
    private RemoteViews mNotificationTemplate;

    /**
     * The Notification
     */
    private Notification mNotification = null;

    /**
     * API 16+ bigContentView
     */
    private RemoteViews mExpandedView;

    final CurrentInfo mCurrentInfo = new CurrentInfo();
    final Scheduler mScheduler = Schedulers.computation();
    Subscription mArtworkSubscription;
    Scheduler oScheduler;
    Service mService;

    static class CurrentInfo {
        Track track;
        ArtInfo artInfo;
        Bitmap bitmap;
        boolean isPlaying;

        boolean hasAnyNull() {
            return track == null || artInfo == null || bitmap == null;
        }
    }

    @Inject
    public NotificationHelper(@ForApplication Context context, NotificationManager notificationManager,
            ArtworkProviderHelper artworkHelper) {
        mContext = context;
        mNotificationManager = notificationManager;
        mArtworkHelper = artworkHelper;
    }

    public void setService(Service service, Handler handler) {
        mService = service;
        oScheduler = AndroidSchedulers.handlerThread(handler);
    }

    /**
     * Call this to build the {@link Notification}.
     */
    public void buildNotification(Track track, boolean isPlaying, MediaSession.Token mediaToken) {
        if (mService == null || oScheduler == null) {
            Timber.e("must setService");
            return;
        }

        if (track.equals(mCurrentInfo.track) && isPlaying == mCurrentInfo.isPlaying) {
            return;
        }

        mCurrentInfo.track = track;
        mCurrentInfo.isPlaying = isPlaying;

        final ArtInfo artInfo = UtilsArt.makeBestfitArtInfo(track.albumArtistName, track.artistName,
                track.albumName, track.artworkUri);

        if (artInfo.equals(mCurrentInfo.artInfo)) {
            if (mCurrentInfo.bitmap != null) {
                buildNotificationInternal();
            } //else wait for artwork
        } else {
            mCurrentInfo.artInfo = artInfo;
            if (mArtworkSubscription != null) {
                mArtworkSubscription.unsubscribe();
            }
            mArtworkSubscription = mArtworkHelper.getArtwork(artInfo, ArtworkType.THUMBNAIL).subscribeOn(mScheduler)
                    .observeOn(oScheduler).subscribe(new Action1<Bitmap>() {
                        @Override
                        public void call(Bitmap bitmap) {
                            mCurrentInfo.bitmap = bitmap;
                            buildNotificationInternal();
                        }
                    }, new Action1<Throwable>() {
                        @Override
                        public void call(Throwable throwable) {
                            Timber.w(throwable, "getArtwork");
                            mArtworkSubscription = null;
                        }
                    }, new Action0() {
                        @Override
                        public void call() {
                            mArtworkSubscription = null;
                        }
                    });
        }
    }

    private void buildNotificationInternal() {
        if (mCurrentInfo.hasAnyNull() || mService == null) {
            return;
        }
        // Default notfication layout
        mNotificationTemplate = new RemoteViews(mContext.getPackageName(), R.layout.notification_template_base);

        // Set up the content view
        initCollapsedLayout(mCurrentInfo.track.name, mCurrentInfo.track.artistName, mCurrentInfo.bitmap);

        // Notification Builder
        mNotification = new NotificationCompat.Builder(mContext).setSmallIcon(R.drawable.stat_notify_music)
                .setContentIntent(getPendingIntent()).setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .setCategory(NotificationCompat.CATEGORY_TRANSPORT).setContent(mNotificationTemplate).build();
        // Control playback from the notification
        initPlaybackActions(mCurrentInfo.isPlaying);
        if (VersionUtils.hasJellyBean()) {
            // Expanded notifiction style
            mExpandedView = new RemoteViews(mContext.getPackageName(),
                    R.layout.notification_template_expanded_base);
            mNotification.bigContentView = mExpandedView;
            // Control playback from the notification
            initExpandedPlaybackActions(mCurrentInfo.isPlaying);
            // Set up the expanded content view
            initExpandedLayout(mCurrentInfo.track.name, mCurrentInfo.track.albumName, mCurrentInfo.track.artistName,
                    mCurrentInfo.bitmap);
        }

        mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
        mService.startForeground(APOLLO_MUSIC_SERVICE, mNotification);
    }

    /**
     * Remove notification
     */
    public void killNotification() {
        if (mService != null) {
            mService.stopForeground(true);
        }
        mNotification = null;
        if (mArtworkSubscription != null) {
            mArtworkSubscription.unsubscribe();
        }
    }

    /**
     * Changes the playback controls in and out of a paused state
     *
     * @param isPlaying True if music is playing, false otherwise
     */
    public void updatePlayState(final boolean isPlaying) {
        if (mCurrentInfo.isPlaying == isPlaying) {
            return;
        }
        mCurrentInfo.isPlaying = isPlaying;

        if (mNotification == null || mNotificationManager == null) {
            return;
        }

        if (mNotificationTemplate != null) {
            mNotificationTemplate.setImageViewResource(R.id.notification_base_play, getPlayPauseIcon(isPlaying));
        }

        if (VersionUtils.hasJellyBean() && mExpandedView != null) {
            mExpandedView.setImageViewResource(R.id.notification_expanded_base_play, getPlayPauseIcon(isPlaying));
        }

        mNotificationManager.notify(APOLLO_MUSIC_SERVICE, mNotification);
    }

    /**
     * Open to the now playing screen
     */
    private PendingIntent getPendingIntent() {
        return PendingIntent.getActivity(mContext, 0, NavUtils.makeLauncherIntent(mContext),
                PendingIntent.FLAG_UPDATE_CURRENT);
    }

    /**
     * Lets the buttons in the remote view control playback in the expanded
     * layout
     */
    private void initExpandedPlaybackActions(boolean isPlaying) {
        // Play and pause
        mExpandedView.setOnClickPendingIntent(R.id.notification_expanded_base_play, retreivePlaybackActions(1));

        // Skip tracks
        mExpandedView.setOnClickPendingIntent(R.id.notification_expanded_base_next, retreivePlaybackActions(2));

        // Previous tracks
        mExpandedView.setOnClickPendingIntent(R.id.notification_expanded_base_previous, retreivePlaybackActions(3));

        // Stop and collapse the notification
        mExpandedView.setOnClickPendingIntent(R.id.notification_expanded_base_collapse, retreivePlaybackActions(4));

        // Update the play button image
        mExpandedView.setImageViewResource(R.id.notification_expanded_base_play, getPlayPauseIcon(isPlaying));
    }

    /**
     * Lets the buttons in the remote view control playback in the normal layout
     */
    private void initPlaybackActions(boolean isPlaying) {
        // Play and pause
        mNotificationTemplate.setOnClickPendingIntent(R.id.notification_base_play, retreivePlaybackActions(1));

        // Skip tracks
        mNotificationTemplate.setOnClickPendingIntent(R.id.notification_base_next, retreivePlaybackActions(2));

        // Stop and collapse the notification
        mNotificationTemplate.setOnClickPendingIntent(R.id.notification_base_collapse, retreivePlaybackActions(4));

        // Update the play button image
        mNotificationTemplate.setImageViewResource(R.id.notification_base_play, getPlayPauseIcon(isPlaying));
    }

    /**
     * @param which Which {@link PendingIntent} to return
     * @return A {@link PendingIntent} ready to control playback
     */
    private PendingIntent retreivePlaybackActions(final int which) {
        Intent action;
        PendingIntent pendingIntent;
        final ComponentName serviceName = new ComponentName(mContext, PlaybackService.class);
        switch (which) {
        case 1:
            // Play and pause
            action = new Intent(PlaybackConstants.TOGGLEPAUSE_ACTION);
            action.setComponent(serviceName);
            pendingIntent = PendingIntent.getService(mContext, 1, action, 0);
            return pendingIntent;
        case 2:
            // Skip tracks
            action = new Intent(PlaybackConstants.NEXT_ACTION);
            action.setComponent(serviceName);
            pendingIntent = PendingIntent.getService(mContext, 2, action, 0);
            return pendingIntent;
        case 3:
            // Previous tracks
            action = new Intent(PlaybackConstants.PREVIOUS_ACTION);
            action.setComponent(serviceName);
            pendingIntent = PendingIntent.getService(mContext, 3, action, 0);
            return pendingIntent;
        case 4:
            // Stop and collapse the notification
            action = new Intent(PlaybackConstants.STOP_ACTION);
            action.setComponent(serviceName);
            pendingIntent = PendingIntent.getService(mContext, 4, action, 0);
            return pendingIntent;
        default:
            break;
        }
        return null;
    }

    /**
     * Sets the track name, artist name, and album art in the normal layout
     */
    private void initCollapsedLayout(final String trackName, final String artistName, final Bitmap albumArt) {
        // Track name (line one)
        mNotificationTemplate.setTextViewText(R.id.notification_base_line_one, trackName);
        // Artist name (line two)
        mNotificationTemplate.setTextViewText(R.id.notification_base_line_two, artistName);
        // Album art
        mNotificationTemplate.setImageViewBitmap(R.id.notification_base_image, albumArt);
    }

    /**
     * Sets the track name, album name, artist name, and album art in the
     * expanded layout
     */
    private void initExpandedLayout(final String trackName, final String artistName, final String albumName,
            final Bitmap albumArt) {
        // Track name (line one)
        mExpandedView.setTextViewText(R.id.notification_expanded_base_line_one, trackName);
        // Album name (line two)
        mExpandedView.setTextViewText(R.id.notification_expanded_base_line_two, albumName);
        // Artist name (line three)
        mExpandedView.setTextViewText(R.id.notification_expanded_base_line_three, artistName);
        // Album art
        mExpandedView.setImageViewBitmap(R.id.notification_expanded_base_image, albumArt);
    }

    private int getPlayPauseIcon(boolean isPlaying) {
        if (VersionUtils.hasLollipop()) {
            return isPlaying ? R.drawable.ic_pause_black_36dp : R.drawable.ic_play_arrow_black_36dp;
        } else {
            return isPlaying ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_arrow_white_36dp;
        }
    }

}