com.wojtechnology.sunami.TheBrain.java Source code

Java tutorial

Introduction

Here is the source code for com.wojtechnology.sunami.TheBrain.java

Source

/*
    
Sunami - An Android music player which knows what you want to listen to.
Copyright (C) 2015 Wojtek Swiderski
    
Sunami is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
Sunami is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
The GNU General Public License can be found at the root of this repository.
    
To contact me, email me at wojtek.technology@gmail.com
    
 */

package com.wojtechnology.sunami;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.MediaPlayer;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.widget.RemoteViews;
import android.widget.Toast;

import org.json.JSONArray;
import org.json.JSONException;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.List;

/**
 * Created by wojtekswiderski on 15-04-19.
 */

// Class that manages the smart shuffle
public class TheBrain extends Service {

    public static final String TOGGLE_PLAY = "toggle_play";
    public static final String PLAY_NEXT = "play_next";
    public static final String PLAY_LAST = "play_last";
    public static final String PLAY_STOP = "play_stop";
    public static final String UPDATE_SETTINGS = "update_settings";
    private final String WAKE_LOCK_ID = "SunamiWakeLock";

    private final String SAVE_FILE_BASE = "__app_data_";

    private static final int HISTORY_SIZE = 10;

    private MainActivity mContext;
    private boolean mIsInit;
    private boolean mBound;
    private boolean mHasAudioFocus;
    private boolean mLoaded;
    private boolean mPreparing;

    private Bitmap mThumbnail;

    // Determines whether the song will be paused after it is prepared
    private boolean mPauseAfterLoad;

    // Keeps track of how many save request were made
    private int mSaveCount;

    // Contains list of songs
    private SongManager mSongManager;
    private SongHistory mSongHistory;
    private PlayTimer mPlayTimer;
    private ShuffleController mShuffleController;
    public MainPrefs mMainPrefs;

    // Contains list of genres
    private GenreGraph mGenreGraph;
    public FireMixtape mPlaying;
    public MediaPlayer mMediaPlayer;
    private AudioManager mAudioManager;
    private PowerManager.WakeLock mWakeLock;
    public UpNext mUpNext;
    AudioManager.OnAudioFocusChangeListener mAFChangeListener;

    // Sessions and Notifications
    private MediaSessionCompat mSession;
    private Notification mNotification;
    private RemoteViews mNotificationView;

    private final IBinder mBinder = new LocalBinder();

