com.naman14.timber.musicplayer.MusicService.java Source code

Java tutorial

Introduction

Here is the source code for com.naman14.timber.musicplayer.MusicService.java

Source

/*
 * Copyright (C) 2012 Andrew Neal
 * Copyright (C) 2014 The CyanogenMod Project
 * Copyright (C) 2015 Naman Dwivedi
 *
 * 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 com.naman14.timber.musicplayer;

import android.annotation.SuppressLint;
import android.app.AlarmManager;
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.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.audiofx.AudioEffect;
import android.net.Uri;
import android.os.Build;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.app.NotificationCompat;
import android.support.v7.graphics.Palette;
import android.text.TextUtils;
import android.util.Log;

import com.naman14.timber.R;
import com.naman14.timber.helpers.MediaButtonIntentReceiver;
import com.naman14.timber.helpers.MusicPlaybackTrack;
import com.naman14.timber.helpers.Song;
import com.naman14.timber.lastfmapi.LastFmClient;
import com.naman14.timber.lastfmapi.models.LastfmUserSession;
import com.naman14.timber.lastfmapi.models.ScrobbleQuery;
import com.naman14.timber.provider.MusicPlaybackState;
import com.naman14.timber.provider.RecentStore;
import com.naman14.timber.provider.SongPlayCount;
import com.naman14.timber.utils.NavigationUtils;
import com.naman14.timber.utils.TimberUtils;
import com.naman14.timber.utils.TimberUtils.IdType;
import com.nostra13.universalimageloader.core.ImageLoader;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import de.Maxr1998.trackselectorlib.NotificationHelper;

@SuppressLint("NewApi")
public class MusicService extends Service {

    private static final String TAG = "MusicPlaybackService";
    private static final boolean D = true;

    public static final String PLAYSTATE_CHANGED = "com.naman14.timber.playstatechanged";
    public static final String POSITION_CHANGED = "com.naman14.timber.positionchanged";
    public static final String META_CHANGED = "com.naman14.timber.metachanged";
    public static final String QUEUE_CHANGED = "com.naman14.timber.queuechanged";
    public static final String PLAYLIST_CHANGED = "com.naman14.timber.playlistchanged";
    public static final String REPEATMODE_CHANGED = "com.naman14.timber.repeatmodechanged";
    public static final String SHUFFLEMODE_CHANGED = "com.naman14.timber.shufflemodechanged";
    public static final String TRACK_ERROR = "com.naman14.timber.trackerror";
    public static final String PLAYER_PREPARED = "com.naman14.timber.playerprepared";
    public static final String BUFFERING_STATUS_CHANGED = "com.naman14.timber.bufferingstatuschanged";
    public static final String TIMBER_PACKAGE_NAME = "com.naman14.timber";
    public static final String MUSIC_PACKAGE_NAME = "com.android.music";
    public static final String SERVICECMD = "com.naman14.timber.musicservicecommand";
    public static final String TOGGLEPAUSE_ACTION = "com.naman14.timber.togglepause";
    public static final String PAUSE_ACTION = "com.naman14.timber.pause";
    public static final String STOP_ACTION = "com.naman14.timber.stop";
    public static final String PREVIOUS_ACTION = "com.naman14.timber.previous";
    public static final String PREVIOUS_FORCE_ACTION = "com.naman14.timber.previous.force";
    public static final String NEXT_ACTION = "fcom.naman14.timber.next";
    public static final String REPEAT_ACTION = "com.naman14.timber.repeat";
    public static final String SHUFFLE_ACTION = "com.naman14.timber.shuffle";
    public static final String FROM_MEDIA_BUTTON = "frommediabutton";
    public static final String REFRESH = "com.naman14.timber.refresh";
    public static final String UPDATE_LOCKSCREEN = "com.naman14.timber.updatelockscreen";
    private static final String SHUTDOWN = "com.naman14.timber.shutdown";
    public static final String CMDNAME = "command";
    public static final String CMDTOGGLEPAUSE = "togglepause";
    public static final String CMDSTOP = "stop";
    public static final String CMDPAUSE = "pause";
    public static final String CMDPLAY = "play";
    public static final String CMDPREVIOUS = "previous";
    public static final String CMDNEXT = "next";
    public static final String CMDNOTIF = "buttonId";

    public static final int NEXT = 2;
    public static final int LAST = 3;
    public static final int SHUFFLE_NONE = 0;
    public static final int SHUFFLE_NORMAL = 1;
    public static final int SHUFFLE_AUTO = 2;
    public static final int REPEAT_NONE = 0;
    public static final int REPEAT_CURRENT = 1;
    public static final int REPEAT_ALL = 2;
    public static final int MAX_HISTORY_SIZE = 1000;

    static final int IDCOLIDX = 0;
    static final int TRACK_ENDED = 1;
    static final int TRACK_WENT_TO_NEXT = 2;
    static final int RELEASE_WAKELOCK = 3;
    static final int SERVER_DIED = 4;
    static final int FOCUSCHANGE = 5;
    static final int FADEDOWN = 6;
    static final int FADEUP = 7;
    static final int PREPARED = 8;
    static final int BUFFERING_STATUS = 9;

    static final int IDLE_DELAY = 5 * 60 * 1000;
    static final long REWIND_INSTEAD_PREVIOUS_THRESHOLD = 15000;

    //    private static final String[] PROJECTION = new String[]{
    //            "audio._id AS _id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
    //            MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
    //            MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
    //            MediaStore.Audio.Media.ARTIST_ID
    //    };
    //    private static final String[] ALBUM_PROJECTION = new String[]{
    //            MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ARTIST,
    //            MediaStore.Audio.Albums.LAST_YEAR
    //    };
    //    private static final String[] NOTIFICATION_PROJECTION = new String[]{
    //            "audio._id AS _id", AudioColumns.ALBUM_ID, AudioColumns.TITLE,
    //            AudioColumns.ARTIST, AudioColumns.DURATION
    //    };
    //    private static final String[] PROJECTION_MATRIX = new String[]{
    //            "_id", MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM,
    //            MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA,
    //            MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.ALBUM_ID,
    //            MediaStore.Audio.Media.ARTIST_ID
    //    };

    private static final Shuffler mShuffler = new Shuffler();
    private static final int NOTIFY_MODE_NONE = 0;
    private static final int NOTIFY_MODE_FOREGROUND = 1;
    private static final int NOTIFY_MODE_BACKGROUND = 2;

    private int mNotifyMode = NOTIFY_MODE_NONE;

    private static LinkedList<Integer> mHistory = new LinkedList<>();
    private final IBinder mBinder = new ServiceStub(this);
    MultiPlayer2 mPlayer;
    private String mFileToPlay;
    WakeLock mWakeLock;
    private AlarmManager mAlarmManager;
    private PendingIntent mShutdownIntent;
    private boolean mShutdownScheduled;
    private NotificationManagerCompat mNotificationManager;
    private AudioManager mAudioManager;
    private SharedPreferences mPreferences;

    private boolean mServiceInUse = false;
    private boolean mIsSupposedToBePlaying = false;
    private long mLastPlayedTime;

    private long mNotificationPostTime = 0;
    boolean mPausedByTransientLossOfFocus = false;

    private MediaSessionCompat mSession;

    private ComponentName mMediaButtonReceiverComponent;

    private int mCardId;

    int mPlayPos = -1;

    int mNextPlayPos = -1;

    private int mOpenFailedCounter = 0;

    private int mMediaMountedCount = 0;

    private int mShuffleMode = SHUFFLE_NONE;

    int mRepeatMode = REPEAT_NONE;

    private int mServiceStartId = -1;

    List<MusicPlaybackTrack> mPlaylist = new ArrayList<>(100);

    public List<MusicPlaybackTrack> getPlaylist() {
        return mPlaylist;
    }

    public void setPlaylist(List<MusicPlaybackTrack> mPlaylist) {
        this.mPlaylist = mPlaylist;
    }

    private long[] mAutoShuffleList = null;

    private MusicPlayerHandler mPlayerHandler;
    private final OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {

        @Override
        public void onAudioFocusChange(final int focusChange) {
            mPlayerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
        }
    };
    private HandlerThread mHandlerThread;
    private BroadcastReceiver mUnmountReceiver = null;
    private MusicPlaybackState mPlaybackStateStore;
    private boolean mShowAlbumArtOnLockscreen;
    private SongPlayCount mSongPlayCount;
    private RecentStore mRecentStore;

    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(final Context context, final Intent intent) {
            final String command = intent.getStringExtra(CMDNAME);
            handleCommandIntent(intent);
        }
    };
    //    private ContentObserver mMediaStoreObserver;

    @Override
    public IBinder onBind(final Intent intent) {
        if (D)
            Log.d(TAG, "Service bound, intent = " + intent);
        cancelShutdown();
        mServiceInUse = true;
        return mBinder;
    }

    @Override
    public boolean onUnbind(final Intent intent) {
        if (D)
            Log.d(TAG, "Service unbound");
        mServiceInUse = false;
        saveQueue(true);

        if (mIsSupposedToBePlaying || mPausedByTransientLossOfFocus) {

            return true;

        } else if (mPlaylist.size() > 0 || mPlayerHandler.hasMessages(TRACK_ENDED)) {
            scheduleDelayedShutdown();
            return true;
        }
        stopSelf(mServiceStartId);

        return true;
    }

    @Override
    public void onRebind(final Intent intent) {
        cancelShutdown();
        mServiceInUse = true;
    }

    @Override
    public void onCreate() {
        if (D)
            Log.d(TAG, "Creating service");
        super.onCreate();

        mNotificationManager = NotificationManagerCompat.from(this);

        // gets a pointer to the playback state store
        mPlaybackStateStore = MusicPlaybackState.getInstance(this);
        mSongPlayCount = SongPlayCount.getInstance(this);
        mRecentStore = RecentStore.getInstance(this);

        mHandlerThread = new HandlerThread("MusicPlayerHandler", android.os.Process.THREAD_PRIORITY_BACKGROUND);
        mHandlerThread.start();

        mPlayerHandler = new MusicPlayerHandler(this, mHandlerThread.getLooper());

        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        mMediaButtonReceiverComponent = new ComponentName(getPackageName(),
                MediaButtonIntentReceiver.class.getName());
        mAudioManager.registerMediaButtonEventReceiver(mMediaButtonReceiverComponent);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setUpMediaSession();
        }

        mPreferences = getSharedPreferences("Service", 0);
        //        mCardId = getCardId();

        //        registerExternalStorageListener();

        mPlayer = new MultiPlayer2(this);
        mPlayer.setHandler(mPlayerHandler);

        // Initialize the intent filter and each action
        final IntentFilter filter = new IntentFilter();
        filter.addAction(SERVICECMD);
        filter.addAction(TOGGLEPAUSE_ACTION);
        filter.addAction(PAUSE_ACTION);
        filter.addAction(STOP_ACTION);
        filter.addAction(NEXT_ACTION);
        filter.addAction(PREVIOUS_ACTION);
        filter.addAction(PREVIOUS_FORCE_ACTION);
        filter.addAction(REPEAT_ACTION);
        filter.addAction(SHUFFLE_ACTION);

        // Attach the broadcast listener
        registerReceiver(mIntentReceiver, filter);

        //        mMediaStoreObserver = new MediaStoreObserver(mPlayerHandler);
        //        getContentResolver().registerContentObserver(
        //                MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mMediaStoreObserver);
        //        getContentResolver().registerContentObserver(
        //                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mMediaStoreObserver);

        // Initialize the wake lock
        final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
        mWakeLock.setReferenceCounted(false);

        final Intent shutdownIntent = new Intent(this, MusicService.class);
        shutdownIntent.setAction(SHUTDOWN);

        mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        mShutdownIntent = PendingIntent.getService(this, 0, shutdownIntent, 0);

        scheduleDelayedShutdown();

        //        reloadQueueAfterPermissionCheck();
        notifyChange(QUEUE_CHANGED);
        notifyChange(META_CHANGED);
    }

    private void setUpMediaSession() {
        mSession = new MediaSessionCompat(this, "Timber");
        mSession.setCallback(new MediaSessionCompat.Callback() {
            @Override
            public void onPause() {
                pause();
                mPausedByTransientLossOfFocus = false;
            }

            @Override
            public void onPlay() {
                play();
            }

            @Override
            public void onSeekTo(long pos) {
                seek(pos);
            }

            @Override
            public void onSkipToNext() {
                gotoNext(true);
            }

            @Override
            public void onSkipToPrevious() {
                prev(false);
            }

            @Override
            public void onStop() {
                pause();
                mPausedByTransientLossOfFocus = false;
                seek(0);
                releaseServiceUiAndStop();
            }
        });
        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
    }

    @Override
    public void onDestroy() {
        if (D)
            Log.d(TAG, "Destroying service");
        super.onDestroy();
        // Remove any sound effects
        final Intent audioEffectsIntent = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
        audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
        audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
        sendBroadcast(audioEffectsIntent);

        mAlarmManager.cancel(mShutdownIntent);

        mPlayerHandler.removeCallbacksAndMessages(null);

        if (TimberUtils.isJellyBeanMR2())
            mHandlerThread.quitSafely();
        else
            mHandlerThread.quit();

        mPlayer.release();
        mPlayer = null;

        mAudioManager.abandonAudioFocus(mAudioFocusListener);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
            mSession.release();

        //        getContentResolver().unregisterContentObserver(mMediaStoreObserver);

        closeCursor();

        unregisterReceiver(mIntentReceiver);
        if (mUnmountReceiver != null) {
            unregisterReceiver(mUnmountReceiver);
            mUnmountReceiver = null;
        }

        mWakeLock.release();
    }

    @Override
    public int onStartCommand(final Intent intent, final int flags, final int startId) {
        if (D)
            Log.d(TAG, "Got new intent " + intent + ", startId = " + startId);
        mServiceStartId = startId;

        if (intent != null) {
            final String action = intent.getAction();

            if (SHUTDOWN.equals(action)) {
                mShutdownScheduled = false;
                releaseServiceUiAndStop();
                return START_NOT_STICKY;
            }

            handleCommandIntent(intent);
        }

        scheduleDelayedShutdown();

        if (intent != null && intent.getBooleanExtra(FROM_MEDIA_BUTTON, false)) {
            MediaButtonIntentReceiver.completeWakefulIntent(intent);
        }

        return START_NOT_STICKY; //no sense to use START_STICKY with using startForeground
    }

    void scrobble() {
        if (LastfmUserSession.getSession(this) != null) {
            Log.d("Scrobble", "to LastFM");
            LastFmClient.getInstance(this).Scrobble(new ScrobbleQuery(getArtistName(), getTrackName(),
                    (System.currentTimeMillis() - duration()) / 1000));
        }
    }

    private void releaseServiceUiAndStop() {
        if (isPlaying() || mPausedByTransientLossOfFocus || mPlayerHandler.hasMessages(TRACK_ENDED)) {
            return;
        }

        if (D) {
            Log.d(TAG, "Nothing is playing anymore, releasing notification");
        }
        cancelNotification();
        mAudioManager.abandonAudioFocus(mAudioFocusListener);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
            mSession.setActive(false);

        if (!mServiceInUse) {
            saveQueue(true);
            stopSelf(mServiceStartId);
        }
    }

    private void handleCommandIntent(Intent intent) {
        final String action = intent.getAction();
        final String command = SERVICECMD.equals(action) ? intent.getStringExtra(CMDNAME) : null;

        if (D) {
            Log.d(TAG, "handleCommandIntent: action = " + action + ", command = " + command);
        }

        if (NotificationHelper.checkIntent(intent)) {
            goToPosition(mPlayPos + NotificationHelper.getPosition(intent));
            return;
        }

        if (CMDNEXT.equals(command) || NEXT_ACTION.equals(action)) {
            gotoNext(true);
        } else if (CMDPREVIOUS.equals(command) || PREVIOUS_ACTION.equals(action)
                || PREVIOUS_FORCE_ACTION.equals(action)) {

            prev(PREVIOUS_FORCE_ACTION.equals(action));
        } else if (CMDTOGGLEPAUSE.equals(command) || TOGGLEPAUSE_ACTION.equals(action)) {
            if (isPlaying()) {
                pause();
                mPausedByTransientLossOfFocus = false;
            } else {
                play();
            }
        } else if (CMDPAUSE.equals(command) || PAUSE_ACTION.equals(action)) {
            pause();
            mPausedByTransientLossOfFocus = false;
        } else if (CMDPLAY.equals(command)) {
            play();
        } else if (CMDSTOP.equals(command) || STOP_ACTION.equals(action)) {
            pause();
            mPausedByTransientLossOfFocus = false;
            seek(0);
            releaseServiceUiAndStop();
        } else if (REPEAT_ACTION.equals(action)) {
            cycleRepeat();
        } else if (SHUFFLE_ACTION.equals(action)) {
            cycleShuffle();
        }
    }

    void updateNotification() {
        final int newNotifyMode;
        if (isPlaying()) {
            newNotifyMode = NOTIFY_MODE_FOREGROUND;
        } else if (recentlyPlayed()) {
            newNotifyMode = NOTIFY_MODE_BACKGROUND;
        } else {
            newNotifyMode = NOTIFY_MODE_NONE;
        }

        int notificationId = hashCode();
        if (mNotifyMode != newNotifyMode) {
            if (mNotifyMode == NOTIFY_MODE_FOREGROUND) {
                if (TimberUtils.isLollipop()) {
                    stopForeground(newNotifyMode == NOTIFY_MODE_NONE);
                } else {
                    stopForeground(newNotifyMode == NOTIFY_MODE_NONE || newNotifyMode == NOTIFY_MODE_BACKGROUND);
                }
            } else if (newNotifyMode == NOTIFY_MODE_NONE) {
                mNotificationManager.cancel(notificationId);
                mNotificationPostTime = 0;
            }
        }

        if (newNotifyMode == NOTIFY_MODE_FOREGROUND) {
            startForeground(notificationId, buildNotification());
        } else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) {
            mNotificationManager.notify(notificationId, buildNotification());
        }

        mNotifyMode = newNotifyMode;
    }

    private void cancelNotification() {
        stopForeground(true);
        mNotificationManager.cancel(hashCode());
        mNotificationPostTime = 0;
        mNotifyMode = NOTIFY_MODE_NONE;
    }

    //    private int getCardId() {
    //        if (TimberUtils.isMarshmallow()) {
    //            if (Nammu.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
    //                return getmCardId();
    //            }
    //            else {
    //                return 0;
    //            }
    //        }
    //        else {
    //            return getmCardId();
    //        }
    //    }

    //    private int getmCardId() {
    //        final ContentResolver resolver = getContentResolver();
    //        Cursor cursor = resolver.query(Uri.parse("content://media/external/fs_id"), null, null,
    //                null, null);
    //        int mCardId = -1;
    //        if (cursor != null && cursor.moveToFirst()) {
    //            mCardId = cursor.getInt(0);
    //            cursor.close();
    //            cursor = null;
    //        }
    //        return mCardId;
    //    }
    //
    //    public void closeExternalStorageFiles(final String storagePath) {
    //        stop(true);
    //        notifyChange(QUEUE_CHANGED);
    //        notifyChange(META_CHANGED);
    //    }
    //
    //    public void registerExternalStorageListener() {
    //        if (mUnmountReceiver == null) {
    //            mUnmountReceiver = new BroadcastReceiver() {
    //
    //
    //                @Override
    //                public void onReceive(final Context context, final Intent intent) {
    //                    final String action = intent.getAction();
    //                    if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
    //                        saveQueue(true);
    //                        mQueueIsSaveable = false;
    //                        closeExternalStorageFiles(intent.getData().getPath());
    //                    } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
    //                        mMediaMountedCount++;
    //                        mCardId = getCardId();
    //                        reloadQueueAfterPermissionCheck();
    //                        mQueueIsSaveable = true;
    //                        notifyChange(QUEUE_CHANGED);
    //                        notifyChange(META_CHANGED);
    //                    }
    //                }
    //            };
    //            final IntentFilter filter = new IntentFilter();
    //            filter.addAction(Intent.ACTION_MEDIA_EJECT);
    //            filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
    //            filter.addDataScheme("file");
    //            registerReceiver(mUnmountReceiver, filter);
    //        }
    //    }

    private void scheduleDelayedShutdown() {
        if (D)
            Log.v(TAG, "Scheduling shutdown in " + IDLE_DELAY + " ms");
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + IDLE_DELAY,
                mShutdownIntent);
        mShutdownScheduled = true;
    }

    private void cancelShutdown() {
        if (D)
            Log.d(TAG, "Cancelling delayed shutdown, scheduled = " + mShutdownScheduled);
        if (mShutdownScheduled) {
            mAlarmManager.cancel(mShutdownIntent);
            mShutdownScheduled = false;
        }
    }

    private void stop(final boolean goToIdle) {
        if (D) {
            Log.d(TAG, "Stopping playback, goToIdle = " + goToIdle);
        }
        if (mPlayer.isInitialized()) {
            mPlayer.stop();
        }
        mFileToPlay = null;
        closeCursor();
        if (goToIdle) {
            setIsSupposedToBePlaying(false, false);
        } else {
            if (TimberUtils.isLollipop()) {
                stopForeground(false);
            } else {
                stopForeground(true);
            }
        }
    }

    private int removeTracksInternal(int first, int last) {
        synchronized (this) {
            if (last < first) {
                return 0;
            } else if (first < 0) {
                first = 0;
            } else if (last >= mPlaylist.size()) {
                last = mPlaylist.size() - 1;
            }

            boolean gotonext = false;
            if (first <= mPlayPos && mPlayPos <= last) {
                mPlayPos = first;
                gotonext = true;
            } else if (mPlayPos > last) {
                mPlayPos -= last - first + 1;
            }
            final int numToRemove = last - first + 1;

            if (first == 0 && last == mPlaylist.size() - 1) {
                mPlayPos = -1;
                mNextPlayPos = -1;
                mPlaylist.clear();
                mHistory.clear();
            } else {
                for (int i = 0; i < numToRemove; i++) {
                    mPlaylist.remove(first);
                }

                ListIterator<Integer> positionIterator = mHistory.listIterator();
                while (positionIterator.hasNext()) {
                    int pos = positionIterator.next();
                    if (pos >= first && pos <= last) {
                        positionIterator.remove();
                    } else if (pos > last) {
                        positionIterator.set(pos - numToRemove);
                    }
                }
            }
            if (gotonext) {
                if (mPlaylist.size() == 0) {
                    stop(true);
                    mPlayPos = -1;
                    closeCursor();
                } else {
                    if (mShuffleMode != SHUFFLE_NONE) {
                        mPlayPos = getNextPosition(true);
                    } else if (mPlayPos >= mPlaylist.size()) {
                        mPlayPos = 0;
                    }
                    final boolean wasPlaying = isPlaying();
                    stop(false);
                    openCurrentAndNext();
                    if (wasPlaying) {
                        play();
                    }
                }
                notifyChange(META_CHANGED);
            }
            return last - first + 1;
        }
    }

    private void addToPlayList(final List<Song> songs, int position, long sourceId, TimberUtils.IdType sourceType) {
        final int addlen = songs.size();
        if (position < 0) {
            mPlaylist.clear();
            position = 0;
        }

        //        mPlaylist.ensureCapacity(mPlaylist.size() + addlen);
        if (position > mPlaylist.size()) {
            position = mPlaylist.size();
        }

        final ArrayList<MusicPlaybackTrack> arrayList = new ArrayList<>(addlen);
        for (int i = 0; i < songs.size(); i++) {
            MusicPlaybackTrack playbackTrack = new MusicPlaybackTrack(songs.get(i).title, songs.get(i).url);
            if (sourceType.equals(IdType.NA) && playbackTrack.url == null) {
                playbackTrack.url = "content://media/external/audio/media/" + songs.get(i).id;
            }
            arrayList.add(playbackTrack);
        }

        mPlaylist.addAll(position, arrayList);

        if (mPlaylist.size() == 0) {
            closeCursor();
            notifyChange(META_CHANGED);
        }
    }

    void updateCursor(final long trackId) {
        updateCursor("_id=" + trackId, null);
    }

    private void updateCursor(final String selection, final String[] selectionArgs) {
        //        synchronized (this) {
        //            closeCursor();
        //            mCursor = openCursorAndGoToFirst(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        //                    PROJECTION, selection, selectionArgs);
        //        }
        //        updateAlbumCursor();
    }

    private void updateCursor(final Uri uri) {
        //        synchronized (this) {
        //            closeCursor();
        //            mCursor = openCursorAndGoToFirst(uri, PROJECTION, null, null);
        //        }
        //        updateAlbumCursor();
    }

    private void updateAlbumCursor() {
        //        long albumId = getAlbumId();
        //        if (albumId >= 0) {
        //            mAlbumCursor = openCursorAndGoToFirst(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
        //                    ALBUM_PROJECTION, "_id=" + albumId, null);
        //        } else {
        //            mAlbumCursor = null;
        //        }
    }

    //    private Cursor openCursorAndGoToFirst(Uri uri, String[] projection,
    //                                          String selection, String[] selectionArgs) {
    //        Cursor c = getContentResolver().query(uri, projection,
    //                selection, selectionArgs, null);
    //        if (c == null) {
    //            return null;
    //        }
    //        if (!c.moveToFirst()) {
    //            c.close();
    //            return null;
    //        }
    //        return c;
    //    }

    private synchronized void closeCursor() {
        /*if (mAlbumCursor != null) {
        mAlbumCursor.close();
        mAlbumCursor = null;
        }*/
    }

    void openCurrentAndNext() {
        openCurrentAndMaybeNext(true);
    }

    private void openCurrentAndMaybeNext(final boolean openNext) {
        synchronized (this) {
            closeCursor();

            if (mPlaylist.size() == 0) {
                return;
            }
            stop(false);

            boolean shutdown = false;

            updateCursor(mPlaylist.get(mPlayPos).mId);
            while (true) {
                //Opening cursor
                /*if (mCursor != null
                    && openFile(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/"
                    + mCursor.getLong(IDCOLIDX))) {*/
                if (openFile(mPlaylist.get(mPlayPos).url)) {
                    break;
                }

                closeCursor();
                if (mOpenFailedCounter++ < 10 && mPlaylist.size() > 1) {
                    final int pos = getNextPosition(false);
                    if (pos < 0) {
                        shutdown = true;
                        break;
                    }
                    mPlayPos = pos;
                    stop(false);
                    mPlayPos = pos;
                    updateCursor(mPlaylist.get(mPlayPos).mId);
                } else {
                    mOpenFailedCounter = 0;
                    Log.w(TAG, "Failed to open file for playback");
                    shutdown = true;
                    break;
                }
            }

            if (shutdown) {
                scheduleDelayedShutdown();
                if (mIsSupposedToBePlaying) {
                    mIsSupposedToBePlaying = false;
                    notifyChange(PLAYSTATE_CHANGED);
                }
            } else if (openNext) {
                setNextTrack();
            }
        }
    }

    void sendErrorMessage(final String trackName) {
        final Intent i = new Intent(TRACK_ERROR);
        i.putExtra(TrackErrorExtra.TRACK_NAME, trackName);
        sendBroadcast(i);
    }

    private int getNextPosition(final boolean force) {
        if (mPlaylist == null || mPlaylist.isEmpty()) {
            return -1;
        }
        if (!force && mRepeatMode == REPEAT_CURRENT) {
            if (mPlayPos < 0) {
                return 0;
            }
            return mPlayPos;
        } else if (mShuffleMode == SHUFFLE_NORMAL) {
            final int numTracks = mPlaylist.size();

            final int[] trackNumPlays = new int[numTracks];
            for (int i = 0; i < numTracks; i++) {
                trackNumPlays[i] = 0;
            }

            //            final int numHistory = mHistory.size();
            //            for (int i = 0; i < numHistory; i++) {
            //                final int idx = mHistory.get(i).intValue();
            //                if (idx >= 0 && idx < numTracks) {
            //                    trackNumPlays[idx]++;
            //                }
            //            }

            if (mPlayPos >= 0 && mPlayPos < numTracks) {
                trackNumPlays[mPlayPos]++;
            }

            int minNumPlays = Integer.MAX_VALUE;
            int numTracksWithMinNumPlays = 0;
            for (int i = 0; i < trackNumPlays.length; i++) {
                if (trackNumPlays[i] < minNumPlays) {
                    minNumPlays = trackNumPlays[i];
                    numTracksWithMinNumPlays = 1;
                } else if (trackNumPlays[i] == minNumPlays) {
                    numTracksWithMinNumPlays++;
                }
            }

            if (minNumPlays > 0 && numTracksWithMinNumPlays == numTracks && mRepeatMode != REPEAT_ALL && !force) {
                return -1;
            }

            int skip = mShuffler.nextInt(numTracksWithMinNumPlays);
            for (int i = 0; i < trackNumPlays.length; i++) {
                if (trackNumPlays[i] == minNumPlays) {
                    if (skip == 0) {
                        return i;
                    } else {
                        skip--;
                    }
                }
            }

            if (D) {
                Log.e(TAG, "Getting the next position resulted did not get a result when it should have");
            }
            return -1;
        } else if (mShuffleMode == SHUFFLE_AUTO) {
            doAutoShuffleUpdate();
            return mPlayPos + 1;
        } else {
            if (mPlayPos >= mPlaylist.size() - 1) {
                if (mRepeatMode == REPEAT_NONE && !force) {
                    return -1;
                } else if (mRepeatMode == REPEAT_ALL || force) {
                    return 0;
                }
                return -1;
            } else {
                return mPlayPos + 1;
            }
        }
    }

    void setNextTrack() {
        setNextTrack(getNextPosition(false));
    }

    private void setNextTrack(int position) {
        mNextPlayPos = position;
        if (D) {
            Log.d(TAG, "setNextTrack: next play position = " + mNextPlayPos);
        }
        if (mNextPlayPos >= 0 && mPlaylist != null && mNextPlayPos < mPlaylist.size()) {
            final long id = mPlaylist.get(mNextPlayPos).mId;
            mPlayer.setNextDataSource(
                    mPlaylist.get(position).url/*MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id*/);
        } else {
            mPlayer.setNextDataSource(null);
        }
    }

    private boolean makeAutoShuffleList() {
        Cursor cursor = null;
        try {
            cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                    new String[] { MediaStore.Audio.Media._ID }, MediaStore.Audio.Media.IS_MUSIC + "=1", null,
                    null);
            if (cursor == null || cursor.getCount() == 0) {
                return false;
            }
            final int len = cursor.getCount();
            final long[] list = new long[len];
            for (int i = 0; i < len; i++) {
                cursor.moveToNext();
                list[i] = cursor.getLong(0);
            }
            mAutoShuffleList = list;
            return true;
        } catch (final RuntimeException e) {
        } finally {
            if (cursor != null) {
                cursor.close();
                cursor = null;
            }
        }
        return false;
    }

    private void doAutoShuffleUpdate() {
        boolean notify = false;
        if (mPlayPos > 10) {
            removeTracks(0, mPlayPos - 9);
            notify = true;
        }
        final int toAdd = 7 - (mPlaylist.size() - (mPlayPos < 0 ? -1 : mPlayPos));
        for (int i = 0; i < toAdd; i++) {
            int lookback = mHistory.size();
            int idx = -1;
            while (true) {
                idx = mShuffler.nextInt(mAutoShuffleList.length);
                if (!wasRecentlyUsed(idx, lookback)) {
                    break;
                }
                lookback /= 2;
            }
            mHistory.add(idx);
            if (mHistory.size() > MAX_HISTORY_SIZE) {
                mHistory.remove(0);
            }
            mPlaylist.add(new MusicPlaybackTrack(mAutoShuffleList[idx], -1, TimberUtils.IdType.NA, -1));
            notify = true;
        }
        if (notify) {
            notifyChange(QUEUE_CHANGED);
        }
    }

    private boolean wasRecentlyUsed(final int idx, int lookbacksize) {
        if (lookbacksize == 0) {
            return false;
        }
        final int histsize = mHistory.size();
        if (histsize < lookbacksize) {
            lookbacksize = histsize;
        }
        final int maxidx = histsize - 1;
        for (int i = 0; i < lookbacksize; i++) {
            final long entry = mHistory.get(maxidx - i);
            if (entry == idx) {
                return true;
            }
        }
        return false;
    }

    public static final String INTENT_EXTRA_BUFFERED_PERCENTAGE = "buffered_percentage";

    void notifyChange(final String what) {
        if (D) {
            Log.d(TAG, "notifyChange: what = " + what);
        }

        // Update the lockscreen controls
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            updateMediaSession(what);
        }

        if (what.equals(POSITION_CHANGED)) {
            return;
        }

        final Intent intent = new Intent(what);
        intent.putExtra("id", getAudioId());
        intent.putExtra("artist", getArtistName());
        intent.putExtra("album", getAlbumName());
        intent.putExtra("track", getTrackName());
        intent.putExtra("playing", isPlaying());
        intent.putExtra(INTENT_EXTRA_BUFFERED_PERCENTAGE, mBufferedPercentage);

        sendStickyBroadcast(intent);

        final Intent musicIntent = new Intent(intent);
        musicIntent.setAction(what.replace(TIMBER_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
        sendStickyBroadcast(musicIntent);

        if (what.equals(META_CHANGED)) {

            mRecentStore.addSongId(getAudioId()); // This is used to track recently played songs
            mSongPlayCount.bumpSongCount(getAudioId()); //This is to track top played songs

        } else if (what.equals(QUEUE_CHANGED)) {
            saveQueue(true);
            if (isPlaying()) {

                if (mNextPlayPos >= 0 && mNextPlayPos < mPlaylist.size() && getShuffleMode() != SHUFFLE_NONE) {
                    setNextTrack(mNextPlayPos);
                } else {
                    setNextTrack();
                }
            }
        } else {
            saveQueue(false);
        }

        if (what.equals(PLAYSTATE_CHANGED)) {
            updateNotification();
        }

    }

    private void updateMediaSession(final String what) {
        int playState = mIsSupposedToBePlaying ? PlaybackStateCompat.STATE_PLAYING
                : PlaybackStateCompat.STATE_PAUSED;

        if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                mSession.setPlaybackState(new PlaybackStateCompat.Builder().setState(playState, position(), 1.0f)
                        .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
                                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
                                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
                        .build());
            }
        } else if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {
            //TODO: Replace below Image download using Picasso
            Bitmap albumArt = ImageLoader.getInstance()
                    .loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());
            if (albumArt != null) {

                Bitmap.Config config = albumArt.getConfig();
                if (config == null) {
                    config = Bitmap.Config.ARGB_8888;
                }
                albumArt = albumArt.copy(config, false);
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                mSession.setMetadata(new MediaMetadataCompat.Builder()
                        .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, getArtistName())
                        .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, getAlbumArtistName())
                        .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, getAlbumName())
                        .putString(MediaMetadataCompat.METADATA_KEY_TITLE, getTrackName())
                        .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration())
                        .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getQueuePosition() + 1)
                        .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getQueue().size())
                        .putString(MediaMetadataCompat.METADATA_KEY_GENRE, getGenreName())
                        .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART,
                                mShowAlbumArtOnLockscreen ? albumArt : null)
                        .build());

                mSession.setPlaybackState(new PlaybackStateCompat.Builder().setState(playState, position(), 1.0f)
                        .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
                                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
                                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
                        .build());
            }
        }
    }

    private Notification buildNotification() {
        final String albumName = getAlbumName();
        final String artistName = getArtistName();
        final boolean isPlaying = isPlaying();
        String text = TextUtils.isEmpty(albumName) ? artistName : artistName + " - " + albumName;

        int playButtonResId = isPlaying ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_white_36dp;

        Intent nowPlayingIntent = NavigationUtils.getNowPlayingIntent(this);
        PendingIntent clickIntent = PendingIntent.getActivity(this, 0, nowPlayingIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        //TODO: Replace below Image download using Picasso
        Bitmap artwork;
        artwork = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());

        if (artwork == null) {
            artwork = ImageLoader.getInstance().loadImageSync("drawable://" + R.drawable.ic_empty_music2);
        }

        if (mNotificationPostTime == 0) {
            mNotificationPostTime = System.currentTimeMillis();
        }

        android.support.v4.app.NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_notification).setLargeIcon(artwork).setContentIntent(clickIntent)
                .setContentTitle(getTrackName()).setContentText(text).setWhen(mNotificationPostTime)
                .addAction(R.drawable.ic_skip_previous_white_36dp, "", retrievePlaybackAction(PREVIOUS_ACTION))
                .addAction(playButtonResId, "", retrievePlaybackAction(TOGGLEPAUSE_ACTION))
                .addAction(R.drawable.ic_skip_next_white_36dp, "", retrievePlaybackAction(NEXT_ACTION));

        if (TimberUtils.isJellyBeanMR1()) {
            builder.setShowWhen(false);
        }
        if (TimberUtils.isLollipop()) {
            builder.setVisibility(Notification.VISIBILITY_PUBLIC);
            NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle()
                    .setMediaSession(mSession.getSessionToken()).setShowActionsInCompactView(0, 1, 2, 3);
            builder.setStyle(style);
        }
        if (artwork != null && TimberUtils.isLollipop()) {
            builder.setColor(Palette.from(artwork).generate().getVibrantColor(Color.parseColor("#403f4d")));
        }
        Notification n = builder.build();

        //        if (PreferencesUtility.getInstance(this).getXPosedTrackselectorEnabled()) {
        //            addXTrackSelector(n);
        //        }

        return n;
    }

    //    private void addXTrackSelector(Notification n) {
    //        if (NotificationHelper.isSupported(n)) {
    //            StringBuilder selection = new StringBuilder();
    //            StringBuilder order = new StringBuilder().append("CASE _id \n");
    //            for (int i = 0; i < mPlaylist.size(); i++) {
    //                selection.append("_id=").append(mPlaylist.get(i).mId).append(" OR ");
    //                order.append("WHEN ").append(mPlaylist.get(i).mId).append(" THEN ").append(i).append("\n");
    //            }
    //            order.append("END");
    //            Cursor c = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, NOTIFICATION_PROJECTION, selection.substring(0, selection.length() - 3), null, order.toString());
    //            if (c != null && c.getCount() != 0) {
    //                c.moveToFirst();
    //                ArrayList<Bundle> list = new ArrayList<>();
    //                do {
    //                    TrackItem t = new TrackItem()
    //                            .setArt(ImageLoader.getInstance()
    //                                    .loadImageSync(TimberUtils.getAlbumArtUri(c.getLong(c.getColumnIndexOrThrow(AudioColumns.ALBUM_ID))).toString()))
    //                            .setTitle(c.getString(c.getColumnIndexOrThrow(AudioColumns.TITLE)))
    //                            .setArtist(c.getString(c.getColumnIndexOrThrow(AudioColumns.ARTIST)))
    //                            .setDuration(TimberUtils.makeShortTimeString(this, c.getInt(c.getColumnIndexOrThrow(AudioColumns.DURATION)) / 1000));
    //                    list.add(t.get());
    //                } while (c.moveToNext());
    //                try {
    //                    NotificationHelper.insertToNotification(n, list, this, getQueuePosition());
    //                } catch (ModNotInstalledException e) {
    //                    e.printStackTrace();
    //                }
    //                c.close();
    //            }
    //        }
    //    }

    private final PendingIntent retrievePlaybackAction(final String action) {
        final ComponentName serviceName = new ComponentName(this, MusicService.class);
        Intent intent = new Intent(action);
        intent.setComponent(serviceName);

        return PendingIntent.getService(this, 0, intent, 0);
    }

    private void saveQueue(final boolean full) {
        //        if (!mQueueIsSaveable) {
        //            return;
        //        }
        //
        //        final SharedPreferences.Editor editor = mPreferences.edit();
        //        if (full) {
        //            mPlaybackStateStore.saveState(mPlaylist,
        //                    mShuffleMode != SHUFFLE_NONE ? mHistory : null);
        //            editor.putInt("cardid", mCardId);
        //        }
        //        editor.putInt("curpos", mPlayPos);
        //        if (mPlayer.isInitialized()) {
        //            editor.putLong("seekpos", mPlayer.position());
        //        }
        //        editor.putInt("repeatmode", mRepeatMode);
        //        editor.putInt("shufflemode", mShuffleMode);
        //        editor.apply();
    }

    //    private void reloadQueueAfterPermissionCheck() {
    //        if (TimberUtils.isMarshmallow()) {
    //            if (Nammu.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
    //                reloadQueue();
    //            }
    //        } else {
    //            reloadQueue();
    //        }
    //    }

    //    private void reloadQueue() {
    //        int id = mCardId;
    //        if (mPreferences.contains("cardid")) {
    //            id = mPreferences.getInt("cardid", ~mCardId);
    //        }
    //        if (id == mCardId) {
    //            mPlaylist = mPlaybackStateStore.getQueue();
    //        }
    //        if (mPlaylist.size() > 0) {
    //            final int pos = mPreferences.getInt("curpos", 0);
    //            if (pos < 0 || pos >= mPlaylist.size()) {
    //                mPlaylist.clear();
    //                return;
    //            }
    //            mPlayPos = pos;
    //            updateCursor(mPlaylist.get(mPlayPos).mId);
    //            /*if (mCursor == null) {
    //                SystemClock.sleep(3000);
    //                updateCursor(mPlaylist.get(mPlayPos).mId);
    //            }*/
    //            synchronized (this) {
    //                closeCursor();
    //                mOpenFailedCounter = 20;
    //                openCurrentAndNext();
    //            }
    //            if (!mPlayer.isInitialized()) {
    //                mPlaylist.clear();
    //                return;
    //            }
    //
    //            final long seekpos = mPreferences.getLong("seekpos", 0);
    //            seek(seekpos >= 0 && seekpos < duration() ? seekpos : 0);
    //
    //            if (D) {
    //                Log.d(TAG, "restored queue, currently at mPosition "
    //                        + position() + "/" + duration()
    //                        + " (requested " + seekpos + ")");
    //            }
    //
    //            int repmode = mPreferences.getInt("repeatmode", REPEAT_NONE);
    //            if (repmode != REPEAT_ALL && repmode != REPEAT_CURRENT) {
    //                repmode = REPEAT_NONE;
    //            }
    //            mRepeatMode = repmode;
    //
    //            int shufmode = mPreferences.getInt("shufflemode", SHUFFLE_NONE);
    //            if (shufmode != SHUFFLE_AUTO && shufmode != SHUFFLE_NORMAL) {
    //                shufmode = SHUFFLE_NONE;
    //            }
    //            if (shufmode != SHUFFLE_NONE) {
    //                mHistory = mPlaybackStateStore.getHistory(mPlaylist.size());
    //            }
    //            if (shufmode == SHUFFLE_AUTO) {
    //                if (!makeAutoShuffleList()) {
    //                    shufmode = SHUFFLE_NONE;
    //                }
    //            }
    //            mShuffleMode = shufmode;
    //        }
    //    }

    public void setPlayList(List<MusicPlaybackTrack> songs, int position) {
        mPlaylist = songs;
        mPlayPos = position;
    }

    public boolean openFile(final String path) {
        if (D) {
            Log.d(TAG, "openFile: path = " + path);
        }
        synchronized (this) {
            if (path == null) {
                return false;
            }

            //            Uri uri = Uri.parse(path);
            //            boolean shouldAddToPlaylist = true;
            //            long id = -1;
            //            try {
            //                id = Long.valueOf(uri.getLastPathSegment());
            //            } catch (NumberFormatException ex) {
            //                // Ignore
            //            }
            //
            //            if (id != -1 && path.startsWith(
            //                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
            //                updateCursor(uri);
            //
            //            } else if (id != -1 && path.startsWith(
            //                    MediaStore.Files.getContentUri("external").toString())) {
            //                updateCursor(id);
            //
            //            } else if (path.startsWith("content://downloads/")) {
            //
            //                String mpUri = getValueForDownloadedFile(this, uri, "mediaprovider_uri");
            //                if (D) Log.i(TAG, "Downloaded file's MP uri : " + mpUri);
            //                if (!TextUtils.isEmpty(mpUri)) {
            //                    if (openFile(mpUri)) {
            //                        notifyChange(META_CHANGED);
            //                        return true;
            //                    } else {
            //                        return false;
            //                    }
            //                } else {
            //                    updateCursorForDownloadedFile(this, uri);
            //                    shouldAddToPlaylist = false;
            //                }
            //
            //            } else {
            //                String where = MediaStore.Audio.Media.DATA + "=?";
            //                String[] selectionArgs = new String[]{path};
            //                updateCursor(where, selectionArgs);
            //            }
            //            try {
            //                if (shouldAddToPlaylist) {
            //                    mPlaylist.clear();
            //                    mPlaylist.add(new MusicPlaybackTrack(id, -1, TimberUtils.IdType.NA, -1));
            //                    notifyChange(QUEUE_CHANGED);
            //                    mPlayPos = 0;
            //                    mHistory.clear();
            //                }
            //            } catch (final UnsupportedOperationException ex) {
            //                // Ignore
            //            }

            mFileToPlay = path;
            mPlayer.setDataSource(mFileToPlay);
            if (mPlayer.isInitialized()) {
                mOpenFailedCounter = 0;
                return true;
            }

            String trackName = getTrackName();
            if (TextUtils.isEmpty(trackName)) {
                trackName = path;
            }
            Log.d(TAG, "sendErrorMessage from openFile");
            sendErrorMessage(trackName);

            stop(true);
            return false;
        }
    }

    //    private void updateCursorForDownloadedFile(Context context, Uri uri) {
    //        synchronized (this) {
    //            closeCursor();
    //            MatrixCursor cursor = new MatrixCursor(PROJECTION_MATRIX);
    //            String title = getValueForDownloadedFile(this, uri, "title");
    //            cursor.addRow(new Object[]{
    //                    null,
    //                    null,
    //                    null,
    //                    title,
    //                    null,
    //                    null,
    //                    null,
    //                    null
    //            });
    //            //mCursor = cursor;
    //            //mCursor.moveToFirst();
    //        }
    //    }

    //    private String getValueForDownloadedFile(Context context, Uri uri, String column) {
    //
    //        Cursor cursor = null;
    //        final String[] projection = {
    //                column
    //        };
    //
    //        try {
    //            cursor = context.getContentResolver().query(uri, projection, null, null, null);
    //            if (cursor != null && cursor.moveToFirst()) {
    //                return cursor.getString(0);
    //            }
    //        } finally {
    //            if (cursor != null) {
    //                cursor.close();
    //            }
    //        }
    //        return null;
    //    }

    public int getAudioSessionId() {
        synchronized (this) {
            return mPlayer.getAudioSessionId();
        }
    }

    public int getMediaMountedCount() {
        return mMediaMountedCount;
    }

    public int getShuffleMode() {
        return mShuffleMode;
    }

    public void setShuffleMode(final int shufflemode) {
        synchronized (this) {
            if (mShuffleMode == shufflemode && mPlaylist.size() > 0) {
                return;
            }

            mShuffleMode = shufflemode;
            if (mShuffleMode == SHUFFLE_AUTO) {
                if (makeAutoShuffleList()) {
                    mPlaylist.clear();
                    doAutoShuffleUpdate();
                    mPlayPos = 0;
                    openCurrentAndNext();
                    play();
                    notifyChange(META_CHANGED);
                    return;
                } else {
                    mShuffleMode = SHUFFLE_NONE;
                }
            } else {
                setNextTrack();
            }
            saveQueue(false);
            notifyChange(SHUFFLEMODE_CHANGED);
        }
    }

    public int getRepeatMode() {
        return mRepeatMode;
    }

    public void setRepeatMode(final int repeatmode) {
        synchronized (this) {
            mRepeatMode = repeatmode;
            setNextTrack();
            saveQueue(false);
            notifyChange(REPEATMODE_CHANGED);
        }
    }

    public int removeTrack(final long id) {
        int numremoved = 0;
        synchronized (this) {
            for (int i = 0; i < mPlaylist.size(); i++) {
                if (mPlaylist.get(i).mId == id) {
                    numremoved += removeTracksInternal(i, i);
                    i--;
                }
            }
        }
        if (numremoved > 0) {
            notifyChange(QUEUE_CHANGED);
        }
        return numremoved;
    }

    public boolean removeTrackAtPosition(final long id, final int position) {
        synchronized (this) {
            if (position >= 0 && position < mPlaylist.size() && mPlaylist.get(position).mId == id) {

                return removeTracks(position, position) > 0;
            }
        }
        return false;
    }

    public int removeTracks(final int first, final int last) {
        final int numremoved = removeTracksInternal(first, last);
        if (numremoved > 0) {
            notifyChange(QUEUE_CHANGED);
        }
        return numremoved;
    }

    public int getQueuePosition() {
        synchronized (this) {
            return mPlayPos;
        }
    }

    public void setQueuePosition(final int index) {
        synchronized (this) {
            stop(false);
            mPlayPos = index;
            openCurrentAndNext();
            play();
            notifyChange(META_CHANGED);
            if (mShuffleMode == SHUFFLE_AUTO) {
                doAutoShuffleUpdate();
            }
        }
    }

    public int getQueueHistorySize() {
        synchronized (this) {
            return mHistory.size();
        }
    }

    public int getQueueHistoryPosition(int position) {
        synchronized (this) {
            if (position >= 0 && position < mHistory.size()) {
                return mHistory.get(position);
            }
        }

        return -1;
    }

    public int[] getQueueHistoryList() {
        synchronized (this) {
            int[] history = new int[mHistory.size()];
            for (int i = 0; i < mHistory.size(); i++) {
                history[i] = mHistory.get(i);
            }

            return history;
        }
    }

    public String getPath() {
        /*synchronized (this) {
        if (mCursor == null) {
            return null;
        }
        return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.DATA));
        }*/
        synchronized (this) {
            if (mPlayPos < 0 || mPlayPos >= mPlaylist.size()) {
                return "";
            }
            return mPlaylist.get(mPlayPos).url;
        }
    }

    public String getAlbumName() {
        /*synchronized (this) {
        if (mCursor == null) {
            return null;
        }
        return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.ALBUM));
        }*/
        synchronized (this) {
            if (mPlayPos < 0 || mPlayPos >= mPlaylist.size()) {
                return "";
            }
            return mPlaylist.get(mPlayPos).title;
        }
    }

    public String getTrackName() {
        /*synchronized (this) {
        if (mCursor == null) {
            return null;
        }
        return mCursor.getString(mCursor.getColumnIndexOrThrow(AudioColumns.TITLE));
        }*/
        synchronized (this) {
            if (mPlayPos < 0 || mPlayPos >= mPlaylist.size()) {
                return "";
            }
            return mPlaylist.get(mPlayPos).title;
        }
    }

    public String getGenreName() {
        synchronized (this) {
            if (mPlayPos < 0 || mPlayPos >= mPlaylist.size()) {
                return null;
            }
            // TODO: Return Genre
            return /*mPlaylist.get(mPlayPos).title*/null;

            //            String[] genreProjection = {MediaStore.Audio.Genres.NAME};
            //            Uri genreUri = MediaStore.Audio.Genres.getContentUriForAudioId("external",
            //                    (int) mPlaylist.get(mPlayPos).mId);
            //            Cursor genreCursor = getContentResolver().query(genreUri, genreProjection,
            //                    null, null, null);
            //            if (genreCursor != null) {
            //                try {
            //                    if (genreCursor.moveToFirst()) {
            //                        return genreCursor.getString(
            //                                genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME));
            //                    }
            //                } finally {
            //                    genreCursor.close();
            //                }
            //            }
            //            return null;
        }
    }

    public String getArtistName() {
        return null;
    }

    public String getAlbumArtistName() {
        return null;
    }

    public long getAlbumId() {
        return -1;
    }

    public long getArtistId() {
        return -1;
    }

    public long getAudioId() {
        MusicPlaybackTrack track = getCurrentTrack();
        if (track != null) {
            return track.mId;
        }

        return -1;
    }

    public MusicPlaybackTrack getCurrentTrack() {
        return getTrack(mPlayPos);
    }

    public synchronized MusicPlaybackTrack getTrack(int index) {
        if (index >= 0 && index < mPlaylist.size() && mPlayer.isInitialized()) {
            return mPlaylist.get(index);
        }

        return null;
    }

    public long getNextAudioId() {
        synchronized (this) {
            if (mNextPlayPos >= 0 && mNextPlayPos < mPlaylist.size() && mPlayer.isInitialized()) {
                return mPlaylist.get(mNextPlayPos).mId;
            }
        }
        return -1;
    }

    public long getPreviousAudioId() {
        synchronized (this) {
            if (mPlayer.isInitialized()) {
                int pos = getPreviousPlayPosition(false);
                if (pos >= 0 && pos < mPlaylist.size()) {
                    return mPlaylist.get(pos).mId;
                }
            }
        }
        return -1;
    }

    public long seek(long position) {
        if (mPlayer.isInitialized()) {
            if (position < 0) {
                position = 0;
            } else if (position > mPlayer.duration()) {
                position = mPlayer.duration();
            }
            long result = mPlayer.seek(position);
            notifyChange(POSITION_CHANGED);
            return result;
        }
        return -1;
    }

    public void seekRelative(long deltaInMs) {
        synchronized (this) {
            if (mPlayer.isInitialized()) {
                final long newPos = position() + deltaInMs;
                final long duration = duration();
                if (newPos < 0) {
                    prev(true);
                    // seek to the new duration + the leftover position
                    seek(duration() + newPos);
                } else if (newPos >= duration) {
                    gotoNext(true);
                    // seek to the leftover duration
                    seek(newPos - duration);
                } else {
                    seek(newPos);
                }
            }
        }
    }

    public long position() {
        if (mPlayer.isInitialized()) {
            return mPlayer.position();
        }
        return -1;
    }

    public long duration() {
        if (mPlayer.isInitialized()) {
            return mPlayer.duration();
        }
        return -1;
    }

    public List<Song> getQueue() {
        synchronized (this) {
            final int len = mPlaylist.size();
            final List<Song> list = new ArrayList<>();
            for (int i = 0; i < len; i++) {

                list.add(new Song(mPlaylist.get(i).title, mPlaylist.get(i).url));
            }
            return list;
        }
    }

    public long getQueueItemAtPosition(int position) {
        synchronized (this) {
            if (position >= 0 && position < mPlaylist.size()) {
                return mPlaylist.get(position).mId;
            }
        }

        return -1;
    }

    public int getQueueSize() {
        synchronized (this) {
            return mPlaylist.size();
        }
    }

    public boolean isPlaying() {
        return mIsSupposedToBePlaying;
    }

    private void setIsSupposedToBePlaying(boolean value, boolean notify) {
        if (mIsSupposedToBePlaying != value) {
            mIsSupposedToBePlaying = value;

            if (!mIsSupposedToBePlaying) {
                scheduleDelayedShutdown();
                mLastPlayedTime = System.currentTimeMillis();
            }

            if (notify) {
                notifyChange(PLAYSTATE_CHANGED);
            }
        }
    }

    private boolean recentlyPlayed() {
        return isPlaying() || System.currentTimeMillis() - mLastPlayedTime < IDLE_DELAY;
    }

    public void open(final List<Song> songs, final int position, long sourceId, TimberUtils.IdType sourceType) {
        synchronized (this) {
            if (mShuffleMode == SHUFFLE_AUTO) {
                mShuffleMode = SHUFFLE_NORMAL;
            }
            final long oldId = getAudioId();
            final int listlength = songs.size();
            boolean newlist = true;
            if (mPlaylist.size() == listlength) {
                newlist = false;
                for (int i = 0; i < listlength; i++) {
                    if (!songs.get(i).url.equals(mPlaylist.get(i).url)) { // we will check for SongID over here later
                        newlist = true;
                        break;
                    }
                }
            }
            if (newlist) {
                addToPlayList(songs, -1, sourceId, sourceType);
                notifyChange(QUEUE_CHANGED);
            }
            if (position >= 0) {
                mPlayPos = position;
            } else {
                mPlayPos = mShuffler.nextInt(mPlaylist.size());
            }
            mHistory.clear();
            openCurrentAndNext();
            if (oldId != getAudioId()) {
                notifyChange(META_CHANGED);
            }
        }
    }

    public void stop() {
        stop(true);
    }

    public void play() {
        play(true);
    }

    public void play(boolean createNewNextTrack) {
        int status = mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN);

        if (D) {
            Log.d(TAG, "Starting playback: audio focus request status = " + status);
        }

        if (status != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            return;
        }

        final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
        intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
        intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
        sendBroadcast(intent);

        mAudioManager.registerMediaButtonEventReceiver(
                new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName()));
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
            mSession.setActive(true);

        if (createNewNextTrack) {
            setNextTrack();
        } else {
            setNextTrack(mNextPlayPos);
        }

        if (mPlayer.isInitialized()) {
            final long duration = mPlayer.duration();
            if (mRepeatMode != REPEAT_CURRENT && duration > 2000 && mPlayer.position() >= duration - 2000) {
                gotoNext(true);
            }

            mPlayer.start();
            mPlayerHandler.removeMessages(FADEDOWN);
            mPlayerHandler.sendEmptyMessage(FADEUP);

            setIsSupposedToBePlaying(true, true);

            cancelShutdown();
            updateNotification();
            notifyChange(META_CHANGED);
        } else if (mPlaylist.size() <= 0) {
            setShuffleMode(SHUFFLE_AUTO);
        }
    }

    public void pause() {
        if (D) {
            Log.d(TAG, "Pausing playback");
        }
        synchronized (this) {
            mPlayerHandler.removeMessages(FADEUP);
            if (mIsSupposedToBePlaying) {
                final Intent intent = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
                intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
                intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
                sendBroadcast(intent);

                mPlayer.pause();
                notifyChange(META_CHANGED);
                setIsSupposedToBePlaying(false, true);
            }
        }
    }

    public void gotoNext(final boolean force) {
        if (D) {
            Log.d(TAG, "Going to next track");
        }
        synchronized (this) {
            if (mPlaylist.size() <= 0) {
                if (D) {
                    Log.d(TAG, "No play queue");
                }
                scheduleDelayedShutdown();
                return;
            }
            int pos = mNextPlayPos;
            if (pos < 0) {
                pos = getNextPosition(force);
            }

            if (pos < 0) {
                setIsSupposedToBePlaying(false, true);
                return;
            }

            stop(false);
            setAndRecordPlayPos(pos);
            openCurrentAndNext();
            play();
            notifyChange(META_CHANGED);
        }
    }

    public void goToPosition(int pos) {
        synchronized (this) {
            if (mPlaylist.size() <= 0) {
                if (D) {
                    Log.d(TAG, "No play queue");
                }
                scheduleDelayedShutdown();
                return;
            }
            if (pos < 0) {
                return;
            }
            if (pos == mPlayPos) {
                if (!isPlaying()) {
                    play();
                }
                return;
            }
            stop(false);
            setAndRecordPlayPos(pos);
            openCurrentAndNext();
            play();
            notifyChange(META_CHANGED);
        }
    }

    public void setAndRecordPlayPos(int nextPos) {
        synchronized (this) {

            if (mShuffleMode != SHUFFLE_NONE) {
                mHistory.add(mPlayPos);
                if (mHistory.size() > MAX_HISTORY_SIZE) {
                    mHistory.remove(0);
                }
            }

            mPlayPos = nextPos;
        }
    }

    public void prev(boolean forcePrevious) {
        synchronized (this) {

            boolean goPrevious = getRepeatMode() != REPEAT_CURRENT
                    && (position() < REWIND_INSTEAD_PREVIOUS_THRESHOLD || forcePrevious);

            if (goPrevious) {
                if (D) {
                    Log.d(TAG, "Going to previous track");
                }
                int pos = getPreviousPlayPosition(true);

                if (pos < 0) {
                    return;
                }
                mNextPlayPos = mPlayPos;
                mPlayPos = pos;
                stop(false);
                openCurrent();
                play(false);
                notifyChange(META_CHANGED);
            } else {
                if (D) {
                    Log.d(TAG, "Going to beginning of track");
                }
                seek(0);
                play(false);
            }
        }
    }

    public int getPreviousPlayPosition(boolean removeFromHistory) {
        synchronized (this) {
            if (mShuffleMode == SHUFFLE_NORMAL) {

                final int histsize = mHistory.size();
                if (histsize == 0) {
                    return -1;
                }
                final Integer pos = mHistory.get(histsize - 1);
                if (removeFromHistory) {
                    mHistory.remove(histsize - 1);
                }
                return pos.intValue();
            } else {
                if (mPlayPos > 0) {
                    return mPlayPos - 1;
                } else {
                    return mPlaylist.size() - 1;
                }
            }
        }
    }

    private void openCurrent() {
        openCurrentAndMaybeNext(false);
    }

    public void moveQueueItem(int index1, int index2) {
        synchronized (this) {
            if (index1 >= mPlaylist.size()) {
                index1 = mPlaylist.size() - 1;
            }
            if (index2 >= mPlaylist.size()) {
                index2 = mPlaylist.size() - 1;
            }

            if (index1 == index2) {
                return;
            }

            final MusicPlaybackTrack track = mPlaylist.remove(index1);
            if (index1 < index2) {
                mPlaylist.add(index2, track);
                if (mPlayPos == index1) {
                    mPlayPos = index2;
                } else if (mPlayPos >= index1 && mPlayPos <= index2) {
                    mPlayPos--;
                }
            } else if (index2 < index1) {
                mPlaylist.add(index2, track);
                if (mPlayPos == index1) {
                    mPlayPos = index2;
                } else if (mPlayPos >= index2 && mPlayPos <= index1) {
                    mPlayPos++;
                }
            }
            notifyChange(QUEUE_CHANGED);
        }
    }

    public void enqueue(final List<Song> songs, final int action, long sourceId, IdType sourceType) {
        synchronized (this) {
            if (action == NEXT && mPlayPos + 1 < mPlaylist.size()) {
                addToPlayList(songs, mPlayPos + 1, sourceId, sourceType);
                mNextPlayPos = mPlayPos + 1;
                notifyChange(QUEUE_CHANGED);
            } else {
                addToPlayList(songs, Integer.MAX_VALUE, sourceId, sourceType);
                notifyChange(QUEUE_CHANGED);
            }

            if (mPlayPos < 0) {
                mPlayPos = 0;
                openCurrentAndNext();
                play();
                notifyChange(META_CHANGED);
            }
        }
    }

    private void cycleRepeat() {
        if (mRepeatMode == REPEAT_NONE) {
            setRepeatMode(REPEAT_CURRENT);
            if (mShuffleMode != SHUFFLE_NONE) {
                setShuffleMode(SHUFFLE_NONE);
            }
        } else {
            setRepeatMode(REPEAT_NONE);
        }
    }

    private void cycleShuffle() {
        if (mShuffleMode == SHUFFLE_NONE) {
            setShuffleMode(SHUFFLE_NORMAL);
            // This was originally commented by Author
            //            if (mRepeatMode == REPEAT_CURRENT) {
            //                setRepeatMode(REPEAT_ALL);
            //            }
        } else if (mShuffleMode == SHUFFLE_NORMAL || mShuffleMode == SHUFFLE_AUTO) {
            setShuffleMode(SHUFFLE_NONE);
        }
    }

    public void refresh() {
        notifyChange(REFRESH);
    }

    public void playlistChanged() {
        notifyChange(PLAYLIST_CHANGED);
    }

    public void setLockscreenAlbumArt(boolean enabled) {
        mShowAlbumArtOnLockscreen = enabled;
        notifyChange(META_CHANGED);
    }

    private int mBufferedPercentage = -1;

    public int getBufferedPercentage() {
        return mBufferedPercentage;
    }

    public void updateBufferingStatus(int percentage) {
        mBufferedPercentage = percentage;
    }

    public interface TrackErrorExtra {

        String TRACK_NAME = "trackname";
    }

}