com.mine.psf.PsfPlaybackService.java Source code

Java tutorial

Introduction

Here is the source code for com.mine.psf.PsfPlaybackService.java

Source

/*************************************************************************
 * MinePsfPlayer is an Android App that plays psf and minipsf files.
 * Copyright (C) 2010-2012  Lei YU
 *
 * This program 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.
 *
 * This program 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ************************************************************************/

package com.mine.psf;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.LinkedList;
import java.util.Random;

import com.mine.psf.sexypsf.MineSexyPsfPlayer;
import com.mine.psf.sexypsf.MineSexyPsfPlayer.RepeatState;
import com.mine.psfplayer.R;

import android.annotation.TargetApi;
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.content.SharedPreferences.Editor;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;

/**
 * Provides background psf playback capabilities, allowing the
 * user to switch between activities without stopping playback.
 */
@TargetApi(Build.VERSION_CODES.FROYO)
public class PsfPlaybackService extends Service implements MineSexyPsfPlayer.PsfPlayerState {

    private static final String LOGTAG = "PsfPlaybackService";
    public static final int PLAYBACKSERVICE_STATUS = 1;

    private BroadcastReceiver mUnmountReceiver = null;
    private BroadcastReceiver mNoisyReceiver = null;
    private WakeLock mWakeLock;
    private int mServiceStartId = -1;
    private AudioManager PsfAudioManager;
    private ComponentName PsfControlResponder;
    private MineSexyPsfPlayer PsfPlayer;

    public static final String CMDNAME = "command";
    public static final String CMDTOGGLEPAUSE = "togglepause";
    public static final String CMDPLAY = "play";
    public static final String CMDSTOP = "stop";
    public static final String CMDPAUSE = "pause";
    public static final String CMDPREV = "previous";
    public static final String CMDNEXT = "next";
    public static final String ACTION_CMD = "com.mine.psf.servicecmd";
    //public static final String ACTION_TOGGLE_PLAYPAUSE = "com.mine.psf.psfservicecmd.togglepause";
    //public static final String ACTION_PAUSE = "com.mine.psf.psfservicecmd.pause";

    public static final String META_CHANGED = "com.mine.psf.metachanged";
    public static final String PLAYBACK_COMPLETE = "com.mine.psf.playbackcomplete";
    public static final String PLAYSTATE_CHANGED = "com.mine.psf.playstatechanged";
    //public static final String PLAYSTATE_FAILED = "com.mine.psf.playstatefailed";
    public static final String REPEATSTATE_CHANGED = "com.mine.psf.repeatstatechanged";

    public static final int MSG_JUMP_NEXT = STATE_MSG_MAX + 1;
    public static final int MSG_JUMP_PREV = STATE_MSG_MAX + 2;

    // interval after which we stop the service when idle
    private static final int IDLE_DELAY = 10 * 1000 * 60;

    // playlist that should be set by browser
    private String[] playList = null;
    private boolean playShuffle = false;
    private int[] shuffleList = null;
    private int curPos;
    private boolean mServiceInUse = false;
    private String playingFile;

    private SharedPreferences mPreferences;
    // Save playlist and shufflelist
    private static final String SavedListFileName = "savedList";

    @Override
    public void onCreate() {
        super.onCreate();

        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
        mWakeLock.setReferenceCounted(false);
        Log.d(LOGTAG, "onCreate, Acquire Wake Lock");

        PsfAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        PsfControlResponder = new ComponentName(getPackageName(), RemoteControlReceiver.class.getName());
        PsfAudioManager.registerMediaButtonEventReceiver(PsfControlResponder);

        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        reloadQueue();

        // If the service was idle, but got killed before it stopped itself, the
        // system will relaunch it. Make sure it gets stopped again in that case.
        Message msg = mDelayedStopHandler.obtainMessage();
        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);

        // Register sd card mount/unmount listener
        registerExternalStorageListener();

