<commit_before>//
// FPlayAndroid is distributed under the FreeBSD License
//
// Copyright (c) 2013-2014, <NAME>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project.
//
// https://github.com/carlosrafaelgn/FPlayAndroid
//
package br.com.carlosrafaelgn.fplay;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Build;
import android.text.InputType;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils.TruncateAt;
import android.text.style.DynamicDrawableSpan;
import android.text.style.ImageSpan;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import br.com.carlosrafaelgn.fplay.activity.ActivityVisualizer;
import br.com.carlosrafaelgn.fplay.list.FileSt;
import br.com.carlosrafaelgn.fplay.list.Song;
import br.com.carlosrafaelgn.fplay.list.SongList;
import br.com.carlosrafaelgn.fplay.playback.ExternalFx;
import br.com.carlosrafaelgn.fplay.playback.Player;
import br.com.carlosrafaelgn.fplay.ui.BackgroundActivityMonitor;
import br.com.carlosrafaelgn.fplay.ui.BgButton;
import br.com.carlosrafaelgn.fplay.ui.BgDialog;
import br.com.carlosrafaelgn.fplay.ui.BgListView;
import br.com.carlosrafaelgn.fplay.ui.BgSeekBar;
import br.com.carlosrafaelgn.fplay.ui.CustomContextMenu;
import br.com.carlosrafaelgn.fplay.ui.SongView;
import br.com.carlosrafaelgn.fplay.ui.UI;
import br.com.carlosrafaelgn.fplay.ui.drawable.BorderDrawable;
import br.com.carlosrafaelgn.fplay.ui.drawable.ColorDrawable;
import br.com.carlosrafaelgn.fplay.ui.drawable.TextIconDrawable;
import br.com.carlosrafaelgn.fplay.util.Timer;
import br.com.carlosrafaelgn.fplay.visualizer.AlbumArtVisualizer;
import br.com.carlosrafaelgn.fplay.visualizer.OpenGLVisualizerJni;
import br.com.carlosrafaelgn.fplay.visualizer.SimpleVisualizerJni;
import br.com.carlosrafaelgn.fplay.visualizer.Visualizer;

//
//How to create a ListView using ArrayAdapter in Android
//http://anujarosha.wordpress.com/2011/11/17/how-to-create-a-listview-using-arrayadapter-in-android/
//
//Customizing Android ListView Items with Custom ArrayAdapter
//http://www.ezzylearning.com/tutorial.aspx?tid=1763429
//
//Communicating with the UI Thread
//https://developer.android.com/training/multiple-threads/communicate-ui.html
//
//Difference of px, dp, dip and sp in Android?
//http://stackoverflow.com/questions/2025282/difference-of-px-dp-dip-and-sp-in-android
//
//Supporting Keyboard Navigation
//http://developer.android.com/training/keyboard-input/navigation.html
//
//Why are nested weights bad for performance? Alternatives?
//http://stackoverflow.com/questions/9430764/why-are-nested-weights-bad-for-performance-alternatives
//
//Maintain/Save/Restore scroll position when returning to a ListView
//http://stackoverflow.com/questions/3014089/maintain-save-restore-scroll-position-when-returning-to-a-listview
//
public final class ActivityMain extends ActivityItemView implements Timer.TimerHandler, Player.PlayerObserver, View.OnClickListener, BgSeekBar.OnBgSeekBarChangeListener, BgListView.OnAttachedObserver, BgListView.OnBgListViewKeyDownObserver, ActivityFileSelection.OnFileSelectionListener, BgButton.OnPressingChangeListener {
	private static final int MAX_SEEK = 10000, MNU_ADDSONGS = 100, MNU_CLEARLIST = 101, MNU_LOADLIST = 102, MNU_SAVELIST = 103, MNU_TOGGLECONTROLMODE = 104, MNU_RANDOMMODE = 105, MNU_EFFECTS = 106, MNU_VISUALIZER = 107, MNU_SETTINGS = 108, MNU_EXIT = 109, MNU_SORT_BY_TITLE = 110, MNU_SORT_BY_ARTIST = 111, MNU_SORT_BY_ALBUM = 112, MNU_VISUALIZER_SPECTRUM = 113, MNU_REPEAT = 114, MNU_REPEAT_ONE = 115, MNU_VISUALIZER_BLUETOOTH = 116, MNU_VISUALIZER_LIQUID = 117, MNU_VISUALIZER_SPIN = 118, MNU_VISUALIZER_PARTICLE = 119, MNU_VISUALIZER_IMMERSIVE_PARTICLE = 120, MNU_VISUALIZER_ALBUMART = 121, MNU_REPEAT_NONE = 122, MNU_VISUALIZER_IMMERSIVE_PARTICLE_VR = 123, MNU_VISUALIZER_SPECTRUM2 = 124;
	private View vwVolume;
	private TextView lblTitle, lblArtist, lblTrack, lblAlbum, lblLength, lblMsgSelMove;
	private TextIconDrawable lblTitleIcon;
	private BgSeekBar barSeek, barVolume;
	private ViewGroup panelControls, panelSecondary, panelSelection;
	private BgButton btnAdd, btnPrev, btnPlay, btnNext, btnMenu, btnMoreInfo, btnMoveSel, btnRemoveSel, btnCancelSel, btnDecreaseVolume, btnIncreaseVolume, btnVolume;
	private BgListView list;
	private Timer tmrSong, tmrUpdateVolumeDisplay, tmrVolume;
	private int firstSel, lastSel, lastTime, volumeButtonPressed, tmrVolumeInitialDelay, vwVolumeId, pendingListCommand;
	private boolean skipToDestruction, forceFadeOut, isCreatingLayout;//, ignoreAnnouncement;
	private StringBuilder timeBuilder, volumeBuilder;
	public static boolean localeHasBeenChanged;

	@Override
	public CharSequence getTitle() {
		return "FPlay" + UI.collon() + Player.getCurrentTitle(false);
	}

	@Override
	public int getSystemBgColor() {
		return (Player.controlMode ? UI.color_control_mode : UI.color_window);
	}

	private String volumeToString(int volume) {
		switch (Player.volumeControlType) {
		case Player.VOLUME_CONTROL_STREAM:
			//The parameter volume is only used to help properly synchronize when
			//Player.volumeControlType == Player.VOLUME_CONTROL_STREAM
			return Integer.toString(volume);
		case Player.VOLUME_CONTROL_DB:
			if (volume <= Player.VOLUME_MIN_DB)
				return "-\u221E dB";
			if (volume >= 0)
				return "-0 dB";
			volumeBuilder.delete(0, volumeBuilder.length());
			volumeBuilder.append('-');
			volumeBuilder.append(-volume / 100);
			volumeBuilder.append(" dB");
			return volumeBuilder.toString();
		default:
			volumeBuilder.delete(0, volumeBuilder.length());
			volumeBuilder.append(Player.getVolumeInPercentage());
			volumeBuilder.append('%');
			return volumeBuilder.toString();
		}
	}
	
