com.bayapps.android.robophish.ui.MediaBrowserFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.bayapps.android.robophish.ui.MediaBrowserFragment.java

Source

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bayapps.android.robophish.ui;

import android.app.Activity;
import android.app.Fragment;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.TabLayout;
import android.support.v4.app.FragmentActivity;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.bayapps.android.robophish.R;
import com.bayapps.android.robophish.model.MusicProvider;
import com.bayapps.android.robophish.model.MusicProviderSource;
import com.bayapps.android.robophish.utils.Downloader;
import com.bayapps.android.robophish.utils.LogHelper;
import com.bayapps.android.robophish.utils.MediaIDHelper;
import com.bayapps.android.robophish.utils.NetworkHelper;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.JsonHttpResponseHandler;
import com.loopj.android.http.RequestParams;
import com.loopj.android.http.SyncHttpClient;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import cz.msebera.android.httpclient.Header;

import static com.bayapps.android.robophish.utils.MediaIDHelper.extractShowFromMediaID;

/**
 * A Fragment that lists all the various browsable queues available
 * from a {@link android.service.media.MediaBrowserService}.
 * <p/>
 * It uses a {@link MediaBrowserCompat} to connect to the {@link com.bayapps.android.robophish.MusicService}.
 * Once connected, the fragment subscribes to get all the children.
 * All {@link MediaBrowserCompat.MediaItem}'s that can be browsed are shown in a ListView.
 */
public class MediaBrowserFragment extends Fragment {

    private static final String TAG = LogHelper.makeLogTag(MediaBrowserFragment.class);

    private static final String ARG_MEDIA_ID = "media_id";
    private static final String ARG_TITLE = "title";
    private static final String ARG_SUBTITLE = "subtitle";

    private BrowseAdapter mBrowserAdapter;
    private String mMediaId;
    private MediaFragmentListener mMediaFragmentListener;
    private View mErrorView;
    private TextView mErrorMessage;
    private JSONObject mShowData;

    private final BroadcastReceiver mConnectivityChangeReceiver = new BroadcastReceiver() {
        private boolean oldOnline = false;

        @Override
        public void onReceive(Context context, Intent intent) {
            // We don't care about network changes while this fragment is not associated
            // with a media ID (for example, while it is being initialized)
            if (mMediaId != null) {
                boolean isOnline = NetworkHelper.isOnline(context);
                if (isOnline != oldOnline) {
                    oldOnline = isOnline;
                    checkForUserVisibleErrors(false);
                    if (isOnline) {
                        mBrowserAdapter.notifyDataSetChanged();
                    }
                }
            }
        }
    };

    // Receive callbacks from the MediaController. Here we update our state such as which queue
    // is being shown, the current title and description and the PlaybackState.
    private final MediaControllerCompat.Callback mMediaControllerCallback = new MediaControllerCompat.Callback() {
        @Override
        public void onMetadataChanged(MediaMetadataCompat metadata) {
            super.onMetadataChanged(metadata);
            if (metadata == null) {
                return;
            }
            LogHelper.d(TAG, "Received metadata change to media ", metadata.getDescription().getMediaId());
            mBrowserAdapter.notifyDataSetChanged();
        }

        @Override
        public void onPlaybackStateChanged(@NonNull PlaybackStateCompat state) {
            super.onPlaybackStateChanged(state);
            LogHelper.d(TAG, "Received state change: ", state);
            checkForUserVisibleErrors(false);
            mBrowserAdapter.notifyDataSetChanged();
        }
    };

    private final MediaBrowserCompat.SubscriptionCallback mSubscriptionCallback = new MediaBrowserCompat.SubscriptionCallback() {
        @Override
        public void onChildrenLoaded(@NonNull String parentId,
                @NonNull List<MediaBrowserCompat.MediaItem> children) {
            try {
                LogHelper.d(TAG, "fragment onChildrenLoaded, parentId=" + parentId + "  count=" + children.size());
                checkForUserVisibleErrors(children.isEmpty());
                mBrowserAdapter.clear();
                for (MediaBrowserCompat.MediaItem item : children) {
                    mBrowserAdapter.add(item);
                }
                mBrowserAdapter.notifyDataSetChanged();
            } catch (Throwable t) {
                LogHelper.e(TAG, "Error on childrenloaded", t);
            }
        }

        @Override
        public void onError(@NonNull String id) {
            LogHelper.e(TAG, "browse fragment subscription onError, id=" + id);
            Toast.makeText(getActivity(), R.string.error_loading_media, Toast.LENGTH_LONG).show();
            checkForUserVisibleErrors(true);
        }
    };

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // If used on an activity that doesn't implement MediaFragmentListener, it
        // will throw an exception as expected:
        mMediaFragmentListener = (MediaFragmentListener) activity;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        switch (id) {
        case 0:
            // start DownloadManager
            String showId = extractShowFromMediaID(getMediaId());
            Downloader dl = new Downloader(getActivity(), showId, mShowData);
            return true;
        }