    // Receiver for plugging out earphones
    private class NoisyAudioStreamReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
                pausePlayback();
                setUI(false);
            }
        }
    }

    private NoisyAudioStreamReceiver mNoisyAudioStreamReceiver;

    private MediaPlayer.OnPreparedListener mMPPreparedListener;

    private class GetArtworkTask extends AsyncTask<FireMixtape, Integer, Void> {
        private Bitmap mBM;

        @Override
        protected Void doInBackground(FireMixtape... params) {
            FireMixtape song = params[0];

            if (song.isSoundcloud) {
                mBM = AlbumArtHelper.decodeBitmapFromURL(song.album_art_url, true);
            } else {
                mBM = AlbumArtHelper.decodeBitmapFromAlbumId(mContext, Long.parseLong(song.album_id));
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            if (mBM != null) {
                mThumbnail = mBM;
            } else {
                mThumbnail = BitmapFactory.decodeResource(getResources(), R.drawable.fire_mixtape_default_large);
            }
            if (mBound) {
                mContext.setArtwork(mBM);
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                setNotificationStatus(isPlaying());
            }
        }
    }

    private class LoadAppDataTask extends AsyncTask<Void, Integer, Void> {

        private void readNew(JSONArray ja) {
            mGenreGraph.populateGraphJSON(ja);
            mSongManager.genresFromDB(mGenreGraph.getGenreSet());
        }

        private void readOld(JSONArray ja) throws JSONException {
            mGenreGraph.populateGraphJSON(ja.getJSONArray(1));
            mSongManager.updateGenres(mGenreGraph.getGenreSet(), ja.getJSONArray(2));
        }

        private void attemptReadOld() {
            boolean isNew = false;
            int numTries = 0;
            int fileNum = getExistingFile();
            InputStream is;
            do {
                try {
                    String fileName = SAVE_FILE_BASE + fileNum;
                    is = TheBrain.this.openFileInput(fileName);
                } catch (FileNotFoundException e) {
                    is = TheBrain.this.getResources().openRawResource(R.raw.genres);
                    isNew = true;
                }
                try {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                    String line = reader.readLine();
                    String jString = "";
                    while (line != null) {
                        jString += line;
                        line = reader.readLine();
                    }
                    reader.close();
                    is.close();
                    JSONArray ja = new JSONArray(jString);
                    if (isNew)
                        readNew(ja);
                    else
                        readOld(ja);

                    // Enable shuffle controller to use calculated values to choose songs
                    mShuffleController.setLoadCompleted();
                    numTries = 2;
                } catch (IOException e) {
                    e.printStackTrace();
                    numTries++;
                    fileNum = (fileNum + 1) % 2;
                    if (numTries == 2) {
                        corruptedFile();
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                    numTries++;
                    fileNum = (fileNum + 1) % 2;
                    if (numTries == 2) {
                        corruptedFile();
                    }
                }
            } while (numTries < 2);
        }

        // Should never be called but here just in case...
        private void corruptedFile() {
            InputStream is = TheBrain.this.getResources().openRawResource(R.raw.genres);
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                String line = reader.readLine();
                String jString = "";
                while (line != null) {
                    jString += line;
                    line = reader.readLine();
                }
                reader.close();
                is.close();
                JSONArray ja = new JSONArray(jString);
                readNew(ja);

                // Enable shuffle controller to use calculated values to choose songs
                mShuffleController.setLoadCompleted();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        @Override
        protected Void doInBackground(Void... params) {
            attemptReadOld();
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            mLoaded = true;
            if (mBound) {
                mContext.refreshRecyclerViewData();
            }
        }
    }

    private int getExistingFile() {
        String[] files = fileList();
        for (int i = 0; i < files.length; i++) {
            String firstFile = files[i];
            if (firstFile.contains(SAVE_FILE_BASE)) {
                return Integer.parseInt(firstFile.replace(SAVE_FILE_BASE, ""));
            }
        }
        return -1;
    }

    private class SaveAppDataTask extends AsyncTask<Void, Integer, Void> {

        @Override
        protected Void doInBackground(Void... params) {
            if (mSaveCount <= 0) {
                return null;
            }
            int fileNum = getExistingFile();
            String fileName = SAVE_FILE_BASE + ((fileNum <= 0) ? 1 : 0);
            try {
                FileOutputStream fileOS = TheBrain.this.openFileOutput(fileName, Context.MODE_PRIVATE);
                JSONArray ja = new JSONArray();
                ja.put(0, 1);
                ja.put(1, mGenreGraph.getGraphJSON());
                ja.put(2, mSongManager.getSongJSON());
                fileOS.write(ja.toString().getBytes());
                fileOS.close();
                deleteFile(SAVE_FILE_BASE + ((fileNum <= 0) ? 0 : 1));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (JSONException e) {
                e.printStackTrace();
            }

            if (mSaveCount > 1) {
                mSaveCount = 1;
            } else {
                mSaveCount = 0;
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
        }
    }

    @Override
    public void onCreate() {
        mSaveCount = 0;
        mIsInit = false;
        mBound = false;
        mHasAudioFocus = false;
        mLoaded = false;
        mPreparing = false;
        mPauseAfterLoad = false;
        mUpNext = new UpNext();
        mPlayTimer = new PlayTimer();
        mMainPrefs = new MainPrefs(this);
        mThumbnail = BitmapFactory.decodeResource(getResources(), R.drawable.fire_mixtape_default_large);
        mNoisyAudioStreamReceiver = new NoisyAudioStreamReceiver();
        mMPPreparedListener = new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                if (!mPauseAfterLoad) {
                    resumePlayback();
                    setUI(true);
                } else {
                    setUI(false);
                    mPauseAfterLoad = false;
                }
                mPreparing = false;
            }
        };
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getAction();
        if (mIsInit && action != null) {
            switch (intent.getAction()) {
            case TOGGLE_PLAY:
                togglePlay();
                break;

            case PLAY_NEXT:
                playNext();
                break;

            case PLAY_LAST:
                playLast();
                break;

            case PLAY_STOP:
                pausePlayback();
                unregisterAudio();
                stopForeground(true);
                if (mBound) {
                    mContext.updateSongView();
                } else {
                    mContext.serviceFinished();
                    mMediaPlayer.release();
                    stopSelf();
                }
                break;

            case UPDATE_SETTINGS:
                mMainPrefs.updateValues();
                mShuffleController.updateValues();
                break;

            default:
                break;
            }
        }
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        mBound = false;
        return super.onUnbind(intent);
    }

    public class LocalBinder extends Binder {
        public TheBrain getServiceInstance() {
            return TheBrain.this;
        }
    }

    public void registerClient(MainActivity activity) {
        mContext = activity;
        mBound = true;
        if (!mIsInit) {
            init();
            mIsInit = true;
        } else {
            mContext.setRecyclerViewData();
            if (mPlaying != null) {
                mContext.playSong(mPlaying);
                setNotification(mPlaying);
            }
        }
    }

    private void setNotificationStatus(boolean isPlaying) {
        if (mPlaying == null) {
            stopForeground(true);
            return;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mNotification = getLollipopNotifBuilder(isPlaying).setLargeIcon(mThumbnail)
                    .setContentTitle(mPlaying.title).setContentText(mPlaying.artist).build();
        } else {
            if (isPlaying) {
                mNotificationView.setInt(R.id.play_notif_button, "setBackgroundResource", R.drawable.ic_pause_hint);
            } else {
                mNotificationView.setInt(R.id.play_notif_button, "setBackgroundResource", R.drawable.ic_play_hint);
            }
        }
        startForeground(534, mNotification);
    }

    // This is needed to keep the background service running
    private void setNotification(FireMixtape song) {
        if (mPlaying == null) {
            stopForeground(true);
            return;
        }
        setMetadata(song);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // Notification is created in setMetadata
            return;
        }

        if (mNotification == null || mNotificationView == null) {
            Intent notificationIntent = new Intent(this, MainActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 1, notificationIntent, 0);

            mNotificationView = new RemoteViews(getPackageName(), R.layout.notification);

            mNotification = new Notification.Builder(this).setSmallIcon(R.mipmap.sunaminotif)
                    .setContentIntent(pendingIntent).setContent(mNotificationView).build();
        }

        Intent servicePlayIntent = new Intent(getApplicationContext(), TheBrain.class);
        servicePlayIntent.setAction(TheBrain.TOGGLE_PLAY);
        servicePlayIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        PendingIntent pendingPlayIntent = PendingIntent.getService(mContext, 1, servicePlayIntent, 0);
        Intent serviceNextIntent = new Intent(getApplicationContext(), TheBrain.class);
        serviceNextIntent.setAction(TheBrain.PLAY_NEXT);
        serviceNextIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        PendingIntent pendingNextIntent = PendingIntent.getService(mContext, 1, serviceNextIntent, 0);
        Intent serviceStopIntent = new Intent(getApplicationContext(), TheBrain.class);
        serviceStopIntent.setAction(TheBrain.PLAY_STOP);
        serviceStopIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        PendingIntent pendingStopIntent = PendingIntent.getService(mContext, 1, serviceStopIntent, 0);

        mNotificationView.setTextViewText(R.id.notif_title, song.title);
        mNotificationView.setTextViewText(R.id.notif_artist, song.artist);
        mNotificationView.setOnClickPendingIntent(R.id.play_notif_button, pendingPlayIntent);
        mNotificationView.setOnClickPendingIntent(R.id.next_notif_button, pendingNextIntent);
        mNotificationView.setOnClickPendingIntent(R.id.close_notif_button, pendingStopIntent);

        setNotificationStatus(true);
    }

    private Notification.Builder getLollipopNotifBuilder(boolean isPlaying) {
        Intent notificationIntent = new Intent(this, MainActivity.class);
        notificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 1, notificationIntent, 0);

        Intent serviceLastIntent = new Intent(getApplicationContext(), TheBrain.class);
        serviceLastIntent.setAction(TheBrain.PLAY_LAST);
        serviceLastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        PendingIntent pendingLastIntent = PendingIntent.getService(mContext, 1, serviceLastIntent, 0);
        Intent servicePlayIntent = new Intent(getApplicationContext(), TheBrain.class);
        servicePlayIntent.setAction(TheBrain.TOGGLE_PLAY);
        servicePlayIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        PendingIntent pendingPlayIntent = PendingIntent.getService(mContext, 1, servicePlayIntent, 0);
        Intent serviceNextIntent = new Intent(getApplicationContext(), TheBrain.class);
        serviceNextIntent.setAction(TheBrain.PLAY_NEXT);
        serviceNextIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        PendingIntent pendingNextIntent = PendingIntent.getService(mContext, 1, serviceNextIntent, 0);
        Intent serviceStopIntent = new Intent(getApplicationContext(), TheBrain.class);
        serviceStopIntent.setAction(TheBrain.PLAY_STOP);
        serviceStopIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        PendingIntent pendingStopIntent = PendingIntent.getService(mContext, 1, serviceStopIntent, 0);

        Notification.MediaStyle style = new Notification.MediaStyle();
        style.setShowActionsInCompactView(1, 2);
        Notification.Builder builder = new Notification.Builder(this).setSmallIcon(R.mipmap.sunaminotif)
                .setContentIntent(pendingIntent).setStyle(style);
        builder.addAction(R.drawable.ic_last_hint, "Last", pendingLastIntent);
        builder.addAction(isPlaying ? R.drawable.ic_pause_hint : R.drawable.ic_play_hint, "Play",
                pendingPlayIntent);
        builder.addAction(R.drawable.ic_next_hint, "Next", pendingNextIntent);
        builder.addAction(R.drawable.ic_stop_notif, "Stop", pendingStopIntent);
        return builder;
    }

    private void setMetadata(FireMixtape song) {
        new GetArtworkTask().execute(song);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mNotification = getLollipopNotifBuilder(true).setLargeIcon(mThumbnail).setContentTitle(song.title)
                    .setContentText(song.artist).build();
            startForeground(534, mNotification);
        } else {
            PlaybackStateCompat state = new PlaybackStateCompat.Builder()
                    .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE
                            | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_SEEK_TO
                            | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
                    .setState(PlaybackStateCompat.STATE_PLAYING, 0, 0.0f).build();
            MediaSessionCompatHelper.applyState(mSession, state);
            mSession.setMetadata(
                    new MediaMetadataCompat.Builder().putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title)
                            .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.artist)
                            .putLong(MediaMetadata.METADATA_KEY_DURATION, Long.parseLong(song.duration)).build());
        }
    }

    public void setProgress(int pos, boolean isPlaying) {
        mMediaPlayer.seekTo(pos);
        setProgressUI(pos, isPlaying);
    }

    public void setProgressUI(int pos, boolean isPlaying) {
        int speed = isPlaying ? 1 : 0;
        int playState = isPlaying ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED;
        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
                .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE
                        | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_SEEK_TO
                        | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
                .setState(playState, pos, speed).build();
        MediaSessionCompatHelper.applyState(mSession, state);
    }

    // save all data that needs to persist in between sessions
    public void savePersistentState() {
        mSaveCount++;
        if (mLoaded) {
            saveAppData();
        }
    }

    private void init() {
        mSongManager = new SongManager(this);
        mGenreGraph = new GenreGraph(this);
        mSongHistory = new SongHistory(HISTORY_SIZE);
        mShuffleController = new ShuffleController(this, mGenreGraph, mSongManager, mUpNext);
        mMediaPlayer = new MediaPlayer();

        // Setting wake locks
        mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
        mWakeLock.acquire();

        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                if (!mPreparing) {
                    playNext();
                }
            }
        });
        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                return false;
            }
        });
        mMediaPlayer.setOnPreparedListener(mMPPreparedListener);
        mContext.setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }

    public void postInit() {
        // Set the list for the shuffle controller to use
        mShuffleController.updateList();

        if (mBound) {
            mContext.setRecyclerViewData();
            mContext.showSong();
        }
        readAppData();
    }

    public FireMixtape getSongPlaying() {
        return mPlaying;
    }

    private void readAppData() {
        new LoadAppDataTask().execute();
    }

    private void saveAppData() {
        new SaveAppDataTask().execute();
    }

    public List<FireMixtape> getDataByTitle() {
        return mSongManager.getByTitle();
    }

    public List<FireMixtape> getDataByArtist() {
        return mSongManager.getByArtist();
    }

    public List<FireMixtape> getUpNext() {
        return mUpNext.data();
    }

    // Main functions that starts playback of a song
    public void playSong(FireMixtape song, boolean saveLast) {
        FireMixtape oldPlaying = mPlaying;
        mPlaying = song;

        if (song.isSoundcloud && !isSoundcloudEnabled()) {
            playNext();
            return;
        }

        if (mPlaying != null) {
            // request audio focus if already doesn't have it
            registerAudio();
            updateListItem(mPlaying);
            if (oldPlaying != null) {
                donePlayback(oldPlaying, mPlayTimer.reset());
                if (saveLast) {
                    mSongHistory.push(oldPlaying);
                }
            } else {
                mShuffleController.setSongValuesAsync();
            }
            mMediaPlayer.reset();
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            try {
                mMediaPlayer.setDataSource(mPlaying.data);
                mMediaPlayer.prepareAsync();
                mPreparing = true;
                setNotification(mPlaying);
                if (mBound) {
                    mContext.playSong(mPlaying);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void addSongToFront(FireMixtape song) {
        if (!checkAndNotifySoundcloudEnabled(song))
            return;
        mUpNext.pushFront(song);
        updateListItem(song);
        updateUpNextUI();
    }

    public void addSongToQueue(FireMixtape song) {
        if (!checkAndNotifySoundcloudEnabled(song))
            return;
        mUpNext.pushBackUser(song);
        updateListItem(song);
        updateUpNextUI();
    }

    public void removeSong(FireMixtape song) {
        boolean isAuto = mUpNext.remove(song);
        updateListItem(song);
        updateUpNextUI();

        if (isAuto) {
            // Send an empty song play
            PlayInstance.setSongDate(song);
            mShuffleController.recalculateSong(song);
        } else {
            mShuffleController.loadNext();
        }
    }

    public void toggleSongInLibrary(FireMixtape song) {
        if (!mSongManager.addSong(song)) {
            mSongManager.removeSong(song);
        }
        if (mBound) {
            mContext.refreshRecyclerViewData();
        }
        savePersistentState();
        mShuffleController.updateList();
        mShuffleController.setSongValuesAsync();
    }

    public boolean isSongInLibrary(FireMixtape song) {
        return mSongManager.containsSong(song);
    }

    public void updateListItem(FireMixtape song) {
        mContext.mListAdapter.updateItem(song);
    }

    public void updateUpNextUI() {
        if (mBound) {
            mContext.mDrawerFragment.updateRecyclerView();
        }
    }

    public void updateUpNextUIAsync() {
        if (mBound) {
            mContext.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mContext.mDrawerFragment.updateRecyclerView();
                }
            });
        }
    }

    private void donePlayback(FireMixtape song, int duration) {
        PlayInstance playInstance = new PlayInstance(song, duration);
        mShuffleController.addPlayInstance(playInstance);
        savePersistentState();
    }

    // Registers audio and media session
    private void registerAudio() {
        if (mHasAudioFocus || !mIsInit) {
            return;
        }
        mHasAudioFocus = true;

        // Add audio focus change listener
        mAFChangeListener = new AudioManager.OnAudioFocusChangeListener() {
            public void onAudioFocusChange(int focusChange) {
                if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
                    pausePlayback();
                    setUI(false);
                } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
                    // Does nothing cause made me play music at work
                } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
                    pausePlayback();
                    unregisterAudio();
                    setUI(false);
                }
            }
        };

        mAudioManager.requestAudioFocus(mAFChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

        // Add headphone out listener
        registerReceiver(mNoisyAudioStreamReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));

        // Add notification and transport controls
        ComponentName eventReceiver = new ComponentName(getPackageName(),
                RemoteControlEventReceiver.class.getName());
        mSession = new MediaSessionCompat(this, "FireSession", eventReceiver, null);
        mSession.setFlags(
                MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS | MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
        mSession.setPlaybackToLocal(AudioManager.STREAM_MUSIC);
        mSession.setMetadata(new MediaMetadataCompat.Builder().putString(MediaMetadataCompat.METADATA_KEY_TITLE, "")
                .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, "")
                .putLong(MediaMetadata.METADATA_KEY_DURATION, -1).build());

        mSession.setCallback(new MediaSessionCompat.Callback() {

            @Override
            public void onSeekTo(long pos) {
                super.onSeekTo(pos);
                setProgress((int) pos, isPlaying());
            }
        });
        mSession.setActive(true);
    }

    private void unregisterAudio() {
        if (!mHasAudioFocus)
            return;
        mHasAudioFocus = false;
        unregisterReceiver(mNoisyAudioStreamReceiver);
        mAudioManager.abandonAudioFocus(mAFChangeListener);
        mSession.release();
    }

    public void togglePlay() {
        if (isPlaying() || mPreparing) {
            pausePlayback();
            setUI(false);
        } else {
            if (!hasSong()) {
                playNext();
            } else {
                resumePlayback();
                registerAudio();
                setMetadata(mPlaying);
                setUI(true);
            }
        }
    }

    private void pausePlayback() {
        if (mPreparing) {
            mPauseAfterLoad = true;
            return;
        }
        mPlayTimer.stop();
        mMediaPlayer.pause();
    }

    private void resumePlayback() {
        mPlayTimer.start();
        mMediaPlayer.start();
    }

    // Changes UI as required
    private void setUI(boolean isPlaying) {
        setProgressUI(mMediaPlayer.getCurrentPosition(), isPlaying);
        setNotificationStatus(isPlaying);
        if (mBound) {
            mContext.updateSongView();
        }
    }

    public void playNext() {
        mShuffleController.loadNext();
        if (mUpNext.size() > 0) {
            playSong(mUpNext.popFront(), true);
        }
        updateUpNextUI();
    }

    public void playLast() {
        if (mSongHistory.isEmpty()) {
            return;
        }
        FireMixtape song = mSongHistory.pop();
        if (mPlaying != null) {
            mUpNext.pushFront(mPlaying);
        }
        playSong(song, false);
        updateUpNextUI();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mWakeLock.release();
        mMediaPlayer.release();
        mSession.release();
    }

    public boolean hasSong() {
        return mPlaying != null;
    }

    public boolean isPlaying() {
        try {
            return mMediaPlayer.isPlaying();
        } catch (Exception e) {
            return false;
        }
    }

    public boolean isSoundcloudEnabled() {
        if (!mMainPrefs.isSoundcloudEnabled)
            return false;
        ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(
                Service.CONNECTIVITY_SERVICE);
        NetworkInfo wifi = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
        NetworkInfo connection = connectivityManager.getActiveNetworkInfo();
        return wifi.isConnected() || (!mMainPrefs.forceOverWifi && connection != null && connection.isConnected());
    }

    public boolean checkAndNotifySoundcloudEnabled(FireMixtape song) {
        if (song.isSoundcloud && !isSoundcloudEnabled()) {
            if (!mMainPrefs.isSoundcloudEnabled) {
                Toast.makeText(this,
                        "You have disabled Soundcloud integration. To play this song, change your settings.",
                        Toast.LENGTH_SHORT).show();
            } else if (mMainPrefs.forceOverWifi) {
                Toast.makeText(this,
                        "You are not connected to a wifi network. To play this song, change your settings.",
                        Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "You are not connected to the internet.", Toast.LENGTH_SHORT).show();
            }
            return false;
        }
        return true;
    }

    public boolean isSmartEnabled() {
        return mMainPrefs.isSmartEnabled;
    }
}