public class SimpleListMenuAdapter extends RecyclerView.Adapter<VHImageListItem> {
    public static final int SORT_TYPE_ARTIST = 0;
    public static final int SORT_TYPE_TITLE = 1;
    public static final int SORT_TYPE_ALBUM = 2;
    public static final int SORT_TYPE_GENRE = 3;
    public static final int SORT_TYPE_THEME = 4;


    private ArrayList list;
    private Context appContext;
    private int type;
    private int resource;
    private ArrayList<MenuMeta> metaList;
    private int lastPosistion;

    public SimpleListMenuAdapter(Context context, int resource,ArrayList list) {
        super();
        appContext = context;
        this.resource = resource;
        this.list = list;
    }
    public void setArrayListType(int type){
        this.type = type;
        metaList = new ArrayList<>();
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            metaList.add(new MenuMeta(getTiltleFromBean(iterator.next()),false));
        }

        this.notifyDataSetChanged();
    }

    @Override
    public VHImageListItem onCreateViewHolder(ViewGroup parent, int viewType) {
        if (appContext == null)
            appContext = parent.getContext();
        View view = LayoutInflater.from(appContext).inflate(resource, parent, false);
        VHImageListItem ret = new VHImageListItem(view);
        return ret;
    }

    @Override
    public void onBindViewHolder(VHImageListItem holder, int position) {
        Object bean = list.get(position);
        if (bean instanceof SongBean && type == SORT_TYPE_ALBUM) {
            holder.configure(getTiltleFromBean(bean),
                    MediaLibrary.getStaticInstance(null).getCoverUriByAlbum(getTiltleFromBean(list.get(position))),
                    metaList.get(position).highlight ? ListItemHighlighted : ListItemNormal);
        } else {
            if (type == SORT_TYPE_ALBUM) {
                holder.configure(getTiltleFromBean(bean),
                        MediaLibrary.getStaticInstance(null).getCoverUriByAlbum((String) bean),
                        metaList.get(position).highlight ? ListItemHighlighted : ListItemNormal);
            } else {
                holder.configure(getTiltleFromBean(bean),
                        null,
                        metaList.get(position).highlight ? ListItemHighlighted : ListItemNormal);
            }
        }
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    public Object getItem(int position) {
        return list.get(position);
    }

    class MenuMeta{
        public String itemName;
        public boolean highlight;
        public MenuMeta(String arg1,boolean arg2){
            itemName = arg1;
            highlight = arg2;
        }
    }
    public void highlightItem(int position){
        if (metaList.size() == 0){
            return;
        }
        metaList.get(lastPosistion).highlight = false;
        lastPosistion = position;
        metaList.get(position).highlight = true;
    }

    private String getTiltleFromBean(Object bean){
        switch (type){
            case SORT_TYPE_ARTIST :
                return (String) bean;
            case SORT_TYPE_ALBUM :
                return (String) bean;
            case SORT_TYPE_TITLE :
                return ((SongBean) bean).getTitle();
            case SORT_TYPE_GENRE :
                return (String) bean;
            case SORT_TYPE_THEME:
                return (String) bean;
        }
        return "";
    }

    public int getType() {
        return type;
    }

    public ArrayList<SongBean> getList() {
        return list;
    }
} public interface OnTickListener {
    public void onNextTick();
    public void onPreviousTick();
    public SelectionDetail getCurrentSelection();
} public class CoverflowFragment extends Fragment implements OnTickListener {

    private ViewPager pager;
    private CoverflowPagerAdapter pagerAdapter;
    private PagerContainer pagerContainer;
    private CoverFlow flow;
    private View emptyView;
    private boolean resized;

    public CoverflowFragment() {
        resized = false;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState){
        final View ret = inflater.inflate(R.layout.layout_coverflow, parent, false);
        pagerContainer = (PagerContainer) ret.findViewById(R.id.pager_container);
        pager = (ViewPager) ret.findViewById(R.id.view_pager);
        pagerAdapter = new CoverflowPagerAdapter();
        pager.setAdapter(pagerAdapter);
        pager.setOffscreenPageLimit(3);
        flow = new CoverFlow.Builder()
                .with(pager)
                .pagerMargin(0)
                .scale(0.05f)
                .spaceSize(0f)
                .rotationY(30f)
                .build();
        pagerContainer.setOverlapEnabled(true);
        pager.post(new Runnable() {
            @Override public void run() {
                View view = (View) pager.getAdapter().instantiateItem(pager, 0);
                ViewCompat.setElevation(view, 8.0f);
            }
        });
        ret.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int height = pagerContainer.getHeight();
                if (height <= 0 || resized) {
                    return;
                }
                ViewGroup.LayoutParams lp = pager.getLayoutParams();
                int margin = getResources().getDimensionPixelSize(R.dimen.cover_flow_margin);
                lp.height = height - margin * 2;
                lp.width = lp.height;
                pager.setLayoutParams(lp);
                pagerAdapter.resized = true;
                pagerContainer.invalidate();
                pagerAdapter.notifyDataSetChanged();
                pager.post(new Runnable() {
                    @Override
                    public void run() {
                        pager.setCurrentItem(pagerAdapter.getCount() / 2);
                    }
                });
                resized = true;

                if (pagerAdapter.getCount() == 0) {
                    emptyView.setVisibility(View.VISIBLE);
                    pagerContainer.setVisibility(View.GONE);
                } else {
                    emptyView.setVisibility(View.GONE);
                    pagerContainer.setVisibility(View.VISIBLE);
                }
            }
        });
        emptyView = ret.findViewById(R.id.id_empty_view);

        return ret;
    }


    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onNextTick() {
        int current = pager.getCurrentItem();
        if (!pagerAdapter.canGoNext(current)) {
            return;
        }
        current += 1;
        pager.setCurrentItem(current, true);
    }

    @Override
    public void onPreviousTick() {
        int current = pager.getCurrentItem();
        if (!pagerAdapter.canGoBack(current)) {
            return;
        }
        current -= 1;
        pager.setCurrentItem(current, true);
    }

    @Override
    public SelectionDetail getCurrentSelection() {
        if (pagerAdapter.getCount() == 0) {
            return null;
        }
        SelectionDetail ret = new SelectionDetail();
        ret.setMenuType(MENU_TYPE_ALBUM);
        ret.setData(pagerAdapter.getItem(pager.getCurrentItem()).getName());
        return ret;
    }

    class CoverflowPagerAdapter extends PagerAdapter {

        private ArrayList<AlbumBean> imgs;
        public boolean resized;

        public CoverflowPagerAdapter() {
            imgs = MediaLibrary.getStaticInstance(getContext()).getAllAlbumsWrapped();
            resized = false;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            View ret = LayoutInflater.from(container.getContext()).inflate(R.layout.layout_coverflow_img, null);
            if (imgs.size() == 0)
                return ret;
            ImageView img = (ImageView) ret.findViewById(R.id.cover_image);
            Picasso.with(container.getContext())
                    .load(Uri.parse(imgs.get(position).getCover()))
                    .placeholder(R.drawable.album)
                    .fit()
                    .centerInside()
                    .into(img);
            ((TextView) ret.findViewById(R.id.cover_text)).setText(imgs.get(position).getName());
            container.addView(ret);
            return ret;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }

        @Override
        public int getCount() {
            return resized ? imgs.size() : 0;
        }

        public AlbumBean getItem(int position) {
            return imgs.get(position);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        public boolean canGoNext(int current) {
            return current != imgs.size();
        }

        public boolean canGoBack(int current) {
            return current != 0;
        }
    }
} public class MainMenuFragment extends TwoPanelFragment implements OnTickListener {
    RecyclerView theList;
    int currentItemIndex;
    ImageView imageView;
    FrameLayout rightPanelContent;
    LinearLayout nowPlayingPage;
    LinearLayout rightPanel;
    FrameLayout leftPanel;
    AlbumStack albumStack;
    AIDLDumper dumper;
    TextView npTitle, npArtist;

    private static int menuIcons[] = {
            R.drawable.artist,
            R.drawable.album,
            R.drawable.cover_flow,
            R.drawable.songs,
            R.drawable.playlist,
            R.drawable.genre,
            R.drawable.shuffle,
            R.drawable.settings,
            R.drawable.songs
    };
    @Override
    public View onCreateView(LayoutInflater inflater,ViewGroup parent,Bundle savedInstanceState){
        View ret = inflater.inflate(R.layout.layout_main_menu,parent,false);
        ret.setBackgroundColor(Color.TRANSPARENT);
        theList = (RecyclerView) ret.findViewById(R.id.id_list_view_main_menu);
        theList.setAdapter(MenuAdapter.getStaticInstance(getActivity()).getMainMenuAdapter());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            theList.setLayoutManager(new LinearLayoutManager(getContext()));
        } else {
            theList.setLayoutManager(new LinearLayoutManager(parent.getContext()));
        }
//        theList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        currentItemIndex = 0;
        MenuAdapter.getStaticInstance(null).HighlightItem(0);
        imageView = (ImageView) ret.findViewById(R.id.id_main_menu_image);
        nowPlayingPage = (LinearLayout) ret.findViewById(R.id.id_mainmenu_nowplaying);
        imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(),menuIcons[0]));
        rightPanelContent = (FrameLayout) ret.findViewById(R.id.right_panel_content);
        albumStack = (AlbumStack) ret.findViewById(R.id.id_album_stack);
        albumStack.init();

        leftPanel = (FrameLayout) ret.findViewById(R.id.main_menu_left);
        rightPanel = (LinearLayout) ret.findViewById(R.id.main_menu_right);

        npTitle = (TextView) ret.findViewById(R.id.id_mainmenu_nowplaying_title);
        npArtist = (TextView) ret.findViewById(R.id.id_mainmenu_nowplaying_artist);
        loadTheme();
        return ret;
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if (!hidden)
            refreshCurrentSongIfNeeded();
    }

    @Override
    public void onResume() {
        super.onResume();
        refreshCurrentSongIfNeeded();
        dumper = AIDLDumper.getInstance((MainActivity) getActivity());
    }

    @Override
    public void onNextTick() {
        if(currentItemIndex >= theList.getAdapter().getItemCount()-1){
            currentItemIndex = theList.getAdapter().getItemCount()-1;
            return;
        }
        currentItemIndex+=1;
        theList.requestFocus();
        if(currentItemIndex >= ((LinearLayoutManager)theList.getLayoutManager()).findLastCompletelyVisibleItemPosition())
            theList.smoothScrollToPosition(currentItemIndex);
        MenuAdapter.getStaticInstance(null).HighlightItem(currentItemIndex);
        if (currentItemIndex == theList.getAdapter().getItemCount() -1){
            refreshCurrentSong();
            return;
        } else if (currentItemIndex == 1 && albumStack.getAdapter().getCount() > 0) {
            albumStack.setVisibility(View.VISIBLE);
            imageView.setVisibility(View.GONE);
            return;
        }
        albumStack.setVisibility(View.GONE);
        imageView.setVisibility(View.VISIBLE);
        imageView.destroyDrawingCache();
        imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(),menuIcons[currentItemIndex]));
    }

    @Override
    public void onPreviousTick() {
        if(currentItemIndex < 1){
            return;
        }
        if (currentItemIndex == theList.getAdapter().getItemCount()-1) {
            rightPanelContent.setVisibility(View.VISIBLE);
            nowPlayingPage.setVisibility(View.GONE);
        }
        currentItemIndex -= 1;
        if (currentItemIndex == 1 && albumStack.getAdapter().getCount() > 1) {
            albumStack.setVisibility(View.VISIBLE);
            imageView.setVisibility(View.GONE);
        } else {
            albumStack.setVisibility(View.GONE);
            imageView.setVisibility(View.VISIBLE);
        }
        theList.requestFocus();
        if(currentItemIndex <= ((LinearLayoutManager)theList.getLayoutManager()).findFirstCompletelyVisibleItemPosition())
            theList.smoothScrollToPosition(currentItemIndex);
        MenuAdapter.getStaticInstance(null).HighlightItem(currentItemIndex);
        imageView.destroyDrawingCache();
        imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), menuIcons[currentItemIndex]));
    }

    @Override
    public SelectionDetail getCurrentSelection(){
        SelectionDetail ret = new SelectionDetail();
        ret.setMenuType(ret.MENU_TPYE_MAIN);
        ret.setDataType(ret.DATA_TYPE_STRING);
        ret.setData((((MenuAdapter.MainMenuAdapter)theList.getAdapter()).getItem(currentItemIndex)).menuType);
        return ret;
    }

    public void refreshCurrentSong() {
        rightPanelContent.setVisibility(View.GONE);
        nowPlayingPage.setVisibility(View.VISIBLE);
        PlayerServiceAIDL playerService = ((MainActivity) getActivity()).playerService;
        if (playerService == null || dumper.getCurrentSong() == null){
            ((TextView) nowPlayingPage.findViewById(R.id.id_mainmenu_nowplaying_artist)).setText(R.string.nothing);
            ((TextView) nowPlayingPage.findViewById(R.id.id_mainmenu_nowplaying_title)).setText(R.string.nobody);
            ((ImageView) nowPlayingPage.findViewById(R.id.id_now_playing_cover)).setImageResource(R.drawable.album);
            return;
        }
        SongBean song = dumper.getCurrentSong();
        npArtist.setText(song.getArtist());
        npTitle.setText(song.getTitle());
        ImageView currentCover = (ImageView) nowPlayingPage.findViewById(R.id.id_now_playing_cover);
        String img = MediaLibrary.getStaticInstance(nowPlayingPage.getContext()).getCoverUriByAlbumId(song.getAlbumId());

        Picasso.with(getActivity())
                .load(Uri.parse(img)).fit().centerInside()
                .config(Bitmap.Config.RGB_565)
                .placeholder(R.drawable.album)
                .error(R.drawable.album)
                .into(currentCover);
    }

    public void refreshCurrentSongIfNeeded () {
        if (currentItemIndex == theList.getAdapter().getItemCount() -1) {
            refreshCurrentSong();
        }
    }

    @NonNull
    @Override
    public View getLeftPanel() {
        return leftPanel;
    }

    @NonNull
    @Override
    public View getRightPanel() {
        return rightPanel;
    }

    public void loadTheme() {
        Theme theme = ThemeManager.getInstance(getActivity().getApplicationContext())
                .loadCurrentTheme();
        npArtist.setTextColor(theme.getTextColor());
        npTitle.setTextColor(theme.getTextColor());
    }
} public class NowPlayingFragment extends Fragment implements OnTickListener {

    enum ViewMode {
        Playing,
        Volume,
        Seek,
    }

    SongBean song;
    View view, contentView, seekView;
    NumberProgressBar progressView, seeker;
    TextView currentTime, totalTime, seekerTitle, seekerHint, title, album, artist;
    Runnable dismissRunnable;
    VolumeUtil volume;
    ViewMode viewMode;
    Runnable progressFetcher;
    AIDLDumper dumper;

    private final float seekStep = 0.1f;
    int seekedPosistiton;
    private long lastTick;
    private Theme theme;

    public NowPlayingFragment() {
        viewMode = ViewMode.Playing;
    }

    @Override
    public View onCreateView(LayoutInflater layoutInflater,
                             ViewGroup parent,
                             Bundle savedInstanceState
                             ){
        View ret = layoutInflater.inflate(R.layout.layout_now_playing,parent,false);
        view = ret;
        contentView = view.findViewById(R.id.id_now_playing_wrapper);
        seekView = view.findViewById(R.id.id_seeker_wrapper);
        progressView = (NumberProgressBar) view.findViewById(R.id.id_progress_view);
        seeker = (NumberProgressBar) view.findViewById(R.id.id_seeker);
        seekerTitle = (TextView) view.findViewById(R.id.id_seeker_title);
        seekerHint = (TextView) view.findViewById(R.id.id_seeker_hint);
        currentTime = (TextView) view.findViewById(R.id.current_time);
        totalTime = (TextView) view.findViewById(R.id.total_time);
        title = (TextView) view.findViewById(R.id.id_now_playing_text_view_title);
        album = (TextView) view.findViewById(R.id.id_now_playing_text_view_album);
        artist = (TextView) view.findViewById(R.id.id_now_playing_text_view_artist);
        volume = VolumeUtil.getStaticInstance(getActivity());

        loadTheme();
        return ret;
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if (hidden) {
            if (progressFetcher != null)
                view.removeCallbacks(progressFetcher);
        } else {
            if (progressFetcher == null) {
                progressFetcher = new Runnable() {
                    @Override
                    public void run() {
                        onProcessChanged(dumper.getCurrent(), dumper.getDuration());
                        view.postDelayed(this, 1000);
                    }
                };
            }
            view.post(progressFetcher);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        dumper = AIDLDumper.getInstance((MainActivity) getActivity());
        refreshSong();
    }

    @Override
    public void onPause() {
        if (progressFetcher != null)
            view.removeCallbacks(progressFetcher);

        super.onPause();
    }

    public void setSong(SongBean songBean){
        if (progressFetcher != null)
            view.removeCallbacks(progressFetcher);

        song = songBean;
        title.setText(song.getTitle());
        artist.setText(song.getArtist());
        album.setText(song.getAlbum());
        progressView.setMax(100);
        onProcessChanged(0, song.getDuration());
        String img = MediaLibrary.getStaticInstance(view.getContext())
                .getCoverUriByAlbumId(songBean.getAlbumId());
        Picasso.with(getActivity())
                .load(Uri.parse(img))
                .placeholder(R.drawable.album)
                .config(Bitmap.Config.RGB_565)
                .into((ImageView) view.findViewById(R.id.id_nowplaying_image_view_cover));

        viewMode = ViewMode.Playing;

        if (dismissRunnable == null) {
            dismissRunnable = new Runnable() {
                @Override
                public void run() {
                    long current = System.currentTimeMillis();
                    if (current - lastTick > 2000) {
                        setViewMode(ViewMode.Playing);

                        seekView.setVisibility(View.INVISIBLE);
                        viewMode = ViewMode.Playing;
                    }
                }
            };
        }
    }

    @Override
    public void onNextTick() {
        lastTick = System.currentTimeMillis();
        switch (viewMode) {
            case Playing:
                setViewMode(ViewMode.Volume);
                break;
            case Volume:
                volume.raiseVolume();
                seeker.setProgress(volume.getCurrent());
                break;
            case Seek:
                //Seek here
                seekedPosistiton = seeker.getProgress();
                seekedPosistiton += seeker.getMax() * seekStep;
                seekedPosistiton = seekedPosistiton > song.getDuration() ? seeker.getMax() : seekedPosistiton;
                seeker.setProgress(seekedPosistiton);
                break;
        }
        seekView.postDelayed(dismissRunnable, 2000);
    }

    @Override
    public void onPreviousTick() {
        lastTick = System.currentTimeMillis();
        switch (viewMode) {
            case Playing:
                setViewMode(ViewMode.Volume);
                break;
            case Volume:
                volume.reduceVolume();
                seeker.setProgress(volume.getCurrent());
                break;
            case Seek:
                seekedPosistiton = seeker.getProgress();
                seekedPosistiton -= seeker.getMax() * seekStep;
                seekedPosistiton = seekedPosistiton < 0 ? 0 : seekedPosistiton;
                seeker.setProgress(seekedPosistiton);
                //Seek here
                break;
        }
        seekView.postDelayed(dismissRunnable, 2000);
    }

    @Override
    public SelectionDetail getCurrentSelection() {
        //Switch view mode here
        switch (viewMode) {
            case Playing:
                this.setViewMode(ViewMode.Seek);
                break;
            case Volume:
                this.setViewMode(ViewMode.Playing);
                seekView.getHandler().removeCallbacks(dismissRunnable);
                break;
            case Seek:
                this.setViewMode(ViewMode.Playing);
                seekView.getHandler().removeCallbacks(dismissRunnable);
                doSeek();
                break;
        }
        return null;
    }

    public void refreshSong() {
        PlayerServiceAIDL serviceAIDL = ((MainActivity) getActivity()).playerService;
        if (serviceAIDL == null)
            return;
        SongBean bean = dumper.getCurrentSong();
        if (bean == null)
            return;
        setSong(bean);
        if (!isHidden()) {
            view.removeCallbacks(progressFetcher);
            view.post(progressFetcher);
        }
    }

    public void onProcessChanged(final int current, final int total) {
        if (total == -1)
            return;
        if (progressView != null )
        view.post(new Runnable() {
            @Override
            public void run() {
                progressView.setProgress((int) (((float)current / (float)total) * 100));
                currentTime.setText(String.format("%02d:%02d", (current / 1000 / 60), (current / 1000 % 60)));
                totalTime.setText(String.format("%02d:%02d", (total / 1000 / 60), (total / 1000 % 60)));
            }
        });

    }

    public void setViewMode(ViewMode viewMode) {
        this.viewMode = viewMode;
        lastTick = 0;
        switch (viewMode) {
            case Playing:
                seekView.setVisibility(View.GONE);
                break;
            case Volume:
                seeker.setMax(volume.getMax());
                seeker.setProgress(volume.getCurrent());
                seekView.setVisibility(View.VISIBLE);
                seekerTitle.setText("Volume");
                seekerHint.setText("Click on middle button to dismiss.");
                break;
            case Seek:
                seekedPosistiton = progressView.getProgress();
                seeker.setMax(100);
                seeker.setProgress(progressView.getProgress());
                seekView.setVisibility(View.VISIBLE);
                seekerTitle.setText("Seek");
                seekerHint.setText("Click on middle button to confirm.");
                break;
        }
        return;
    }

    private void doSeek() {
        dumper.seek(seekedPosistiton);
    }

    public void loadTheme() {
        theme = ThemeManager.getInstance(getActivity().getApplicationContext()).loadCurrentTheme();
        title.setTextColor(theme.getTextColor());
        album.setTextColor(theme.getTextColor());
        artist.setTextColor(theme.getTextColor());
    }
} public class SimpleListFragment extends Fragment implements OnTickListener {

    private RecyclerView listView;
    private View emptyView;
    private SimpleListMenuAdapter adapter;
    int currentItemIndex;
    @Override
    public View onCreateView(LayoutInflater inflater,
                             ViewGroup parent,
                             Bundle savedInstanceState){
        View ret = inflater.inflate(R.layout.layout_simple_list_menu, parent, false);
        emptyView = ret.findViewById(R.id.id_empty_view);
        listView = (RecyclerView) ret.findViewById(R.id.id_list_view_main_menu);
        listView.setAdapter(adapter);
        listView.setLayoutManager(new LinearLayoutManager(parent.getContext()));
        if (adapter != null)
            adapter.highlightItem(0);
        currentItemIndex = 0;
        checkEmpty();
        return ret;
    }

    public void setAdapter(SimpleListMenuAdapter adapter){
        this.adapter = adapter;
    }

    private void checkEmpty() {
        if (adapter == null || adapter.getItemCount() == 0) {
            emptyView.setVisibility(View.VISIBLE);
            listView.setVisibility(View.GONE);
        } else {
            emptyView.setVisibility(View.GONE);
            listView.setVisibility(View.VISIBLE);
        }
    }

    /**
     * Compromise for the playinglist fragment.
     */
    public SimpleListMenuAdapter getAdapter(){
        return adapter;
    }

    @Override
    public void onNextTick() {
        if (listView.getAdapter().getItemCount() == 0) {
            return;
        }
        VHImageListItem holder;
        if(currentItemIndex >= listView.getAdapter().getItemCount()-1){
            currentItemIndex = listView.getAdapter().getItemCount()-1;
            holder = (VHImageListItem) listView.findViewHolderForAdapterPosition(currentItemIndex);
            adapter.onBindViewHolder(holder, currentItemIndex);
            return;
        }
        currentItemIndex+=1;
        adapter.highlightItem(currentItemIndex);
        holder = (VHImageListItem) listView.findViewHolderForAdapterPosition(currentItemIndex - 1);
        if (holder != null)
            adapter.onBindViewHolder(holder, currentItemIndex - 1);
        if(currentItemIndex > ((LinearLayoutManager) listView.getLayoutManager()).findLastCompletelyVisibleItemPosition()) {
            listView.getLayoutManager().scrollToPosition(currentItemIndex);
            return;
        }
        holder = (VHImageListItem) listView.findViewHolderForAdapterPosition(currentItemIndex);
        if (holder != null) {
            adapter.onBindViewHolder(holder, currentItemIndex);
        }
    }

    @Override
    public void onPreviousTick() {
        if (listView.getAdapter().getItemCount() == 0) {
            return;
        }
        VHImageListItem holder;
        if(currentItemIndex < 1){
            holder = (VHImageListItem) listView.findViewHolderForAdapterPosition(currentItemIndex);
            adapter.onBindViewHolder(holder, currentItemIndex);
            return;
        }
        currentItemIndex -= 1;
        adapter.highlightItem(currentItemIndex);
        holder = (VHImageListItem) listView.findViewHolderForAdapterPosition(currentItemIndex + 1);
        if (holder != null)
            adapter.onBindViewHolder(holder, currentItemIndex + 1);
        if(currentItemIndex < ((LinearLayoutManager) listView.getLayoutManager()).findFirstCompletelyVisibleItemPosition()) {
            listView.getLayoutManager().scrollToPosition(currentItemIndex);
            return;
        }
        holder = (VHImageListItem) listView.findViewHolderForAdapterPosition(currentItemIndex);
        if (holder != null) {
            adapter.onBindViewHolder(holder, currentItemIndex);
        }

    }

    @Override
    public SelectionDetail getCurrentSelection() {
        SelectionDetail ret = new SelectionDetail();
        if (adapter.getItemCount() == 0){
            return ret;
        }
        switch (adapter.getType()){
            case SimpleListMenuAdapter.SORT_TYPE_TITLE:
                ret.setMenuType(ret.MENU_TYPE_SONGS);
                ret.setDataType(ret.DATA_TYPE_SONG);
                ret.setData(adapter.getItem(currentItemIndex));
                if (UserDefaults.getStaticInstance(null).isShuffle()) {
                    ret.setPlaylist(MediaLibrary.getStaticInstance(null).shuffleList(adapter.getList()));
                } else {
                    ret.setPlaylist(adapter.getList());
                }
                ret.setIndexOfList(currentItemIndex);
                break;
            case SimpleListMenuAdapter.SORT_TYPE_ARTIST:
                ret.setMenuType(ret.MENU_TYPE_ARTIST);
                ret.setData(ret.DATA_TYPE_STRING);
                ret.setData(adapter.getItem(currentItemIndex));
                break;
            case SimpleListMenuAdapter.SORT_TYPE_ALBUM:
                ret.setMenuType(ret.MENU_TYPE_ALBUM);
                ret.setData(ret.DATA_TYPE_STRING);
                ret.setData(adapter.getItem(currentItemIndex));
                break;
            case SimpleListMenuAdapter.SORT_TYPE_GENRE:
                ret.setMenuType(ret.MENU_TYPE_GENRES);
                ret.setData(adapter.getItem(currentItemIndex));
                break;
            case SimpleListMenuAdapter.SORT_TYPE_THEME:
                ret.setMenuType(ret.MENU_TYPE_THEMES);
                ret.setData(adapter.getItem(currentItemIndex));
                break;
        }
        return ret;
    }
} public abstract class TwoPanelFragment extends Fragment {

    private int finishCount;

    @NonNull
    abstract public View getLeftPanel();
    @NonNull
    abstract public View getRightPanel();

    public void dismiss(final DismissCallback callback) {
        finishCount = 0;
        View left, right;
        left = getLeftPanel();
        right = getRightPanel();
        TranslateAnimation lAnim, rAnim;
        lAnim = new TranslateAnimation(0, 0 - left.getRight(), 0, 0);
        lAnim.setDuration(300);
        lAnim.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                increaseAnimCount(callback);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        rAnim = new TranslateAnimation(0, right.getRight(), 0, 0);
        rAnim.setDuration(300);
        rAnim.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                increaseAnimCount(callback);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        left.startAnimation(lAnim);
        right.startAnimation(rAnim);
    }

    public void show(final DismissCallback callback) {
        finishCount = 0;
        View left, right;
        left = getLeftPanel();
        right = getRightPanel();
        TranslateAnimation lAnim, rAnim;
        lAnim = new TranslateAnimation(0 - left.getRight(), 0, 0, 0);
        lAnim.setDuration(300);
        lAnim.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                increaseAnimCount(callback);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        rAnim = new TranslateAnimation(right.getRight(), 0, 0, 0);
        rAnim.setDuration(300);
        rAnim.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                increaseAnimCount(callback);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        left.startAnimation(lAnim);
        right.startAnimation(rAnim);
    }

    synchronized private void increaseAnimCount(DismissCallback callback) {
        if (finishCount == 1) {
            if (callback != null)
                callback.dismissed();
            return;
        }
        finishCount ++;
    }

    public interface DismissCallback {
        void dismissed();
    }
} public class BDIntroActivity extends IntroActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState){
        setFullscreen(false);
        super.onCreate(savedInstanceState);

        setButtonBackFunction(BUTTON_BACK_FUNCTION_BACK);

        addSlide(new IntroStepOne());
        addSlide(new IntroStepTwo());
        addSlide(new IntroStepThree());

        addOnNavigationBlockedListener(new OnNavigationBlockedListener() {
            @Override
            public void onNavigationBlocked(int position, int direction) {
                if (position == 2 && direction == DIRECTION_FORWARD) {
                    UserDefaults.getStaticInstance(BDIntroActivity.this.getApplicationContext()).introShown();
                    finish();
                }
            }
        });

    }

    @Override
    public void onBackPressed() {
        UserDefaults.getStaticInstance(getApplicationContext()).introShown();
        super.onBackPressed();
    }
} public class MediaLibrary {
    private static MediaLibrary staticInstance;
    private Context appContext;
    private ContentResolver contentResolver;
    public static final int ORDER_BY_ARTIST = 1;
    public static final int ORDER_BY_ALBUM = 2;
    public static final int ORDER_BY_GENERES = 3;
    private MediaLibrary(Context context){
        appContext = context;
        contentResolver = appContext.getContentResolver();
    }

    public static MediaLibrary getStaticInstance(Context context){
        if(staticInstance == null)
            staticInstance = new MediaLibrary(context);
        return staticInstance;
    }

    public ArrayList<SongBean> getSongsByArtist(String artist){
        ArrayList ret = new ArrayList<SongBean>();
        Cursor cursor;
        String artistId;
        cursor = contentResolver.query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
                new String[]{"_id"},
                "artist=?",
                new String[]{artist},
                null);
        if(!cursor.moveToNext()){
            return ret;
        }
        artistId = ""+(cursor.getInt(cursor.getColumnIndexOrThrow("_id")));
        cursor.close();

        cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                null,
                "artist_id=?",
                new String[]{artistId},
                "artist_id asc");
        SongBean addBean;
        while(cursor.moveToNext()){
            addBean = new SongBean();
            addBean.populateBean(cursor);
            ret.add(addBean);
        }
        cursor.close();
        return ret;
    }

    public ArrayList<SongBean> getSongsByAlbum(String album){
        ArrayList ret = new ArrayList<SongBean>();
        Cursor cursor;
        String albumId;
        cursor = contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
                new String[]{"_id"},
                "album=?",
                new String[]{album},
                null);
        if(!cursor.moveToNext()){
            return ret;
        }
        albumId = ""+(cursor.getInt(cursor.getColumnIndexOrThrow("_id")));
        cursor.close();

        cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                null,
                "album_id=?",
                new String[]{albumId},
                null);
        SongBean addBean;
        while(cursor.moveToNext()){
            addBean = new SongBean();
            addBean.populateBean(cursor);
            ret.add(addBean);
        }
        cursor.close();
        return ret;
    }

    public ArrayList<String> getAlbumsByArtist(String artist){

        ArrayList ret = new ArrayList<String>();
        Cursor cursor;
        String artistId;
        cursor = contentResolver.query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
                new String[]{"_id"},
                "artist=?",
                new String[]{artist},
                null);
        if(!cursor.moveToNext()){
            return ret;
        }
        artistId = ""+(cursor.getInt(cursor.getColumnIndexOrThrow("_id")));
        cursor.close();

        cursor = contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
                null,
                "artist_id=?",
                new String[]{artistId},
                "artist_id asc");

        while(cursor.moveToNext()){
            ret.add(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.AlbumColumns.ALBUM)));
        }
        cursor.close();
        return ret;

    }
    public ArrayList<SongBean> getAllSongs(int order){
        ArrayList ret = new ArrayList<SongBean>();
        Cursor cursor;
        switch (order){
            case ORDER_BY_ALBUM:
                cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                        null,
                        null,
                        null,
                        "album_id asc"
                );
                break;
            case ORDER_BY_ARTIST:
                cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                        null,
                        null,
                        null,
                        "artist_id asc"
                );
                break;
            default:
                cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                        null,
                        null,
                        null,
                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER
                );
                break;
        }

        SongBean addBean;
        while (cursor.moveToNext()){
            addBean = new SongBean();
            addBean.populateBean(cursor);
            ret.add(addBean);
        }
        addBean = null;
        cursor.close();
        return ret;
    }

    public ArrayList<String> getAllAlbums(){
        ArrayList ret = new ArrayList<String>();
        Cursor cursor;
        cursor = contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
                null,
                null,
                null,
                null
        );
        while(cursor.moveToNext()){
            ret.add(cursor.getString(cursor.getColumnIndexOrThrow("album")));
        }
        cursor.close();
        return ret;
    }

    public ArrayList<AlbumBean> getAllAlbumsWrapped() {
        ArrayList ret = new ArrayList<AlbumBean>();
        Cursor cursor;
        cursor = contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
                null,
                null,
                null,
                null
        );
        AlbumBean bean;
        while(cursor.moveToNext()){
            bean = new AlbumBean();
            bean.populateBean(cursor);
            ret.add(bean);
        }
        cursor.close();
        return ret;
    }

    public ArrayList<String> getAllArtists(){
        ArrayList ret = new ArrayList<String>();
        Cursor cursor;
        cursor = contentResolver.query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
                    null,
                    null,
                    null,
                    null
                    );
        while(cursor.moveToNext()){
            ret.add(cursor.getString(cursor.getColumnIndexOrThrow("artist")));
        }
        cursor.close();
        return ret;
    }

    public ArrayList<String> getAllGenre(){
        ArrayList ret = new ArrayList<String>();
        Cursor cursor;
        cursor = contentResolver.query(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
                null,
                null,
                null,
                null
        );
        while(cursor.moveToNext()){
            ret.add(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.GenresColumns.NAME)));
        }
        cursor.close();
        return ret;
    }

    public ArrayList<String> getArtistsByGenre(String genre){
        ArrayList ret = new ArrayList<String>();
        Cursor cursor;
        cursor = contentResolver.query(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
                new String[]{"_id"},
                "name=?",
                new String[]{genre},
                null);
        if(!cursor.moveToNext()){
            return ret;
        }
        Long genreId = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
        cursor.close();
        cursor = contentResolver.query(MediaStore.Audio.Genres.Members.getContentUri("external",genreId),
                new String[]{"distinct "+MediaStore.Audio.Genres.Members.ARTIST},
                null,
                null,
                null
        );
        while(cursor.moveToNext()){
            ret.add(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.Members.ARTIST)));
        }
        cursor.close();
        return ret;
    }

    public ArrayList<SongBean> shuffleList(ArrayList<SongBean> originalList){
        ArrayList<SongBean> ret = new ArrayList<>();
        originalList = (ArrayList<SongBean>) originalList.clone();
        while(originalList.size() >0 ){
            ret.add(originalList.remove(new Random().nextInt(originalList.size())));
        }
        return ret;
    }

    public Bitmap getCoverImageBySong(long songId){
        Uri sArtworkUri = Uri.parse("content://media/external/audio/media/" + songId + "/albumart");
        ContentResolver res = appContext.getContentResolver();
        InputStream in;
        Bitmap ret = null;
        try {
            in = res.openInputStream(sArtworkUri);
            ret = BitmapFactory.decodeStream(in);
            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            ret = BitmapFactory.decodeResource(appContext.getResources(), R.drawable.album);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ret;
    }

    public String getCoverUriByAlbumId(long albumId){
        Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
        Uri uri = ContentUris.withAppendedId(sArtworkUri, albumId);
        return uri.toString();
    }

    public Bitmap getCoverImageByAlbum(long albumId){
        Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
        Uri uri = ContentUris.withAppendedId(sArtworkUri, albumId);
        ContentResolver res = appContext.getContentResolver();
        InputStream in = null;
        Bitmap ret = null;
        try {
            in = res.openInputStream(uri);
            ret = BitmapFactory.decodeStream(in);
            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            ret = BitmapFactory.decodeResource(appContext.getResources(), R.drawable.ic_launcher);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ret;
    }

    public Bitmap getCoverImageByAlbum(String albumName){
        Cursor cursor;
        long albumId;
        cursor = contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
                new String[]{"_id"},
                "album=?",
                new String[]{albumName},
                null);
        if(!cursor.moveToNext()){
            return BitmapFactory.decodeResource(appContext.getResources(), R.drawable.ic_launcher);
        }
        albumId = (cursor.getLong(cursor.getColumnIndexOrThrow("_id")));

        Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart/" + albumId);
        ContentResolver res = appContext.getContentResolver();
        InputStream in = null;
        try {
            in = res.openInputStream(sArtworkUri);
            return BitmapFactory.decodeStream(in);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return BitmapFactory.decodeResource(appContext.getResources(), R.drawable.ic_launcher);
        }
    }

    public String getCoverUriByAlbum(String albumName){
        Cursor cursor;
        long albumId;
        cursor = contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
                new String[]{"_id"},
                "album=?",
                new String[]{albumName},
                null);
        if(!cursor.moveToNext()){
            return "content://media/external/audio/albumart/-1";
        }
        albumId = (cursor.getLong(cursor.getColumnIndexOrThrow("_id")));
        cursor.close();
        return "content://media/external/audio/albumart/"+albumId;
    }

    public ArrayList<String> getAllCoverUries() {
        ArrayList<String> ret = new ArrayList<String>();
        HashSet<Long> ids = new HashSet<Long>();
        Cursor cursor;
        long albumId;
        cursor = contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
                new String[]{"_id"},
                null,
                null,
                null);
        while (cursor.moveToNext()) {
            albumId = (cursor.getLong(cursor.getColumnIndexOrThrow("_id")));
            if (ids.add(albumId)) {
                ret.add("content://media/external/audio/albumart/"+albumId);
            }
        }
        cursor.close();
        return ret;
    }

    public SongBean getSongById(String id){
        SongBean ret = null;
        Cursor cursor;
        cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                null,
                "_id=?",
                new String[]{id},
                null
                );
        while (cursor.moveToNext()){
            ret = new SongBean();
            ret.populateBean(cursor);
        }
        cursor.close();
        return ret;
    }
} public class MenuMeta {
    public enum MenuType {
        Artists,
        Albums,
        Coverflow,
        Songs,
        Playlist,
        Genres,
        ShuffleSongs,
        Settings,
        NowPlaying,
        //Settings
        About,
        ShuffleSettings,
        RepeatSettings,
        ThemeSettings,
        GetSourceCode,
        ContactUs,
    }
    public String itemName;
    public boolean highlight;
    public MenuType menuType;

    public MenuMeta(String arg1,boolean arg2, MenuType type){
        itemName = arg1;
        highlight = arg2;
        menuType = type;
    }
} public class SelectionDetail {

    public static final int MENU_TPYE_MAIN = 1;
    public static final int MENU_TYPE_ARTIST = 2;
    public static final int MENU_TYPE_ALBUM = 3;
    public static final int MENU_TYPE_SONGS = 4;
    public static final int MENU_TYPE_GENRES = 5;
    public static final int MENU_TYPE_SETTING = 6;
    public static final int MENU_TYPE_THEMES = 7;
    public static final int MENU_TYPE_UNUSED = -1;

    public static final int DATA_TYPE_STRING = 1;
    public static final int DATA_TYPE_SONG = 2;


    private int menuType;
    private int dataType;
    private Object data;
    private int superType;
    private int subType;
    private ArrayList<SongBean> playlist;
    private int indexOfList;

    public int getMenuType() {
        return menuType;
    }

    public void setMenuType(int menuType) {
        this.menuType = menuType;
    }

    public int getDataType() {
        return dataType;
    }

    public void setDataType(int dataType) {
        this.dataType = dataType;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public int getSuperType() {
        return superType;
    }

    public void setSuperType(int superType) {
        this.superType = superType;
    }

    public int getSubType() {
        return subType;
    }

    public void setSubType(int subType) {
        this.subType = subType;
    }

    public ArrayList<SongBean> getPlaylist() {
        return playlist;
    }

    public void setPlaylist(ArrayList<SongBean> playlist) {
        this.playlist = playlist;
    }

    public int getIndexOfList() {
        return indexOfList;
    }

    public void setIndexOfList(int indexOfList) {
        this.indexOfList = indexOfList;
    }
} public class SettingAdapter {
    private static SettingAdapter staticInstance;
    private Context context;
    private ArrayList<MenuMeta> menutItems;
    private int lastPosistion;
    private SettingsAdapter adapter;
    private SettingAdapter(Context context){
        this.context = context;
        initMenuItems();
    }
    public static SettingAdapter getStaticInstance(Context context){
        if (staticInstance == null)
            staticInstance = new SettingAdapter(context);
        return staticInstance;
    }
    private void initMenuItems(){
        ResUtil r = ResUtil.getInstance(null);
        menutItems = new ArrayList();
        menutItems.add(new MenuMeta(r.getLocalized(R.string.shuffle),false, MenuMeta.MenuType.ShuffleSettings));
        menutItems.add(new MenuMeta(r.getLocalized(R.string.repeat), false, MenuMeta.MenuType.RepeatSettings));
        menutItems.add(new MenuMeta("Themes", false, MenuMeta.MenuType.ThemeSettings));
        menutItems.add(new MenuMeta(r.getLocalized(R.string.get_source_code),false, MenuMeta.MenuType.GetSourceCode));
        menutItems.add(new MenuMeta(r.getLocalized(R.string.contact_us),false, MenuMeta.MenuType.ContactUs));
        menutItems.add(new MenuMeta(r.getLocalized(R.string.about),false, MenuMeta.MenuType.About));
    }

    public SettingsAdapter getAdapter(){
        if (adapter == null)
            adapter = new SettingsAdapter(this.context,R.layout.layout_simple_list_menu,menutItems);
        return adapter;
    }

    public void HighlightItem(int position){
        menutItems.get(lastPosistion).highlight = false;
        lastPosistion = position;
        menutItems.get(position).highlight = true;
        adapter.notifyDataSetChanged();
    }

    public class SettingsAdapter extends RecyclerView.Adapter<VHSetting> {
        ArrayList<MenuMeta> arrayList;
        ResUtil resUtil;
        UserDefaults ud;

        public SettingsAdapter(Context context, int resource,ArrayList<MenuMeta> list) {
            super();
            this.arrayList = list;
            resUtil = ResUtil.getInstance(context);
            ud = UserDefaults.getStaticInstance(context);
        }

        @Override
        public VHSetting onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_settings, parent, false);
            VHSetting ret = new VHSetting(view);
            return ret;
        }

        @Override
        public void onBindViewHolder(VHSetting holder, int position) {
            MenuMeta item = arrayList.get(position);
            switch (item.menuType) {
                case About:
                    holder.configureWithStrings(item.itemName, null, item.highlight);
                    break;
                case ShuffleSettings:
                    holder.configureWithStrings(item.itemName, resUtil.getBoolString(ud.isShuffle()), item.highlight);
                    break;
                case RepeatSettings:
                    holder.configureWithStrings(item.itemName, resUtil.getSettingsString(ud.getRepeat()), item.highlight);
                    break;
                case ThemeSettings:
                    holder.configureWithStrings(item.itemName, null, item.highlight);
                    break;
                case GetSourceCode:
                    holder.configureWithStrings(item.itemName, null, item.highlight);
                    break;
                case ContactUs:
                    holder.configureWithStrings(item.itemName, null, item.highlight);
                    break;
            }
//            holder.configureWithString(arrayList.get(position).itemName, arrayList.get(position).highlight ? VHListItem.Status.ListItemHighlighted : VHListItem.Status.ListItemNormal);
        }

        @Override
        public int getItemCount() {
            return arrayList.size();
        }

        public MenuMeta getItem(int position) {
            return arrayList.get(position);
        }
    }
} public class SongBean implements Serializable, Parcelable {
    private String title;
    private String artist;
    private String album;
    private long albumId;
    private String genre;
    private String fileName;
    private String filePath;
    private int duration;
    private long size;
    private long id;

    public SongBean() {
        id = -1;
    }

    public SongBean(Parcel in) {
        title = in.readString();
        artist = in.readString();
        album = in.readString();
        albumId = in.readLong();
        genre = in.readString();
        fileName = in.readString();
        filePath = in.readString();
        duration = in.readInt();
        size = in.readLong();
        id = in.readLong();
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeString(title);
        out.writeString(artist);
        out.writeString(album);
        out.writeLong(albumId);
        out.writeString(genre);
        out.writeString(fileName);
        out.writeString(filePath);
        out.writeInt(duration);
        out.writeLong(size);
        out.writeLong(id);
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getArtist() {
        return artist;
    }

    public void setArtist(String artist) {
        this.artist = artist;
    }

    public String getAlbum() {
        return album;
    }

    public void setAlbum(String album) {
        this.album = album;
    }

    public String getGenre() {
        return genre;
    }

    public void setGenre(String genre) {
        this.genre = genre;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public int getDuration() {
        return duration;
    }

    public void setDuration(int duration) {
        this.duration = duration;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }

    public String getFilePath() {
        return filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public long getAlbumId() {
        return albumId;
    }

    public SongBean setAlbumId(long albumId) {
        this.albumId = albumId;
        return this;
    }

    public void populateBean(Cursor cursor){
        setId(cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)));
        setAlbum(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)));
        setArtist(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)));
        setTitle(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)));