	private void setVolumeIcon(int volume) {
		final String icon;
		final int max;
		switch (Player.volumeControlType) {
		case Player.VOLUME_CONTROL_STREAM:
			//The parameter volume is only used to help properly synchronize when
			//Player.volumeControlType == Player.VOLUME_CONTROL_STREAM
			max = Player.volumeStreamMax;
			break;
		case Player.VOLUME_CONTROL_DB:
		case Player.VOLUME_CONTROL_PERCENT:
			max = -Player.VOLUME_MIN_DB;
			volume += max;
			break;
		default:
			if (btnVolume != null)
				btnVolume.setText(UI.ICON_VOLUME4);
			return;
		}
		icon = ((volume == max) ? UI.ICON_VOLUME4 :
					((volume == 0) ? UI.ICON_VOLUME0 :
						((volume > ((max + 1) >> 1)) ? UI.ICON_VOLUME3 :
							((volume > (max >> 2)) ? UI.ICON_VOLUME2 :
								UI.ICON_VOLUME1))));
		if (btnVolume != null)
			btnVolume.setText(icon);
		else if (barVolume != null)
			barVolume.setIcon(icon);
	}
	
	private void updateVolumeDisplay(int volume) {
		final int value;
		if (Player.volumeControlType == Player.VOLUME_CONTROL_STREAM) {
			if (volume == Integer.MIN_VALUE)
				volume = Player.getSystemStreamVolume();
			value = volume;
		} else {
			if (volume == Integer.MIN_VALUE)
				volume = Player.localVolumeDB;
			value = ((Player.volumeControlType == Player.VOLUME_CONTROL_DB) ?
				((volume - Player.VOLUME_MIN_DB) / 100) :
					((volume - Player.VOLUME_MIN_DB) / (-Player.VOLUME_MIN_DB / 100)));
		}
		setVolumeIcon(volume);
		if (barVolume != null) {
			barVolume.setValue(value);
			barVolume.setText(volumeToString(volume));
		}
	}

	private void showMoreInfo() {
		final int p;
		if ((p = Player.songs.getSelection()) < 0 || p >= Player.songs.getCount())
			return;
		final Song song = Player.songs.getItemT(p);
		if (song == null)
			return;
		final StringBuilder stringBuilder = new StringBuilder(1024);

		stringBuilder.append(getText(R.string.path));
		stringBuilder.append('\n');
		stringBuilder.append(song.getHumanReadablePath());

		stringBuilder.append("\n\n");
		stringBuilder.append(getText(R.string.title));
		stringBuilder.append('\n');
		stringBuilder.append(song.title);

		if (song.artist.length() > 0 && !song.artist.equals("-")) {
			stringBuilder.append("\n\n");
			stringBuilder.append(getText(R.string.artist));
			stringBuilder.append('\n');
			stringBuilder.append(song.artist);
		}

		if (song.album.length() > 0 && !song.album.equals("-")) {
			stringBuilder.append("\n\n");
			stringBuilder.append(song.isHttp ? getText(R.string.url) : getText(R.string.album));
			stringBuilder.append('\n');
			stringBuilder.append(song.album);
		}

		if (song.track > 0) {
			stringBuilder.append("\n\n");
			stringBuilder.append(getText(R.string.track));
			stringBuilder.append('\n');
			stringBuilder.append(song.track);
		}

		if (song.year > 0) {
			stringBuilder.append("\n\n");
			stringBuilder.append(getText(R.string.year));
			stringBuilder.append('\n');
			stringBuilder.append(song.year);
		}

		if (song.lengthMS > 0) {
			stringBuilder.append("\n\n");
			stringBuilder.append(getText(R.string.duration));
			stringBuilder.append('\n');
			stringBuilder.append(song.length);
		}

		final LinearLayout l = (LinearLayout)UI.createDialogView(getHostActivity(), null);
		l.addView(UI.createDialogEditText(getHostActivity(), 0, stringBuilder.toString(), null, InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS));
		final BgDialog dialog = new BgDialog(getHostActivity(), l, null);
		dialog.setTitle(R.string.information);
		dialog.setPositiveButton(R.string.ok);
		dialog.show();
	}

	@SuppressWarnings("deprecation")
	private void startSelecting() {
		if (firstSel >= 0) {
			if (UI.isLargeScreen) {
				final ViewGroup.LayoutParams p = lblMsgSelMove.getLayoutParams();
				if (p.height != UI.defaultControlSize) {
					p.height = UI.defaultControlSize;
					lblMsgSelMove.setLayoutParams(p);
				}
			} else if (!UI.isLandscape) {
				final int h = (UI.defaultControlSize << 1) + panelControls.getPaddingBottom() + panelSecondary.getPaddingBottom();
				final ViewGroup.LayoutParams p = panelSelection.getLayoutParams();
				if (p != null && p.height != h) {
					p.height = h;
					panelSelection.setLayoutParams(p);
				}
			}
			UI.animationReset();
			UI.animationAddViewToShow(btnMoreInfo);
			UI.animationAddViewToShow(btnMoveSel);
			UI.animationAddViewToShow(btnRemoveSel);
			btnCancelSel.setText(R.string.cancel);
			UI.animationAddViewToHide(panelControls);
			UI.animationAddViewToHide(panelSecondary);
			UI.animationAddViewToShow(panelSelection);
			lblMsgSelMove.setText(R.string.msg_sel);
			if (!UI.isLargeScreen)
				UI.animationAddViewToHide(lblTitle);
			UI.animationAddViewToShow(lblMsgSelMove);
			lblMsgSelMove.setSelected(true);
			if (UI.isLargeScreen) {
				btnCancelSel.setNextFocusLeftId(R.id.btnRemoveSel);
				UI.setNextFocusForwardId(list, R.id.btnMoreInfo);
			} else if (UI.isLandscape) {
				btnCancelSel.setNextFocusUpId(R.id.btnRemoveSel);
				UI.setNextFocusForwardId(list, R.id.btnMoreInfo);
			} else {
				btnCancelSel.setNextFocusRightId(R.id.btnMoreInfo);
				UI.setNextFocusForwardId(btnCancelSel, R.id.btnMoreInfo);
				UI.setNextFocusForwardId(list, R.id.btnCancelSel);
			}
			Player.songs.selecting = true;
			Player.songs.moving = false;
			list.skipUpDownTranslation = true;
			UI.animationCommit(isCreatingLayout, null);
		}
	}
	
	private void startMovingSelection() {
		if (Player.songs.getFirstSelectedPosition() >= 0) {
			UI.animationReset();
			UI.animationAddViewToHide(btnMoreInfo);
			UI.animationAddViewToHide(btnMoveSel);
			UI.animationAddViewToHide(btnRemoveSel);
			if (UI.animationEnabled) {
				UI.animationAddViewToHide(btnCancelSel);
				UI.animationAddViewToHide(lblMsgSelMove);
				btnCancelSel.setTag(getText(R.string.done));
				lblMsgSelMove.setTag(getText(R.string.msg_move));
				UI.animationAddViewToShow(btnCancelSel);
				UI.animationAddViewToShow(lblMsgSelMove);
			} else {
				btnCancelSel.setText(R.string.done);
				lblMsgSelMove.setText(R.string.msg_move);
			}
			UI.animationCommit(isCreatingLayout, null);
			lblMsgSelMove.setSelected(true);
			if (UI.isLargeScreen) {
				btnCancelSel.setNextFocusLeftId(R.id.list);
			} else if (UI.isLandscape) {
				btnCancelSel.setNextFocusUpId(R.id.list);
			} else {
				btnCancelSel.setNextFocusRightId(R.id.list);
				UI.setNextFocusForwardId(btnCancelSel, R.id.list);
			}
			UI.setNextFocusForwardId(list, R.id.btnCancelSel);
			Player.songs.selecting = false;
			Player.songs.moving = true;
			list.skipUpDownTranslation = true;
		}
	}
	