        // Register noisy listener, it should
        // 1) Playing with headset, unplug headset, sound should be paused;
        // 2) Playing with speaker, plug headset, sound should keep playing.
        registerNoisyListener();
    }

    @Override
    public void onDestroy() {
        // Check that we're not being destroyed while something is still playing.
        if (isPlaying()) {
            Log.e(LOGTAG, "Service being destroyed while still playing.");
        }
        if (PsfPlayer != null) {
            cancelAudioFocus();
            PsfPlayer.Stop();
            closeFile();
            PsfPlayer = null;
        }

        // make sure there aren't any other messages coming
        mDelayedStopHandler.removeCallbacksAndMessages(null);
        mMediaplayerHandler.removeCallbacksAndMessages(null);

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

        if (mNoisyReceiver != null) {
            unregisterReceiver(mNoisyReceiver);
            mNoisyReceiver = null;
        }
        PsfAudioManager.unregisterMediaButtonEventReceiver(PsfControlResponder);

        Log.d(LOGTAG, "onDestroy, Release Wake Lock");
        mWakeLock.release();
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent arg0) {
        mDelayedStopHandler.removeCallbacksAndMessages(null);
        mServiceInUse = true;
        PsfAudioManager.registerMediaButtonEventReceiver(PsfControlResponder);
        return binder;
    }

    @Override
    public void onRebind(Intent intent) {
        mDelayedStopHandler.removeCallbacksAndMessages(null);
        mServiceInUse = true;
        PsfAudioManager.registerMediaButtonEventReceiver(PsfControlResponder);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        mServiceInUse = false;
        saveQueue();
        if (PsfPlayer != null) {
            if (PsfPlayer.isPlaying() /* || mPausedByTransientLossOfFocus*/) {
                // something is currently playing, don't stop the service now.
                return true;
            }

            // If there is a playlist but playback is paused, then wait a while
            // before stopping the service, so that pause/resume isn't slow.
            // Also delay stopping the service if we're transitioning between tracks.
            if (playList.length > 0 || mMediaplayerHandler.hasMessages(STATE_STOPPED)) {
                Message msg = mDelayedStopHandler.obtainMessage();
                mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
                Log.d(LOGTAG, "wait a while before stopping service");
                return true;
            }
        }
        Log.d(LOGTAG, "stop service");
        stopSelf(mServiceStartId);
        return true;
    }

    // This is the old onStart method that will be called on the pre-2.0
    // platform.  On 2.0 or later we override onStartCommand() so this
    // method will not be called.
    @Override
    public void onStart(Intent intent, int startId) {
        handleCommand(intent, startId);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        handleCommand(intent, startId);
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        return START_STICKY;
    }

    private void handleCommand(Intent intent, int startId) {
        mServiceStartId = startId;
        mDelayedStopHandler.removeCallbacksAndMessages(null);
        if (intent != null) {
            String action = intent.getAction();
            String cmd = intent.getStringExtra(CMDNAME);
            Log.v(LOGTAG, "onStartCommand " + action + " / " + cmd);
            if (action != null && cmd != null) {
                if (!action.equals(ACTION_CMD)) {
                    Log.w(LOGTAG, "Unknown action");
                } else {
                    if (CMDTOGGLEPAUSE.equals(cmd)) {
                        if (isPlaying()) {
                            pause();
                        } else {
                            play();
                        }
                    } else if (CMDPLAY.equals(cmd)) {
                        if (!isPlaying()) {
                            play();
                        }
                    } else if (CMDPAUSE.equals(cmd)) {
                        if (isPlaying()) {
                            pause();
                        }
                    } else if (CMDSTOP.equals(cmd)) {
                        stop();
                    } else if (CMDNEXT.equals(cmd)) {
                        next();
                    } else if (CMDPREV.equals(cmd)) {
                        prev();
                    }
                }
            }
        }
        PsfAudioManager.registerMediaButtonEventReceiver(PsfControlResponder);
        // make sure the service will shut down on its own if it was
        // just started but not bound to and nothing is playing
        mDelayedStopHandler.removeCallbacksAndMessages(null);
        Message msg = mDelayedStopHandler.obtainMessage();
        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
    }

    private Handler mMediaplayerHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case STATE_STOPPED:
                Log.v(LOGTAG, "get STOPPED message");
                autoNext();
                //notifyChange(PLAYBACK_COMPLETE);
                break;
            //case RELEASE_WAKELOCK:
            //    mWakeLock.release();
            //    break;
            case MSG_JUMP_NEXT:
                Log.v(LOGTAG, "get NEXT message");
                next();
                break;
            case MSG_JUMP_PREV:
                Log.v(LOGTAG, "get PREV message");
                prev();
                break;
            default:
                break;
            }
        }
    };

    private Handler mDelayedStopHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // Check again to make sure nothing is playing right now
            if (isPlaying() || /*mPausedByTransientLossOfFocus ||*/ mServiceInUse
                    || mMediaplayerHandler.hasMessages(STATE_STOPPED)) {
                Log.d(LOGTAG, "ignore stop msg");
                return;
            }
            // save the queue again, because it might have changed
            // since the user exited the music app (because of
            // party-shuffle or because the play-position changed)
            saveQueue();
            Log.d(LOGTAG, "stop service");
            stopSelf(mServiceStartId);
        }
    };

    // Set the playlist with full path
    public void setPlaylist(String[] list, boolean shuffle) {
        synchronized (this) {
            playList = list;
            playShuffle = shuffle;
            generateShuffleList();
            saveQueue();
        }
    }

    private boolean openFile(String path) {
        synchronized (this) {
            if (PsfPlayer == null) {
                PsfPlayer = new MineSexyPsfPlayer();
                PsfPlayer.setHandler(mMediaplayerHandler);
            }
            if (PsfPlayer.isActive()) {
                cancelAudioFocus();
                PsfPlayer.Stop();
                closeFile();
            }
            boolean ret;
            playingFile = path;
            path = PsfFileNavigationUtils.getPsfPath(this, path);
            ret = PsfPlayer.Open(path);
            if (ret) {
                notifyChange(META_CHANGED);
                Log.d(LOGTAG, "openFile: " + path + " successfully");
            } else {
                Log.d(LOGTAG, "openFile: " + path + " failed!");
                Toast.makeText(this, R.string.psf_open_fail, Toast.LENGTH_LONG).show();
            }
            return ret;
        }
    }

    private void closeFile() {
        PsfFileNavigationUtils.cleanupPsfPath(this, playingFile);
    }

    public void stop() {
        synchronized (this) {
            if (PsfPlayer != null) {
                Log.d(LOGTAG, "stop");
                cancelAudioFocus();
                PsfPlayer.Stop();
                closeFile();
                gotoIdleState();
                notifyChange(PLAYSTATE_CHANGED);
            }
        }
    }

    public void pause() {
        synchronized (this) {
            if (PsfPlayer != null) {
                Log.d(LOGTAG, "pause");
                PsfPlayer.Play(MineSexyPsfPlayer.PSFPAUSE);
                cancelAudioFocus();
                gotoIdleState();
                notifyChange(PLAYSTATE_CHANGED);
            }
        }
    }

    public void play() {
        synchronized (this) {
            if (PsfPlayer != null) {
                Log.d(LOGTAG, "play");
                requestAudioFocus();
                PsfPlayer.Play(MineSexyPsfPlayer.PSFPLAY);
                notifyChange(PLAYSTATE_CHANGED);
                notifyPlaying();
            }
        }
    }

    private void open(int pos) {
        if (pos < 0 || pos >= playList.length) {
            Log.e(LOGTAG, "open pos out of range, pos: " + pos + ", len: " + playList.length);
            return;
        }
        curPos = pos;
        int playPos;
        if (playShuffle) {
            playPos = shuffleList[pos];
        } else {
            playPos = pos;
        }
        boolean ret;
        ret = openFile(playList[playPos]);
        if (!ret) {
            removeFromList(pos);
            curPos--;
        }
    }

    // This function opens the file in playlist and play it
    public void play(int pos) {
        synchronized (this) {
            open(pos);
            play();
            saveCurPos();
        }
    }

    public void next() {
        synchronized (this) {
            Log.d(LOGTAG, "next");
            int pos = goNext(true);
            if (pos == -1) {
                Log.d(LOGTAG, "No Next Track!");
                return;
            }
            boolean ret;
            ret = openFile(playList[pos]);
            if (ret) {
                play();
                saveCurPos();
            } else {
                // Remove the file from list and next
                removeFromList(pos);
                curPos--;
                mMediaplayerHandler.sendEmptyMessage(MSG_JUMP_NEXT);
            }
        }
    }

    public void prev() {
        synchronized (this) {
            Log.d(LOGTAG, "prev");
            int pos = goPrev();
            if (pos == -1) {
                Log.e(LOGTAG, "No Prev Track!");
                return;
            }
            boolean ret;
            ret = openFile(playList[pos]);
            if (ret) {
                play();
                saveCurPos();
            } else {
                // Remove the file from list and go prev
                removeFromList(pos);
                curPos++;
                mMediaplayerHandler.sendEmptyMessage(MSG_JUMP_PREV);
            }
        }
    }

    public void repeat() {
        synchronized (this) {
            if (PsfPlayer != null) {
                Log.d(LOGTAG, "repeat");
                PsfPlayer.ToggleRepeat();
                notifyChange(REPEATSTATE_CHANGED);
            }
        }
    }

    public int getRepeatState() {
        int ret = RepeatState.REPEAT_OFF;
        if (PsfPlayer != null) {
            ret = PsfPlayer.GetRepeatState();
        }
        Log.d(LOGTAG, "getRepeatState: " + ret);
        return ret;
    }

    public int getPlaylistPosition() {
        if (playShuffle && shuffleList != null) {
            return shuffleList[curPos];
        } else {
            return curPos;
        }
    }

    public String[] getPlaylist() {
        return playList;
    }

    public void setShuffle(boolean shuffle) {
        synchronized (this) {
            playShuffle = shuffle;
            saveShuffleState();
        }
    }

    public boolean isPlaying() {
        if (PsfPlayer != null) {
            return PsfPlayer.isPlaying();
        }
        return false;
    }

    public boolean isActive() {
        if (PsfPlayer != null) {
            return PsfPlayer.isActive();
        }
        return false;
    }

    public long duration() {
        synchronized (this) {
            if (PsfPlayer != null) {
                return PsfPlayer.GetDuration();
            }
            return 0;
        }
    }

    public long position() {
        synchronized (this) {
            if (PsfPlayer != null) {
                return PsfPlayer.GetPosition();
            }
            return 0;
        }
    }

    public String getTrackName() {
        synchronized (this) {
            if (PsfPlayer != null) {
                return PsfPlayer.GetTrack();
            }
            return "";
        }
    }

    public String getAlbumName() {
        synchronized (this) {
            if (PsfPlayer != null) {
                return PsfPlayer.GetAlbum();
            }
            return "";
        }
    }

    public String getArtistName() {
        if (PsfPlayer != null) {
            return PsfPlayer.GetArtist();
        }
        return "";
    }

    public void quit() {
        if (PsfPlayer != null) {
            cancelAudioFocus();
            PsfPlayer.Quit();
        }
        gotoIdleState();
        stopSelf(mServiceStartId);
    }

    private void notifyChange(String what) {
        Intent i = new Intent(what);
        //i.putExtra("album", getAlbumName());
        //i.putExtra("track", getTrackName());
        sendBroadcast(i);
    }

    private void removeFromList(int pos) {
        // Remove the file from the list at pos
        // This includes removing the file from playList
        // and update the shufflelist, curPos
        // It's a slow process since it's re-constructing the array
        if (pos < 0 || pos >= playList.length) {
            return;
        }
        Log.d(LOGTAG, "remove file " + playList[pos] + " from list, pos: " + pos);
        //Log.d(LOGTAG, "Before removing:");
        //dumpPlayList();

        String[] newList = new String[playList.length - 1];
        int[] newShuffleList = new int[shuffleList.length - 1];
        int shuffleIndex = 0;
        for (int i = 0; i < playList.length; ++i) {
            // copy to new play list
            if (i < pos) {
                newList[i] = playList[i];
            } else if (i > pos) {
                newList[i - 1] = playList[i];
            }

            // copy to new shuffle list
            if (shuffleList[i] == pos) {
                continue;
            }
            newShuffleList[shuffleIndex] = shuffleList[i];
            if (newShuffleList[shuffleIndex] > pos) {
                newShuffleList[shuffleIndex]--;
            }
            shuffleIndex++;
        }
        playList = newList;
        shuffleList = newShuffleList;
        //Log.d(LOGTAG, "After removing:");
        //dumpPlayList();
    }
    /*
        private void dumpPlayList() {
           Log.d(LOGTAG, "Dump Playlist...");
           StringBuffer sb = new StringBuffer();
           for (int i = 0; i < playList.length; ++i) {
      Log.d(LOGTAG, playList[i]);
      sb.append(shuffleList[i] + " ");
           }
           Log.d(LOGTAG, "Shuffle List: " + sb.toString());
        }
    */

    /**
     * Registers an intent to listen for ACTION_MEDIA_EJECT notifications.
     * The intent will call closeExternalStorageFiles() if the external media
     * is going to be ejected, so applications can clean up any files they have open.
     */
    public void registerExternalStorageListener() {
        if (mUnmountReceiver == null) {
            mUnmountReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    String action = intent.getAction();
                    if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
                        Log.d(LOGTAG, "SD Card Ejected, stop...");
                        saveQueue();
                        stop();
                        notifyChange(META_CHANGED);
                    } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
                        reloadQueue();
                        notifyChange(META_CHANGED);
                        Log.d(LOGTAG, "SD Card Mounted...");
                    }
                }
            };
            IntentFilter iFilter = new IntentFilter();
            iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
            iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
            iFilter.addDataScheme("file");
            registerReceiver(mUnmountReceiver, iFilter);
        }
    }

    private void registerNoisyListener() {
        if (mNoisyReceiver == null) {
            mNoisyReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    String action = intent.getAction();
                    if (action.equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
                        Log.d(LOGTAG, "To become noisy, pause");
                        pause();
                    }
                }
            };
            IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
            registerReceiver(mNoisyReceiver, filter);
        }
    }

    private void generateShuffleList() {
        shuffleList = new int[playList.length];
        if (playShuffle) {
            // make a shuffle list
            // algro: get rand(),
            LinkedList<Integer> tmpList = new LinkedList<Integer>();
            for (int i = 0; i < playList.length; ++i) {
                tmpList.add(i);
            }
            Random r = new Random();
            for (int i = 0; i < playList.length; ++i) {
                int tmp = r.nextInt(playList.length - i);
                shuffleList[i] = tmpList.get(tmp);
                tmpList.remove(tmp);
            }
        } else {
            for (int i = 0; i < playList.length; ++i) {
                shuffleList[i] = i;
            }
        }
        //       StringBuilder sb = new StringBuilder();
        //       for (int i = 0; i < playList.length; ++i) {
        //          sb.append(shuffleList[i]);
        //          sb.append(",");
        //       }
        //       Log.d(LOGTAG, "GetShuffleList: " + sb.toString());
    }

    // forceNext true to ignore REPEAT_ONE case
    private int goNext(boolean forceNext) {
        if (shuffleList == null) {
            return 0;
        }
        int repeatState = PsfPlayer.GetRepeatState();

        // Loop-one in case of auto next
        if (!forceNext && repeatState == RepeatState.REPEAT_ONE) {
            curPos--;
        }

        if (curPos + 1 >= playList.length) {
            if (repeatState != RepeatState.REPEAT_ALL) {
                // Play to the end
                return -1;
            } else {
                // Loop-all
                curPos = -1;
            }
        }
        if (playShuffle) {
            return shuffleList[++curPos];
        } else {
            return ++curPos;
        }
    }

    private int goPrev() {
        if (shuffleList == null) {
            return 0;
        }
        int repeatState = PsfPlayer.GetRepeatState();

        if (curPos == 0) {
            // At beginning of playlist
            if (repeatState != RepeatState.REPEAT_ALL) {
                return -1;
            } else {
                // Loop-all
                curPos = playList.length;
            }
        }
        if (playShuffle) {
            return shuffleList[--curPos];
        } else {
            return --curPos;
        }
    }

    /**
     * Go to next music when a psf play to the end
     * If it's the last item in the playlist, stop
     */
    private void autoNext() {
        synchronized (this) {
            Log.d(LOGTAG, "autonext");
            int pos = goNext(false);
            if (pos == -1) {
                Log.d(LOGTAG, "No Next Track!");
                return;
            }
            boolean ret;
            ret = openFile(playList[pos]);
            if (ret) {
                play();
                saveCurPos();
            } else {
                // Remove the file from list and next
                removeFromList(pos);
                curPos--;
                mMediaplayerHandler.sendEmptyMessage(MSG_JUMP_NEXT);
            }
        }
    }

    private final IBinder binder = new ServiceBinder(this);

    public static class ServiceBinder extends Binder {
        private final PsfPlaybackService service;

        public ServiceBinder(PsfPlaybackService service) {
            this.service = service;
        }

        public PsfPlaybackService getService() {
            return service;
        }
    }

    private void gotoIdleState() {
        mDelayedStopHandler.removeCallbacksAndMessages(null);
        Message msg = mDelayedStopHandler.obtainMessage();
        mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);
        notifyPauseStop();
    }

    private void saveQueue() {
        if (playList == null || shuffleList == null) {
            return;
        }
        // Save playlist and shufflelist
        try {
            FileOutputStream fos = openFileOutput(SavedListFileName, Context.MODE_PRIVATE);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(playList);
            oos.writeObject(shuffleList);
            oos.close();
            fos.close();
        } catch (Exception e) {
            Log.e(LOGTAG, "unable to save queue");
            e.printStackTrace();
            return;
        }

        Log.d(LOGTAG, "saveQueue");
        // Save other parameters
        Editor ed = mPreferences.edit();
        ed.putInt("curpos", curPos);
        ed.putBoolean("shufflemode", playShuffle);
        ed.commit();
    }

    private void reloadQueue() {
        // Get playlist and shufflelist
        try {
            FileInputStream fis = openFileInput(SavedListFileName);
            ObjectInputStream ois = new ObjectInputStream(fis);
            playList = (String[]) ois.readObject();
            shuffleList = (int[]) ois.readObject();
        } catch (Exception e) {
            // It's ok if SavedListFileName does not exist
            // So don't print stacktrace here
            Log.d(LOGTAG, "unable to reload queue");
            playList = null;
            shuffleList = null;
            curPos = 0;
            playShuffle = false;
            return;
        }

        Log.d(LOGTAG, "reloadQueue");
        // Get other parameters
        playShuffle = mPreferences.getBoolean("shufflemode", false);

        if (playList != null) {
            int pos = mPreferences.getInt("curpos", 0);
            if (pos < 0 || pos >= playList.length) {
                // The saved playlist is bogus, discard it
                playList = null;
                shuffleList = null;
                return;
            }
            curPos = pos;
            open(curPos);
        }
    }

    private void saveCurPos() {
        Editor ed = mPreferences.edit();
        ed.putInt("curpos", curPos);
        ed.commit();
    }

    private void saveShuffleState() {
        Editor ed = mPreferences.edit();
        ed.putBoolean("shufflemode", playShuffle);
        ed.commit();
    }

    // Start foreground service and show the notification
    private void notifyPlaying() {
        Intent notificationIntent = new Intent(this, PsfPlaybackActivity.class);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.notification_psfplaying).setContentTitle(getAlbumName())
                .setContentText(getTrackName());
        builder.setContentIntent(contentIntent);

        // Next button, only work after v11
        Intent nextTrackIntent = new Intent(this, PsfPlaybackService.class);
        nextTrackIntent.setAction(ACTION_CMD);
        nextTrackIntent.putExtra(CMDNAME, CMDNEXT);
        PendingIntent nextTrackPendingIntent = PendingIntent.getService(this, 0, nextTrackIntent,
                PendingIntent.FLAG_CANCEL_CURRENT);

        // Prev button
        //    Intent pauseTrackIntent = new Intent(this, PsfPlaybackService.class);
        //    pauseTrackIntent.setAction(ACTION_CMD);
        //    pauseTrackIntent.putExtra(CMDNAME, CMDPAUSE);
        //    PendingIntent pauseTrackPendingIntent =
        //        PendingIntent.getService(this, 1, pauseTrackIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        // Prev button
        Intent prevTrackIntent = new Intent(this, PsfPlaybackService.class);
        prevTrackIntent.setAction(ACTION_CMD);
        prevTrackIntent.putExtra(CMDNAME, CMDPREV);
        PendingIntent prevTrackPendingIntent = PendingIntent.getService(this, 2, prevTrackIntent,
                PendingIntent.FLAG_CANCEL_CURRENT);

        builder.addAction(android.R.drawable.ic_media_previous, "Prev", prevTrackPendingIntent);
        //    builder.addAction(android.R.drawable.ic_media_pause, "", pauseTrackPendingIntent);
        builder.addAction(android.R.drawable.ic_media_next, "Next", nextTrackPendingIntent);

        startForeground(PLAYBACKSERVICE_STATUS, builder.build());
    }

    // Stop foreground service and remove the notification
    private void notifyPauseStop() {
        stopForeground(true);
    }

    private void pauseAndKeepAudioFocus() {
        synchronized (this) {
            if (PsfPlayer != null) {
                Log.d(LOGTAG, "pause");
                PsfPlayer.Play(MineSexyPsfPlayer.PSFPAUSE);
                gotoIdleState();
                notifyChange(PLAYSTATE_CHANGED);
            }
        }
    }

    private final OnAudioFocusChangeListener AudioFocusChangeListener = new OnAudioFocusChangeListener() {
        public void onAudioFocusChange(int focusChange) {
            switch (focusChange) {
            case AudioManager.AUDIOFOCUS_GAIN:
                Log.d(LOGTAG, "Gain audio focus");
                // Resume
                play();
                // TODO: reset volume
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                Log.d(LOGTAG, "Loss transient audio focus");
                // Pause
                pauseAndKeepAudioFocus();
                break;
            case AudioManager.AUDIOFOCUS_LOSS:
                Log.d(LOGTAG, "Permanent loss of audio focus");
                // Stop
                pause();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                Log.d(LOGTAG, "Duck loss audio focus");
                // TODO: lower volume
                break;
            }
        }
    };

    // Request audio focus
    private void requestAudioFocus() {
        // Request audio focus for playback
        int result = PsfAudioManager.requestAudioFocus(AudioFocusChangeListener,
                // Use the music stream.
                AudioManager.STREAM_MUSIC,
                // Request permanent focus.
                AudioManager.AUDIOFOCUS_GAIN);

        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            Log.d(LOGTAG, "Audio focus granted");
        } else {
            Log.w(LOGTAG, "Audio focus not granted: " + result);
        }
    }

    // Request audio focus
    private void cancelAudioFocus() {
        PsfAudioManager.abandonAudioFocus(AudioFocusChangeListener);
    }
}