//        setGenre(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.)));
        setFileName(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)));
        setFilePath(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)));
        setDuration(cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)));
        setSize(cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)));
        setAlbumId(cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID)));
    }

    @Override
    public boolean equals(Object another) {
        boolean ret = false;
        SongBean bean = null;
        try {
            bean = (SongBean) another;
        } catch (ClassCastException e) {
            ret = false;
        }
        if (bean != null) {
            ret = bean.getId() == this.id;
        }
        return ret;
    }

    public static final Creator<SongBean> CREATOR = new Creator<SongBean>() {

        @Override
        public SongBean createFromParcel(Parcel source) {
            return new SongBean(source);
        }

        @Override
        public SongBean[] newArray(int size) {
            return new SongBean[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }
} public class PlayerService extends MediaBrowserServiceCompat implements MediaPlayer.OnCompletionListener,
        AudioManager.OnAudioFocusChangeListener, MediaPlayer.OnPreparedListener {

    public final static int cmdPlay   = 0x60;
    public final static int cmdPause  = 0x61;
    public final static int cmdNext   = 0x62;
    public final static int cmdPrev   = 0x63;

    private MediaPlayer mediaPlayer;
    private ArrayList<SongBean> playlist;
    private SongBean currentSong;
    private int index, repeatMode;
    private boolean shuffle;
    private AudioManager audioManager;
    private MediaSessionCompat mediaSession;

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

        Fabric.with(this, new Crashlytics());
        repeatMode = AppConstants.RepeatAll;
        shuffle = false;

        if(mediaPlayer != null){
            mediaPlayer.reset();
            mediaPlayer.release();
            mediaPlayer = null;
        }
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setOnCompletionListener(this);
        mediaPlayer.setOnPreparedListener(this);
        audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
        audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
        initMediaSession();
    }

    private void initMediaSession() {
        ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class);
        mediaSession = new MediaSessionCompat(getApplicationContext(), "PrdPlayer", mediaButtonReceiver, null);
//        mediaSession.setCallback(mMediaSessionCallback);
        mediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS );

        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
        mediaButtonIntent.setClass(this, MediaButtonReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
        mediaSession.setMediaButtonReceiver(pendingIntent);
        setSessionToken(mediaSession.getSessionToken());

    }

    @Override
    public int onStartCommand(Intent intent, int flags,int startId){
        switch (intent.getIntExtra("CMD", -1)) {
            case cmdPause:
                onPause();
                break;
            case cmdNext:
                onNext();
                break;
            case cmdPrev:
                onPrevious();
                break;
            case cmdPlay:
                onPause();
                break;
            default:
                break;
        }
        return START_REDELIVER_INTENT;
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        if (repeatMode == AppConstants.RepeatOne) {
            if (currentSong == null)
                return;
            mediaPlayer.stop();
            mediaPlayer.reset();
            try {
                mediaPlayer.setDataSource(currentSong.getFilePath());
                mediaPlayer.prepare();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if (repeatMode == AppConstants.RepeatNone) {
            if (playlist != null && playlist.size() > 0) {
                if (index < playlist.size() - 1) {
                    onNext();
                } else {
                    mediaPlayer.stop();
                    mediaPlayer.reset();
                }
            }
        } else {
            onNext();
        }
    }

    private void onNext(){
        mediaSession.setActive(false);
        if (playlist == null || index >= playlist.size() - 1){
            if (playlist == null)
                return;
            if (playlist.size() == 0)
                return;
            if (index >= playlist.size() - 1) {
                //We will increase this later.
                index = -1;
            } else {
                return;
            }
        }
        index++;
        mediaPlayer.stop();
        mediaPlayer.reset();
        try {
            mediaPlayer.setDataSource(playlist.get(index).getFilePath());
            mediaPlayer.prepare();
            currentSong = playlist.get(index);
            Intent msg = new Intent(AppConstants.broadcastSongChange);
            msg.setPackage(this.getPackageName());
            sendBroadcast(msg);
        } catch (IOException e) {
            e.printStackTrace();
            Crashlytics.logException(e);
        }
        mediaSession.setActive(true);
        sendNotification(currentSong);
    }
    private void onPrevious(){
        mediaSession.setActive(false);
        if (playlist == null || index <= 0){
            return;
        }
        index--;
        mediaPlayer.stop();
        mediaPlayer.reset();
        try {
            mediaPlayer.setDataSource(playlist.get(index).getFilePath());
            mediaPlayer.prepare();
            currentSong = playlist.get(index);
            Intent msg = new Intent(AppConstants.broadcastSongChange);
            msg.setPackage(this.getPackageName());
            sendBroadcast(msg);
        } catch (IOException e) {
            e.printStackTrace();
            Crashlytics.logException(e);
        }
        mediaSession.setActive(true);
        sendNotification(playlist.get(index));
    }

    private void onPause() {
        if (mediaPlayer.isPlaying()){
            mediaPlayer.pause();
            mediaSession.setActive(false);
            sendNotification(currentSong);
        } else {
            mediaPlayer.start();
            mediaSession.setActive(true);
            sendNotification(currentSong);
        }
    }

    private void onSeek(int position) {
        if (position == -1) {
            return;
        }
        int duration = mediaPlayer.getDuration();
        int seek = (int) (duration * (position / 100f));
        mediaPlayer.seekTo(seek);
    }
    @Override
    public IBinder onBind(Intent intent) {
        return new PlayerServiceBinder();
    }

    @Nullable
    @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
        if(TextUtils.equals(clientPackageName, getPackageName())) {
            return new BrowserRoot(getString(R.string.app_name), null);
        }
        return null;
    }

    @Override
    public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
        result.sendResult(null);
    }

    @Override
    public boolean onUnbind(Intent intent){
        return super.onUnbind(intent);
    }
    @Override
    public void onDestroy() {
        audioManager.abandonAudioFocus(this);
        mediaSession.setActive(false);
        NotificationUtil.getStaticInstance(getApplicationContext()).dismiss();
        try{
            mediaPlayer.reset();
            mediaPlayer.release();
        }catch (IllegalStateException e){

        }
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
        if (focusChange <= 0) {
            if (mediaPlayer.isPlaying())
                mediaPlayer.pause();
        }
        switch (focusChange) {
            case AudioManager.AUDIOFOCUS_LOSS: {
                if(mediaPlayer.isPlaying()) {
                    mediaPlayer.pause();
                }
                break;
            }
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: {
                mediaPlayer.pause();
                break;
            }
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
                if(mediaPlayer != null) {
                    mediaPlayer.setVolume(0.3f, 0.3f);
                }
                break;
            }
            case AudioManager.AUDIOFOCUS_GAIN: {
                if(mediaPlayer != null) {
                    if( !mediaPlayer.isPlaying() ) {
                        mediaPlayer.start();
                    }
                    mediaPlayer.setVolume(1.0f, 1.0f);
                }
                break;
            }
            default: {
                if (focusChange <= 0) {
                    if (mediaPlayer.isPlaying()) {
                        mediaPlayer.pause();
                    }
                }
                break;
            }
        }
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        mp.start();
    }


    class PlayerServiceBinder extends PlayerServiceAIDL.Stub {
        @Override
        public boolean isPlaying() throws RemoteException {
            return mediaPlayer.isPlaying();
        }

        @Override
        public List getPlayList() throws RemoteException {
            return playlist;
        }

        @Override
        public void setPlayList(List list) throws RemoteException {
            playlist = (ArrayList<SongBean>) list;
        }

        @Override
        public long getDuration() throws RemoteException {
            if (mediaPlayer.isPlaying())
                return mediaPlayer.getDuration();
            if (playlist != null && playlist.size() > 0)
                return playlist.get(index).getDuration();
            return -1;
        }

        @Override
        public long getCurrent() throws RemoteException {
            if (mediaPlayer.isPlaying())
                return mediaPlayer.getCurrentPosition();
            return 0;
        }

        @Override
        public SongBean getCurrentSong() throws RemoteException {
            return currentSong;
        }

        @Override
        public void updateSettings(int repeatMode, boolean shuffle) throws RemoteException {
            PlayerService.this.repeatMode = repeatMode;
            PlayerService.this.shuffle = shuffle;
        }

        @Override
        public SongBean getPrevSong() {
            if (index <= 0 || playlist.size() == 0) {
                return null;
            }
            return playlist.get(index - 1);
        }

        @Override
        public SongBean getNextSong() {
            if (index >= playlist.size() - 1 || playlist.size() == 0) {
                return null;
            }
            return playlist.get(index + 1);
        }

        @Override
        
        public void play(final SongBean song, int index) throws RemoteException {
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    if (playlist == null) {
                        PlayerService.this.index = -1;
                    } else {
                        PlayerService.this.index = playlist.indexOf(song);
                    }
                    if(mediaPlayer.isPlaying()){
                        mediaPlayer.stop();
                    }
                    mediaPlayer.reset();
                    try {
                        mediaPlayer.setDataSource(song.getFilePath());
                        mediaPlayer.prepare();
                        currentSong = song;
                        Intent msg = new Intent(AppConstants.broadcastSongChange);
                        msg.setPackage(PlayerService.this.getPackageName());
                        sendBroadcast(msg);
                    } catch (IOException e) {
                        e.printStackTrace();
                        Crashlytics.logException(e);
                    }
                    mediaSession.setActive(true);
                    sendNotification(song);
                    }
                });
        }

        @Override
        public void next() throws RemoteException {
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    onNext();
                }
            });
        }

        @Override
        public void previous() throws RemoteException {
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    onPrevious();
                }
            });
        }

        @Override
        public void pause() throws RemoteException {
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    onPause();
                }
            });
        }

        @Override
        public void resume(final SongBean songF) throws RemoteException {
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    SongBean song = songF;
                    if (song != null) {
                        //Song from caller, means current not playing anything.
                        if (!mediaPlayer.isPlaying()) {
                            try {
                                mediaPlayer.reset();
                                mediaPlayer.prepare();
                                mediaPlayer.setDataSource(song.getFilePath());
                                currentSong = song;
                            } catch (IOException e) {
                                e.printStackTrace();
                            } catch (IllegalStateException e) {
                                e.printStackTrace();
                                Crashlytics.logException(e);
                            }
                        }
                    } else {
                        //Current is playing something
                        mediaPlayer.start();
                    }
                    song = song == null ? currentSong : song;
                    if (song != null) {
                        mediaSession.setActive(true);
                        sendNotification(song);
                    }
                }
            });
        }

        @Override
        public void prepare(int index) throws RemoteException {
            PlayerService.this.index = index;
        }

        @Override
        public void seek(int position) throws RemoteException {
            final int positionF = position;
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    onSeek(positionF);
                }
            });
        }
    }

    private MediaMetadataCompat getMediaMetaFrom(SongBean bean, Bitmap bitmap) {
        MediaMetadataCompat ret;
        ret = new MediaMetadataCompat.Builder()
                .putText(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, bean.getArtist())
                .putText(MediaMetadataCompat.METADATA_KEY_ALBUM, bean.getAlbum())
                .putText(MediaMetadataCompat.METADATA_KEY_TITLE, bean.getTitle())
                .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, bean.getDuration())
                .putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap)
                .build();
        return ret;
    }

    private void sendNotification(final SongBean bean) {
        if (bean == null) {
            return;
        }
        Picasso.with(getApplicationContext())
                .load(MediaLibrary.getStaticInstance(getApplicationContext()).getCoverUriByAlbumId(bean.getAlbumId()))
                .config(Bitmap.Config.RGB_565)
                .error(R.drawable.album)
                .into(new Target() {
                    @Override
                    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                        MediaMetadataCompat metaData = getMediaMetaFrom(bean, bitmap);
                        mediaSession.setMetadata(metaData);
                        NotificationUtil.getStaticInstance(getApplicationContext())
                                .showPlayingNotification(PlayerService.this, mediaSession);
                    }

                    @Override
                    public void onBitmapFailed(Drawable errorDrawable) {
                        BitmapFactory.Options options = new BitmapFactory.Options();
                        options.inPreferredConfig = Bitmap.Config.RGB_565;
                        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
                                R.drawable.album, options);

                        MediaMetadataCompat metaData = getMediaMetaFrom(bean, bitmap);
                        mediaSession.setMetadata(metaData);
                        NotificationUtil.getStaticInstance(getApplicationContext())
                                .showPlayingNotification(PlayerService.this, mediaSession);
                    }

                    @Override
                    public void onPrepareLoad(Drawable placeHolderDrawable) {

                    }
                });
    }
} public class Theme {

    static private Theme defaultTheme;

    class Icons {
        @SerializedName("next")
        public String icNext;
        @SerializedName("prev")
        public String icPrev;
        @SerializedName("play")
        public String icPlay;
        @SerializedName("menu")
        public String icMenu;
    }

    private Icons icons;
    @SerializedName("wheel_outer")
    private float outer;
    @SerializedName("wheel_inner")
    private float inner;
    @SerializedName("button_size")
    private float buttonSize;
    @SerializedName("wheel_color")
    private String wheelColor;
    @SerializedName("button_color")
    private String buttonColor;
    @SerializedName("background_color")
    private String backgroundColor;
    @SerializedName("card_color")
    private String cardColor;
    @SerializedName("item_color")
    private String itemColor;
    @SerializedName("text_color")
    private String textColor;
    private String name;

    @SerializedName("wheel_shape")
    private String wheelShape;

    private int shape;
    @SerializedName("polygon_sides")
    public int sides;

    public Theme(String icNext,
                 String icPrev,
                 String icPlay,
                 String icMenu,
                 float outer,
                 float inner,
                 float buttonSize,
                 String wheelColor,
                 String buttonColor,
                 String backgroundColor,
                 String cardColor,
                 String itemColor,
                 String textColor,
                 int shape,
                 int sides) {
        this.icons = new Icons();
        this.icons.icNext = icNext;
        this.icons.icPrev = icPrev;
        this.icons.icPlay = icPlay;
        this.icons.icMenu = icMenu;

        this.outer = outer;
        this.inner = inner;
        this.buttonSize = buttonSize;
        this.wheelColor = wheelColor;
        this.buttonColor = buttonColor;
        this.backgroundColor = backgroundColor;
        this.cardColor = cardColor;
        this.itemColor = itemColor;
        this.textColor = textColor;
        this.shape = shape;
        this.sides = sides;
    }

    public Theme setName(String name) {
        this.name = name;
        return this;
    }

    public String getName() {
        return name;
    }

    public String getNextIcon() {
        return themeFolder() + icons.icNext;
    }

    public String getPrevIcon() {
        return themeFolder() + icons.icPrev;
    }

    public String getPlayIcon() {
        return themeFolder() + icons.icPlay;
    }

    public String getMenuIcon() {
        return themeFolder() + icons.icMenu;
    }

    public int getWheelColor() {
        return Color.parseColor(wheelColor);
    }

    public int getButtonColor() {
        return Color.parseColor(buttonColor);
    }

    public int getBackgroundColor() {
        return Color.parseColor(backgroundColor);
    }

    public int getCardColor() {
        return Color.parseColor(cardColor);
    }

    public int getItemColor() {
        return Color.parseColor(itemColor);
    }

    public int getTextColor() {
        return Color.parseColor(textColor);
    }

    public int getShape() {
        if (wheelShape == null) {
            return AppConstants.ThemeShapeOval;
        }
        switch (wheelShape) {
            case "rect":
                shape =  AppConstants.ThemeShapeRect;
            break;
            case "oval":
                shape = AppConstants.ThemeShapeOval;
            break;
            case "polygon":
                shape = AppConstants.ThemeShapePolygon;
            break;

            default:
                shape = AppConstants.ThemeShapeOval;
            break;
        }
        return shape;
    }

    public float getOuter() {
        return outer;
    }

    public float getInner() {
        return 1.0f - inner;
    }

    public float getButtonSize() {
        return buttonSize;
    }

    private String themeFolder() {
        return Environment.getExternalStorageDirectory() + AppConstants.themeFolder + name + "/";
    }

    public static @NonNull Theme defaultTheme() {
        if (defaultTheme == null) {
            defaultTheme = new Theme(null, null, null, null, 1.0f, 0.3f, 20, "#FFFFFF", "#000000FF", "#578EBB", "#FFFFFF", "#578EBB", "#000000", AppConstants.ThemeShapeOval, 8);
            defaultTheme.setName("Default");
        }
        return defaultTheme;
    }
} public class ThemeManager {

    private static ThemeManager instance;
    private volatile Theme currentTheme = null;
    private Context context;
    private Gson gson;

    private ThemeManager(Context ctx) {
        context = ctx;
        gson = new Gson();
        copyThemesIfNeeded();
    }

    public static ThemeManager getInstance(Context context) {
        if (instance == null)
            instance = new ThemeManager(context);

        return instance;
    }

    private void copyThemesIfNeeded() {
        String themeDirInExt = Environment.getExternalStorageDirectory().getAbsolutePath();
        themeDirInExt += AppConstants.themeFolder;
        File themeDir = new File(themeDirInExt);
        if (themeDir.exists()) {
            return;
        }
        themeDir.mkdirs();

        AssetManager am = context.getAssets();
        InputStream inputStream = null;
        OutputStream outputStream = null;
        boolean copied = false;
        try {
            inputStream = am.open("themes.zip");
            String fileName = themeDirInExt + "builtin.zip";
            outputStream = new FileOutputStream(fileName);

            byte[] buffer = new byte[1024];
            int read;
            while ((read = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, read);
            }
            inputStream.close();
            outputStream.flush();
            outputStream.close();
            copied = true;
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (copied) {
            unzip();
        }

    }


    private void unzip() { //Oh, naughty~
        String folder = Environment.getExternalStorageDirectory().getAbsolutePath() + AppConstants.themeFolder;
        String zipFile = folder + "builtin.zip";
        try  {
            FileInputStream fin = new FileInputStream(zipFile);
            ZipInputStream zin = new ZipInputStream(fin);
            ZipEntry ze = null;
            while ((ze = zin.getNextEntry()) != null) {
                if(ze.isDirectory()) {
                    File f = new File(folder + ze.getName());
                    if(!f.isDirectory()) {
                        f.mkdirs();
                    }
                } else {
                    FileOutputStream fout = new FileOutputStream(folder + ze.getName());
                    BufferedOutputStream bufout = new BufferedOutputStream(fout);
                    byte[] buffer = new byte[1024];
                    int read = 0;
                    while ((read = zin.read(buffer)) != -1) {
                        bufout.write(buffer, 0, read);
                    }
                    bufout.close();
                    zin.closeEntry();
                    fout.close();
                }

            }
            zin.close();
            new File(zipFile).delete();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public @NonNull Theme loadThemeNamed(@NonNull String name) {
        if (name != null && "Default".equalsIgnoreCase(name)) {
            return Theme.defaultTheme();
        }
        if (!validateByName(name)) {
            UserDefaults.getStaticInstance(context).setTheme("Default");
            return Theme.defaultTheme();
        }
        Theme ret = null;
        File folder = new File(Environment.getExternalStorageDirectory() + AppConstants.themeFolder + name);
        if (!folder.exists()) {
            currentTheme = Theme.defaultTheme();
            return currentTheme;
        }
        File config = new File(Environment.getExternalStorageDirectory() + AppConstants.themeFolder + name + "/config.json");
        if (!config.exists()) {
            currentTheme = Theme.defaultTheme();
            return currentTheme;
        }
        JsonParser parser = new JsonParser();
        JsonReader reader = null;
        JsonObject json;
        try {
            reader = new JsonReader(new FileReader(config));
            json = parser.parse(reader).getAsJsonObject();
            ret = gson.fromJson(json, Theme.class);
            ret.setName(name);
            reader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (ret == null) {
            ret = Theme.defaultTheme();
        } else {
            UserDefaults.getStaticInstance(context).setTheme(name);
        }
        currentTheme = ret;
        return ret;
    }

    public @NonNull Theme loadCurrentTheme() {
        String name = UserDefaults.getStaticInstance(context).getTheme();
        Theme ret;
        ret = loadThemeNamed(name);
        if (ret == null) {
            ret = Theme.defaultTheme();
        }
        currentTheme = ret;
        return ret;
    }

    public @NonNull Theme getCurrentTheme() {
        if (currentTheme == null) {
            currentTheme = loadCurrentTheme();
        }
        return currentTheme;
    }

    public boolean validateByName(String name) {
        boolean ret = true;
        File theme = new File(Environment.getExternalStorageDirectory() + AppConstants.themeFolder + '/' + name);
        if (!theme.isDirectory()) {
            return false;
        }
        String[] files = theme.list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return "config.json".equalsIgnoreCase(name);
            }
        });
        if (files.length == 0) {
            return false;
        }
        return ret;
    }

    public @NonNull ArrayList<String> getAllThemes() {
        ArrayList<String> ret = new ArrayList<>();
        ret.add(Theme.defaultTheme().getName());
        File folder = new File(Environment.getExternalStorageDirectory() + AppConstants.themeFolder);
        String[] themes = folder.list();
        if (themes == null) {
            ret.add("Default");
            return ret;
        }
        for (String t : themes) {
            if (validateByName(t)) {
                ret.add(t);
            }
        }
        return ret;
    }

} public class AIDLDumper {

    private MainActivity context;
    private PlayerServiceAIDL service;
    private static AIDLDumper instance;
    private ArrayList<RemoteOperation> pending;

    private AIDLDumper(MainActivity context) {
        this.context = context;
        pending = new ArrayList<>();
    }

    public static AIDLDumper getInstance(MainActivity context) {
        if (instance == null) {
            instance = new AIDLDumper(context);
        }
        return instance;
    }

    private PlayerServiceAIDL getService() {
        if (service == null) {
            service = context.playerService;
        }
        return service;
    }

    public void onServiceBinded() {
        for (RemoteOperation ro : pending) {
            ro.run();
        }
        pending.clear();
    }

    public boolean isPlaying() {
        PlayerServiceAIDL service = getService();
        boolean ret = false;
        try {
            ret = service.isPlaying();
        } catch (RemoteException e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
        return ret;
    }

    public ArrayList<SongBean> getPlayList() {
        PlayerServiceAIDL service = getService();
        ArrayList ret = null;
        try {
            ret = (ArrayList) service.getPlayList();
        } catch (RemoteException e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
        return ret;
    }

    public SongBean getCurrentSong() {
        PlayerServiceAIDL service = getService();
        SongBean ret = null;
        try {
            ret = service.getCurrentSong();
        } catch (RemoteException e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
        return ret;
    }

    public void setPlaylist(ArrayList list) {
        PlayerServiceAIDL service = getService();
        try {
            service.setPlayList(list);
        } catch (RemoteException e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
    }

    public int getCurrent() {
        PlayerServiceAIDL service = getService();
        long ret = 0;
        try {
            ret = service.getCurrent();
        } catch (RemoteException e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
        return (int) ret;
    }

    public int getDuration() {
        PlayerServiceAIDL service = getService();
        long ret = 0;
        try {
            ret = service.getDuration();
        } catch (RemoteException e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
        return (int) ret;
    }

    public void updateSettings() {
        PlayerServiceAIDL service = getService();
        UserDefaults ud = UserDefaults.getStaticInstance(null);
        if (service == null) {
            class Fetcher {}
            pending.add(new RemoteOperation(Fetcher.class.getEnclosingMethod(), ud.getRepeat(), ud.isShuffle()));
            return;
        }
        try {
            service.updateSettings(ud.getRepeat(), ud.isShuffle());
        } catch (RemoteException e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
    }

    public SongBean getPrevSong() {
        PlayerServiceAIDL service = getService();
        SongBean ret = null;
        try {
            ret = service.getPrevSong();
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
        return ret;
    }
    public SongBean getNextSong() {
        PlayerServiceAIDL service = getService();
        SongBean ret = null;
        try {
            ret = service.getNextSong();
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
        return ret;
    }
    public void play(final SongBean song, final int index) {
        final PlayerServiceAIDL service = getService();
        if (service == null) {
            class Fetcher {}
            pending.add(new RemoteOperation(Fetcher.class.getEnclosingMethod(), song, index));
            return;
        }
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                try {
                    service.play(song, index);
                } catch (Exception e) {
                    Logger.dExp(e);
                    Crashlytics.logException(e);
                }
            }
        });
    }
    public void next() {
        PlayerServiceAIDL service = getService();
        if (service == null) {
            class Fetcher {}
            pending.add(new RemoteOperation(Fetcher.class.getEnclosingMethod()));
            return;
        }
        try {
            service.next();
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
    }
    public void previous() {
        PlayerServiceAIDL service = getService();
        if (service == null) {
            class Fetcher {}
            pending.add(new RemoteOperation(Fetcher.class.getEnclosingMethod()));
            return;
        }
        try {
            service.previous();
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
    }
    public void pause() {
        PlayerServiceAIDL service = getService();
        if (service == null) {
            class Fetcher {}
            pending.add(new RemoteOperation(Fetcher.class.getEnclosingMethod()));
            return;
        }
        try {
            service.pause();
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
    }
    public void resume(SongBean song) {
        PlayerServiceAIDL service = getService();
        if (service == null) {
            class Fetcher {}
            pending.add(new RemoteOperation(Fetcher.class.getEnclosingMethod(), song));
            return;
        }
        try {
            service.resume(song);
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
    }
    public void prepare(int index) {
        PlayerServiceAIDL service = getService();
        if (service == null) {
            class Fetcher {}
            pending.add(new RemoteOperation(Fetcher.class.getEnclosingMethod(), index));
            return;
        }
        try {
            service.prepare(index);
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
    }
    public void seek(int position) {
        PlayerServiceAIDL service = getService();
        if (service == null) {
            class Fetcher {}
            pending.add(new RemoteOperation(Fetcher.class.getEnclosingMethod(), position));
            return;
        }
        try {
            service.seek(position);
        } catch (Exception e) {
            Logger.dExp(e);
            Crashlytics.logException(e);
        }
    }

    class RemoteOperation implements Runnable {
        private Method method;
        private Object[] params;

        public RemoteOperation(Method method, Object... params) {
            this.method = method;
            this.params = params;
        }

        @Override
        public void run() {
            try {
                method.invoke(AIDLDumper.this, params);
            } catch (IllegalAccessException e) {
                Logger.dExp(e);
                Crashlytics.logException(e);
            } catch (InvocationTargetException e) {
                Logger.dExp(e);
                Crashlytics.logException(e);
            }
        }
    }
} public class AppConstants {

    public static final int RepeatAll       =   0x233;
    public static final int RepeatOne       =   0x234;
    public static final int RepeatNone      =   0x235;

    //Theme
    public static final int ThemeShapeOval    = 0;
    public static final int ThemeShapeRect    = 1;
    public static final int ThemeShapePolygon = 2;


    public static final String pref_tag = "mpod_app_settings";
    public static final String kShuffle = "mpod_app_settings.key.shuffle";
    public static final String kRepeat = "mpod_app_settings.key.repeat";

    public static final String broadcastSongChange = "sun.bob.bender.songchanged";
    public static final String broadcastPermission = "sun.bob.bender.allow_broadcast";

    private static final String playlistfile = "/data/data/bob.sun.bender/playlistobject";
    private static final String packageFolder = "/data/data/bob.sun.bender/";

    public static final String themeFolder = "/data/Prodigal/Themes/";

    public static String getPlayistfile() {
        return Environment.getExternalStorageDirectory().getPath() + playlistfile;
    }

    public static String getExtFolder() {
        return Environment.getExternalStorageDirectory().getPath() + packageFolder;
    }

} public class NotificationUtil {

    public static final int NOTIFICATION_ID = 0x5020;


    private static NotificationUtil staticInstance;
    private Context appContext;
    private NotificationManager notificationManager;
    private Notification notification;
    private NotificationCompat.Builder builder;
    private PendingIntent clickIntent, nextIntent, prevIntent, playIntent, pauseIntent;
    private RemoteViews bigView, normalView;
    private Target imgLoaderTarget;

    private ComponentName receiverName;

    private NotificationUtil(Context context){
        appContext = context;
        notificationManager = (NotificationManager) appContext.getSystemService(Context.NOTIFICATION_SERVICE);
        init();

        builder = new NotificationCompat.Builder(appContext);
        builder.setCustomBigContentView(bigView)
                .setCustomContentView(normalView)
                .setSmallIcon(R.drawable.pod_notification)
                .setContentIntent(clickIntent);
        notification = builder.build();
        notification.flags = Notification.FLAG_ONGOING_EVENT;
        receiverName = new ComponentName(context.getApplicationContext(), MediaButtonReceiver.class);
    }

    private void init() {
        Intent intentMain = new Intent(appContext, MainActivity.class);
        intentMain.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        clickIntent = PendingIntent.getActivity(appContext, 1, intentMain, PendingIntent.FLAG_UPDATE_CURRENT);

        Intent intentPlay = new Intent(appContext, PlayerService.class);
        intentPlay.putExtra("CMD", PlayerService.cmdPause);
        playIntent = PendingIntent.getService(appContext, PlayerService.cmdPause, intentPlay, PendingIntent.FLAG_UPDATE_CURRENT);

        Intent intentPause = new Intent(appContext, PlayerService.class);
        intentPause.putExtra("CMD", PlayerService.cmdPlay);
        pauseIntent = PendingIntent.getService(appContext, PlayerService.cmdPlay, intentPause, PendingIntent.FLAG_UPDATE_CURRENT);

        Intent intentNext = new Intent(appContext, PlayerService.class);
        intentNext.putExtra("CMD", PlayerService.cmdNext);
        nextIntent = PendingIntent.getService(appContext, PlayerService.cmdNext, intentNext, PendingIntent.FLAG_UPDATE_CURRENT);

        Intent intentPrev = new Intent(appContext, PlayerService.class);
        intentPrev.putExtra("CMD", PlayerService.cmdPrev);
        prevIntent = PendingIntent.getService(appContext, PlayerService.cmdPrev, intentPrev, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    public static NotificationUtil getStaticInstance(Context context){
        if (staticInstance == null)
            staticInstance = new NotificationUtil(context);
        return staticInstance;
    }


    public void dismiss() {
        notificationManager.cancel(NOTIFICATION_ID);
    }


    public void showPlayingNotification(PlayerService service, MediaSessionCompat session) {
        NotificationCompat.Builder builder = notificationBuilder(service, session);
        if( builder == null ) {
            return;
        }
        NotificationCompat.Action action;
        if (session.isActive()) {
            action = new NotificationCompat.Action(android.R.drawable.ic_media_pause, "Pause", playIntent);
        } else {
            action = new NotificationCompat.Action(android.R.drawable.ic_media_play, "Play", pauseIntent);
        }
        builder.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_previous, "Previous",
                prevIntent));
        builder.addAction(action);
        builder.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_next, "Next",
                nextIntent));

        builder.setOngoing(true);

        builder.setStyle(new NotificationCompat.MediaStyle().setShowActionsInCompactView(1).setMediaSession(session.getSessionToken()));
        builder.setSmallIcon(R.mipmap.ic_launcher);
        NotificationManagerCompat.from(service).notify(NOTIFICATION_ID, builder.build());
    }

    private NotificationCompat.Builder notificationBuilder(PlayerService context, MediaSessionCompat session) {
        MediaControllerCompat controller = session.getController();
        MediaMetadataCompat mediaMetadata = controller.getMetadata();
        MediaDescriptionCompat description = mediaMetadata.getDescription();

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        builder
                .setContentTitle(description.getTitle())
                .setContentText(description.getSubtitle())
                .setSubText(description.getDescription())
                .setLargeIcon(mediaMetadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ART))
                .setContentIntent(clickIntent)
                .setDeleteIntent(
                        MediaButtonReceiver.buildMediaButtonPendingIntent(context, receiverName, PlaybackStateCompat.ACTION_STOP))
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
        return builder;
    }
} public class UserDefaults {
    private static UserDefaults staticInstance;
    private SharedPreferences preferences;
    private String appVersion;
    private String kIntroShown = "kIntroShownFor:";
    private String kThemeName = "kThemeName";

    private UserDefaults(Context context){
        preferences = context.getSharedPreferences(pref_tag,0);

        appVersion = BuildConfig.VERSION_NAME;
        kIntroShown += appVersion;

        if (!preferences.contains(kRepeat)) {
            setRepeat(RepeatAll);
        }
        if (!preferences.contains(kShuffle)) {
            setShuffle(false);
        }
        if (!preferences.contains(kIntroShown)) {
            preferences.edit().putBoolean(kIntroShown, false).commit();
        }
    }

    public static UserDefaults getStaticInstance(Context context){
        if (staticInstance == null)
            staticInstance = new UserDefaults(context.getApplicationContext());
        return staticInstance;
    }

    public SharedPreferences getPreferences(){
        return preferences;
    }

    public int getRepeat() {
        return preferences.getInt(kRepeat, AppConstants.RepeatAll);
    }

    private void setRepeat(int mode) {
        preferences.edit().putInt(kRepeat, mode).commit();
    }

    public boolean isShuffle(){
        boolean ret = preferences.getBoolean(kShuffle, false);
        return ret;
    }

    private void setShuffle(boolean shuffle) {
        preferences.edit().putBoolean(kShuffle, shuffle).commit();
    }

    public void rollShuffle() {
        boolean current = isShuffle();
        setShuffle(!current);
    }

    public void rollRepeat() {
        int current = getRepeat();
        if (current == RepeatNone) {
            setRepeat(RepeatAll);
        } else {
            setRepeat(current + 1);
        }
    }

    public boolean shouldShowIntro() {
        return !preferences.getBoolean(kIntroShown, false);
    }

    public void introShown() {
        preferences.edit().putBoolean(kIntroShown, true).commit();
    }

    public void setTheme(String name) {
        preferences.edit().putString(kThemeName, name).commit();
    }

    public String getTheme() {
        return preferences.getString(kThemeName, "Default");
    }
} public class VibrateUtil {
    private static VibrateUtil staticInstance;
    private Context context;
    private Vibrator vibrator;
    private VibrateUtil(Context context){
        this.context = context;
        vibrator = (Vibrator) this.context.getSystemService(Context.VIBRATOR_SERVICE);
    }
    public static VibrateUtil getStaticInstance(Context context){
        if(staticInstance == null)
            staticInstance = new VibrateUtil(context);
        return staticInstance;
    }

    public void TickVibrate(){
        vibrator.vibrate(20);
    }

} public class WheelView extends View {


    private Point center;
    private Path viewBound;
    private int radiusOut,radiusIn;
    private Paint paintOut, paintIn, ripplePaint;
    private OnTickListener onTickListener;
    private float startDeg = Float.NaN;
    private OnButtonListener onButtonListener;
    private float buttonWidth, buttonHeight;
    private Theme theme;

    public WheelView(Context context, AttributeSet attrs) {
        super(context, attrs);
        center = new Point();
        paintIn = new Paint();
        paintOut = new Paint();
        ripplePaint = new Paint();
        paintIn.setStrokeCap(Paint.Cap.ROUND);
        paintOut.setAntiAlias(true);
        paintIn.setAntiAlias(true);

        buttonWidth = getResources().getDimensionPixelSize(R.dimen.button_width);
        buttonHeight = getResources().getDimensionPixelSize(R.dimen.button_height);
        this.setBackgroundColor(Color.TRANSPARENT);
    }

    public void loadTheme() {
        if (isInEditMode()) {
            this.theme = Theme.defaultTheme();
            this.invalidate();
            return;
        }
        this.theme = ThemeManager.getInstance(getContext().getApplicationContext()).loadCurrentTheme();
        paintOut.setColor(theme.getWheelColor());
        paintIn.setColor(Color.TRANSPARENT);
        paintIn.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
        this.invalidate();
    }

    @Override
    protected void onMeasure(int measureWidthSpec,int measureHeightSpec){
        super.onMeasure(measureWidthSpec,measureHeightSpec);
        int measuredHeight = measureHeight(measureHeightSpec);
        int measuredWidth = measuredHeight;
        this.setMeasuredDimension(measuredWidth,measuredHeight);
        radiusOut = (measuredHeight - 20)/ 2;
        radiusIn = radiusOut/3;
        center.x = measuredWidth/2;
        center.y = measuredHeight/2;
        setLayerType(LAYER_TYPE_SOFTWARE, null);

        viewBound = new Path();
        viewBound.addCircle(center.x, center.y, radiusOut, Path.Direction.CW);
    }

    @Override
    public void onDraw(Canvas canvas){
        if (this.theme == null) {
            loadTheme();
        }
        switch (theme.getShape()) {
            case AppConstants.ThemeShapeOval:
                drawCircle(canvas);
                break;
            case AppConstants.ThemeShapePolygon:
                drawPolygon(canvas, theme.sides);
                break;
            case AppConstants.ThemeShapeRect:
                drawRect(canvas);
                break;
            default:
                drawCircle(canvas);
        }
    }

    private void drawCircle(Canvas canvas) {
        float radiusIn = this.radiusIn * theme.getInner();
        float radiusOut = this.radiusOut * theme.getOuter();
        canvas.drawCircle(center.x,center.y,radiusOut,paintOut);
        canvas.save();
        if (viewBound != null)
            canvas.clipPath(viewBound, Region.Op.REPLACE);

        canvas.drawCircle(center.x,center.y,radiusIn,paintIn);

        if (Build.VERSION.SDK_INT != 23)
            canvas.restore();
    }

    private void drawRect(Canvas canvas) {
        float radiusIn = this.radiusIn * theme.getInner();
        float radiusOut = this.radiusOut * theme.getOuter();
        canvas.drawRect(center.x - radiusOut, center.y - radiusOut, center.x + radiusOut, center.y + radiusOut, paintOut);
        canvas.save();
        if (viewBound != null) {
            canvas.clipPath(viewBound, Region.Op.REPLACE);
        }
        canvas.drawRect(center.x - radiusIn, center.y - radiusIn, center.x + radiusIn, center.y + radiusIn, paintIn);
        if (Build.VERSION.SDK_INT != 23) {
            canvas.restore();
        }
    }

    private void drawPolygon(Canvas canvas, int sides) {
        float radiusIn = this.radiusIn * theme.getInner();
        float radiusOut = this.radiusOut * theme.getOuter();
        Path pathIn = new Path();
        Path pathOut = new Path();
        if (sides <= 3) {
            sides = 4;
        }
        pathIn.moveTo(center.x, center.y - radiusIn);
        pathOut.moveTo(center.x, center.y - radiusOut);
        Paint tp = null;
        if (BuildConfig.DEBUG) {
            tp = new Paint();
            tp.setTextSize(50);
            tp.setColor(Color.BLACK);
        }
        for(int side = 0; side < sides; side++) {
            double theta = 2 * Math.PI / sides * side + Math.PI / 2;
            double xOut = center.x + radiusOut * Math.cos(theta);
            double yOut = center.y - radiusOut * Math.sin(theta);
            double xIn = center.x + radiusIn * Math.cos(theta);
            double yIn = center.y - radiusIn * Math.sin(theta);
            pathIn.lineTo((float) xIn, (float) yIn);
            pathOut.lineTo((float) xOut, (float) yOut);
            if (BuildConfig.DEBUG) {
                canvas.drawText("" + side, (float) xOut, (float)yOut, tp);
            }
        }
        pathOut.close();
        pathIn.close();
        canvas.drawPath(pathOut, paintOut);
        canvas.drawPath(pathIn, paintIn);
    }

    private float xyToDegrees(float x, float y) {
        float distanceFromCenter = PointF.length((x - 0.5f), (y - 0.5f));
        if (distanceFromCenter < 0.15f
                || distanceFromCenter > 0.5f) { // ignore center and out of bounds events
            return Float.NaN;
        } else {
            return (float) Math.toDegrees(Math.atan2(x - 0.5f, y - 0.5f));
        }
    }


    private int measureWidth(int measureSpec) {
       int specMode = MeasureSpec.getMode(measureSpec);
       int specSize = MeasureSpec.getSize(measureSpec);
       int result = 0;
       if (specMode == MeasureSpec.AT_MOST) {
               result = getWidth();
           } else if (specMode == MeasureSpec.EXACTLY) {
               result = specSize;
           }
       return result;
    }
    private int measureHeight(int measureSpec) {
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        int result = 0;
        if (specMode == MeasureSpec.AT_MOST) {
            result = getHeight();
            if (result == 0)
                result = specSize;
        } else if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                float x = event.getX() / (center.x * 2);
                float y = event.getY() / (center.y * 2);

                startDeg = xyToDegrees(x, y);
                return true;

            case MotionEvent.ACTION_MOVE:
                if (!Float.isNaN(startDeg)) {
                    float currentDeg = xyToDegrees((event.getX() - (getWidth() - getHeight())/2) / ((float) getHeight()),
                            event.getY() / getHeight());

                    if (!Float.isNaN(currentDeg)) {
                        float degPerTick = 72f;
                        float deltaDeg = startDeg - currentDeg;
                        if(Math.abs(deltaDeg) < 72f){
                            return true;
                        }
                        int ticks = (int) (Math.signum(deltaDeg)
                                * Math.floor(Math.abs(deltaDeg) / degPerTick));
                        if(ticks == 1){
                            startDeg = currentDeg;
                            if(onTickListener !=null)
                                onTickListener.onNextTick();
                            VibrateUtil.getStaticInstance(null).TickVibrate();
                        }
                        if(ticks == -1){
                            startDeg = currentDeg;
                            if(onTickListener !=null)
                                onTickListener.onPreviousTick();
                            VibrateUtil.getStaticInstance(null).TickVibrate();
                        }
                    }
                    startDeg = currentDeg;
                    return true;
                } else {
                    return false;
                }

            case MotionEvent.ACTION_UP:
                if ((Math.pow(event.getX() - getWidth() / 2f,2) + Math.pow(event.getY() - getHeight() / 2f,2) <= radiusIn*radiusIn )){
                    if(onButtonListener !=null)
                        onButtonListener.onSelect();
                    return true;
                }
                //TODO
                return true;
            default:
                return super.onTouchEvent(event);
        }
    }

    public void setOnTickListener(OnTickListener listener){
        this.onTickListener = listener;
    }

    public void setOnButtonListener(OnButtonListener listener){
        this.onButtonListener = listener;
    }
}
import android.Manifest;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.graphics.ColorUtils;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.CardView;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.Toast;
import com.anthonycr.grant.PermissionsManager;
import com.anthonycr.grant.PermissionsResultAction;
import com.crashlytics.android.Crashlytics;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Transformation;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Stack;
import bob.sun.bender.adapters.SimpleListMenuAdapter;
import bob.sun.bender.controller.OnButtonListener;
import bob.sun.bender.controller.OnTickListener;
import bob.sun.bender.fragments.AboutFragment;
import bob.sun.bender.fragments.CoverflowFragment;
import bob.sun.bender.fragments.MainMenuFragment;
import bob.sun.bender.fragments.NowPlayingFragment;
import bob.sun.bender.fragments.SettingsFragment;
import bob.sun.bender.fragments.SimpleListFragment;
import bob.sun.bender.fragments.TwoPanelFragment;
import bob.sun.bender.intro.BDIntroActivity;
import bob.sun.bender.model.MediaLibrary;
import bob.sun.bender.model.MenuMeta;
import bob.sun.bender.model.PlayList;
import bob.sun.bender.model.SelectionDetail;
import bob.sun.bender.model.SettingAdapter;
import bob.sun.bender.model.SongBean;
import bob.sun.bender.service.PlayerService;
import bob.sun.bender.theme.Theme;
import bob.sun.bender.theme.ThemeManager;
import bob.sun.bender.utils.AIDLDumper;
import bob.sun.bender.utils.AppConstants;
import bob.sun.bender.utils.NotificationUtil;
import bob.sun.bender.utils.ResUtil;
import bob.sun.bender.utils.UserDefaults;
import bob.sun.bender.utils.VibrateUtil;
import bob.sun.bender.view.WheelView;
import io.fabric.sdk.android.Fabric;
import jp.wasabeef.blurry.internal.Blur;
import jp.wasabeef.blurry.internal.BlurFactor;
            SimpleListMenuAdapter adapter = new SimpleListMenuAdapter(this,R.layout.item_simple_list_view,MediaLibrary.getStaticInstance(this).getAllAlbums());
            adapter.setArrayListType(SimpleListMenuAdapter.SORT_TYPE_ALBUM);
            albumsList = new SimpleListFragment();
            albumsList.setAdapter(adapter);
            fragmentManager.beginTransaction().add(R.id.id_screen_fragment_container,albumsList,"albumList").hide(albumsList).commit();
        }

        coverFlow = (CoverflowFragment) fragmentManager.findFragmentByTag("coverFlow");
        if (coverFlow == null) {
            coverFlow = new CoverflowFragment();
            fragmentManager.beginTransaction().add(R.id.id_screen_fragment_container, coverFlow, "coverFlow").hide(coverFlow).commit();
        }

        genresList = (SimpleListFragment) fragmentManager.findFragmentByTag("genresList");
        if (genresList == null){
            SimpleListMenuAdapter adapter = new SimpleListMenuAdapter(this,R.layout.item_simple_list_view,MediaLibrary.getStaticInstance(this).getAllGenre());
            adapter.setArrayListType(SimpleListMenuAdapter.SORT_TYPE_GENRE);
            genresList = new SimpleListFragment();
            genresList.setAdapter(adapter);
            fragmentManager.beginTransaction().add(R.id.id_screen_fragment_container,genresList,"genresList").hide(genresList).commit();
        }

        settingMenu = (SettingsFragment) fragmentManager.findFragmentByTag("settingMenu");
        if (settingMenu == null){
            settingMenu = new SettingsFragment();
            SettingAdapter adapter = SettingAdapter.getStaticInstance(this);
            settingMenu.setAdapter(adapter);
            fragmentManager.beginTransaction().add(R.id.id_screen_fragment_container,settingMenu,"settingMenu").hide(settingMenu).commit();
        }

        fragmentStack = new Stack<>();
        currentFragment = mainMenu;

        wheelView.setOnButtonListener(this);
        wheelView.setOnTickListener(mainMenu);
        this.currentTickObject = mainMenu;
        return;
    }

    private void initOnButtonListener(){
        View view = findViewById(R.id.id_menu_button);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onMenu();
            }
        });
        view = findViewById(R.id.id_play_button);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onPlay();
            }
        });
        view = findViewById(R.id.id_previous_button);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onPrevious();
            }
        });
        view = findViewById(R.id.id_next_button);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onNext();
            }
        });
    }

    private void requestPermission() {
        if (PermissionsManager.hasAllPermissions(MainActivity.this,
                new String[]{
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                })) {
            ThemeManager.getInstance(getApplicationContext());
            permissionGranted = true;
            return;
        }
        PermissionsManager.getInstance().requestAllManifestPermissionsIfNecessary(this, new PermissionsResultAction() {
            @Override
            public void onGranted() {
                permissionGranted = true;
                findViewById(R.id.id_holder_no_permission).setVisibility(View.GONE);
                ThemeManager.getInstance(getApplicationContext());
            }

            @Override
            public void onDenied(String permission) {
                permissionGranted = PermissionsManager.hasAllPermissions(MainActivity.this,
                        new String[]{
                                Manifest.permission.READ_EXTERNAL_STORAGE,
                                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        });
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults);
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            playerService = PlayerServiceAIDL.Stub.asInterface(service);
            if (lastPlayList != null)
                dumper.setPlaylist(lastPlayList);
            dumper.onServiceBinded();
            dumper.updateSettings();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            playerService = null;
        }
    };
    private void startService(){
        serviceIntent = new Intent(this,PlayerService.class);