/* * Copyright (C) 2014 Fastboot Mobile, LLC. * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; * if not, see <http://www.gnu.org/licenses>. */ package com.fastbootmobile.encore.app.fragments; import android.app.Activity; import android.app.SearchManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.TransactionTooLargeException; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; import android.support.v7.widget.CardView; import android.text.Spannable; import android.text.SpannableString; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.EditText; import android.widget.TextView; import com.fastbootmobile.encore.api.echonest.AutoMixBucket; import com.fastbootmobile.encore.api.echonest.AutoMixManager; import com.fastbootmobile.encore.app.AppActivity; import com.fastbootmobile.encore.app.MainActivity; import com.fastbootmobile.encore.app.R; import com.fastbootmobile.encore.app.SearchActivity; import com.fastbootmobile.encore.app.SettingsActivity; import com.fastbootmobile.encore.app.adapters.ListenNowAdapter; import com.fastbootmobile.encore.app.ui.ParallaxScrollListView; import com.fastbootmobile.encore.app.ui.ScrollStatusBarColorListener; import com.fastbootmobile.encore.framework.ListenLogger; import com.fastbootmobile.encore.framework.PluginsLookup; import com.fastbootmobile.encore.model.Album; import com.fastbootmobile.encore.model.Artist; import com.fastbootmobile.encore.model.Playlist; import com.fastbootmobile.encore.model.SearchResult; import com.fastbootmobile.encore.model.Song; import com.fastbootmobile.encore.providers.ILocalCallback; import com.fastbootmobile.encore.providers.IMusicProvider; import com.fastbootmobile.encore.providers.ProviderAggregator; import com.fastbootmobile.encore.providers.ProviderConnection; import com.fastbootmobile.encore.utils.Utils; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * A simple {@link Fragment} subclass showing ideas of tracks and albums to listen to. * Use the {@link ListenNowFragment#newInstance} factory method to * create an instance of this fragment. */ public class ListenNowFragment extends Fragment implements ILocalCallback { private static final String TAG = "ListenNowFragment"; private static final boolean DEBUG = false; private static final String PREFS = "listen_now"; private static final String LANDCARD_NO_CUSTOM_PROVIDERS = "card_no_custom"; private static final String LANDCARD_SOUND_EFFECTS = "card_sound_effects"; private View mHeaderView; private int mBackgroundColor; private EditText mSearchBox; private CardView mCardSearchBox; private AbsListView.OnScrollListener mScrollListener; private ListenNowAdapter mAdapter; private Handler mHandler; private Thread mItemsSetupThread; /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @return A new instance of fragment ListenNowFragment. */ public static ListenNowFragment newInstance() { return new ListenNowFragment(); } /** * Default empty constructor */ public ListenNowFragment() { mScrollListener = new ScrollStatusBarColorListener() { @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (view.getChildCount() == 0 || getActivity() == null) { return; } final float heroHeight = mHeaderView.getMeasuredHeight(); final float scrollY = getScroll(view); final float toolbarBgAlpha = Math.min(1, scrollY / heroHeight); final int toolbarAlphaInteger = (((int) (toolbarBgAlpha * 255)) << 24) | 0xFFFFFF; mColorDrawable.setColor(toolbarAlphaInteger & mBackgroundColor); SpannableString spannableTitle = new SpannableString(((MainActivity) getActivity()).getFragmentTitle()); mAlphaSpan.setAlpha(toolbarBgAlpha); ActionBar actionbar = ((AppActivity) getActivity()).getSupportActionBar(); if (actionbar != null) { actionbar.setBackgroundDrawable(mColorDrawable); spannableTitle.setSpan(mAlphaSpan, 0, spannableTitle.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); actionbar.setTitle(spannableTitle); } mCardSearchBox.setAlpha(1.0f - toolbarBgAlpha); } }; } /** * {@inheritDoc} */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBackgroundColor = getResources().getColor(R.color.primary); mHandler = new Handler(); } @Override public void onDestroy() { super.onDestroy(); } public float getToolbarAlpha() { return 1.0f - mCardSearchBox.getAlpha(); } /** * {@inheritDoc} */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_listen_now, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { View root = getView(); if (root != null) { ParallaxScrollListView listView = (ParallaxScrollListView) root; listView.setOnScrollListener(mScrollListener); setupHeader(listView); mAdapter = new ListenNowAdapter(); listView.setAdapter(mAdapter); setupItems(); } } @Override public void onDetach() { if (mItemsSetupThread != null && mItemsSetupThread.isAlive()) { mItemsSetupThread.interrupt(); } super.onDetach(); } private void setupHeader(ParallaxScrollListView listView) { LayoutInflater inflater = LayoutInflater.from(listView.getContext()); mHeaderView = inflater.inflate(R.layout.header_listen_now, listView, false); mCardSearchBox = (CardView) mHeaderView.findViewById(R.id.cardSearchBox); mSearchBox = (EditText) mHeaderView.findViewById(R.id.ebSearch); mSearchBox.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { final String query = v.getText().toString(); Intent intent = new Intent(getActivity(), SearchActivity.class); intent.setAction(Intent.ACTION_SEARCH); intent.putExtra(SearchManager.QUERY, query); v.getContext().startActivity(intent); // Clear the box once searched v.setText(null); return true; } }); listView.addParallaxedHeaderView(mHeaderView); } private void setupItems() { mItemsSetupThread = new Thread() { public void run() { final Context context = getActivity(); if (context == null) { Log.e(TAG, "Invalid context when generating Listen Now items!"); return; } final List<ListenNowAdapter.ListenNowItem> items = new ArrayList<>(); final ProviderAggregator aggregator = ProviderAggregator.getDefault(); final PluginsLookup plugins = PluginsLookup.getDefault(); final List<Playlist> playlists = aggregator.getAllPlaylists(); final List<Song> songs = new ArrayList<>(); // Put a card to notify of sound effects final SharedPreferences prefs = context.getSharedPreferences(PREFS, 0); if (!prefs.getBoolean(LANDCARD_SOUND_EFFECTS, false)) { // Show the "You have no custom providers" card final ListenNowAdapter.CardItem item = new ListenNowAdapter.CardItem(getString(R.string.ln_landcard_sfx_title), getString(R.string.ln_landcard_sfx_body), getString(R.string.browse), new View.OnClickListener() { @Override public void onClick(View v) { prefs.edit().putBoolean(LANDCARD_SOUND_EFFECTS, true).apply(); v.getContext().startActivity(new Intent(v.getContext(), SettingsActivity.class)); } }, getString(R.string.ln_landcard_dismiss), new View.OnClickListener() { @Override public void onClick(View v) { prefs.edit().putBoolean(LANDCARD_SOUND_EFFECTS, true).apply(); // This item must always be the first of the list mAdapter.removeItem((ListenNowAdapter.ListenNowItem) v.getTag()); mAdapter.notifyDataSetChanged(); } }); mAdapter.addItem(item); } // Put cards for new providers Set<ProviderConnection> newPlugins = PluginsLookup.getDefault().getNewPlugins(); if (newPlugins != null) { for (final ProviderConnection plugin : newPlugins) { final ListenNowAdapter.CardItem item; if (plugin.getConfigurationActivity() == null) { item = new ListenNowAdapter.CardItem(String.format(getString(R.string.ln_landcard_plugin_installed_title), plugin.getProviderName()), getString(R.string.ln_landcard_plugin_installed_body), getString(R.string.ln_landcard_dismiss), new View.OnClickListener() { @Override public void onClick(View v) { prefs.edit().putBoolean(LANDCARD_SOUND_EFFECTS, true).apply(); // This item must always be the first of the list mAdapter.removeItem((ListenNowAdapter.ListenNowItem) v.getTag()); mAdapter.notifyDataSetChanged(); } }); } else { item = new ListenNowAdapter.CardItem(String.format(getString(R.string.ln_landcard_plugin_installed_title), plugin.getProviderName()), getString(R.string.ln_landcard_plugin_installed_body_configure), getString(R.string.ln_landcard_dismiss), new View.OnClickListener() { @Override public void onClick(View v) { prefs.edit().putBoolean(LANDCARD_SOUND_EFFECTS, true).apply(); // This item must always be the first of the list mAdapter.removeItem((ListenNowAdapter.ListenNowItem) v.getTag()); mAdapter.notifyDataSetChanged(); } }, getString(R.string.configure), new View.OnClickListener() { @Override public void onClick(View v) { mAdapter.removeItem((ListenNowAdapter.ListenNowItem) v.getTag()); mAdapter.notifyDataSetChanged(); Intent intent = new Intent(); intent.setClassName(plugin.getPackage(), plugin.getConfigurationActivity()); startActivity(intent); } }); } mAdapter.addItem(item); } PluginsLookup.getDefault().resetNewPlugins(); } // Get the list of songs first final List<ProviderConnection> providers = plugins.getAvailableProviders(); for (ProviderConnection provider : providers) { int limit = 50; int offset = 0; while (!isInterrupted()) { try { List<Song> providerSongs = provider.getBinder().getSongs(offset, limit); if (providerSongs != null) { songs.addAll(providerSongs); offset += providerSongs.size(); if (providerSongs.size() < limit) { if (DEBUG) Log.d(TAG, "Got " + providerSongs.size() + " instead of " + limit + ", assuming end of list"); break; } } else { break; } } catch (TransactionTooLargeException e) { limit -= 5; if (limit <= 0) { Log.e(TAG, "Error getting songs from " + provider.getProviderName() + ": transaction too large even with limit = 5"); break; } } catch (Exception e) { Log.e(TAG, "Error getting songs from " + provider.getProviderName() + ": " + e.getMessage()); break; } } } if (isInterrupted() || isDetached()) return; // Add a card if we have local music, but no cloud providers if (providers.size() <= PluginsLookup.BUNDLED_PROVIDERS_COUNT && songs.size() > 0) { if (!prefs.getBoolean(LANDCARD_NO_CUSTOM_PROVIDERS, false)) { // Show the "You have no custom providers" card final ListenNowAdapter.CardItem item = new ListenNowAdapter.CardItem(getString(R.string.ln_landcard_nocustomprovider_title), getString(R.string.ln_landcard_nocustomprovider_body), getString(R.string.browse), new View.OnClickListener() { @Override public void onClick(View v) { ProviderDownloadDialog.newInstance(false).show(getFragmentManager(), "DOWN"); } }, getString(R.string.ln_landcard_dismiss), new View.OnClickListener() { @Override public void onClick(View v) { prefs.edit().putBoolean(LANDCARD_NO_CUSTOM_PROVIDERS, true).apply(); // This item must always be the first of the list mAdapter.removeItem((ListenNowAdapter.ListenNowItem) v.getTag()); mAdapter.notifyDataSetChanged(); } }); items.add(item); } } if (isInterrupted() || isDetached()) return; // Add a card if there's no music at all (no songs and no playlists) if (providers.size() <= PluginsLookup.BUNDLED_PROVIDERS_COUNT && songs.size() == 0 && playlists.size() == 0) { items.add(new ListenNowAdapter.CardItem(getString(R.string.ln_card_nothing_title), getString(R.string.ln_card_nothing_body), getString(R.string.browse), new View.OnClickListener() { @Override public void onClick(View v) { ProviderDownloadDialog.newInstance(false).show(getFragmentManager(), "DOWN"); } }, getString(R.string.configure), new View.OnClickListener() { public void onClick(View v) { ((MainActivity) context).openSection(MainActivity.SECTION_SETTINGS); } })); items.add(new ListenNowAdapter.CardItem(getString(R.string.ln_card_nothinghint_title), getString(R.string.ln_card_nothinghint_body), null, null)); } if (isInterrupted() || isDetached()) return; // Add the "Recently played" section if we have recent tracks final ListenLogger logger = new ListenLogger(context); List<ListenLogger.LogEntry> logEntries = logger.getEntries(50); if (logEntries.size() > 0 && !isDetached()) { items.add(new ListenNowAdapter.SectionHeaderItem(getString(R.string.ln_section_recents), R.drawable.ic_nav_history_active, getString(R.string.more), new View.OnClickListener() { @Override public void onClick(View v) { ((MainActivity) context).openSection(MainActivity.SECTION_HISTORY); } })); int i = 0; List<ListenNowAdapter.ItemCardItem> itemsCouple = new ArrayList<>(); for (ListenLogger.LogEntry entry : logEntries) { if (i == 4) { // Stop here, add remaining item if (itemsCouple.size() > 0) { for (ListenNowAdapter.ItemCardItem item : itemsCouple) { items.add(item); } } break; } Song song = aggregator.retrieveSong(entry.getReference(), entry.getIdentifier()); if (song != null) { int type = Utils.getRandom(2); if (song.getAlbum() != null && (type == 0 || type == 1 && song.getArtist() == null)) { Album album = aggregator.retrieveAlbum(song.getAlbum(), song.getProvider()); if (album != null) { itemsCouple.add(new ListenNowAdapter.ItemCardItem(album)); ++i; } } else if (song.getArtist() != null) { Artist artist = aggregator.retrieveArtist(song.getArtist(), song.getProvider()); if (artist != null) { itemsCouple.add(new ListenNowAdapter.ItemCardItem(artist)); ++i; } } } if (itemsCouple.size() == 2) { ListenNowAdapter.CardRowItem row = new ListenNowAdapter.CardRowItem( itemsCouple.get(0), itemsCouple.get(1) ); items.add(row); itemsCouple.clear(); } } } if (isInterrupted() || isDetached()) return; // Add playlists section items.add(new ListenNowAdapter.SectionHeaderItem(getString(R.string.ln_section_playlists), R.drawable.ic_nav_playlist_active, getString(R.string.browse), new View.OnClickListener() { @Override public void onClick(View v) { ((MainActivity) context).openSection(MainActivity.SECTION_PLAYLISTS); } })); if (playlists != null && playlists.size() > 0) { int i = 0; List<ListenNowAdapter.ItemCardItem> itemsCouple = new ArrayList<>(); for (Playlist playlist : playlists) { if (i == 4) { // Stop here, add remaining item if (itemsCouple.size() > 0) { for (ListenNowAdapter.ItemCardItem item : itemsCouple) { items.add(item); } } break; } if (playlist != null) { ListenNowAdapter.ItemCardItem item = new ListenNowAdapter.ItemCardItem(playlist); itemsCouple.add(item); ++i; } if (itemsCouple.size() == 2) { ListenNowAdapter.CardRowItem row = new ListenNowAdapter.CardRowItem( itemsCouple.get(0), itemsCouple.get(1) ); items.add(row); itemsCouple.clear(); } } } if (isInterrupted() || isDetached()) return; // Add automix section items.add(new ListenNowAdapter.SectionHeaderItem(getString(R.string.lb_section_automixes), R.drawable.ic_nav_automix_active, getString(R.string.create), new View.OnClickListener() { @Override public void onClick(View v) { ((MainActivity) context).openSection(MainActivity.SECTION_AUTOMIX); } })); List<AutoMixBucket> buckets = AutoMixManager.getDefault().getBuckets(); if (buckets == null || buckets.size() == 0) { items.add(new ListenNowAdapter.GetStartedItem(getString(R.string.ln_automix_getstarted_body), getString(R.string.ln_action_getstarted), new View.OnClickListener() { @Override public void onClick(View v) { ((MainActivity) context).onNavigationDrawerItemSelected(MainActivity.SECTION_AUTOMIX); } })); } else { for (final AutoMixBucket bucket : buckets) { items.add(new ListenNowAdapter.SimpleItem(bucket.getName(), new View.OnClickListener() { @Override public void onClick(View v) { new Thread() { public void run() { AutoMixManager.getDefault().startPlay(bucket); } }.start(); } })); } } if (isInterrupted() || isDetached()) return; mHandler.post(new Runnable() { public void run() { for (ListenNowAdapter.ListenNowItem item : items) { mAdapter.addItem(item); } mAdapter.notifyDataSetChanged(); } }); } }; mItemsSetupThread.start(); } /* * {@inheritDoc} */ @Override public void onAttach(Activity activity) { super.onAttach(activity); MainActivity mainActivity = (MainActivity) activity; mainActivity.onSectionAttached(MainActivity.SECTION_LISTEN_NOW); } /** * {@inheritDoc} */ @Override public void onResume() { super.onResume(); ProviderAggregator.getDefault().addUpdateCallback(this); } /** * {@inheritDoc} */ @Override public void onPause() { super.onPause(); ProviderAggregator.getDefault().removeUpdateCallback(this); } /** * {@inheritDoc} */ @Override public void onSongUpdate(final List<Song> s) { } /** * {@inheritDoc} */ @Override public void onAlbumUpdate(final List<Album> a) { } /** * {@inheritDoc} */ @Override public void onPlaylistUpdate(List<Playlist> p) { } @Override public void onPlaylistRemoved(String ref) { } /** * {@inheritDoc} */ @Override public void onArtistUpdate(final List<Artist> a) { } /** * {@inheritDoc} */ @Override public void onProviderConnected(IMusicProvider provider) { } /** * {@inheritDoc} */ @Override public void onSearchResult(List<SearchResult> searchResult) { } }