diff options
author | Scott Jackson <daneren2005@gmail.com> | 2012-07-02 21:24:02 -0700 |
---|---|---|
committer | Scott Jackson <daneren2005@gmail.com> | 2012-07-02 21:24:02 -0700 |
commit | a1a18f77a50804e0127dfa4b0f5240c49c541184 (patch) | |
tree | 19a38880afe505beddb5590379a8134d7730a277 /subsonic-android/src/net/sourceforge/subsonic/androidapp/activity | |
parent | b61d787706979e7e20f4c3c4f93c1f129d92273f (diff) | |
download | dsub-a1a18f77a50804e0127dfa4b0f5240c49c541184.tar.gz dsub-a1a18f77a50804e0127dfa4b0f5240c49c541184.tar.bz2 dsub-a1a18f77a50804e0127dfa4b0f5240c49c541184.zip |
Initial Commit
Diffstat (limited to 'subsonic-android/src/net/sourceforge/subsonic/androidapp/activity')
14 files changed, 3749 insertions, 0 deletions
diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/DownloadActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/DownloadActivity.java new file mode 100644 index 00000000..68144481 --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/DownloadActivity.java @@ -0,0 +1,874 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.activity; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.Bundle; +import android.os.Handler; +import android.view.ContextMenu; +import android.view.Display; +import android.view.GestureDetector; +import android.view.GestureDetector.OnGestureListener; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.AnimationUtils; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.ViewFlipper; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.domain.PlayerState; +import net.sourceforge.subsonic.androidapp.domain.RepeatMode; +import net.sourceforge.subsonic.androidapp.service.DownloadFile; +import net.sourceforge.subsonic.androidapp.service.DownloadService; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.HorizontalSlider; +import net.sourceforge.subsonic.androidapp.util.SilentBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.SongView; +import net.sourceforge.subsonic.androidapp.util.Util; +import net.sourceforge.subsonic.androidapp.view.VisualizerView; + +import static net.sourceforge.subsonic.androidapp.domain.PlayerState.*; + +public class DownloadActivity extends SubsonicTabActivity implements OnGestureListener { + + private static final int DIALOG_SAVE_PLAYLIST = 100; + private static final int PERCENTAGE_OF_SCREEN_FOR_SWIPE = 5; + private static final int COLOR_BUTTON_ENABLED = Color.rgb(129, 201, 54); + private static final int COLOR_BUTTON_DISABLED = Color.rgb(164, 166, 158); + + private ViewFlipper playlistFlipper; + private ViewFlipper buttonBarFlipper; + private TextView emptyTextView; + private TextView songTitleTextView; + private TextView albumTextView; + private TextView artistTextView; + private ImageView albumArtImageView; + private ListView playlistView; + private TextView positionTextView; + private TextView durationTextView; + private TextView statusTextView; + private HorizontalSlider progressBar; + private View previousButton; + private View nextButton; + private View pauseButton; + private View stopButton; + private View startButton; + private View shuffleButton; + private ImageButton repeatButton; + private Button equalizerButton; + private Button visualizerButton; + private Button jukeboxButton; + private View toggleListButton; + private ScheduledExecutorService executorService; + private DownloadFile currentPlaying; + private long currentRevision; + private EditText playlistNameView; + private GestureDetector gestureScanner; + private int swipeDistance; + private int swipeVelocity; + private VisualizerView visualizerView; + + /** + * Called when the activity is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.download); + + WindowManager w = getWindowManager(); + Display d = w.getDefaultDisplay(); + swipeDistance = (d.getWidth() + d.getHeight()) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100; + swipeVelocity = (d.getWidth() + d.getHeight()) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100; + gestureScanner = new GestureDetector(this); + + playlistFlipper = (ViewFlipper) findViewById(R.id.download_playlist_flipper); + buttonBarFlipper = (ViewFlipper) findViewById(R.id.download_button_bar_flipper); + emptyTextView = (TextView) findViewById(R.id.download_empty); + songTitleTextView = (TextView) findViewById(R.id.download_song_title); + albumTextView = (TextView) findViewById(R.id.download_album); + artistTextView = (TextView) findViewById(R.id.download_artist); + albumArtImageView = (ImageView) findViewById(R.id.download_album_art_image); + positionTextView = (TextView) findViewById(R.id.download_position); + durationTextView = (TextView) findViewById(R.id.download_duration); + statusTextView = (TextView) findViewById(R.id.download_status); + progressBar = (HorizontalSlider) findViewById(R.id.download_progress_bar); + playlistView = (ListView) findViewById(R.id.download_list); + previousButton = findViewById(R.id.download_previous); + nextButton = findViewById(R.id.download_next); + pauseButton = findViewById(R.id.download_pause); + stopButton = findViewById(R.id.download_stop); + startButton = findViewById(R.id.download_start); + shuffleButton = findViewById(R.id.download_shuffle); + repeatButton = (ImageButton) findViewById(R.id.download_repeat); + equalizerButton = (Button) findViewById(R.id.download_equalizer); + visualizerButton = (Button) findViewById(R.id.download_visualizer); + jukeboxButton = (Button) findViewById(R.id.download_jukebox); + LinearLayout visualizerViewLayout = (LinearLayout) findViewById(R.id.download_visualizer_view_layout); + + toggleListButton = findViewById(R.id.download_toggle_list); + + View.OnTouchListener touchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent me) { + return gestureScanner.onTouchEvent(me); + } + }; + previousButton.setOnTouchListener(touchListener); + nextButton.setOnTouchListener(touchListener); + pauseButton.setOnTouchListener(touchListener); + stopButton.setOnTouchListener(touchListener); + startButton.setOnTouchListener(touchListener); + equalizerButton.setOnTouchListener(touchListener); + visualizerButton.setOnTouchListener(touchListener); + jukeboxButton.setOnTouchListener(touchListener); + buttonBarFlipper.setOnTouchListener(touchListener); + emptyTextView.setOnTouchListener(touchListener); + albumArtImageView.setOnTouchListener(touchListener); + + albumArtImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + toggleFullscreenAlbumArt(); + } + }); + + previousButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + warnIfNetworkOrStorageUnavailable(); + getDownloadService().previous(); + onCurrentChanged(); + onProgressChanged(); + } + }); + + nextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + warnIfNetworkOrStorageUnavailable(); + if (getDownloadService().getCurrentPlayingIndex() < getDownloadService().size() - 1) { + getDownloadService().next(); + onCurrentChanged(); + onProgressChanged(); + } + } + }); + + pauseButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + getDownloadService().pause(); + onCurrentChanged(); + onProgressChanged(); + } + }); + + stopButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + getDownloadService().reset(); + onCurrentChanged(); + onProgressChanged(); + } + }); + + startButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + warnIfNetworkOrStorageUnavailable(); + start(); + onCurrentChanged(); + onProgressChanged(); + } + }); + + shuffleButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + getDownloadService().shuffle(); + Util.toast(DownloadActivity.this, R.string.download_menu_shuffle_notification); + } + }); + + repeatButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + RepeatMode repeatMode = getDownloadService().getRepeatMode().next(); + getDownloadService().setRepeatMode(repeatMode); + onDownloadListChanged(); + switch (repeatMode) { + case OFF: + Util.toast(DownloadActivity.this, R.string.download_repeat_off); + break; + case ALL: + Util.toast(DownloadActivity.this, R.string.download_repeat_all); + break; + case SINGLE: + Util.toast(DownloadActivity.this, R.string.download_repeat_single); + break; + default: + break; + } + } + }); + + equalizerButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + startActivity(new Intent(DownloadActivity.this, EqualizerActivity.class)); + } + }); + + visualizerButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + boolean active = !visualizerView.isActive(); + visualizerView.setActive(active); + getDownloadService().setShowVisualization(visualizerView.isActive()); + updateButtons(); + Util.toast(DownloadActivity.this, active ? R.string.download_visualizer_on : R.string.download_visualizer_off); + } + }); + + jukeboxButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + boolean jukeboxEnabled = !getDownloadService().isJukeboxEnabled(); + getDownloadService().setJukeboxEnabled(jukeboxEnabled); + updateButtons(); + Util.toast(DownloadActivity.this, jukeboxEnabled ? R.string.download_jukebox_on : R.string.download_jukebox_off, false); + } + }); + + toggleListButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + toggleFullscreenAlbumArt(); + } + }); + + progressBar.setOnSliderChangeListener(new HorizontalSlider.OnSliderChangeListener() { + @Override + public void onSliderChanged(View view, int position, boolean inProgress) { + Util.toast(DownloadActivity.this, Util.formatDuration(position / 1000), true); + if (!inProgress) { + getDownloadService().seekTo(position); + onProgressChanged(); + } + } + }); + playlistView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + warnIfNetworkOrStorageUnavailable(); + getDownloadService().play(position); + onCurrentChanged(); + onProgressChanged(); + } + }); + + registerForContextMenu(playlistView); + + DownloadService downloadService = getDownloadService(); + if (downloadService != null && getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, false)) { + warnIfNetworkOrStorageUnavailable(); + downloadService.setShufflePlayEnabled(true); + } + + boolean visualizerAvailable = downloadService != null && downloadService.getVisualizerController() != null; + boolean equalizerAvailable = downloadService != null && downloadService.getEqualizerController() != null; + + if (!equalizerAvailable) { + equalizerButton.setVisibility(View.GONE); + } + if (!visualizerAvailable) { + visualizerButton.setVisibility(View.GONE); + } else { + visualizerView = new VisualizerView(this); + visualizerViewLayout.addView(visualizerView, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT)); + + visualizerView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + visualizerView.setActive(!visualizerView.isActive()); + getDownloadService().setShowVisualization(visualizerView.isActive()); + updateButtons(); + return true; + } + }); + } + + // TODO: Extract to utility method and cache. + Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/Storopia.ttf"); + equalizerButton.setTypeface(typeface); + visualizerButton.setTypeface(typeface); + jukeboxButton.setTypeface(typeface); + } + + @Override + protected void onResume() { + super.onResume(); + + final Handler handler = new Handler(); + Runnable runnable = new Runnable() { + @Override + public void run() { + handler.post(new Runnable() { + @Override + public void run() { + update(); + } + }); + } + }; + + executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.scheduleWithFixedDelay(runnable, 0L, 1000L, TimeUnit.MILLISECONDS); + + DownloadService downloadService = getDownloadService(); + if (downloadService == null || downloadService.getCurrentPlaying() == null) { + playlistFlipper.setDisplayedChild(1); + buttonBarFlipper.setDisplayedChild(1); + } + + onDownloadListChanged(); + onCurrentChanged(); + onProgressChanged(); + scrollToCurrent(); + if (downloadService != null && downloadService.getKeepScreenOn()) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + if (visualizerView != null) { + visualizerView.setActive(downloadService != null && downloadService.getShowVisualization()); + } + + updateButtons(); + } + + private void updateButtons() { + boolean eqEnabled = getDownloadService() != null && getDownloadService().getEqualizerController() != null && + getDownloadService().getEqualizerController().isEnabled(); + equalizerButton.setTextColor(eqEnabled ? COLOR_BUTTON_ENABLED : COLOR_BUTTON_DISABLED); + + if (visualizerView != null) { + visualizerButton.setTextColor(visualizerView.isActive() ? COLOR_BUTTON_ENABLED : COLOR_BUTTON_DISABLED); + } + + boolean jukeboxEnabled = getDownloadService() != null && getDownloadService().isJukeboxEnabled(); + jukeboxButton.setTextColor(jukeboxEnabled ? COLOR_BUTTON_ENABLED : COLOR_BUTTON_DISABLED); + } + + // Scroll to current playing/downloading. + private void scrollToCurrent() { + if (getDownloadService() == null) { + return; + } + + for (int i = 0; i < playlistView.getAdapter().getCount(); i++) { + if (currentPlaying == playlistView.getItemAtPosition(i)) { + playlistView.setSelectionFromTop(i, 40); + return; + } + } + DownloadFile currentDownloading = getDownloadService().getCurrentDownloading(); + for (int i = 0; i < playlistView.getAdapter().getCount(); i++) { + if (currentDownloading == playlistView.getItemAtPosition(i)) { + playlistView.setSelectionFromTop(i, 40); + return; + } + } + } + + @Override + protected void onPause() { + super.onPause(); + executorService.shutdown(); + if (visualizerView != null) { + visualizerView.setActive(false); + } + } + + @Override + protected Dialog onCreateDialog(int id) { + if (id == DIALOG_SAVE_PLAYLIST) { + AlertDialog.Builder builder; + + LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); + final View layout = inflater.inflate(R.layout.save_playlist, (ViewGroup) findViewById(R.id.save_playlist_root)); + playlistNameView = (EditText) layout.findViewById(R.id.save_playlist_name); + + builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.download_playlist_title); + builder.setMessage(R.string.download_playlist_name); + builder.setPositiveButton(R.string.common_save, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + savePlaylistInBackground(String.valueOf(playlistNameView.getText())); + } + }); + builder.setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + builder.setView(layout); + builder.setCancelable(true); + + return builder.create(); + } else { + return super.onCreateDialog(id); + } + } + + @Override + protected void onPrepareDialog(int id, Dialog dialog) { + if (id == DIALOG_SAVE_PLAYLIST) { + String playlistName = getDownloadService().getSuggestedPlaylistName(); + if (playlistName != null) { + playlistNameView.setText(playlistName); + } else { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + playlistNameView.setText(dateFormat.format(new Date())); + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.nowplaying, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem savePlaylist = menu.findItem(R.id.menu_save_playlist); + boolean savePlaylistEnabled = !Util.isOffline(this); + savePlaylist.setEnabled(savePlaylistEnabled); + savePlaylist.setVisible(savePlaylistEnabled); + MenuItem screenOption = menu.findItem(R.id.menu_screen_on_off); + if (getDownloadService().getKeepScreenOn()) { + screenOption.setTitle(R.string.download_menu_screen_off); + } else { + screenOption.setTitle(R.string.download_menu_screen_on); + } + return super.onPrepareOptionsMenu(menu); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + if (view == playlistView) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + DownloadFile downloadFile = (DownloadFile) playlistView.getItemAtPosition(info.position); + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.nowplaying_context, menu); + + if (downloadFile.getSong().getParent() == null) { + menu.findItem(R.id.menu_show_album).setVisible(false); + } + if (Util.isOffline(this)) { + menu.findItem(R.id.menu_lyrics).setVisible(false); + menu.findItem(R.id.menu_save_playlist).setVisible(false); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + DownloadFile downloadFile = (DownloadFile) playlistView.getItemAtPosition(info.position); + return menuItemSelected(menuItem.getItemId(), downloadFile) || super.onContextItemSelected(menuItem); + } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + return menuItemSelected(menuItem.getItemId(), null) || super.onOptionsItemSelected(menuItem); + } + + private boolean menuItemSelected(int menuItemId, DownloadFile song) { + switch (menuItemId) { + case R.id.menu_show_album: + Intent intent = new Intent(this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, song.getSong().getParent()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, song.getSong().getAlbum()); + Util.startActivityWithoutTransition(this, intent); + return true; + case R.id.menu_lyrics: + intent = new Intent(this, LyricsActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ARTIST, song.getSong().getArtist()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_TITLE, song.getSong().getTitle()); + Util.startActivityWithoutTransition(this, intent); + return true; + case R.id.menu_remove: + getDownloadService().remove(song); + onDownloadListChanged(); + return true; + case R.id.menu_remove_all: + getDownloadService().setShufflePlayEnabled(false); + getDownloadService().clear(); + onDownloadListChanged(); + return true; + case R.id.menu_screen_on_off: + if (getDownloadService().getKeepScreenOn()) { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + getDownloadService().setKeepScreenOn(false); + } else { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + getDownloadService().setKeepScreenOn(true); + } + return true; + case R.id.menu_shuffle: + getDownloadService().shuffle(); + Util.toast(this, R.string.download_menu_shuffle_notification); + return true; + case R.id.menu_save_playlist: + showDialog(DIALOG_SAVE_PLAYLIST); + return true; + default: + return false; + } + } + + private void update() { + if (getDownloadService() == null) { + return; + } + + if (currentRevision != getDownloadService().getDownloadListUpdateRevision()) { + onDownloadListChanged(); + } + + if (currentPlaying != getDownloadService().getCurrentPlaying()) { + onCurrentChanged(); + } + + onProgressChanged(); + } + + private void savePlaylistInBackground(final String playlistName) { + Util.toast(DownloadActivity.this, getResources().getString(R.string.download_playlist_saving, playlistName)); + getDownloadService().setSuggestedPlaylistName(playlistName); + new SilentBackgroundTask<Void>(this) { + @Override + protected Void doInBackground() throws Throwable { + List<MusicDirectory.Entry> entries = new LinkedList<MusicDirectory.Entry>(); + for (DownloadFile downloadFile : getDownloadService().getDownloads()) { + entries.add(downloadFile.getSong()); + } + MusicService musicService = MusicServiceFactory.getMusicService(DownloadActivity.this); + musicService.createPlaylist(null, playlistName, entries, DownloadActivity.this, null); + return null; + } + + @Override + protected void done(Void result) { + Util.toast(DownloadActivity.this, R.string.download_playlist_done); + } + + @Override + protected void error(Throwable error) { + String msg = getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error); + Util.toast(DownloadActivity.this, msg); + } + }.execute(); + } + + private void toggleFullscreenAlbumArt() { + scrollToCurrent(); + if (playlistFlipper.getDisplayedChild() == 1) { + playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_in)); + playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_out)); + playlistFlipper.setDisplayedChild(0); + buttonBarFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_in)); + buttonBarFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_out)); + buttonBarFlipper.setDisplayedChild(0); + + + } else { + playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_in)); + playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_out)); + playlistFlipper.setDisplayedChild(1); + buttonBarFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_in)); + buttonBarFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_out)); + buttonBarFlipper.setDisplayedChild(1); + } + } + + private void start() { + DownloadService service = getDownloadService(); + PlayerState state = service.getPlayerState(); + if (state == PAUSED || state == COMPLETED) { + service.start(); + } else if (state == STOPPED || state == IDLE) { + warnIfNetworkOrStorageUnavailable(); + int current = service.getCurrentPlayingIndex(); + // TODO: Use play() method. + if (current == -1) { + service.play(0); + } else { + service.play(current); + } + } + } + + private void onDownloadListChanged() { + DownloadService downloadService = getDownloadService(); + if (downloadService == null) { + return; + } + + List<DownloadFile> list = downloadService.getDownloads(); + + playlistView.setAdapter(new SongListAdapter(list)); + emptyTextView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE); + currentRevision = downloadService.getDownloadListUpdateRevision(); + + switch (downloadService.getRepeatMode()) { + case OFF: + repeatButton.setImageResource(R.drawable.media_repeat_off); + break; + case ALL: + repeatButton.setImageResource(R.drawable.media_repeat_all); + break; + case SINGLE: + repeatButton.setImageResource(R.drawable.media_repeat_single); + break; + default: + break; + } + } + + private void onCurrentChanged() { + if (getDownloadService() == null) { + return; + } + + currentPlaying = getDownloadService().getCurrentPlaying(); + if (currentPlaying != null) { + MusicDirectory.Entry song = currentPlaying.getSong(); + songTitleTextView.setText(song.getTitle()); + albumTextView.setText(song.getAlbum()); + artistTextView.setText(song.getArtist()); + getImageLoader().loadImage(albumArtImageView, song, true, true); + } else { + songTitleTextView.setText(null); + albumTextView.setText(null); + artistTextView.setText(null); + getImageLoader().loadImage(albumArtImageView, null, true, false); + } + } + + private void onProgressChanged() { + if (getDownloadService() == null) { + return; + } + + if (currentPlaying != null) { + + int millisPlayed = Math.max(0, getDownloadService().getPlayerPosition()); + Integer duration = getDownloadService().getPlayerDuration(); + int millisTotal = duration == null ? 0 : duration; + + positionTextView.setText(Util.formatDuration(millisPlayed / 1000)); + durationTextView.setText(Util.formatDuration(millisTotal / 1000)); + progressBar.setMax(millisTotal == 0 ? 100 : millisTotal); // Work-around for apparent bug. + progressBar.setProgress(millisPlayed); + progressBar.setSlidingEnabled(currentPlaying.isCompleteFileAvailable() || getDownloadService().isJukeboxEnabled()); + } else { + positionTextView.setText("0:00"); + durationTextView.setText("-:--"); + progressBar.setProgress(0); + progressBar.setSlidingEnabled(false); + } + + PlayerState playerState = getDownloadService().getPlayerState(); + + switch (playerState) { + case DOWNLOADING: + long bytes = currentPlaying.getPartialFile().length(); + statusTextView.setText(getResources().getString(R.string.download_playerstate_downloading, Util.formatLocalizedBytes(bytes, this))); + break; + case PREPARING: + statusTextView.setText(R.string.download_playerstate_buffering); + break; + case STARTED: + if (getDownloadService().isShufflePlayEnabled()) { + statusTextView.setText(R.string.download_playerstate_playing_shuffle); + } else { + statusTextView.setText(null); + } + break; + default: + statusTextView.setText(null); + break; + } + + switch (playerState) { + case STARTED: + pauseButton.setVisibility(View.VISIBLE); + stopButton.setVisibility(View.GONE); + startButton.setVisibility(View.GONE); + break; + case DOWNLOADING: + case PREPARING: + pauseButton.setVisibility(View.GONE); + stopButton.setVisibility(View.VISIBLE); + startButton.setVisibility(View.GONE); + break; + default: + pauseButton.setVisibility(View.GONE); + stopButton.setVisibility(View.GONE); + startButton.setVisibility(View.VISIBLE); + break; + } + + jukeboxButton.setTextColor(getDownloadService().isJukeboxEnabled() ? COLOR_BUTTON_ENABLED : COLOR_BUTTON_DISABLED); + } + + private class SongListAdapter extends ArrayAdapter<DownloadFile> { + public SongListAdapter(List<DownloadFile> entries) { + super(DownloadActivity.this, android.R.layout.simple_list_item_1, entries); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + SongView view; + if (convertView != null && convertView instanceof SongView) { + view = (SongView) convertView; + } else { + view = new SongView(DownloadActivity.this); + } + DownloadFile downloadFile = getItem(position); + view.setSong(downloadFile.getSong(), false); + return view; + } + } + + @Override + public boolean onTouchEvent(MotionEvent me) { + return gestureScanner.onTouchEvent(me); + } + + @Override + public boolean onDown(MotionEvent me) { + return false; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + + DownloadService downloadService = getDownloadService(); + if (downloadService == null) { + return false; + } + + // Right to Left swipe + if (e1.getX() - e2.getX() > swipeDistance && Math.abs(velocityX) > swipeVelocity) { + warnIfNetworkOrStorageUnavailable(); + if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) { + downloadService.next(); + onCurrentChanged(); + onProgressChanged(); + } + return true; + } + + // Left to Right swipe + if (e2.getX() - e1.getX() > swipeDistance && Math.abs(velocityX) > swipeVelocity) { + warnIfNetworkOrStorageUnavailable(); + downloadService.previous(); + onCurrentChanged(); + onProgressChanged(); + return true; + } + + // Top to Bottom swipe + if (e2.getY() - e1.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) { + warnIfNetworkOrStorageUnavailable(); + downloadService.seekTo(downloadService.getPlayerPosition() + 30000); + onProgressChanged(); + return true; + } + + // Bottom to Top swipe + if (e1.getY() - e2.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) { + warnIfNetworkOrStorageUnavailable(); + downloadService.seekTo(downloadService.getPlayerPosition() - 8000); + onProgressChanged(); + return true; + } + + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + return false; + } + + @Override + public void onShowPress(MotionEvent e) { + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + return false; + } +} diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/EqualizerActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/EqualizerActivity.java new file mode 100644 index 00000000..daf6193e --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/EqualizerActivity.java @@ -0,0 +1,181 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2011 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.activity; + +import java.util.HashMap; +import java.util.Map; + +import android.app.Activity; +import android.media.audiofx.Equalizer; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.TextView; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.audiofx.EqualizerController; +import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl; + +/** + * Equalizer controls. + * + * @author Sindre Mehus + * @version $Id$ + */ +public class EqualizerActivity extends Activity { + + private static final int MENU_GROUP_PRESET = 100; + + private final Map<Short, SeekBar> bars = new HashMap<Short, SeekBar>(); + private EqualizerController equalizerController; + private Equalizer equalizer; + + @Override + public void onCreate(Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.equalizer); + equalizerController = DownloadServiceImpl.getInstance().getEqualizerController(); + equalizer = equalizerController.getEqualizer(); + + initEqualizer(); + + final View presetButton = findViewById(R.id.equalizer_preset); + registerForContextMenu(presetButton); + presetButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + presetButton.showContextMenu(); + } + }); + + CheckBox enabledCheckBox = (CheckBox) findViewById(R.id.equalizer_enabled); + enabledCheckBox.setChecked(equalizer.getEnabled()); + enabledCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + setEqualizerEnabled(b); + } + }); + } + + @Override + protected void onPause() { + super.onPause(); + equalizerController.saveSettings(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + + short currentPreset; + try { + currentPreset = equalizer.getCurrentPreset(); + } catch (Exception x) { + currentPreset = -1; + } + + for (short preset = 0; preset < equalizer.getNumberOfPresets(); preset++) { + MenuItem menuItem = menu.add(MENU_GROUP_PRESET, preset, preset, equalizer.getPresetName(preset)); + if (preset == currentPreset) { + menuItem.setChecked(true); + } + } + menu.setGroupCheckable(MENU_GROUP_PRESET, true, true); + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + short preset = (short) menuItem.getItemId(); + equalizer.usePreset(preset); + updateBars(); + return true; + } + + private void setEqualizerEnabled(boolean enabled) { + equalizer.setEnabled(enabled); + updateBars(); + } + + private void updateBars() { + + for (Map.Entry<Short, SeekBar> entry : bars.entrySet()) { + short band = entry.getKey(); + SeekBar bar = entry.getValue(); + bar.setEnabled(equalizer.getEnabled()); + short minEQLevel = equalizer.getBandLevelRange()[0]; + bar.setProgress(equalizer.getBandLevel(band) - minEQLevel); + } + } + + private void initEqualizer() { + LinearLayout layout = (LinearLayout) findViewById(R.id.equalizer_layout); + + final short minEQLevel = equalizer.getBandLevelRange()[0]; + final short maxEQLevel = equalizer.getBandLevelRange()[1]; + + for (short i = 0; i < equalizer.getNumberOfBands(); i++) { + final short band = i; + + View bandBar = LayoutInflater.from(this).inflate(R.layout.equalizer_bar, null); + TextView freqTextView = (TextView) bandBar.findViewById(R.id.equalizer_frequency); + final TextView levelTextView = (TextView) bandBar.findViewById(R.id.equalizer_level); + SeekBar bar = (SeekBar) bandBar.findViewById(R.id.equalizer_bar); + + freqTextView.setText((equalizer.getCenterFreq(band) / 1000) + " Hz"); + + bars.put(band, bar); + bar.setMax(maxEQLevel - minEQLevel); + short level = equalizer.getBandLevel(band); + bar.setProgress(level - minEQLevel); + bar.setEnabled(equalizer.getEnabled()); + updateLevelText(levelTextView, level); + + bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + short level = (short) (progress + minEQLevel); + if (fromUser) { + equalizer.setBandLevel(band, level); + } + updateLevelText(levelTextView, level); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + layout.addView(bandBar); + } + } + + private void updateLevelText(TextView levelTextView, short level) { + levelTextView.setText((level > 0 ? "+" : "") + level / 100 + " dB"); + } + +} diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/HelpActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/HelpActivity.java new file mode 100644 index 00000000..4b2eb63b --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/HelpActivity.java @@ -0,0 +1,117 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import android.app.Activity; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.Window; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.Button; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.util.Util; + +/** + * An HTML-based help screen with Back and Done buttons at the bottom. + * + * @author Sindre Mehus + */ +public final class HelpActivity extends Activity { + + private WebView webView; + private Button backButton; + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + + setContentView(R.layout.help); + + webView = (WebView) findViewById(R.id.help_contents); + webView.getSettings().setJavaScriptEnabled(true); + webView.setWebViewClient(new HelpClient()); + if (bundle != null) { + webView.restoreState(bundle); + } else { + webView.loadUrl(getResources().getString(R.string.help_url)); + } + + backButton = (Button) findViewById(R.id.help_back); + backButton.setOnClickListener(new Button.OnClickListener() { + @Override + public void onClick(View view) { + webView.goBack(); + } + }); + + Button doneButton = (Button) findViewById(R.id.help_close); + doneButton.setOnClickListener(new Button.OnClickListener() { + @Override + public void onClick(View view) { + finish(); + } + }); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + protected void onSaveInstanceState(Bundle state) { + webView.saveState(state); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (webView.canGoBack()) { + webView.goBack(); + return true; + } + } + return super.onKeyDown(keyCode, event); + } + + private final class HelpClient extends WebViewClient { + @Override + public void onLoadResource(WebView webView, String url) { + setProgressBarIndeterminateVisibility(true); + setTitle(getResources().getString(R.string.help_loading)); + super.onLoadResource(webView, url); + } + + @Override + public void onPageFinished(WebView view, String url) { + setProgressBarIndeterminateVisibility(false); + setTitle(view.getTitle()); + backButton.setEnabled(view.canGoBack()); + } + + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + Util.toast(HelpActivity.this, description); + } + } +} diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/LyricsActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/LyricsActivity.java new file mode 100644 index 00000000..0ec75e2c --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/LyricsActivity.java @@ -0,0 +1,72 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import android.os.Bundle; +import android.widget.TextView; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.Lyrics; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.BackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask; + +/** + * Displays song lyrics. + * + * @author Sindre Mehus + */ +public final class LyricsActivity extends SubsonicTabActivity { + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.lyrics); + load(); + } + + private void load() { + BackgroundTask<Lyrics> task = new TabActivityBackgroundTask<Lyrics>(this) { + @Override + protected Lyrics doInBackground() throws Throwable { + String artist = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ARTIST); + String title = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_TITLE); + MusicService musicService = MusicServiceFactory.getMusicService(LyricsActivity.this); + return musicService.getLyrics(artist, title, LyricsActivity.this, this); + } + + @Override + protected void done(Lyrics result) { + TextView artistView = (TextView) findViewById(R.id.lyrics_artist); + TextView titleView = (TextView) findViewById(R.id.lyrics_title); + TextView textView = (TextView) findViewById(R.id.lyrics_text); + if (result != null && result.getArtist() != null) { + artistView.setText(result.getArtist()); + titleView.setText(result.getTitle()); + textView.setText(result.getText()); + } else { + artistView.setText(R.string.lyrics_nomatch); + } + } + }; + task.execute(); + } +}
\ No newline at end of file diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/MainActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/MainActivity.java new file mode 100644 index 00000000..c63a391b --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/MainActivity.java @@ -0,0 +1,258 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import java.util.Arrays; + +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.service.DownloadService; +import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.MergeAdapter; +import net.sourceforge.subsonic.androidapp.util.Util; +import net.sourceforge.subsonic.androidapp.util.FileUtil; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ImageButton; +import android.widget.ListView; +import android.widget.TextView; + +public class MainActivity extends SubsonicTabActivity { + + private static final int MENU_GROUP_SERVER = 10; + private static final int MENU_ITEM_SERVER_1 = 101; + private static final int MENU_ITEM_SERVER_2 = 102; + private static final int MENU_ITEM_SERVER_3 = 103; + private static final int MENU_ITEM_OFFLINE = 104; + + private String theme; + + private static boolean infoDialogDisplayed; + + /** + * Called when the activity is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_EXIT)) { + exit(); + } + setContentView(R.layout.main); + + loadSettings(); + + View buttons = LayoutInflater.from(this).inflate(R.layout.main_buttons, null); + + final View serverButton = buttons.findViewById(R.id.main_select_server); + final TextView serverTextView = (TextView) serverButton.findViewById(R.id.main_select_server_2); + + final View albumsTitle = buttons.findViewById(R.id.main_albums); + final View albumsNewestButton = buttons.findViewById(R.id.main_albums_newest); + final View albumsRandomButton = buttons.findViewById(R.id.main_albums_random); + final View albumsHighestButton = buttons.findViewById(R.id.main_albums_highest); + final View albumsRecentButton = buttons.findViewById(R.id.main_albums_recent); + final View albumsFrequentButton = buttons.findViewById(R.id.main_albums_frequent); + + final View dummyView = findViewById(R.id.main_dummy); + + int instance = Util.getActiveServer(this); + String name = Util.getServerName(this, instance); + serverTextView.setText(name); + + ListView list = (ListView) findViewById(R.id.main_list); + + MergeAdapter adapter = new MergeAdapter(); + adapter.addViews(Arrays.asList(serverButton), true); + if (!Util.isOffline(this)) { + adapter.addView(albumsTitle, false); + adapter.addViews(Arrays.asList(albumsNewestButton, albumsRandomButton, albumsHighestButton, albumsRecentButton, albumsFrequentButton), true); + } + list.setAdapter(adapter); + registerForContextMenu(dummyView); + + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (view == serverButton) { + dummyView.showContextMenu(); + } else if (view == albumsNewestButton) { + showAlbumList("newest"); + } else if (view == albumsRandomButton) { + showAlbumList("random"); + } else if (view == albumsHighestButton) { + showAlbumList("highest"); + } else if (view == albumsRecentButton) { + showAlbumList("recent"); + } else if (view == albumsFrequentButton) { + showAlbumList("frequent"); + } + } + }); + + // Title: Subsonic + setTitle(R.string.common_appname); + + // Button 1: shuffle + ImageButton actionShuffleButton = (ImageButton)findViewById(R.id.action_button_1); + actionShuffleButton.setImageResource(R.drawable.action_shuffle); + actionShuffleButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(MainActivity.this, DownloadActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true); + Util.startActivityWithoutTransition(MainActivity.this, intent); + } + }); + + // Button 2: search + ImageButton actionSearchButton = (ImageButton)findViewById(R.id.action_button_2); + actionSearchButton.setImageResource(R.drawable.action_search); + actionSearchButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(MainActivity.this, SearchActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true); + Util.startActivityWithoutTransition(MainActivity.this, intent); + } + }); + + // Remember the current theme. + theme = Util.getTheme(this); + + showInfoDialog(); + } + + private void loadSettings() { + PreferenceManager.setDefaultValues(this, R.xml.settings, false); + SharedPreferences prefs = Util.getPreferences(this); + if (!prefs.contains(Constants.PREFERENCES_KEY_CACHE_LOCATION)) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory().getPath()); + editor.commit(); + } + } + + @Override + protected void onResume() { + super.onResume(); + + // Restart activity if theme has changed. + if (theme != null && !theme.equals(Util.getTheme(this))) { + restart(); + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + + MenuItem menuItem1 = menu.add(MENU_GROUP_SERVER, MENU_ITEM_SERVER_1, MENU_ITEM_SERVER_1, Util.getServerName(this, 1)); + MenuItem menuItem2 = menu.add(MENU_GROUP_SERVER, MENU_ITEM_SERVER_2, MENU_ITEM_SERVER_2, Util.getServerName(this, 2)); + MenuItem menuItem3 = menu.add(MENU_GROUP_SERVER, MENU_ITEM_SERVER_3, MENU_ITEM_SERVER_3, Util.getServerName(this, 3)); + MenuItem menuItem4 = menu.add(MENU_GROUP_SERVER, MENU_ITEM_OFFLINE, MENU_ITEM_OFFLINE, Util.getServerName(this, 0)); + menu.setGroupCheckable(MENU_GROUP_SERVER, true, true); + menu.setHeaderTitle(R.string.main_select_server); + + switch (Util.getActiveServer(this)) { + case 0: + menuItem4.setChecked(true); + break; + case 1: + menuItem1.setChecked(true); + break; + case 2: + menuItem2.setChecked(true); + break; + case 3: + menuItem3.setChecked(true); + break; + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + switch (menuItem.getItemId()) { + case MENU_ITEM_OFFLINE: + setActiveServer(0); + break; + case MENU_ITEM_SERVER_1: + setActiveServer(1); + break; + case MENU_ITEM_SERVER_2: + setActiveServer(2); + break; + case MENU_ITEM_SERVER_3: + setActiveServer(3); + break; + default: + return super.onContextItemSelected(menuItem); + } + + // Restart activity + restart(); + return true; + } + + private void setActiveServer(int instance) { + if (Util.getActiveServer(this) != instance) { + DownloadService service = getDownloadService(); + if (service != null) { + service.clearIncomplete(); + } + Util.setActiveServer(this, instance); + } + } + + private void restart() { + Intent intent = new Intent(this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Util.startActivityWithoutTransition(this, intent); + } + + private void exit() { + stopService(new Intent(this, DownloadServiceImpl.class)); + finish(); + } + + private void showInfoDialog() { + if (!infoDialogDisplayed) { + infoDialogDisplayed = true; + if (Util.getRestUrl(this, null).contains("demo.subsonic.org")) { + Util.info(this, R.string.main_welcome_title, R.string.main_welcome_text); + } + } + } + + private void showAlbumList(String type) { + Intent intent = new Intent(this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 20); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); + Util.startActivityWithoutTransition(this, intent); + } +}
\ No newline at end of file diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/PlayVideoActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/PlayVideoActivity.java new file mode 100644 index 00000000..ea332ca0 --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/PlayVideoActivity.java @@ -0,0 +1,147 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import java.lang.reflect.Method; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.media.AudioManager; +import android.os.Bundle; +import android.util.Log; +import android.view.Window; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.Util; + +/** + * Plays videos in a web page. + * + * @author Sindre Mehus + */ +public final class PlayVideoActivity extends Activity { + + private static final String TAG = PlayVideoActivity.class.getSimpleName(); + private WebView webView; + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + setVolumeControlStream(AudioManager.STREAM_MUSIC); + + setContentView(R.layout.play_video); + + webView = (WebView) findViewById(R.id.play_video_contents); + webView.getSettings().setJavaScriptEnabled(true); + webView.getSettings().setPluginsEnabled(true); + webView.getSettings().setAllowFileAccess(true); + webView.getSettings().setSupportZoom(true); + webView.getSettings().setBuiltInZoomControls(true); + + webView.setWebViewClient(new Client()); + if (bundle != null) { + webView.restoreState(bundle); + } else { + webView.loadUrl(getVideoUrl()); + } + + // Show warning if Flash plugin is not installed. + if (isFlashPluginInstalled()) { + Util.toast(this, R.string.play_video_loading, false); + } else { + Util.toast(this, R.string.play_video_noplugin, false); + } + } + + @Override + protected void onPause() { + super.onPause(); + callHiddenWebViewMethod("onPause"); + } + + @Override + protected void onResume() { + super.onResume(); + callHiddenWebViewMethod("onResume"); + } + + private String getVideoUrl() { + String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID); + return MusicServiceFactory.getMusicService(this).getVideoUrl(this, id); + } + + @Override + protected void onSaveInstanceState(Bundle state) { + webView.saveState(state); + } + + private void callHiddenWebViewMethod(String name){ + if( webView != null ){ + try { + Method method = WebView.class.getMethod(name); + method.invoke(webView); + } catch (Throwable x) { + Log.e(TAG, "Failed to invoke " + name, x); + } + } + } + + private boolean isFlashPluginInstalled() { + try { + PackageInfo packageInfo = getPackageManager().getPackageInfo("com.adobe.flashplayer", 0); + return packageInfo != null; + } catch (PackageManager.NameNotFoundException x) { + return false; + } + } + + private final class Client extends WebViewClient { + + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + Util.toast(PlayVideoActivity.this, description); + Log.e(TAG, "Error: " + description); + } + + @Override + public void onLoadResource(WebView view, String url) { + super.onLoadResource(view, url); + Log.d(TAG, "onLoadResource: " + url); + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + Log.d(TAG, "onPageStarted: " + url); + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + Log.d(TAG, "onPageFinished: " + url); + } + } +} diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/QueryReceiverActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/QueryReceiverActivity.java new file mode 100644 index 00000000..35b5ccaf --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/QueryReceiverActivity.java @@ -0,0 +1,56 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import android.app.Activity; +import android.app.SearchManager; +import android.content.Intent; +import android.os.Bundle; +import android.provider.SearchRecentSuggestions; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.Util; +import net.sourceforge.subsonic.androidapp.provider.SearchSuggestionProvider; + +/** + * Receives search queries and forwards to the SelectAlbumActivity. + * + * @author Sindre Mehus + */ +public class QueryReceiverActivity extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String query = getIntent().getStringExtra(SearchManager.QUERY); + + if (query != null) { + SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, SearchSuggestionProvider.AUTHORITY, + SearchSuggestionProvider.MODE); + suggestions.saveRecentQuery(query, null); + + Intent intent = new Intent(QueryReceiverActivity.this, SearchActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_QUERY, query); + Util.startActivityWithoutTransition(QueryReceiverActivity.this, intent); + } + finish(); + Util.disablePendingTransition(this); + } +}
\ No newline at end of file diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SearchActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SearchActivity.java new file mode 100644 index 00000000..73b787a0 --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SearchActivity.java @@ -0,0 +1,368 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Arrays; + +import android.content.Intent; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.View; +import android.view.MenuItem; +import android.widget.AdapterView; +import android.widget.ImageButton; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.net.Uri; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.Artist; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.domain.SearchCritera; +import net.sourceforge.subsonic.androidapp.domain.SearchResult; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.service.DownloadService; +import net.sourceforge.subsonic.androidapp.util.ArtistAdapter; +import net.sourceforge.subsonic.androidapp.util.BackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.EntryAdapter; +import net.sourceforge.subsonic.androidapp.util.MergeAdapter; +import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; + +/** + * Performs searches and displays the matching artists, albums and songs. + * + * @author Sindre Mehus + */ +public class SearchActivity extends SubsonicTabActivity { + + private static final int DEFAULT_ARTISTS = 3; + private static final int DEFAULT_ALBUMS = 5; + private static final int DEFAULT_SONGS = 10; + + private static final int MAX_ARTISTS = 10; + private static final int MAX_ALBUMS = 20; + private static final int MAX_SONGS = 25; + private ListView list; + + private View artistsHeading; + private View albumsHeading; + private View songsHeading; + private TextView searchButton; + private View moreArtistsButton; + private View moreAlbumsButton; + private View moreSongsButton; + private SearchResult searchResult; + private MergeAdapter mergeAdapter; + private ArtistAdapter artistAdapter; + private ListAdapter moreArtistsAdapter; + private EntryAdapter albumAdapter; + private ListAdapter moreAlbumsAdapter; + private ListAdapter moreSongsAdapter; + private EntryAdapter songAdapter; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.search); + + setTitle(R.string.search_title); + + View buttons = LayoutInflater.from(this).inflate(R.layout.search_buttons, null); + + artistsHeading = buttons.findViewById(R.id.search_artists); + albumsHeading = buttons.findViewById(R.id.search_albums); + songsHeading = buttons.findViewById(R.id.search_songs); + + searchButton = (TextView) buttons.findViewById(R.id.search_search); + moreArtistsButton = buttons.findViewById(R.id.search_more_artists); + moreAlbumsButton = buttons.findViewById(R.id.search_more_albums); + moreSongsButton = buttons.findViewById(R.id.search_more_songs); + + list = (ListView) findViewById(R.id.search_list); + + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (view == searchButton) { + onSearchRequested(); + } else if (view == moreArtistsButton) { + expandArtists(); + } else if (view == moreAlbumsButton) { + expandAlbums(); + } else if (view == moreSongsButton) { + expandSongs(); + } else { + Object item = parent.getItemAtPosition(position); + if (item instanceof Artist) { + onArtistSelected((Artist) item); + } else if (item instanceof MusicDirectory.Entry) { + MusicDirectory.Entry entry = (MusicDirectory.Entry) item; + if (entry.isDirectory()) { + onAlbumSelected(entry, false); + } else if (entry.isVideo()) { + onVideoSelected(entry); + } else { + onSongSelected(entry, false, true, true, false); + } + + } + } + } + }); + registerForContextMenu(list); + + // Button 1: gone + findViewById(R.id.action_button_1).setVisibility(View.GONE); + + // Button 2: search + final ImageButton actionSearchButton = (ImageButton)findViewById(R.id.action_button_2); + actionSearchButton.setImageResource(R.drawable.action_search); + actionSearchButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onSearchRequested(); + } + }); + + onNewIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + String query = intent.getStringExtra(Constants.INTENT_EXTRA_NAME_QUERY); + boolean autoplay = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); + boolean requestsearch = intent.getBooleanExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, false); + + if (query != null) { + mergeAdapter = new MergeAdapter(); + list.setAdapter(mergeAdapter); + search(query, autoplay); + } else { + populateList(); + if (requestsearch) + onSearchRequested(); + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + Object selectedItem = list.getItemAtPosition(info.position); + + boolean isArtist = selectedItem instanceof Artist; + boolean isAlbum = selectedItem instanceof MusicDirectory.Entry && ((MusicDirectory.Entry) selectedItem).isDirectory(); + boolean isSong = selectedItem instanceof MusicDirectory.Entry && (!((MusicDirectory.Entry) selectedItem).isDirectory()) + && (!((MusicDirectory.Entry) selectedItem).isVideo()); + + if (isArtist || isAlbum) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_album_context, menu); + } else if (isSong) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_song_context, menu); + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + Object selectedItem = list.getItemAtPosition(info.position); + + Artist artist = selectedItem instanceof Artist ? (Artist) selectedItem : null; + MusicDirectory.Entry entry = selectedItem instanceof MusicDirectory.Entry ? (MusicDirectory.Entry) selectedItem : null; + String id = artist != null ? artist.getId() : entry.getId(); + + switch (menuItem.getItemId()) { + case R.id.album_menu_play_now: + downloadRecursively(id, false, false, true); + break; + case R.id.album_menu_play_last: + downloadRecursively(id, false, true, false); + break; + case R.id.album_menu_pin: + downloadRecursively(id, true, true, false); + break; + case R.id.song_menu_play_now: + onSongSelected(entry, false, false, true, false); + break; + case R.id.song_menu_play_next: + onSongSelected(entry, false, true, false, true); + break; + case R.id.song_menu_play_last: + onSongSelected(entry, false, true, false, false); + break; + default: + return super.onContextItemSelected(menuItem); + } + + return true; + } + + private void search(final String query, final boolean autoplay) { + BackgroundTask<SearchResult> task = new TabActivityBackgroundTask<SearchResult>(this) { + @Override + protected SearchResult doInBackground() throws Throwable { + SearchCritera criteria = new SearchCritera(query, MAX_ARTISTS, MAX_ALBUMS, MAX_SONGS); + MusicService service = MusicServiceFactory.getMusicService(SearchActivity.this); + return service.search(criteria, SearchActivity.this, this); + } + + @Override + protected void done(SearchResult result) { + searchResult = result; + populateList(); + if (autoplay) { + autoplay(); + } + + } + }; + task.execute(); + } + + private void populateList() { + mergeAdapter = new MergeAdapter(); + mergeAdapter.addView(searchButton, true); + + if (searchResult != null) { + List<Artist> artists = searchResult.getArtists(); + if (!artists.isEmpty()) { + mergeAdapter.addView(artistsHeading); + List<Artist> displayedArtists = new ArrayList<Artist>(artists.subList(0, Math.min(DEFAULT_ARTISTS, artists.size()))); + artistAdapter = new ArtistAdapter(this, displayedArtists); + mergeAdapter.addAdapter(artistAdapter); + if (artists.size() > DEFAULT_ARTISTS) { + moreArtistsAdapter = mergeAdapter.addView(moreArtistsButton, true); + } + } + + List<MusicDirectory.Entry> albums = searchResult.getAlbums(); + if (!albums.isEmpty()) { + mergeAdapter.addView(albumsHeading); + List<MusicDirectory.Entry> displayedAlbums = new ArrayList<MusicDirectory.Entry>(albums.subList(0, Math.min(DEFAULT_ALBUMS, albums.size()))); + albumAdapter = new EntryAdapter(this, getImageLoader(), displayedAlbums, false); + mergeAdapter.addAdapter(albumAdapter); + if (albums.size() > DEFAULT_ALBUMS) { + moreAlbumsAdapter = mergeAdapter.addView(moreAlbumsButton, true); + } + } + + List<MusicDirectory.Entry> songs = searchResult.getSongs(); + if (!songs.isEmpty()) { + mergeAdapter.addView(songsHeading); + List<MusicDirectory.Entry> displayedSongs = new ArrayList<MusicDirectory.Entry>(songs.subList(0, Math.min(DEFAULT_SONGS, songs.size()))); + songAdapter = new EntryAdapter(this, getImageLoader(), displayedSongs, false); + mergeAdapter.addAdapter(songAdapter); + if (songs.size() > DEFAULT_SONGS) { + moreSongsAdapter = mergeAdapter.addView(moreSongsButton, true); + } + } + + boolean empty = searchResult.getArtists().isEmpty() && searchResult.getAlbums().isEmpty() && searchResult.getSongs().isEmpty(); + searchButton.setText(empty ? R.string.search_no_match : R.string.search_search); + } + + list.setAdapter(mergeAdapter); + } + + private void expandArtists() { + artistAdapter.clear(); + for (Artist artist : searchResult.getArtists()) { + artistAdapter.add(artist); + } + artistAdapter.notifyDataSetChanged(); + mergeAdapter.removeAdapter(moreArtistsAdapter); + mergeAdapter.notifyDataSetChanged(); + } + + private void expandAlbums() { + albumAdapter.clear(); + for (MusicDirectory.Entry album : searchResult.getAlbums()) { + albumAdapter.add(album); + } + albumAdapter.notifyDataSetChanged(); + mergeAdapter.removeAdapter(moreAlbumsAdapter); + mergeAdapter.notifyDataSetChanged(); + } + + private void expandSongs() { + songAdapter.clear(); + for (MusicDirectory.Entry song : searchResult.getSongs()) { + songAdapter.add(song); + } + songAdapter.notifyDataSetChanged(); + mergeAdapter.removeAdapter(moreSongsAdapter); + mergeAdapter.notifyDataSetChanged(); + } + + private void onArtistSelected(Artist artist) { + Intent intent = new Intent(this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, artist.getName()); + Util.startActivityWithoutTransition(this, intent); + } + + private void onAlbumSelected(MusicDirectory.Entry album, boolean autoplay) { + Intent intent = new Intent(SearchActivity.this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, album.getId()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, album.getTitle()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, autoplay); + Util.startActivityWithoutTransition(SearchActivity.this, intent); + } + + private void onSongSelected(MusicDirectory.Entry song, boolean save, boolean append, boolean autoplay, boolean playNext) { + DownloadService downloadService = getDownloadService(); + if (downloadService != null) { + if (!append) { + downloadService.clear(); + } + downloadService.download(Arrays.asList(song), save, false, playNext); + if (autoplay) { + downloadService.play(downloadService.size() - 1); + } + + Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1)); + } + } + + private void onVideoSelected(MusicDirectory.Entry entry) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(MusicServiceFactory.getMusicService(this).getVideoUrl(this, entry.getId()))); + startActivity(intent); + } + + private void autoplay() { + if (!searchResult.getSongs().isEmpty()) { + onSongSelected(searchResult.getSongs().get(0), false, false, true, false); + } else if (!searchResult.getAlbums().isEmpty()) { + onAlbumSelected(searchResult.getAlbums().get(0), true); + } + } +}
\ No newline at end of file diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java new file mode 100644 index 00000000..b354599f --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SelectAlbumActivity.java @@ -0,0 +1,568 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.activity; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.service.DownloadFile; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.EntryAdapter; +import net.sourceforge.subsonic.androidapp.util.Pair; +import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; + +import java.util.ArrayList; +import java.util.List; + +public class SelectAlbumActivity extends SubsonicTabActivity { + + private static final String TAG = SelectAlbumActivity.class.getSimpleName(); + + private ListView entryList; + private View footer; + private View emptyView; + private Button selectButton; + private Button playNowButton; + private Button playLastButton; + private Button pinButton; + private Button unpinButton; + private Button deleteButton; + private Button moreButton; + private ImageView coverArtView; + private boolean licenseValid; + private ImageButton playAllButton; + + /** + * Called when the activity is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.select_album); + + entryList = (ListView) findViewById(R.id.select_album_entries); + + footer = LayoutInflater.from(this).inflate(R.layout.select_album_footer, entryList, false); + entryList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + entryList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (position >= 0) { + MusicDirectory.Entry entry = (MusicDirectory.Entry) parent.getItemAtPosition(position); + if (entry.isDirectory()) { + Intent intent = new Intent(SelectAlbumActivity.this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, entry.getId()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, entry.getTitle()); + Util.startActivityWithoutTransition(SelectAlbumActivity.this, intent); + } else if (entry.isVideo()) { + playVideo(entry); + } else { + enableButtons(); + } + } + } + }); + + coverArtView = (ImageView) findViewById(R.id.actionbar_home_icon); + selectButton = (Button) findViewById(R.id.select_album_select); + playNowButton = (Button) findViewById(R.id.select_album_play_now); + playLastButton = (Button) findViewById(R.id.select_album_play_last); + pinButton = (Button) footer.findViewById(R.id.select_album_pin); + unpinButton = (Button) footer.findViewById(R.id.select_album_unpin); + unpinButton = (Button) footer.findViewById(R.id.select_album_unpin); + deleteButton = (Button) footer.findViewById(R.id.select_album_delete); + moreButton = (Button) footer.findViewById(R.id.select_album_more); + emptyView = findViewById(R.id.select_album_empty); + + selectButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + selectAllOrNone(); + } + }); + playNowButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + download(false, false, true, false); + selectAll(false, false); + } + }); + playLastButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + download(true, false, false, false); + selectAll(false, false); + } + }); + pinButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + download(true, true, false, false); + selectAll(false, false); + } + }); + unpinButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + unpin(); + selectAll(false, false); + } + }); + deleteButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + delete(); + selectAll(false, false); + } + }); + + registerForContextMenu(entryList); + + enableButtons(); + + String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID); + String name = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_NAME); + String playlistId = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID); + String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); + String albumListType = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); + int albumListSize = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); + int albumListOffset = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0); + + if (playlistId != null) { + getPlaylist(playlistId, playlistName); + } else if (albumListType != null) { + getAlbumList(albumListType, albumListSize, albumListOffset); + } else { + getMusicDirectory(id, name); + } + + // Button 1: play all + playAllButton = (ImageButton) findViewById(R.id.action_button_1); + playAllButton.setImageResource(R.drawable.action_play_all); + playAllButton.setVisibility(View.GONE); + playAllButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + playAll(); + } + }); + + // Button 2: refresh + ImageButton refreshButton = (ImageButton) findViewById(R.id.action_button_2); + refreshButton.setImageResource(R.drawable.action_refresh); + refreshButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + refresh(); + } + }); + } + + private void playAll() { + boolean hasSubFolders = false; + for (int i = 0; i < entryList.getCount(); i++) { + MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(i); + if (entry != null && entry.isDirectory()) { + hasSubFolders = true; + break; + } + } + + String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID); + if (hasSubFolders && id != null) { + downloadRecursively(id, false, false, true); + } else { + selectAll(true, false); + download(false, false, true, false); + selectAll(false, false); + } + } + + private void refresh() { + finish(); + Intent intent = getIntent(); + intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); + Util.startActivityWithoutTransition(this, intent); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + AdapterView.AdapterContextMenuInfo info = + (AdapterView.AdapterContextMenuInfo) menuInfo; + + MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(info.position); + + if (entry.isDirectory()) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_album_context, menu); + } else { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_song_context, menu); + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(info.position); + List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>(10); + songs.add((MusicDirectory.Entry) entryList.getItemAtPosition(info.position)); + switch (menuItem.getItemId()) { + case R.id.album_menu_play_now: + downloadRecursively(entry.getId(), false, false, true); + break; + case R.id.album_menu_play_last: + downloadRecursively(entry.getId(), false, true, false); + break; + case R.id.album_menu_pin: + downloadRecursively(entry.getId(), true, true, false); + break; + case R.id.song_menu_play_now: + getDownloadService().download(songs, false, true, true); + break; + case R.id.song_menu_play_next: + getDownloadService().download(songs, false, false, true); + break; + case R.id.song_menu_play_last: + getDownloadService().download(songs, false, false, false); + break; + default: + return super.onContextItemSelected(menuItem); + } + return true; + } + + private void getMusicDirectory(final String id, String name) { + setTitle(name); + + new LoadTask() { + @Override + protected MusicDirectory load(MusicService service) throws Exception { + boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); + return service.getMusicDirectory(id, refresh, SelectAlbumActivity.this, this); + } + }.execute(); + } + + private void getPlaylist(final String playlistId, String playlistName) { + setTitle(playlistName); + + new LoadTask() { + @Override + protected MusicDirectory load(MusicService service) throws Exception { + return service.getPlaylist(playlistId, SelectAlbumActivity.this, this); + } + }.execute(); + } + + private void getAlbumList(final String albumListType, final int size, final int offset) { + + if ("newest".equals(albumListType)) { + setTitle(R.string.main_albums_newest); + } else if ("random".equals(albumListType)) { + setTitle(R.string.main_albums_random); + } else if ("highest".equals(albumListType)) { + setTitle(R.string.main_albums_highest); + } else if ("recent".equals(albumListType)) { + setTitle(R.string.main_albums_recent); + } else if ("frequent".equals(albumListType)) { + setTitle(R.string.main_albums_frequent); + } + + new LoadTask() { + @Override + protected MusicDirectory load(MusicService service) throws Exception { + return service.getAlbumList(albumListType, size, offset, SelectAlbumActivity.this, this); + } + + @Override + protected void done(Pair<MusicDirectory, Boolean> result) { + if (!result.getFirst().getChildren().isEmpty()) { + pinButton.setVisibility(View.GONE); + unpinButton.setVisibility(View.GONE); + deleteButton.setVisibility(View.GONE); + moreButton.setVisibility(View.VISIBLE); + entryList.addFooterView(footer); + + moreButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SelectAlbumActivity.this, SelectAlbumActivity.class); + String type = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); + int size = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0); + int offset = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0) + size; + + intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset); + Util.startActivityWithoutTransition(SelectAlbumActivity.this, intent); + } + }); + } + super.done(result); + } + }.execute(); + } + + private void selectAllOrNone() { + boolean someUnselected = false; + int count = entryList.getCount(); + for (int i = 0; i < count; i++) { + if (!entryList.isItemChecked(i) && entryList.getItemAtPosition(i) instanceof MusicDirectory.Entry) { + someUnselected = true; + break; + } + } + selectAll(someUnselected, true); + } + + private void selectAll(boolean selected, boolean toast) { + int count = entryList.getCount(); + int selectedCount = 0; + for (int i = 0; i < count; i++) { + MusicDirectory.Entry entry = (MusicDirectory.Entry) entryList.getItemAtPosition(i); + if (entry != null && !entry.isDirectory() && !entry.isVideo()) { + entryList.setItemChecked(i, selected); + selectedCount++; + } + } + + // Display toast: N tracks selected / N tracks unselected + if (toast) { + int toastResId = selected ? R.string.select_album_n_selected + : R.string.select_album_n_unselected; + Util.toast(this, getString(toastResId, selectedCount)); + } + + enableButtons(); + } + + private void enableButtons() { + if (getDownloadService() == null) { + return; + } + + List<MusicDirectory.Entry> selection = getSelectedSongs(); + boolean enabled = !selection.isEmpty(); + boolean unpinEnabled = false; + boolean deleteEnabled = false; + + for (MusicDirectory.Entry song : selection) { + DownloadFile downloadFile = getDownloadService().forSong(song); + if (downloadFile.isCompleteFileAvailable()) { + deleteEnabled = true; + } + if (downloadFile.isSaved()) { + unpinEnabled = true; + } + } + + playNowButton.setEnabled(enabled); + playLastButton.setEnabled(enabled); + pinButton.setEnabled(enabled && !Util.isOffline(this)); + unpinButton.setEnabled(unpinEnabled); + deleteButton.setEnabled(deleteEnabled); + } + + private List<MusicDirectory.Entry> getSelectedSongs() { + List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>(10); + int count = entryList.getCount(); + for (int i = 0; i < count; i++) { + if (entryList.isItemChecked(i)) { + songs.add((MusicDirectory.Entry) entryList.getItemAtPosition(i)); + } + } + return songs; + } + + private void download(final boolean append, final boolean save, final boolean autoplay, final boolean playNext) { + if (getDownloadService() == null) { + return; + } + + final List<MusicDirectory.Entry> songs = getSelectedSongs(); + Runnable onValid = new Runnable() { + @Override + public void run() { + if (!append) { + getDownloadService().clear(); + } + + warnIfNetworkOrStorageUnavailable(); + getDownloadService().download(songs, save, autoplay, playNext); + String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); + if (playlistName != null) { + getDownloadService().setSuggestedPlaylistName(playlistName); + } + if (autoplay) { + Util.startActivityWithoutTransition(SelectAlbumActivity.this, DownloadActivity.class); + } else if (save) { + Util.toast(SelectAlbumActivity.this, + getResources().getQuantityString(R.plurals.select_album_n_songs_downloading, songs.size(), songs.size())); + } else if (append) { + Util.toast(SelectAlbumActivity.this, + getResources().getQuantityString(R.plurals.select_album_n_songs_added, songs.size(), songs.size())); + } + } + }; + + checkLicenseAndTrialPeriod(onValid); + } + + private void delete() { + if (getDownloadService() != null) { + getDownloadService().delete(getSelectedSongs()); + } + } + + private void unpin() { + if (getDownloadService() != null) { + getDownloadService().unpin(getSelectedSongs()); + } + } + + private void playVideo(MusicDirectory.Entry entry) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(MusicServiceFactory.getMusicService(this).getVideoUrl(this, entry.getId()))); + + startActivity(intent); + } + + private void checkLicenseAndTrialPeriod(Runnable onValid) { + if (licenseValid) { + onValid.run(); + return; + } + + int trialDaysLeft = Util.getRemainingTrialDays(this); + Log.i(TAG, trialDaysLeft + " trial days left."); + + if (trialDaysLeft == 0) { + showDonationDialog(trialDaysLeft, null); + } else if (trialDaysLeft < Constants.FREE_TRIAL_DAYS / 2) { + showDonationDialog(trialDaysLeft, onValid); + } else { + Util.toast(this, getResources().getString(R.string.select_album_not_licensed, trialDaysLeft)); + onValid.run(); + } + } + + private void showDonationDialog(int trialDaysLeft, final Runnable onValid) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setIcon(android.R.drawable.ic_dialog_info); + + if (trialDaysLeft == 0) { + builder.setTitle(R.string.select_album_donate_dialog_0_trial_days_left); + } else { + builder.setTitle(getResources().getQuantityString(R.plurals.select_album_donate_dialog_n_trial_days_left, + trialDaysLeft, trialDaysLeft)); + } + + builder.setMessage(R.string.select_album_donate_dialog_message); + + builder.setPositiveButton(R.string.select_album_donate_dialog_now, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.DONATION_URL))); + } + }); + + builder.setNegativeButton(R.string.select_album_donate_dialog_later, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + if (onValid != null) { + onValid.run(); + } + } + }); + + builder.create().show(); + } + + private abstract class LoadTask extends TabActivityBackgroundTask<Pair<MusicDirectory, Boolean>> { + + public LoadTask() { + super(SelectAlbumActivity.this); + } + + protected abstract MusicDirectory load(MusicService service) throws Exception; + + @Override + protected Pair<MusicDirectory, Boolean> doInBackground() throws Throwable { + MusicService musicService = MusicServiceFactory.getMusicService(SelectAlbumActivity.this); + MusicDirectory dir = load(musicService); + boolean valid = musicService.isLicenseValid(SelectAlbumActivity.this, this); + return new Pair<MusicDirectory, Boolean>(dir, valid); + } + + @Override + protected void done(Pair<MusicDirectory, Boolean> result) { + List<MusicDirectory.Entry> entries = result.getFirst().getChildren(); + + int songCount = 0; + for (MusicDirectory.Entry entry : entries) { + if (!entry.isDirectory()) { + songCount++; + } + } + + if (songCount > 0) { + getImageLoader().loadImage(coverArtView, entries.get(0), false, true); + entryList.addFooterView(footer); + selectButton.setVisibility(View.VISIBLE); + playNowButton.setVisibility(View.VISIBLE); + playLastButton.setVisibility(View.VISIBLE); + } + + boolean isAlbumList = getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE); + + emptyView.setVisibility(entries.isEmpty() ? View.VISIBLE : View.GONE); + playAllButton.setVisibility(isAlbumList || entries.isEmpty() ? View.GONE : View.VISIBLE); + entryList.setAdapter(new EntryAdapter(SelectAlbumActivity.this, getImageLoader(), entries, true)); + licenseValid = result.getSecond(); + + boolean playAll = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false); + if (playAll && songCount > 0) { + playAll(); + } + } + } +} diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SelectArtistActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SelectArtistActivity.java new file mode 100644 index 00000000..959066ab --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SelectArtistActivity.java @@ -0,0 +1,228 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ImageButton; +import android.widget.ListView; +import android.widget.TextView; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.Artist; +import net.sourceforge.subsonic.androidapp.domain.Indexes; +import net.sourceforge.subsonic.androidapp.domain.MusicFolder; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.ArtistAdapter; +import net.sourceforge.subsonic.androidapp.util.BackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; + +import java.util.ArrayList; +import java.util.List; + +public class SelectArtistActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener { + + private static final int MENU_GROUP_MUSIC_FOLDER = 10; + + private ListView artistList; + private View folderButton; + private TextView folderName; + private List<MusicFolder> musicFolders; + + /** + * Called when the activity is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.select_artist); + + artistList = (ListView) findViewById(R.id.select_artist_list); + artistList.setOnItemClickListener(this); + + folderButton = LayoutInflater.from(this).inflate(R.layout.select_artist_header, artistList, false); + folderName = (TextView) folderButton.findViewById(R.id.select_artist_folder_2); + + if (!Util.isOffline(this)) { + artistList.addHeaderView(folderButton); + } + + registerForContextMenu(artistList); + + setTitle(Util.isOffline(this) ? R.string.music_library_label_offline : R.string.music_library_label); + + // Button 1: shuffle + ImageButton shuffleButton = (ImageButton) findViewById(R.id.action_button_1); + shuffleButton.setImageResource(R.drawable.action_shuffle); + shuffleButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SelectArtistActivity.this, DownloadActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true); + Util.startActivityWithoutTransition(SelectArtistActivity.this, intent); + } + }); + + // Button 2: refresh + ImageButton refreshButton = (ImageButton) findViewById(R.id.action_button_2); + refreshButton.setImageResource(R.drawable.action_refresh); + refreshButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + refresh(); + } + }); + + musicFolders = null; + load(); + } + + private void refresh() { + finish(); + Intent intent = getIntent(); + intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); + Util.startActivityWithoutTransition(this, intent); + } + + private void selectFolder() { + folderButton.showContextMenu(); + } + + private void load() { + BackgroundTask<Indexes> task = new TabActivityBackgroundTask<Indexes>(this) { + @Override + protected Indexes doInBackground() throws Throwable { + boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); + MusicService musicService = MusicServiceFactory.getMusicService(SelectArtistActivity.this); + if (!Util.isOffline(SelectArtistActivity.this)) { + musicFolders = musicService.getMusicFolders(refresh, SelectArtistActivity.this, this); + } + String musicFolderId = Util.getSelectedMusicFolderId(SelectArtistActivity.this); + return musicService.getIndexes(musicFolderId, refresh, SelectArtistActivity.this, this); + } + + @Override + protected void done(Indexes result) { + List<Artist> artists = new ArrayList<Artist>(result.getShortcuts().size() + result.getArtists().size()); + artists.addAll(result.getShortcuts()); + artists.addAll(result.getArtists()); + artistList.setAdapter(new ArtistAdapter(SelectArtistActivity.this, artists)); + + // Display selected music folder + if (musicFolders != null) { + String musicFolderId = Util.getSelectedMusicFolderId(SelectArtistActivity.this); + if (musicFolderId == null) { + folderName.setText(R.string.select_artist_all_folders); + } else { + for (MusicFolder musicFolder : musicFolders) { + if (musicFolder.getId().equals(musicFolderId)) { + folderName.setText(musicFolder.getName()); + break; + } + } + } + } + } + }; + task.execute(); + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (view == folderButton) { + selectFolder(); + } else { + Artist artist = (Artist) parent.getItemAtPosition(position); + Intent intent = new Intent(this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, artist.getId()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, artist.getName()); + Util.startActivityWithoutTransition(this, intent); + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + + if (artistList.getItemAtPosition(info.position) instanceof Artist) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.select_artist_context, menu); + } else if (info.position == 0) { + String musicFolderId = Util.getSelectedMusicFolderId(this); + MenuItem menuItem = menu.add(MENU_GROUP_MUSIC_FOLDER, -1, 0, R.string.select_artist_all_folders); + if (musicFolderId == null) { + menuItem.setChecked(true); + } + if (musicFolders != null) { + for (int i = 0; i < musicFolders.size(); i++) { + MusicFolder musicFolder = musicFolders.get(i); + menuItem = menu.add(MENU_GROUP_MUSIC_FOLDER, i, i + 1, musicFolder.getName()); + if (musicFolder.getId().equals(musicFolderId)) { + menuItem.setChecked(true); + } + } + } + menu.setGroupCheckable(MENU_GROUP_MUSIC_FOLDER, true, true); + } + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + + Artist artist = (Artist) artistList.getItemAtPosition(info.position); + + if (artist != null) { + switch (menuItem.getItemId()) { + case R.id.artist_menu_play_now: + downloadRecursively(artist.getId(), false, false, true); + break; + case R.id.artist_menu_play_last: + downloadRecursively(artist.getId(), false, true, false); + break; + case R.id.artist_menu_pin: + downloadRecursively(artist.getId(), true, true, false); + break; + default: + return super.onContextItemSelected(menuItem); + } + } else if (info.position == 0) { + MusicFolder selectedFolder = menuItem.getItemId() == -1 ? null : musicFolders.get(menuItem.getItemId()); + String musicFolderId = selectedFolder == null ? null : selectedFolder.getId(); + String musicFolderName = selectedFolder == null ? getString(R.string.select_artist_all_folders) + : selectedFolder.getName(); + Util.setSelectedMusicFolderId(this, musicFolderId); + folderName.setText(musicFolderName); + refresh(); + } + + return true; + } +}
\ No newline at end of file diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SelectPlaylistActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SelectPlaylistActivity.java new file mode 100644 index 00000000..253124b5 --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SelectPlaylistActivity.java @@ -0,0 +1,141 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ImageButton; +import android.widget.ListView; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.Playlist; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.util.BackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.PlaylistAdapter; +import net.sourceforge.subsonic.androidapp.util.TabActivityBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; + +import java.util.List; + +public class SelectPlaylistActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener { + + private static final int MENU_ITEM_PLAY_ALL = 1; + + private ListView list; + private View emptyTextView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.select_playlist); + + list = (ListView) findViewById(R.id.select_playlist_list); + emptyTextView = findViewById(R.id.select_playlist_empty); + list.setOnItemClickListener(this); + registerForContextMenu(list); + + // Title: Playlists + setTitle(R.string.playlist_label); + + // Button 1: gone + ImageButton searchButton = (ImageButton)findViewById(R.id.action_button_1); + searchButton.setVisibility(View.GONE); + + // Button 2: refresh + ImageButton refreshButton = (ImageButton) findViewById(R.id.action_button_2); + refreshButton.setImageResource(R.drawable.action_refresh); + refreshButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + refresh(); + } + }); + + load(); + } + + private void refresh() { + finish(); + Intent intent = new Intent(this, SelectPlaylistActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true); + Util.startActivityWithoutTransition(this, intent); + } + + private void load() { + BackgroundTask<List<Playlist>> task = new TabActivityBackgroundTask<List<Playlist>>(this) { + @Override + protected List<Playlist> doInBackground() throws Throwable { + MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this); + boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false); + return musicService.getPlaylists(refresh, SelectPlaylistActivity.this, this); + } + + @Override + protected void done(List<Playlist> result) { + list.setAdapter(new PlaylistAdapter(SelectPlaylistActivity.this, PlaylistAdapter.PlaylistComparator.sort(result))); + emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE); + } + }; + task.execute(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, view, menuInfo); + menu.add(Menu.NONE, MENU_ITEM_PLAY_ALL, MENU_ITEM_PLAY_ALL, R.string.common_play_now); + } + + @Override + public boolean onContextItemSelected(MenuItem menuItem) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo(); + Playlist playlist = (Playlist) list.getItemAtPosition(info.position); + + switch (menuItem.getItemId()) { + case MENU_ITEM_PLAY_ALL: + Intent intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true); + Util.startActivityWithoutTransition(SelectPlaylistActivity.this, intent); + break; + default: + return super.onContextItemSelected(menuItem); + } + return true; + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + + Playlist playlist = (Playlist) parent.getItemAtPosition(position); + + Intent intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId()); + intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName()); + Util.startActivityWithoutTransition(SelectPlaylistActivity.this, intent); + } + +}
\ No newline at end of file diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SettingsActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SettingsActivity.java new file mode 100644 index 00000000..f726a2af --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SettingsActivity.java @@ -0,0 +1,297 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.activity; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.provider.SearchRecentSuggestions; +import android.util.Log; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.provider.SearchSuggestionProvider; +import net.sourceforge.subsonic.androidapp.service.DownloadService; +import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.ErrorDialog; +import net.sourceforge.subsonic.androidapp.util.FileUtil; +import net.sourceforge.subsonic.androidapp.util.ModalBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; + +import java.io.File; +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.Map; + +public class SettingsActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { + + private static final String TAG = SettingsActivity.class.getSimpleName(); + private final Map<String, ServerSettings> serverSettings = new LinkedHashMap<String, ServerSettings>(); + private boolean testingConnection; + private ListPreference theme; + private ListPreference maxBitrateWifi; + private ListPreference maxBitrateMobile; + private ListPreference cacheSize; + private EditTextPreference cacheLocation; + private ListPreference preloadCount; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings); + + theme = (ListPreference) findPreference(Constants.PREFERENCES_KEY_THEME); + maxBitrateWifi = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI); + maxBitrateMobile = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE); + cacheSize = (ListPreference) findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE); + cacheLocation = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION); + preloadCount = (ListPreference) findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT); + + findPreference("testConnection1").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + testConnection(1); + return false; + } + }); + + findPreference("testConnection2").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + testConnection(2); + return false; + } + }); + + findPreference("testConnection3").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + testConnection(3); + return false; + } + }); + + findPreference("clearSearchHistory").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + SearchRecentSuggestions suggestions = new SearchRecentSuggestions(SettingsActivity.this, SearchSuggestionProvider.AUTHORITY, SearchSuggestionProvider.MODE); + suggestions.clearHistory(); + Util.toast(SettingsActivity.this, R.string.settings_search_history_cleared); + return false; + } + }); + + for (int i = 1; i <= 3; i++) { + String instance = String.valueOf(i); + serverSettings.put(instance, new ServerSettings(instance)); + } + + SharedPreferences prefs = Util.getPreferences(this); + prefs.registerOnSharedPreferenceChangeListener(this); + + update(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + SharedPreferences prefs = Util.getPreferences(this); + prefs.unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + Log.d(TAG, "Preference changed: " + key); + update(); + + if (Constants.PREFERENCES_KEY_HIDE_MEDIA.equals(key)) { + setHideMedia(sharedPreferences.getBoolean(key, false)); + } + else if (Constants.PREFERENCES_KEY_MEDIA_BUTTONS.equals(key)) { + setMediaButtonsEnabled(sharedPreferences.getBoolean(key, true)); + } + else if (Constants.PREFERENCES_KEY_CACHE_LOCATION.equals(key)) { + setCacheLocation(sharedPreferences.getString(key, "")); + } + } + + private void update() { + if (testingConnection) { + return; + } + + theme.setSummary(theme.getEntry()); + maxBitrateWifi.setSummary(maxBitrateWifi.getEntry()); + maxBitrateMobile.setSummary(maxBitrateMobile.getEntry()); + cacheSize.setSummary(cacheSize.getEntry()); + cacheLocation.setSummary(cacheLocation.getText()); + preloadCount.setSummary(preloadCount.getEntry()); + for (ServerSettings ss : serverSettings.values()) { + ss.update(); + } + } + + private void setHideMedia(boolean hide) { + File nomediaDir = new File(FileUtil.getSubsonicDirectory(), ".nomedia"); + if (hide && !nomediaDir.exists()) { + if (!nomediaDir.mkdir()) { + Log.w(TAG, "Failed to create " + nomediaDir); + } + } else if (nomediaDir.exists()) { + if (!nomediaDir.delete()) { + Log.w(TAG, "Failed to delete " + nomediaDir); + } + } + Util.toast(this, R.string.settings_hide_media_toast, false); + } + + private void setMediaButtonsEnabled(boolean enabled) { + if (enabled) { + Util.registerMediaButtonEventReceiver(this); + } else { + Util.unregisterMediaButtonEventReceiver(this); + } + } + + private void setCacheLocation(String path) { + File dir = new File(path); + if (!FileUtil.ensureDirectoryExistsAndIsReadWritable(dir)) { + Util.toast(this, R.string.settings_cache_location_error, false); + + // Reset it to the default. + String defaultPath = FileUtil.getDefaultMusicDirectory().getPath(); + if (!defaultPath.equals(path)) { + SharedPreferences prefs = Util.getPreferences(this); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, defaultPath); + editor.commit(); + cacheLocation.setSummary(defaultPath); + cacheLocation.setText(defaultPath); + } + + // Clear download queue. + DownloadService downloadService = DownloadServiceImpl.getInstance(); + downloadService.clear(); + } + } + + private void testConnection(final int instance) { + ModalBackgroundTask<Boolean> task = new ModalBackgroundTask<Boolean>(this, false) { + private int previousInstance; + + @Override + protected Boolean doInBackground() throws Throwable { + updateProgress(R.string.settings_testing_connection); + + previousInstance = Util.getActiveServer(SettingsActivity.this); + testingConnection = true; + Util.setActiveServer(SettingsActivity.this, instance); + try { + MusicService musicService = MusicServiceFactory.getMusicService(SettingsActivity.this); + musicService.ping(SettingsActivity.this, this); + return musicService.isLicenseValid(SettingsActivity.this, null); + } finally { + Util.setActiveServer(SettingsActivity.this, previousInstance); + testingConnection = false; + } + } + + @Override + protected void done(Boolean licenseValid) { + if (licenseValid) { + Util.toast(SettingsActivity.this, R.string.settings_testing_ok); + } else { + Util.toast(SettingsActivity.this, R.string.settings_testing_unlicensed); + } + } + + @Override + protected void cancel() { + super.cancel(); + Util.setActiveServer(SettingsActivity.this, previousInstance); + } + + @Override + protected void error(Throwable error) { + Log.w(TAG, error.toString(), error); + new ErrorDialog(SettingsActivity.this, getResources().getString(R.string.settings_connection_failure) + + " " + getErrorMessage(error), false); + } + }; + task.execute(); + } + + private class ServerSettings { + private EditTextPreference serverName; + private EditTextPreference serverUrl; + private EditTextPreference username; + private PreferenceScreen screen; + + private ServerSettings(String instance) { + + screen = (PreferenceScreen) findPreference("server" + instance); + serverName = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_SERVER_NAME + instance); + serverUrl = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_SERVER_URL + instance); + username = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_USERNAME + instance); + + serverUrl.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + try { + String url = (String) value; + new URL(url); + if (!url.equals(url.trim()) || url.contains("@")) { + throw new Exception(); + } + } catch (Exception x) { + new ErrorDialog(SettingsActivity.this, R.string.settings_invalid_url, false); + return false; + } + return true; + } + }); + + username.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + String username = (String) value; + if (username == null || !username.equals(username.trim())) { + new ErrorDialog(SettingsActivity.this, R.string.settings_invalid_username, false); + return false; + } + return true; + } + }); + } + + public void update() { + serverName.setSummary(serverName.getText()); + serverUrl.setSummary(serverUrl.getText()); + username.setSummary(username.getText()); + screen.setSummary(serverUrl.getText()); + screen.setTitle(serverName.getText()); + } + } +}
\ No newline at end of file diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java new file mode 100644 index 00000000..8c9c0687 --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/SubsonicTabActivity.java @@ -0,0 +1,383 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ +package net.sourceforge.subsonic.androidapp.activity; + +import java.io.File; +import java.io.PrintWriter; +import java.util.LinkedList; +import java.util.List; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.graphics.Typeface; +import android.media.AudioManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.TextView; +import net.sourceforge.subsonic.androidapp.R; +import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; +import net.sourceforge.subsonic.androidapp.service.DownloadService; +import net.sourceforge.subsonic.androidapp.service.DownloadServiceImpl; +import net.sourceforge.subsonic.androidapp.service.MusicService; +import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.ImageLoader; +import net.sourceforge.subsonic.androidapp.util.ModalBackgroundTask; +import net.sourceforge.subsonic.androidapp.util.Util; + +/** + * @author Sindre Mehus + */ +public class SubsonicTabActivity extends Activity { + + private static final String TAG = SubsonicTabActivity.class.getSimpleName(); + private static ImageLoader IMAGE_LOADER; + + private boolean destroyed; + private View homeButton; + private View musicButton; + private View searchButton; + private View playlistButton; + private View nowPlayingButton; + + @Override + protected void onCreate(Bundle bundle) { + setUncaughtExceptionHandler(); + applyTheme(); + super.onCreate(bundle); + requestWindowFeature(Window.FEATURE_NO_TITLE); + startService(new Intent(this, DownloadServiceImpl.class)); + setVolumeControlStream(AudioManager.STREAM_MUSIC); + } + + @Override + protected void onPostCreate(Bundle bundle) { + super.onPostCreate(bundle); + + homeButton = findViewById(R.id.button_bar_home); + homeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SubsonicTabActivity.this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); + } + }); + + musicButton = findViewById(R.id.button_bar_music); + musicButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SubsonicTabActivity.this, SelectArtistActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); + } + }); + + searchButton = findViewById(R.id.button_bar_search); + searchButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SubsonicTabActivity.this, SearchActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true); + Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); + } + }); + + playlistButton = findViewById(R.id.button_bar_playlists); + playlistButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(SubsonicTabActivity.this, SelectPlaylistActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent); + } + }); + + nowPlayingButton = findViewById(R.id.button_bar_now_playing); + nowPlayingButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class); + } + }); + + if (this instanceof MainActivity) { + homeButton.setEnabled(false); + } else if (this instanceof SelectAlbumActivity || this instanceof SelectArtistActivity) { + musicButton.setEnabled(false); + } else if (this instanceof SearchActivity) { + searchButton.setEnabled(false); + } else if (this instanceof SelectPlaylistActivity) { + playlistButton.setEnabled(false); + } else if (this instanceof DownloadActivity || this instanceof LyricsActivity) { + nowPlayingButton.setEnabled(false); + } + + updateButtonVisibility(); + } + + @Override + protected void onResume() { + super.onResume(); + Util.registerMediaButtonEventReceiver(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case R.id.menu_exit: + Intent intent = new Intent(this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true); + Util.startActivityWithoutTransition(this, intent); + return true; + + case R.id.menu_settings: + startActivity(new Intent(this, SettingsActivity.class)); + return true; + + case R.id.menu_help: + startActivity(new Intent(this, HelpActivity.class)); + return true; + } + + return false; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + destroyed = true; + getImageLoader().clear(); + } + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + boolean isVolumeDown = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN; + boolean isVolumeUp = keyCode == KeyEvent.KEYCODE_VOLUME_UP; + boolean isVolumeAdjust = isVolumeDown || isVolumeUp; + boolean isJukebox = getDownloadService() != null && getDownloadService().isJukeboxEnabled(); + + if (isVolumeAdjust && isJukebox) { + getDownloadService().adjustJukeboxVolume(isVolumeUp); + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public void finish() { + super.finish(); + Util.disablePendingTransition(this); + } + + @Override + public void setTitle(CharSequence title) { + super.setTitle(title); + + // Set the font of title in the action bar. + TextView text = (TextView) findViewById(R.id.actionbar_title_text); + Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/Storopia.ttf"); + text.setTypeface(typeface); + + text.setText(title); + } + + @Override + public void setTitle(int titleId) { + setTitle(getString(titleId)); + } + + private void applyTheme() { + String theme = Util.getTheme(this); + if ("dark".equals(theme)) { + setTheme(android.R.style.Theme); + } else if ("light".equals(theme)) { + setTheme(android.R.style.Theme_Light); + } + } + + public boolean isDestroyed() { + return destroyed; + } + + private void updateButtonVisibility() { + int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE; + searchButton.setVisibility(visibility); + playlistButton.setVisibility(visibility); + } + + public void setProgressVisible(boolean visible) { + View view = findViewById(R.id.tab_progress); + if (view != null) { + view.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + + public void updateProgress(String message) { + TextView view = (TextView) findViewById(R.id.tab_progress_message); + if (view != null) { + view.setText(message); + } + } + + public DownloadService getDownloadService() { + // If service is not available, request it to start and wait for it. + for (int i = 0; i < 5; i++) { + DownloadService downloadService = DownloadServiceImpl.getInstance(); + if (downloadService != null) { + return downloadService; + } + Log.w(TAG, "DownloadService not running. Attempting to start it."); + startService(new Intent(this, DownloadServiceImpl.class)); + Util.sleepQuietly(50L); + } + return DownloadServiceImpl.getInstance(); + } + + protected void warnIfNetworkOrStorageUnavailable() { + if (!Util.isExternalStoragePresent()) { + Util.toast(this, R.string.select_album_no_sdcard); + } else if (!Util.isOffline(this) && !Util.isNetworkConnected(this)) { + Util.toast(this, R.string.select_album_no_network); + } + } + + protected synchronized ImageLoader getImageLoader() { + if (IMAGE_LOADER == null) { + IMAGE_LOADER = new ImageLoader(this); + } + return IMAGE_LOADER; + } + + protected void downloadRecursively(final String id, final boolean save, final boolean append, final boolean autoplay) { + ModalBackgroundTask<List<MusicDirectory.Entry>> task = new ModalBackgroundTask<List<MusicDirectory.Entry>>(this, false) { + + private static final int MAX_SONGS = 500; + + @Override + protected List<MusicDirectory.Entry> doInBackground() throws Throwable { + MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this); + MusicDirectory root = musicService.getMusicDirectory(id, false, SubsonicTabActivity.this, this); + List<MusicDirectory.Entry> songs = new LinkedList<MusicDirectory.Entry>(); + getSongsRecursively(root, songs); + return songs; + } + + private void getSongsRecursively(MusicDirectory parent, List<MusicDirectory.Entry> songs) throws Exception { + if (songs.size() > MAX_SONGS) { + return; + } + + for (MusicDirectory.Entry song : parent.getChildren(false, true)) { + if (!song.isVideo()) { + songs.add(song); + } + } + for (MusicDirectory.Entry dir : parent.getChildren(true, false)) { + MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this); + getSongsRecursively(musicService.getMusicDirectory(dir.getId(), false, SubsonicTabActivity.this, this), songs); + } + } + + @Override + protected void done(List<MusicDirectory.Entry> songs) { + DownloadService downloadService = getDownloadService(); + if (!songs.isEmpty() && downloadService != null) { + if (!append) { + downloadService.clear(); + } + warnIfNetworkOrStorageUnavailable(); + downloadService.download(songs, save, autoplay, false); + Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class); + } + } + }; + + task.execute(); + } + + private void setUncaughtExceptionHandler() { + Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler(); + if (!(handler instanceof SubsonicUncaughtExceptionHandler)) { + Thread.setDefaultUncaughtExceptionHandler(new SubsonicUncaughtExceptionHandler(this)); + } + } + + /** + * Logs the stack trace of uncaught exceptions to a file on the SD card. + */ + private static class SubsonicUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + + private final Thread.UncaughtExceptionHandler defaultHandler; + private final Context context; + + private SubsonicUncaughtExceptionHandler(Context context) { + this.context = context; + defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + } + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + File file = null; + PrintWriter printWriter = null; + try { + + PackageInfo packageInfo = context.getPackageManager().getPackageInfo("net.sourceforge.subsonic.androidapp", 0); + file = new File(Environment.getExternalStorageDirectory(), "subsonic-stacktrace.txt"); + printWriter = new PrintWriter(file); + printWriter.println("Android API level: " + Build.VERSION.SDK); + printWriter.println("Subsonic version name: " + packageInfo.versionName); + printWriter.println("Subsonic version code: " + packageInfo.versionCode); + printWriter.println(); + throwable.printStackTrace(printWriter); + Log.i(TAG, "Stack trace written to " + file); + } catch (Throwable x) { + Log.e(TAG, "Failed to write stack trace to " + file, x); + } finally { + Util.close(printWriter); + if (defaultHandler != null) { + defaultHandler.uncaughtException(thread, throwable); + } + + } + } + } +} + diff --git a/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/VoiceQueryReceiverActivity.java b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/VoiceQueryReceiverActivity.java new file mode 100644 index 00000000..205c2fe7 --- /dev/null +++ b/subsonic-android/src/net/sourceforge/subsonic/androidapp/activity/VoiceQueryReceiverActivity.java @@ -0,0 +1,59 @@ +/* + This file is part of Subsonic. + + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + + Copyright 2009 (C) Sindre Mehus + */ + +package net.sourceforge.subsonic.androidapp.activity; + +import android.app.Activity; +import android.app.SearchManager; +import android.content.Intent; +import android.os.Bundle; +import android.provider.SearchRecentSuggestions; +import net.sourceforge.subsonic.androidapp.util.Constants; +import net.sourceforge.subsonic.androidapp.util.Util; +import net.sourceforge.subsonic.androidapp.provider.SearchSuggestionProvider; + +/** + * Receives voice search queries and forwards to the SearchActivity. + * + * http://android-developers.blogspot.com/2010/09/supporting-new-music-voice-action.html + * + * @author Sindre Mehus + */ +public class VoiceQueryReceiverActivity extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String query = getIntent().getStringExtra(SearchManager.QUERY); + + if (query != null) { + SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, SearchSuggestionProvider.AUTHORITY, + SearchSuggestionProvider.MODE); + suggestions.saveRecentQuery(query, null); + + Intent intent = new Intent(VoiceQueryReceiverActivity.this, SearchActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_QUERY, query); + intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true); + Util.startActivityWithoutTransition(VoiceQueryReceiverActivity.this, intent); + } + finish(); + Util.disablePendingTransition(this); + } +}
\ No newline at end of file |