	private void removeSelection() {
		if (Player.songs.getFirstSelectedPosition() >= 0) {
			Player.songs.removeSelection();
			cancelSelection(true);
		} else {
			list.skipUpDownTranslation = false;
		}
	}
	
	private void cancelSelection(boolean removed) {
		if (list.isInTouchMode()) {
			final int p = Player.songs.getCurrentPosition();
			if (p >= 0 && p < Player.songs.getCount())
				Player.songs.setSelection(p, true);
			else
				Player.songs.setSelection(-1, true);
		} else if (!removed && firstSel >= 0 && lastSel >= 0) {
			int p = Player.songs.getSelection();
			if (p < 0)
				p = ((firstSel < lastSel) ? Player.songs.getFirstSelectedPosition() : Player.songs.getLastSelectedPosition());
			Player.songs.setSelection(p, false);
		}
		Player.songs.selecting = false;
		Player.songs.moving = false;
		list.skipUpDownTranslation = false;
		firstSel = -1;
		lastSel = -1;
		UI.animationReset();
		UI.animationAddViewToHide(lblMsgSelMove);
		if (!UI.isLargeScreen)
			UI.animationAddViewToShow(lblTitle);
		lblTitle.setSelected(true);
		UI.animationAddViewToHide(panelSelection);
		UI.setNextFocusForwardId(list, UI.isLargeScreen ? vwVolumeId : R.id.btnAdd);
		UI.animationAddViewToShow(panelControls);
		UI.animationAddViewToShow(panelSecondary);
		UI.animationCommit(isCreatingLayout, null);
	}
	
	private void bringCurrentIntoView() {
		if (!Player.songs.moving && !Player.songs.selecting) {
			final int p = Player.songs.getCurrentPosition();
			if (p <= list.getFirstVisiblePosition() || p >= list.getLastVisiblePosition()) {
				//this smooth scroll has some serious issues in many devices,
				//specially when scrolling large distances!
				//if (UI.animationEnabled)
				//	list.centerItemSmoothly(p);
				//else
					list.centerItem(p);
			}
		}
	}
	
	private void addSongs(View sourceView) {
		if (Player.state == Player.STATE_ALIVE) {
			Player.alreadySelected = false;
			startActivity(new ActivityBrowser2(), 0, sourceView, sourceView != null);
		}
	}
	
	private boolean decreaseVolume() {
		final int volume = Player.decreaseVolume();
		if (volume != Integer.MIN_VALUE) {
			setVolumeIcon(volume);
			return true;
		}
		return false;
	}

	private boolean increaseVolume() {
		final int volume = Player.increaseVolume();
		if (volume != Integer.MIN_VALUE) {
			setVolumeIcon(volume);
			return true;
		}
		return false;
	}

	@Override
	public void onPlayerChanged(Song currentSong, boolean songHasChanged, boolean preparingHasChanged, Throwable ex) {
		final String icon = (Player.localPlaying ? UI.ICON_PAUSE : UI.ICON_PLAY);
		if (btnPlay != null) {
			btnPlay.setText(icon);
			btnPlay.setContentDescription(getText(Player.localPlaying ? R.string.pause : R.string.play));
		}
		if (lblTitleIcon != null)
			lblTitleIcon.setIcon(icon);
		if (songHasChanged) {
			if (Player.followCurrentSong && list != null && !list.changingCurrentWouldScareUser())
				bringCurrentIntoView();
			if (lblTitle != null) {
				lblTitle.setText(Player.getCurrentTitle(barSeek == null && Player.isPreparing()));
				lblTitle.setSelected(true);
				//if (ignoreAnnouncement)
				//	ignoreAnnouncement = false;
				//else if (UI.accessibilityManager != null && UI.accessibilityManager.isEnabled())
				//	UI.announceAccessibilityText(title);
			}
			if (lblArtist != null)
				lblArtist.setText((currentSong == null) ? "-" : currentSong.artist);
			if (lblTrack != null)
				lblTrack.setText((currentSong == null || currentSong.track <= 0) ? "-" : Integer.toString(currentSong.track));
			if (lblAlbum != null)
				lblAlbum.setText((currentSong == null) ? "-" : currentSong.album);
			if (lblLength != null)
				lblLength.setText((currentSong == null || currentSong.length == null || currentSong.length.length() == 0) ? "-" : currentSong.length);
			//if the user adds a song while in control mode, but quickly leaves control mode, they
			//will be able to see the song that has just been added
			//but if the current song changes, let's stick to it and ignore the recently added song
			if (Player.controlMode)
				Player.positionToCenter = -1;
		} else if (preparingHasChanged) {
			if (barSeek != null) {
				if (Player.isPreparing() && !barSeek.isTracking()) {
					barSeek.setText(R.string.loading);
					barSeek.setValue(0);
				}
			} else if (lblTitle != null) {
				lblTitle.setText(Player.getCurrentTitle(Player.isPreparing()));
				lblTitle.setSelected(true);
			}
		}
		if ((Player.localPlaying || Player.isPreparing()) && !Player.controlMode) {
			if (!tmrSong.isAlive())
				tmrSong.start(250);
		} else {
			tmrSong.stop();
		}
		lastTime = -2;
		handleTimer(tmrSong, null);
	}

	@Override
	public void onPlayerMetadataChanged(Song currentSong) {
		onPlayerChanged(currentSong, true, true, null);
		if (list != null) {
			for (int i = list.getChildCount() - 1; i >= 0; i--) {
				final View view = list.getChildAt(i);
				if (view instanceof SongView)
					((SongView)view).updateIfCurrent();
			}
		}
	}

	@Override
	public void onPlayerControlModeChanged(boolean controlMode) {
		if (Player.songs.selecting || Player.songs.moving)
			cancelSelection(false);
		onCleanupLayout();
		forceFadeOut = !controlMode;
		onCreateLayout(false);
		forceFadeOut = false;
		resume();
		System.gc();
	}
	
	@Override
	public void onPlayerGlobalVolumeChanged(int volume) {
		updateVolumeDisplay(volume);
	}
	
	@Override
	public void onPlayerAudioSinkChanged() {
		//when changing the output, the global volume usually changes
		if (Player.volumeControlType == Player.VOLUME_CONTROL_STREAM) {
			updateVolumeDisplay(Integer.MIN_VALUE);
			if (barVolume != null)
				barVolume.setMax(Player.volumeStreamMax);
			tmrUpdateVolumeDisplay.start(750);
		}
	}
	
	@Override
	public void onPlayerMediaButtonPrevious() {
		if (!Player.controlMode)
			bringCurrentIntoView();
	}
	
	@Override
	public void onPlayerMediaButtonNext() {
		if (!Player.controlMode)
			bringCurrentIntoView();
	}
	
	@Override
 	public View getNullContextMenuView() {
		return ((!Player.songs.selecting && !Player.songs.moving && Player.state == Player.STATE_ALIVE) ? btnMenu : null);
	}
	