        return super.onOptionsItemSelected(item); // important line
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        //menu.add("Download Show");  //TODO: enable once downloads are working
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        LogHelper.d(TAG, "fragment.onCreateView");

        View rootView;

        String mediaId = getMediaId();
        ListView listView;

        if (mediaId != null && MediaIDHelper.isShow(mediaId)) {

            setHasOptionsMenu(true); //show option to download

            rootView = inflater.inflate(R.layout.fragment_list_show, container, false);

            ViewPager viewPager = (ViewPager) rootView.findViewById(R.id.viewpager);
            viewPager.setAdapter(new ShowPagerAdapter(inflater, rootView));
            viewPager.setOffscreenPageLimit(3);

            TabLayout tabLayout = (TabLayout) rootView.findViewById(R.id.sliding_tabs);
            tabLayout.setupWithViewPager(viewPager);

            final WebView setlist = (WebView) rootView.findViewById(R.id.setlist_webview);
            setlist.getSettings().setJavaScriptEnabled(true);

            AsyncHttpClient setlistClient = new AsyncHttpClient();
            RequestParams setlistParams = new RequestParams();
            setlistParams.put("api", "2.0");
            setlistParams.put("method", "pnet.shows.setlists.get");
            setlistParams.put("showdate", getSubTitle());
            setlistParams.put("apikey", "C01AEE2002E80723E9E7");
            setlistParams.put("format", "json");
            setlistClient.get("http://api.phish.net/api.js", setlistParams, new JsonHttpResponseHandler() {

                @Override
                public void onSuccess(int statusCode, Header[] headers, JSONArray response) {
                    super.onSuccess(statusCode, headers, response);
                    try {
                        JSONObject result = response.getJSONObject(0);
                        String city = result.getString("city");
                        String state = result.getString("state");
                        String country = result.getString("country");
                        String venue = result.getString("venue");

                        String header = "<h1>" + venue + "</h1>" + "<h2>" + city + ", " + state + "<br/>" + country
                                + "</h2>";

                        String setlistdata = result.getString("setlistdata");
                        String setlistnotes = result.getString("setlistnotes");
                        setlist.loadData(header + setlistdata + setlistnotes, "text/html", null);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onFailure(int statusCode, Header[] headers, Throwable throwable,
                        JSONObject errorResponse) {
                    super.onFailure(statusCode, headers, throwable, errorResponse);
                }
            });

            final WebView reviews = (WebView) rootView.findViewById(R.id.reviews_webview);
            reviews.getSettings().setJavaScriptEnabled(true);

            AsyncHttpClient reviewsClient = new AsyncHttpClient();
            RequestParams reviewsParams = new RequestParams();
            reviewsParams.put("api", "2.0");
            reviewsParams.put("method", "pnet.reviews.query");
            reviewsParams.put("showdate", getSubTitle());
            reviewsParams.put("apikey", "C01AEE2002E80723E9E7");
            reviewsParams.put("format", "json");
            reviewsClient.get("http://api.phish.net/api.js", reviewsParams, new JsonHttpResponseHandler() {

                @Override
                public void onSuccess(int statusCode, Header[] headers, JSONArray response) {
                    super.onSuccess(statusCode, headers, response);
                    try {
                        String display = "";

                        int len = response.length();
                        for (int i = 0; i < len; i++) {
                            JSONObject entry = response.getJSONObject(i);
                            String author = entry.getString("author");
                            String review = entry.getString("review");
                            String tstamp = entry.getString("tstamp");

                            Date reviewTime = new Date(Long.parseLong(tstamp) * 1000);
                            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                            String reviewDate = dateFormat.format(reviewTime);

                            String reviewSubs = review.replaceAll("\n", "<br/>");

                            display += "<h2>" + author + "</h2>" + "<h4>" + reviewDate + "</h4>";
                            display += reviewSubs + "<br/>";
                        }

                        reviews.loadData(display, "text/html", null);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onFailure(int statusCode, Header[] headers, Throwable throwable,
                        JSONObject errorResponse) {
                    super.onFailure(statusCode, headers, throwable, errorResponse);
                }
            });

            final WebView tapernotesWebview = (WebView) rootView.findViewById(R.id.tapernotes_webview);
            tapernotesWebview.getSettings().setJavaScriptEnabled(true);

            String showId = extractShowFromMediaID(mediaId);
            final AsyncHttpClient tapernotesClient = new AsyncHttpClient();
            tapernotesClient.get("http://phish.in/api/v1/shows/" + showId + ".json", null,
                    new JsonHttpResponseHandler() {

                        @Override
                        public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
                            super.onSuccess(statusCode, headers, response);
                            try {
                                mShowData = response;
                                JSONObject data = response.getJSONObject("data");
                                String tapernotes = data.getString("taper_notes");
                                if (tapernotes.equals("null"))
                                    tapernotes = "Not available";
                                String notesSubs = tapernotes.replaceAll("\n", "<br/>");

                                tapernotesWebview.loadData(notesSubs, "text/html", null);
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void onFailure(int statusCode, Header[] headers, Throwable throwable,
                                JSONObject errorResponse) {
                            super.onFailure(statusCode, headers, throwable, errorResponse);
                        }
                    });

        } else {
            rootView = inflater.inflate(R.layout.fragment_list, container, false);
        }

        mErrorView = rootView.findViewById(R.id.playback_error);
        mErrorMessage = (TextView) mErrorView.findViewById(R.id.error_message);

        mBrowserAdapter = new BrowseAdapter(getActivity());

        listView = (ListView) rootView.findViewById(R.id.list_view);
        listView.setAdapter(mBrowserAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                checkForUserVisibleErrors(false);
                MediaBrowserCompat.MediaItem item = mBrowserAdapter.getItem(position);
                mMediaFragmentListener.onMediaItemSelected(item);
            }
        });

        return rootView;
    }

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

        // fetch browsing information to fill the listview:
        MediaBrowserCompat mediaBrowser = mMediaFragmentListener.getMediaBrowser();

        LogHelper.d(TAG, "fragment.onStart, mediaId=", mMediaId, "  onConnected=" + mediaBrowser.isConnected());

        if (mediaBrowser.isConnected()) {
            onConnected();
        }

        // Registers BroadcastReceiver to track network connection changes.
        this.getActivity().registerReceiver(mConnectivityChangeReceiver,
                new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
    }

    @Override
    public void onStop() {
        super.onStop();
        MediaBrowserCompat mediaBrowser = mMediaFragmentListener.getMediaBrowser();
        if (mediaBrowser != null && mediaBrowser.isConnected() && mMediaId != null) {
            mediaBrowser.unsubscribe(mMediaId);
        }
        MediaControllerCompat controller = ((FragmentActivity) getActivity()).getSupportMediaController();
        if (controller != null) {
            controller.unregisterCallback(mMediaControllerCallback);
        }
        this.getActivity().unregisterReceiver(mConnectivityChangeReceiver);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mMediaFragmentListener = null;
    }

    public String getMediaId() {
        Bundle args = getArguments();
        if (args != null) {
            return args.getString(ARG_MEDIA_ID);
        }
        return null;
    }

    public String getTitle() {
        Bundle args = getArguments();
        if (args != null) {
            return args.getString(ARG_TITLE);
        }
        return null;
    }

    public String getSubTitle() {
        Bundle args = getArguments();
        if (args != null) {
            return args.getString(ARG_SUBTITLE);
        }
        return null;
    }

    public void setMediaId(String title, String subtitle, String mediaId) {
        Bundle args = new Bundle(3);
        args.putString(MediaBrowserFragment.ARG_MEDIA_ID, mediaId);
        args.putString(MediaBrowserFragment.ARG_TITLE, title);
        args.putString(MediaBrowserFragment.ARG_SUBTITLE, subtitle);
        setArguments(args);
    }

    // Called when the MediaBrowser is connected. This method is either called by the
    // fragment.onStart() or explicitly by the activity in the case where the connection
    // completes after the onStart()
    public void onConnected() {
        if (isDetached()) {
            return;
        }
        mMediaId = getMediaId();
        if (mMediaId == null) {
            mMediaId = mMediaFragmentListener.getMediaBrowser().getRoot();
        }
        updateTitle();

        // Unsubscribing before subscribing is required if this mediaId already has a subscriber
        // on this MediaBrowser instance. Subscribing to an already subscribed mediaId will replace
        // the callback, but won't trigger the initial callback.onChildrenLoaded.
        //
        // This is temporary: A bug is being fixed that will make subscribe
        // consistently call onChildrenLoaded initially, no matter if it is replacing an existing
        // subscriber or not. Currently this only happens if the mediaID has no previous
        // subscriber or if the media content changes on the service side, so we need to
        // unsubscribe first.
        mMediaFragmentListener.getMediaBrowser().unsubscribe(mMediaId);

        mMediaFragmentListener.getMediaBrowser().subscribe(mMediaId, mSubscriptionCallback);

        // Add MediaController callback so we can redraw the list when metadata changes:
        MediaControllerCompat controller = ((FragmentActivity) getActivity()).getSupportMediaController();
        if (controller != null) {
            controller.registerCallback(mMediaControllerCallback);
        }
    }

    private void checkForUserVisibleErrors(boolean forceError) {
        boolean showError = forceError;
        // If offline, message is about the lack of connectivity:
        if (!NetworkHelper.isOnline(getActivity())) {
            mErrorMessage.setText(R.string.error_no_connection);
            showError = true;
        } else {
            // otherwise, if state is ERROR and metadata!=null, use playback state error message:
            MediaControllerCompat controller = ((FragmentActivity) getActivity()).getSupportMediaController();
            if (controller != null && controller.getMetadata() != null && controller.getPlaybackState() != null
                    && controller.getPlaybackState().getState() == PlaybackStateCompat.STATE_ERROR
                    && controller.getPlaybackState().getErrorMessage() != null) {
                mErrorMessage.setText(controller.getPlaybackState().getErrorMessage());
                showError = true;
            } else if (forceError) {
                // Finally, if the caller requested to show error, show a generic message:
                mErrorMessage.setText(R.string.error_loading_media);
                showError = true;
            }
        }
        mErrorView.setVisibility(showError ? View.VISIBLE : View.GONE);
        LogHelper.d(TAG, "checkForUserVisibleErrors. forceError=", forceError, " showError=", showError,
                " isOnline=", NetworkHelper.isOnline(getActivity()));
    }

    private void updateTitle() {

        mMediaFragmentListener.updateDrawerToggle();

        if (mMediaId.startsWith(MediaIDHelper.MEDIA_ID_SHOWS_BY_YEAR)) {

            String year = MediaIDHelper.getHierarchy(mMediaId)[1];
            mMediaFragmentListener.setToolbarTitle(year);
            mMediaFragmentListener.setToolbarSubTitle("");
            return;
        }

        if (mMediaId.startsWith(MediaIDHelper.MEDIA_ID_TRACKS_BY_SHOW)) {

            mMediaFragmentListener.setToolbarTitle(getTitle());
            mMediaFragmentListener.setToolbarSubTitle(getSubTitle());
            return;
        }

        if (MediaIDHelper.MEDIA_ID_ROOT.equals(mMediaId)) {
            mMediaFragmentListener.setToolbarTitle(null);
            return;
        }

        MediaBrowserCompat mediaBrowser = mMediaFragmentListener.getMediaBrowser();
        mediaBrowser.getItem(mMediaId, new MediaBrowserCompat.ItemCallback() {
            @Override
            public void onItemLoaded(MediaBrowserCompat.MediaItem item) {
                mMediaFragmentListener.setToolbarTitle(item.getDescription().getTitle());
            }
        });
    }

    // An adapter for showing the list of browsed MediaItem's
    private static class BrowseAdapter extends ArrayAdapter<MediaBrowserCompat.MediaItem> {

        public BrowseAdapter(Activity context) {
            super(context, R.layout.media_list_item, new ArrayList<MediaBrowserCompat.MediaItem>());
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            MediaBrowserCompat.MediaItem item = getItem(position);
            int itemState = MediaItemViewHolder.STATE_NONE;
            if (item.isPlayable()) {
                itemState = MediaItemViewHolder.STATE_PLAYABLE;
                MediaControllerCompat controller = ((FragmentActivity) getContext()).getSupportMediaController();
                if (controller != null && controller.getMetadata() != null) {
                    String currentPlaying = controller.getMetadata().getDescription().getMediaId();
                    String musicId = MediaIDHelper.extractMusicIDFromMediaID(item.getDescription().getMediaId());
                    if (currentPlaying != null && currentPlaying.equals(musicId)) {
                        PlaybackStateCompat pbState = controller.getPlaybackState();
                        if (pbState == null || pbState.getState() == PlaybackStateCompat.STATE_ERROR) {
                            itemState = MediaItemViewHolder.STATE_NONE;
                        } else if (pbState.getState() == PlaybackStateCompat.STATE_PLAYING) {
                            itemState = MediaItemViewHolder.STATE_PLAYING;
                        } else {
                            itemState = MediaItemViewHolder.STATE_PAUSED;
                        }
                    }
                }
            }

            return MediaItemViewHolder.setupView((Activity) getContext(), convertView, parent,
                    item.getDescription(), itemState);
        }
    }

    public interface MediaFragmentListener extends MediaBrowserProvider {
        void onMediaItemSelected(MediaBrowserCompat.MediaItem item);

        void setToolbarTitle(CharSequence title);

        void setToolbarSubTitle(CharSequence title);

        void updateDrawerToggle();
    }

}