/* * 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.os.AsyncTask; import android.os.Bundle; import android.os.DeadObjectException; import android.os.Handler; import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.TransactionTooLargeException; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; import com.fastbootmobile.encore.app.R; import com.fastbootmobile.encore.app.adapters.SongsListAdapter; import com.fastbootmobile.encore.framework.PlaybackProxy; import com.fastbootmobile.encore.framework.PluginsLookup; import com.fastbootmobile.encore.model.Song; import com.fastbootmobile.encore.providers.IMusicProvider; import com.fastbootmobile.encore.providers.ProviderConnection; import com.fastbootmobile.encore.service.BasePlaybackCallback; import com.fastbootmobile.encore.utils.Utils; import java.util.ArrayList; import java.util.List; /** * Fragment showing a list of Songs */ public class SongsFragment extends Fragment { private static final String TAG = "SongsFragment"; private SongsListAdapter mSongsListAdapter; private Handler mHandler; private boolean mAdapterSet = false; private ListView mListView; private BasePlaybackCallback mPlaybackCallback = new BasePlaybackCallback() { @Override public void onSongStarted(boolean buffering, Song s) throws RemoteException { mHandler.post(new Runnable() { public void run() { if (mSongsListAdapter != null) { mSongsListAdapter.notifyDataSetChanged(); } } }); } }; private AdapterView.OnItemClickListener mItemClickListener = new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { // Play the song along with every other Song song = mSongsListAdapter.getItem(i); if (Utils.canPlaySong(song)) { PlaybackProxy.clearQueue(); // Queue all songs List<Song> songs = mSongsListAdapter.getItems(); for (Song s : songs) { PlaybackProxy.queueSong(s, false); } // Play the requested one PlaybackProxy.playAtIndex(i); } else if (song == null) { Log.e(TAG, "Trying to play null song!"); } else { Utils.shortToast(getActivity(), R.string.toast_track_unavailable); } } }; public static SongsFragment newInstance() { return new SongsFragment(); } public SongsFragment() { mHandler = new Handler(Looper.getMainLooper()); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_songs, container, false); assert root != null; mListView = (ListView) root.findViewById(R.id.songsList); mListView.setFastScrollEnabled(true); mSongsListAdapter = new SongsListAdapter(true); new GetAllSongsTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); mListView.setOnItemClickListener(mItemClickListener); return root; } @Override public void onAttach(Activity activity) { super.onAttach(activity); PlaybackProxy.addCallback(mPlaybackCallback); } @Override public void onDetach() { super.onDetach(); PlaybackProxy.removeCallback(mPlaybackCallback); } private class GetAllSongsTask extends AsyncTask<Void, List<Song>, List<Song>> { @Override protected List<Song> doInBackground(Void... params) { android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); List<ProviderConnection> providers = PluginsLookup.getDefault().getAvailableProviders(); final List<Song> songsToAdd = new ArrayList<>(); for (ProviderConnection providerConnection : providers) { try { IMusicProvider provider = providerConnection.getBinder(); if (provider != null) { int limit = 100; int offset = 0; while (true) { try { List<Song> songs = provider.getSongs(offset, limit); if (songs == null || songs.size() == 0) { // Null or empty list, stop here break; } for (Song song : songs) { if (song != null) { songsToAdd.add(song); } } publishProgress(new ArrayList<>(songsToAdd)); songsToAdd.clear(); if (songs.size() < limit) { // Less songs than requested, assume we're at the end Log.d(TAG, "Got " + songs.size() + " songs, limit=" + limit + ", assuming end of list"); break; } else { offset += limit; } } catch (TransactionTooLargeException e) { limit -= 10; if (limit <= 0) { Log.e(TAG, "Transaction failed even at limit = 0, bailing out", e); break; } Log.w(TAG, "Transaction too large, reducing limit to " + limit); } } } } catch (DeadObjectException e) { Log.e(TAG, "Provider died while getting songs"); } catch (RemoteException e) { Log.w(TAG, "Cannot get songs from a provider", e); } } return songsToAdd; } @SafeVarargs @Override protected final void onProgressUpdate(List<Song>... values) { List<Song> songs = values[0]; if (songs.size() > 0) { onPostExecute(songs); } } @Override protected void onPostExecute(List<Song> songs) { mSongsListAdapter.putAll(songs); mSongsListAdapter.sortAll(); if (mAdapterSet) { mSongsListAdapter.notifyDataSetChanged(); } else { mListView.setAdapter(mSongsListAdapter); View root = getView(); if (root != null) { root.findViewById(R.id.songsProgress).setVisibility(View.GONE); } mAdapterSet = true; } } } }