	@Override
	public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
		if (UI.forcedLocale != UI.LOCALE_NONE && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && !localeHasBeenChanged) {
			localeHasBeenChanged = true;
			UI.reapplyForcedLocale(getHostActivity());
		}
		UI.prepare(menu);
		menu.add(0, MNU_ADDSONGS, 0, R.string.add_songs)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_FPLAY));
		UI.separator(menu, 0, 1);
		Menu s2, s = menu.addSubMenu(1, 0, 0, R.string.list)
				.setIcon(new TextIconDrawable(UI.ICON_LIST));
		UI.prepare(s);
		s.add(0, MNU_CLEARLIST, 0, R.string.clear_list)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_REMOVE));
		s.add(0, MNU_LOADLIST, 1, R.string.load_list)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_LOAD));
		s.add(0, MNU_SAVELIST, 2, R.string.save_list)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_SAVE));
		UI.separator(s, 0, 3);
		s.add(0, MNU_SORT_BY_TITLE, 4, R.string.sort_by_title)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_MOVE));
		s.add(0, MNU_SORT_BY_ARTIST, 5, R.string.sort_by_artist)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_MOVE));
		s.add(0, MNU_SORT_BY_ALBUM, 6, R.string.sort_by_album)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_MOVE));
		UI.separator(menu, 1, 1);
		menu.add(2, MNU_TOGGLECONTROLMODE, 0, R.string.control_mode)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(Player.controlMode ? UI.ICON_OPTCHK : UI.ICON_OPTUNCHK));
		if (UI.isLandscape && !UI.isLargeScreen) {
			s = menu.addSubMenu(2, 0, 1, R.string.more)
					.setIcon(new TextIconDrawable(UI.ICON_MENU));
			UI.prepare(s);
		} else {
			s = menu;
		}
		final int mnuId;
		final String mnuIcon;
		if (Player.songs.isInRandomMode()) {
			mnuId = R.string.random_mode;
			mnuIcon = UI.ICON_SHUFFLE;
		} else {
			switch (Player.songs.getRepeatMode()) {
			case SongList.REPEAT_NONE:
				mnuId = R.string.repeat_none;
				mnuIcon = UI.ICON_REPEATNONE;
				break;
			case SongList.REPEAT_ONE:
				mnuId = R.string.repeat_one;
				mnuIcon = UI.ICON_REPEATONE;
				break;
			default:
				mnuId = R.string.repeat_all;
				mnuIcon = UI.ICON_REPEAT;
				break;
			}
		}
		s2 = s.addSubMenu(2, 0, 0, getText(mnuId) + "\u2026")
			.setIcon(new TextIconDrawable(mnuIcon));
		UI.prepare(s2);
		s2.add(2, MNU_REPEAT, 0, getText(R.string.repeat_all))
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable((Player.songs.getRepeatMode() == SongList.REPEAT_ALL && !Player.songs.isInRandomMode()) ? UI.ICON_RADIOCHK : UI.ICON_RADIOUNCHK));
		s2.add(2, MNU_REPEAT_ONE, 1, getText(R.string.repeat_one))
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable((Player.songs.getRepeatMode() == SongList.REPEAT_ONE) ? UI.ICON_RADIOCHK : UI.ICON_RADIOUNCHK));
		UI.separator(s2, 2, 2);
		s2.add(2, MNU_RANDOMMODE, 3, getText(R.string.random_mode))
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(Player.songs.isInRandomMode() ? UI.ICON_RADIOCHK : UI.ICON_RADIOUNCHK));
		UI.separator(s2, 2, 4);
		s2.add(2, MNU_REPEAT_NONE, 5, getText(R.string.repeat_none))
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable((Player.songs.getRepeatMode() == SongList.REPEAT_NONE) ? UI.ICON_RADIOCHK : UI.ICON_RADIOUNCHK));
		UI.separator(s, 2, 1);
		s.add(2, MNU_EFFECTS, 2, R.string.audio_effects)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_EQUALIZER));
		s2 = s.addSubMenu(2, 0, 3, getText(R.string.visualizer) + "\u2026")
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		UI.prepare(s2);
		s2.add(0, MNU_VISUALIZER_SPECTRUM, 0, "Spectrum")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s2.add(0, MNU_VISUALIZER_SPECTRUM2, 1, "Spectrum 2")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s2.add(0, MNU_VISUALIZER_SPIN, 2, "Spinning Rainbow")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s2.add(0, MNU_VISUALIZER_PARTICLE, 3, "Sound Particles")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s2.add(0, MNU_VISUALIZER_IMMERSIVE_PARTICLE, 4, "Into the Particles")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_3DPAN));
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
			s2.add(0, MNU_VISUALIZER_IMMERSIVE_PARTICLE_VR, 5, "Into the Particles (VR)")
				.setOnMenuItemClickListener(this)
				.setIcon(new TextIconDrawable(UI.ICON_3DPAN));
		UI.separator(s2, 1, 0);
		s2.add(1, MNU_VISUALIZER_ALBUMART, 1, R.string.album_art)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_ALBUMART));
		s2.add(1, MNU_VISUALIZER_BLUETOOTH, 2, "Bluetooth + Arduino")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_BLUETOOTH));
		UI.separator(s2, 2, 0);
		s2.add(2, MNU_VISUALIZER_LIQUID, 1, "Liquid Spectrum")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s2.add(2, MNU_VISUALIZER, 2, "Classic")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s.add(2, MNU_SETTINGS, 4, R.string.settings)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_SETTINGS));
		UI.separator(menu, 2, 5);
		menu.add(3, MNU_EXIT, 0, R.string.exit)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_EXIT));
	}

	@TargetApi(Build.VERSION_CODES.M)
	@Override
	public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
		if (requestCode > 100 && requestCode < 200 && Player.state == Player.STATE_ALIVE && grantResults != null && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
			openVisualizer(requestCode);
		} else if (requestCode == 2 && pendingListCommand != 0) {
			if (Player.state == Player.STATE_ALIVE && grantResults != null && grantResults[0] == PackageManager.PERMISSION_GRANTED)
				selectPlaylist(pendingListCommand);
			pendingListCommand = 0;
		}
	}

	@SuppressWarnings({ "PointlessBooleanExpression", "ConstantConditions" })
	private void openVisualizer(int id) {
		if (!BuildConfig.X && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
			if (getHostActivity().checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
				getHostActivity().requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, id);
				return;
			}
		}
		switch (id) {
		case MNU_VISUALIZER:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, SimpleVisualizerJni.class.getName()));
			break;
		case MNU_VISUALIZER_SPECTRUM:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()));
			break;
		case MNU_VISUALIZER_SPECTRUM2:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_SPECTRUM2));
			break;
		case MNU_VISUALIZER_LIQUID:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_LIQUID));
			break;
		case MNU_VISUALIZER_SPIN:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_SPIN));
			break;
		case MNU_VISUALIZER_PARTICLE:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_PARTICLE));
			break;
		case MNU_VISUALIZER_IMMERSIVE_PARTICLE:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_IMMERSIVE_PARTICLE));
			break;
		case MNU_VISUALIZER_IMMERSIVE_PARTICLE_VR:
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
				getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_IMMERSIVE_PARTICLE_VR));
			break;
		case MNU_VISUALIZER_BLUETOOTH:
			startActivity(new ActivitySettings(false, true), 0, null, false);
			break;
		case MNU_VISUALIZER_ALBUMART:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, AlbumArtVisualizer.class.getName()));
			break;
		}
	}

	private void selectPlaylist(int how) {<commit_msg>Adjusted the camera permission for M<commit_after>//
// FPlayAndroid is distributed under the FreeBSD License
//
// Copyright (c) 2013-2014, <NAME>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the FreeBSD Project.
//
// https://github.com/carlosrafaelgn/FPlayAndroid
//
package br.com.carlosrafaelgn.fplay;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Build;
import android.text.InputType;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils.TruncateAt;
import android.text.style.DynamicDrawableSpan;
import android.text.style.ImageSpan;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import br.com.carlosrafaelgn.fplay.activity.ActivityVisualizer;
import br.com.carlosrafaelgn.fplay.list.FileSt;
import br.com.carlosrafaelgn.fplay.list.Song;
import br.com.carlosrafaelgn.fplay.list.SongList;
import br.com.carlosrafaelgn.fplay.playback.ExternalFx;
import br.com.carlosrafaelgn.fplay.playback.Player;
import br.com.carlosrafaelgn.fplay.ui.BackgroundActivityMonitor;
import br.com.carlosrafaelgn.fplay.ui.BgButton;
import br.com.carlosrafaelgn.fplay.ui.BgDialog;
import br.com.carlosrafaelgn.fplay.ui.BgListView;
import br.com.carlosrafaelgn.fplay.ui.BgSeekBar;
import br.com.carlosrafaelgn.fplay.ui.CustomContextMenu;
import br.com.carlosrafaelgn.fplay.ui.SongView;
import br.com.carlosrafaelgn.fplay.ui.UI;
import br.com.carlosrafaelgn.fplay.ui.drawable.BorderDrawable;
import br.com.carlosrafaelgn.fplay.ui.drawable.ColorDrawable;
import br.com.carlosrafaelgn.fplay.ui.drawable.TextIconDrawable;
import br.com.carlosrafaelgn.fplay.util.Timer;
import br.com.carlosrafaelgn.fplay.visualizer.AlbumArtVisualizer;
import br.com.carlosrafaelgn.fplay.visualizer.OpenGLVisualizerJni;
import br.com.carlosrafaelgn.fplay.visualizer.SimpleVisualizerJni;
import br.com.carlosrafaelgn.fplay.visualizer.Visualizer;

//
//How to create a ListView using ArrayAdapter in Android
//http://anujarosha.wordpress.com/2011/11/17/how-to-create-a-listview-using-arrayadapter-in-android/
//
//Customizing Android ListView Items with Custom ArrayAdapter
//http://www.ezzylearning.com/tutorial.aspx?tid=1763429
//
//Communicating with the UI Thread
//https://developer.android.com/training/multiple-threads/communicate-ui.html
//
//Difference of px, dp, dip and sp in Android?
//http://stackoverflow.com/questions/2025282/difference-of-px-dp-dip-and-sp-in-android
//
//Supporting Keyboard Navigation
//http://developer.android.com/training/keyboard-input/navigation.html
//
//Why are nested weights bad for performance? Alternatives?
//http://stackoverflow.com/questions/9430764/why-are-nested-weights-bad-for-performance-alternatives
//
//Maintain/Save/Restore scroll position when returning to a ListView
//http://stackoverflow.com/questions/3014089/maintain-save-restore-scroll-position-when-returning-to-a-listview
//
public final class ActivityMain extends ActivityItemView implements Timer.TimerHandler, Player.PlayerObserver, View.OnClickListener, BgSeekBar.OnBgSeekBarChangeListener, BgListView.OnAttachedObserver, BgListView.OnBgListViewKeyDownObserver, ActivityFileSelection.OnFileSelectionListener, BgButton.OnPressingChangeListener {
	private static final int MAX_SEEK = 10000, MNU_ADDSONGS = 100, MNU_CLEARLIST = 101, MNU_LOADLIST = 102, MNU_SAVELIST = 103, MNU_TOGGLECONTROLMODE = 104, MNU_RANDOMMODE = 105, MNU_EFFECTS = 106, MNU_VISUALIZER = 107, MNU_SETTINGS = 108, MNU_EXIT = 109, MNU_SORT_BY_TITLE = 110, MNU_SORT_BY_ARTIST = 111, MNU_SORT_BY_ALBUM = 112, MNU_VISUALIZER_SPECTRUM = 113, MNU_REPEAT = 114, MNU_REPEAT_ONE = 115, MNU_VISUALIZER_BLUETOOTH = 116, MNU_VISUALIZER_LIQUID = 117, MNU_VISUALIZER_SPIN = 118, MNU_VISUALIZER_PARTICLE = 119, MNU_VISUALIZER_IMMERSIVE_PARTICLE = 120, MNU_VISUALIZER_ALBUMART = 121, MNU_REPEAT_NONE = 122, MNU_VISUALIZER_IMMERSIVE_PARTICLE_VR = 123, MNU_VISUALIZER_SPECTRUM2 = 124;
	private View vwVolume;
	private TextView lblTitle, lblArtist, lblTrack, lblAlbum, lblLength, lblMsgSelMove;
	private TextIconDrawable lblTitleIcon;
	private BgSeekBar barSeek, barVolume;
	private ViewGroup panelControls, panelSecondary, panelSelection;
	private BgButton btnAdd, btnPrev, btnPlay, btnNext, btnMenu, btnMoreInfo, btnMoveSel, btnRemoveSel, btnCancelSel, btnDecreaseVolume, btnIncreaseVolume, btnVolume;
	private BgListView list;
	private Timer tmrSong, tmrUpdateVolumeDisplay, tmrVolume;
	private int firstSel, lastSel, lastTime, volumeButtonPressed, tmrVolumeInitialDelay, vwVolumeId, pendingListCommand;
	private boolean skipToDestruction, forceFadeOut, isCreatingLayout;//, ignoreAnnouncement;
	private StringBuilder timeBuilder, volumeBuilder;
	public static boolean localeHasBeenChanged;

	@Override
	public CharSequence getTitle() {
		return "FPlay" + UI.collon() + Player.getCurrentTitle(false);
	}

	@Override
	public int getSystemBgColor() {
		return (Player.controlMode ? UI.color_control_mode : UI.color_window);
	}

	private String volumeToString(int volume) {
		switch (Player.volumeControlType) {
		case Player.VOLUME_CONTROL_STREAM:
			//The parameter volume is only used to help properly synchronize when
			//Player.volumeControlType == Player.VOLUME_CONTROL_STREAM
			return Integer.toString(volume);
		case Player.VOLUME_CONTROL_DB:
			if (volume <= Player.VOLUME_MIN_DB)
				return "-\u221E dB";
			if (volume >= 0)
				return "-0 dB";
			volumeBuilder.delete(0, volumeBuilder.length());
			volumeBuilder.append('-');
			volumeBuilder.append(-volume / 100);
			volumeBuilder.append(" dB");
			return volumeBuilder.toString();
		default:
			volumeBuilder.delete(0, volumeBuilder.length());
			volumeBuilder.append(Player.getVolumeInPercentage());
			volumeBuilder.append('%');
			return volumeBuilder.toString();
		}
	}
	
	private void setVolumeIcon(int volume) {
		final String icon;
		final int max;
		switch (Player.volumeControlType) {
		case Player.VOLUME_CONTROL_STREAM:
			//The parameter volume is only used to help properly synchronize when
			//Player.volumeControlType == Player.VOLUME_CONTROL_STREAM
			max = Player.volumeStreamMax;
			break;
		case Player.VOLUME_CONTROL_DB:
		case Player.VOLUME_CONTROL_PERCENT:
			max = -Player.VOLUME_MIN_DB;
			volume += max;
			break;
		default:
			if (btnVolume != null)
				btnVolume.setText(UI.ICON_VOLUME4);
			return;
		}
		icon = ((volume == max) ? UI.ICON_VOLUME4 :
					((volume == 0) ? UI.ICON_VOLUME0 :
						((volume > ((max + 1) >> 1)) ? UI.ICON_VOLUME3 :
							((volume > (max >> 2)) ? UI.ICON_VOLUME2 :
								UI.ICON_VOLUME1))));
		if (btnVolume != null)
			btnVolume.setText(icon);
		else if (barVolume != null)
			barVolume.setIcon(icon);
	}
	
	private void updateVolumeDisplay(int volume) {
		final int value;
		if (Player.volumeControlType == Player.VOLUME_CONTROL_STREAM) {
			if (volume == Integer.MIN_VALUE)
				volume = Player.getSystemStreamVolume();
			value = volume;
		} else {
			if (volume == Integer.MIN_VALUE)
				volume = Player.localVolumeDB;
			value = ((Player.volumeControlType == Player.VOLUME_CONTROL_DB) ?
				((volume - Player.VOLUME_MIN_DB) / 100) :
					((volume - Player.VOLUME_MIN_DB) / (-Player.VOLUME_MIN_DB / 100)));
		}
		setVolumeIcon(volume);
		if (barVolume != null) {
			barVolume.setValue(value);
			barVolume.setText(volumeToString(volume));
		}
	}

	private void showMoreInfo() {
		final int p;
		if ((p = Player.songs.getSelection()) < 0 || p >= Player.songs.getCount())
			return;
		final Song song = Player.songs.getItemT(p);
		if (song == null)
			return;
		final StringBuilder stringBuilder = new StringBuilder(1024);

		stringBuilder.append(getText(R.string.path));
		stringBuilder.append('\n');
		stringBuilder.append(song.getHumanReadablePath());

		stringBuilder.append("\n\n");
		stringBuilder.append(getText(R.string.title));
		stringBuilder.append('\n');
		stringBuilder.append(song.title);

		if (song.artist.length() > 0 && !song.artist.equals("-")) {
			stringBuilder.append("\n\n");
			stringBuilder.append(getText(R.string.artist));
			stringBuilder.append('\n');
			stringBuilder.append(song.artist);
		}

		if (song.album.length() > 0 && !song.album.equals("-")) {
			stringBuilder.append("\n\n");
			stringBuilder.append(song.isHttp ? getText(R.string.url) : getText(R.string.album));
			stringBuilder.append('\n');
			stringBuilder.append(song.album);
		}

		if (song.track > 0) {
			stringBuilder.append("\n\n");
			stringBuilder.append(getText(R.string.track));
			stringBuilder.append('\n');
			stringBuilder.append(song.track);
		}

		if (song.year > 0) {
			stringBuilder.append("\n\n");
			stringBuilder.append(getText(R.string.year));
			stringBuilder.append('\n');
			stringBuilder.append(song.year);
		}

		if (song.lengthMS > 0) {
			stringBuilder.append("\n\n");
			stringBuilder.append(getText(R.string.duration));
			stringBuilder.append('\n');
			stringBuilder.append(song.length);
		}

		final LinearLayout l = (LinearLayout)UI.createDialogView(getHostActivity(), null);
		l.addView(UI.createDialogEditText(getHostActivity(), 0, stringBuilder.toString(), null, InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS));
		final BgDialog dialog = new BgDialog(getHostActivity(), l, null);
		dialog.setTitle(R.string.information);
		dialog.setPositiveButton(R.string.ok);
		dialog.show();
	}

	@SuppressWarnings("deprecation")
	private void startSelecting() {
		if (firstSel >= 0) {
			if (UI.isLargeScreen) {
				final ViewGroup.LayoutParams p = lblMsgSelMove.getLayoutParams();
				if (p.height != UI.defaultControlSize) {
					p.height = UI.defaultControlSize;
					lblMsgSelMove.setLayoutParams(p);
				}
			} else if (!UI.isLandscape) {
				final int h = (UI.defaultControlSize << 1) + panelControls.getPaddingBottom() + panelSecondary.getPaddingBottom();
				final ViewGroup.LayoutParams p = panelSelection.getLayoutParams();
				if (p != null && p.height != h) {
					p.height = h;
					panelSelection.setLayoutParams(p);
				}
			}
			UI.animationReset();
			UI.animationAddViewToShow(btnMoreInfo);
			UI.animationAddViewToShow(btnMoveSel);
			UI.animationAddViewToShow(btnRemoveSel);
			btnCancelSel.setText(R.string.cancel);
			UI.animationAddViewToHide(panelControls);
			UI.animationAddViewToHide(panelSecondary);
			UI.animationAddViewToShow(panelSelection);
			lblMsgSelMove.setText(R.string.msg_sel);
			if (!UI.isLargeScreen)
				UI.animationAddViewToHide(lblTitle);
			UI.animationAddViewToShow(lblMsgSelMove);
			lblMsgSelMove.setSelected(true);
			if (UI.isLargeScreen) {
				btnCancelSel.setNextFocusLeftId(R.id.btnRemoveSel);
				UI.setNextFocusForwardId(list, R.id.btnMoreInfo);
			} else if (UI.isLandscape) {
				btnCancelSel.setNextFocusUpId(R.id.btnRemoveSel);
				UI.setNextFocusForwardId(list, R.id.btnMoreInfo);
			} else {
				btnCancelSel.setNextFocusRightId(R.id.btnMoreInfo);
				UI.setNextFocusForwardId(btnCancelSel, R.id.btnMoreInfo);
				UI.setNextFocusForwardId(list, R.id.btnCancelSel);
			}
			Player.songs.selecting = true;
			Player.songs.moving = false;
			list.skipUpDownTranslation = true;
			UI.animationCommit(isCreatingLayout, null);
		}
	}
	
	private void startMovingSelection() {
		if (Player.songs.getFirstSelectedPosition() >= 0) {
			UI.animationReset();
			UI.animationAddViewToHide(btnMoreInfo);
			UI.animationAddViewToHide(btnMoveSel);
			UI.animationAddViewToHide(btnRemoveSel);
			if (UI.animationEnabled) {
				UI.animationAddViewToHide(btnCancelSel);
				UI.animationAddViewToHide(lblMsgSelMove);
				btnCancelSel.setTag(getText(R.string.done));
				lblMsgSelMove.setTag(getText(R.string.msg_move));
				UI.animationAddViewToShow(btnCancelSel);
				UI.animationAddViewToShow(lblMsgSelMove);
			} else {
				btnCancelSel.setText(R.string.done);
				lblMsgSelMove.setText(R.string.msg_move);
			}
			UI.animationCommit(isCreatingLayout, null);
			lblMsgSelMove.setSelected(true);
			if (UI.isLargeScreen) {
				btnCancelSel.setNextFocusLeftId(R.id.list);
			} else if (UI.isLandscape) {
				btnCancelSel.setNextFocusUpId(R.id.list);
			} else {
				btnCancelSel.setNextFocusRightId(R.id.list);
				UI.setNextFocusForwardId(btnCancelSel, R.id.list);
			}
			UI.setNextFocusForwardId(list, R.id.btnCancelSel);
			Player.songs.selecting = false;
			Player.songs.moving = true;
			list.skipUpDownTranslation = true;
		}
	}
	
	private void removeSelection() {
		if (Player.songs.getFirstSelectedPosition() >= 0) {
			Player.songs.removeSelection();
			cancelSelection(true);
		} else {
			list.skipUpDownTranslation = false;
		}
	}
	
	private void cancelSelection(boolean removed) {
		if (list.isInTouchMode()) {
			final int p = Player.songs.getCurrentPosition();
			if (p >= 0 && p < Player.songs.getCount())
				Player.songs.setSelection(p, true);
			else
				Player.songs.setSelection(-1, true);
		} else if (!removed && firstSel >= 0 && lastSel >= 0) {
			int p = Player.songs.getSelection();
			if (p < 0)
				p = ((firstSel < lastSel) ? Player.songs.getFirstSelectedPosition() : Player.songs.getLastSelectedPosition());
			Player.songs.setSelection(p, false);
		}
		Player.songs.selecting = false;
		Player.songs.moving = false;
		list.skipUpDownTranslation = false;
		firstSel = -1;
		lastSel = -1;
		UI.animationReset();
		UI.animationAddViewToHide(lblMsgSelMove);
		if (!UI.isLargeScreen)
			UI.animationAddViewToShow(lblTitle);
		lblTitle.setSelected(true);
		UI.animationAddViewToHide(panelSelection);
		UI.setNextFocusForwardId(list, UI.isLargeScreen ? vwVolumeId : R.id.btnAdd);
		UI.animationAddViewToShow(panelControls);
		UI.animationAddViewToShow(panelSecondary);
		UI.animationCommit(isCreatingLayout, null);
	}
	
	private void bringCurrentIntoView() {
		if (!Player.songs.moving && !Player.songs.selecting) {
			final int p = Player.songs.getCurrentPosition();
			if (p <= list.getFirstVisiblePosition() || p >= list.getLastVisiblePosition()) {
				//this smooth scroll has some serious issues in many devices,
				//specially when scrolling large distances!
				//if (UI.animationEnabled)
				//	list.centerItemSmoothly(p);
				//else
					list.centerItem(p);
			}
		}
	}
	
	private void addSongs(View sourceView) {
		if (Player.state == Player.STATE_ALIVE) {
			Player.alreadySelected = false;
			startActivity(new ActivityBrowser2(), 0, sourceView, sourceView != null);
		}
	}
	
	private boolean decreaseVolume() {
		final int volume = Player.decreaseVolume();
		if (volume != Integer.MIN_VALUE) {
			setVolumeIcon(volume);
			return true;
		}
		return false;
	}

	private boolean increaseVolume() {
		final int volume = Player.increaseVolume();
		if (volume != Integer.MIN_VALUE) {
			setVolumeIcon(volume);
			return true;
		}
		return false;
	}

	@Override
	public void onPlayerChanged(Song currentSong, boolean songHasChanged, boolean preparingHasChanged, Throwable ex) {
		final String icon = (Player.localPlaying ? UI.ICON_PAUSE : UI.ICON_PLAY);
		if (btnPlay != null) {
			btnPlay.setText(icon);
			btnPlay.setContentDescription(getText(Player.localPlaying ? R.string.pause : R.string.play));
		}
		if (lblTitleIcon != null)
			lblTitleIcon.setIcon(icon);
		if (songHasChanged) {
			if (Player.followCurrentSong && list != null && !list.changingCurrentWouldScareUser())
				bringCurrentIntoView();
			if (lblTitle != null) {
				lblTitle.setText(Player.getCurrentTitle(barSeek == null && Player.isPreparing()));
				lblTitle.setSelected(true);
				//if (ignoreAnnouncement)
				//	ignoreAnnouncement = false;
				//else if (UI.accessibilityManager != null && UI.accessibilityManager.isEnabled())
				//	UI.announceAccessibilityText(title);
			}
			if (lblArtist != null)
				lblArtist.setText((currentSong == null) ? "-" : currentSong.artist);
			if (lblTrack != null)
				lblTrack.setText((currentSong == null || currentSong.track <= 0) ? "-" : Integer.toString(currentSong.track));
			if (lblAlbum != null)
				lblAlbum.setText((currentSong == null) ? "-" : currentSong.album);
			if (lblLength != null)
				lblLength.setText((currentSong == null || currentSong.length == null || currentSong.length.length() == 0) ? "-" : currentSong.length);
			//if the user adds a song while in control mode, but quickly leaves control mode, they
			//will be able to see the song that has just been added
			//but if the current song changes, let's stick to it and ignore the recently added song
			if (Player.controlMode)
				Player.positionToCenter = -1;
		} else if (preparingHasChanged) {
			if (barSeek != null) {
				if (Player.isPreparing() && !barSeek.isTracking()) {
					barSeek.setText(R.string.loading);
					barSeek.setValue(0);
				}
			} else if (lblTitle != null) {
				lblTitle.setText(Player.getCurrentTitle(Player.isPreparing()));
				lblTitle.setSelected(true);
			}
		}
		if ((Player.localPlaying || Player.isPreparing()) && !Player.controlMode) {
			if (!tmrSong.isAlive())
				tmrSong.start(250);
		} else {
			tmrSong.stop();
		}
		lastTime = -2;
		handleTimer(tmrSong, null);
	}

	@Override
	public void onPlayerMetadataChanged(Song currentSong) {
		onPlayerChanged(currentSong, true, true, null);
		if (list != null) {
			for (int i = list.getChildCount() - 1; i >= 0; i--) {
				final View view = list.getChildAt(i);
				if (view instanceof SongView)
					((SongView)view).updateIfCurrent();
			}
		}
	}

	@Override
	public void onPlayerControlModeChanged(boolean controlMode) {
		if (Player.songs.selecting || Player.songs.moving)
			cancelSelection(false);
		onCleanupLayout();
		forceFadeOut = !controlMode;
		onCreateLayout(false);
		forceFadeOut = false;
		resume();
		System.gc();
	}
	
	@Override
	public void onPlayerGlobalVolumeChanged(int volume) {
		updateVolumeDisplay(volume);
	}
	
	@Override
	public void onPlayerAudioSinkChanged() {
		//when changing the output, the global volume usually changes
		if (Player.volumeControlType == Player.VOLUME_CONTROL_STREAM) {
			updateVolumeDisplay(Integer.MIN_VALUE);
			if (barVolume != null)
				barVolume.setMax(Player.volumeStreamMax);
			tmrUpdateVolumeDisplay.start(750);
		}
	}
	
	@Override
	public void onPlayerMediaButtonPrevious() {
		if (!Player.controlMode)
			bringCurrentIntoView();
	}
	
	@Override
	public void onPlayerMediaButtonNext() {
		if (!Player.controlMode)
			bringCurrentIntoView();
	}
	
	@Override
 	public View getNullContextMenuView() {
		return ((!Player.songs.selecting && !Player.songs.moving && Player.state == Player.STATE_ALIVE) ? btnMenu : null);
	}
	
	@Override
	public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
		if (UI.forcedLocale != UI.LOCALE_NONE && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && !localeHasBeenChanged) {
			localeHasBeenChanged = true;
			UI.reapplyForcedLocale(getHostActivity());
		}
		UI.prepare(menu);
		menu.add(0, MNU_ADDSONGS, 0, R.string.add_songs)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_FPLAY));
		UI.separator(menu, 0, 1);
		Menu s2, s = menu.addSubMenu(1, 0, 0, R.string.list)
				.setIcon(new TextIconDrawable(UI.ICON_LIST));
		UI.prepare(s);
		s.add(0, MNU_CLEARLIST, 0, R.string.clear_list)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_REMOVE));
		s.add(0, MNU_LOADLIST, 1, R.string.load_list)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_LOAD));
		s.add(0, MNU_SAVELIST, 2, R.string.save_list)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_SAVE));
		UI.separator(s, 0, 3);
		s.add(0, MNU_SORT_BY_TITLE, 4, R.string.sort_by_title)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_MOVE));
		s.add(0, MNU_SORT_BY_ARTIST, 5, R.string.sort_by_artist)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_MOVE));
		s.add(0, MNU_SORT_BY_ALBUM, 6, R.string.sort_by_album)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_MOVE));
		UI.separator(menu, 1, 1);
		menu.add(2, MNU_TOGGLECONTROLMODE, 0, R.string.control_mode)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(Player.controlMode ? UI.ICON_OPTCHK : UI.ICON_OPTUNCHK));
		if (UI.isLandscape && !UI.isLargeScreen) {
			s = menu.addSubMenu(2, 0, 1, R.string.more)
					.setIcon(new TextIconDrawable(UI.ICON_MENU));
			UI.prepare(s);
		} else {
			s = menu;
		}
		final int mnuId;
		final String mnuIcon;
		if (Player.songs.isInRandomMode()) {
			mnuId = R.string.random_mode;
			mnuIcon = UI.ICON_SHUFFLE;
		} else {
			switch (Player.songs.getRepeatMode()) {
			case SongList.REPEAT_NONE:
				mnuId = R.string.repeat_none;
				mnuIcon = UI.ICON_REPEATNONE;
				break;
			case SongList.REPEAT_ONE:
				mnuId = R.string.repeat_one;
				mnuIcon = UI.ICON_REPEATONE;
				break;
			default:
				mnuId = R.string.repeat_all;
				mnuIcon = UI.ICON_REPEAT;
				break;
			}
		}
		s2 = s.addSubMenu(2, 0, 0, getText(mnuId) + "\u2026")
			.setIcon(new TextIconDrawable(mnuIcon));
		UI.prepare(s2);
		s2.add(2, MNU_REPEAT, 0, getText(R.string.repeat_all))
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable((Player.songs.getRepeatMode() == SongList.REPEAT_ALL && !Player.songs.isInRandomMode()) ? UI.ICON_RADIOCHK : UI.ICON_RADIOUNCHK));
		s2.add(2, MNU_REPEAT_ONE, 1, getText(R.string.repeat_one))
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable((Player.songs.getRepeatMode() == SongList.REPEAT_ONE) ? UI.ICON_RADIOCHK : UI.ICON_RADIOUNCHK));
		UI.separator(s2, 2, 2);
		s2.add(2, MNU_RANDOMMODE, 3, getText(R.string.random_mode))
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(Player.songs.isInRandomMode() ? UI.ICON_RADIOCHK : UI.ICON_RADIOUNCHK));
		UI.separator(s2, 2, 4);
		s2.add(2, MNU_REPEAT_NONE, 5, getText(R.string.repeat_none))
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable((Player.songs.getRepeatMode() == SongList.REPEAT_NONE) ? UI.ICON_RADIOCHK : UI.ICON_RADIOUNCHK));
		UI.separator(s, 2, 1);
		s.add(2, MNU_EFFECTS, 2, R.string.audio_effects)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_EQUALIZER));
		s2 = s.addSubMenu(2, 0, 3, getText(R.string.visualizer) + "\u2026")
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		UI.prepare(s2);
		s2.add(0, MNU_VISUALIZER_SPECTRUM, 0, "Spectrum")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s2.add(0, MNU_VISUALIZER_SPECTRUM2, 1, "Spectrum 2")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s2.add(0, MNU_VISUALIZER_SPIN, 2, "Spinning Rainbow")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s2.add(0, MNU_VISUALIZER_PARTICLE, 3, "Sound Particles")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s2.add(0, MNU_VISUALIZER_IMMERSIVE_PARTICLE, 4, "Into the Particles")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_3DPAN));
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
			s2.add(0, MNU_VISUALIZER_IMMERSIVE_PARTICLE_VR, 5, "Into the Particles (VR)")
				.setOnMenuItemClickListener(this)
				.setIcon(new TextIconDrawable(UI.ICON_3DPAN));
		UI.separator(s2, 1, 0);
		s2.add(1, MNU_VISUALIZER_ALBUMART, 1, R.string.album_art)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_ALBUMART));
		s2.add(1, MNU_VISUALIZER_BLUETOOTH, 2, "Bluetooth + Arduino")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_BLUETOOTH));
		UI.separator(s2, 2, 0);
		s2.add(2, MNU_VISUALIZER_LIQUID, 1, "Liquid Spectrum")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s2.add(2, MNU_VISUALIZER, 2, "Classic")
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_VISUALIZER));
		s.add(2, MNU_SETTINGS, 4, R.string.settings)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_SETTINGS));
		UI.separator(menu, 2, 5);
		menu.add(3, MNU_EXIT, 0, R.string.exit)
			.setOnMenuItemClickListener(this)
			.setIcon(new TextIconDrawable(UI.ICON_EXIT));
	}

	@TargetApi(Build.VERSION_CODES.M)
	@Override
	public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
		if (requestCode > 100 && requestCode < 200 && Player.state == Player.STATE_ALIVE && grantResults != null && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
			openVisualizer(requestCode);
		} else if (requestCode == 2 && pendingListCommand != 0) {
			if (Player.state == Player.STATE_ALIVE && grantResults != null && grantResults[0] == PackageManager.PERMISSION_GRANTED)
				selectPlaylist(pendingListCommand);
			pendingListCommand = 0;
		}
	}

	@SuppressWarnings({ "PointlessBooleanExpression", "ConstantConditions" })
	private void openVisualizer(int id) {
		if (!BuildConfig.X && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
			if (getHostActivity().checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
				getHostActivity().requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, id);
				return;
			}
		}
		switch (id) {
		case MNU_VISUALIZER:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, SimpleVisualizerJni.class.getName()));
			break;
		case MNU_VISUALIZER_SPECTRUM:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()));
			break;
		case MNU_VISUALIZER_SPECTRUM2:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_SPECTRUM2));
			break;
		case MNU_VISUALIZER_LIQUID:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_LIQUID));
			break;
		case MNU_VISUALIZER_SPIN:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_SPIN));
			break;
		case MNU_VISUALIZER_PARTICLE:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_PARTICLE));
			break;
		case MNU_VISUALIZER_IMMERSIVE_PARTICLE:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_IMMERSIVE_PARTICLE));
			break;
		case MNU_VISUALIZER_IMMERSIVE_PARTICLE_VR:
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
					if (getHostActivity().checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
						getHostActivity().requestPermissions(new String[]{Manifest.permission.CAMERA}, id);
						return;
					}
				}
				getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, OpenGLVisualizerJni.class.getName()).putExtra(OpenGLVisualizerJni.EXTRA_VISUALIZER_TYPE, OpenGLVisualizerJni.TYPE_IMMERSIVE_PARTICLE_VR));
			}
			break;
		case MNU_VISUALIZER_BLUETOOTH:
			startActivity(new ActivitySettings(false, true), 0, null, false);
			break;
		case MNU_VISUALIZER_ALBUMART:
			getHostActivity().startActivity((new Intent(Player.theApplication, ActivityVisualizer.class)).putExtra(Visualizer.EXTRA_VISUALIZER_CLASS_NAME, AlbumArtVisualizer.class.getName()));
			break;
		}
	}

	private void selectPlaylist(int how) {
{'fraction_non_alphanumeric': 0.12089119792187032, 'fraction_numerical': 0.010623771938588603, 'mean_word_length': 4.938983386075949, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 56, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3394789', 'n_tokens_mistral': 23174, 'n_tokens_neox': 20389, 'n_words': 3619}