org.mythtv.client.ui.MainMenuFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.mythtv.client.ui.MainMenuFragment.java

Source

/**
 * This file is part of MythTV Android Frontend
 *
 * MythTV Android Frontend 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.
 *
 * MythTV Android Frontend 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 MythTV Android Frontend.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This software can be found at <https://github.com/MythTV-Clients/MythTV-Android-Frontend/>
 */
package org.mythtv.client.ui;

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;

import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;

import org.joda.time.DateTime;
import org.mythtv.R;
import org.mythtv.client.ui.dvr.GuideFragment;
import org.mythtv.client.ui.dvr.RecordingRulesFragment;
import org.mythtv.client.ui.dvr.RecordingsParentFragment;
import org.mythtv.client.ui.dvr.UpcomingPagerFragment;
import org.mythtv.client.ui.frontends.Frontend;
import org.mythtv.client.ui.frontends.MythmoteActivity;
import org.mythtv.client.ui.preferences.LocationProfile;
import org.mythtv.client.ui.preferences.MythtvPreferenceActivity;
import org.mythtv.db.channel.ChannelEndpoint;
import org.mythtv.db.http.EtagDaoHelper;
import org.mythtv.service.MythtvService;
import org.mythtv.service.channel.ChannelDownloadService;
import org.mythtv.service.frontends.SendMessageTask;
import org.mythtv.service.guide.ProgramGuideDownloadService;
import org.mythtv.service.util.DateUtils;
import org.mythtv.service.util.NetworkHelper;
import org.mythtv.service.util.RunningServiceHelper;

import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;

/**
 * 
 * @author Thomas G. Kenny Jr
 * 
 */
public class MainMenuFragment extends AbstractMythFragment implements ServiceListener, OnItemSelectedListener {

    private final static String TAG = MainMenuFragment.class.getSimpleName();

    private EtagDaoHelper mEtagDaoHelper = EtagDaoHelper.getInstance();
    private RunningServiceHelper mRunningServiceHelper = RunningServiceHelper.getInstance();

    private ChannelDownloadReceiver channelDownloadReceiver = new ChannelDownloadReceiver();

    public interface ContentFragmentRequestedListener {

        public void OnFragmentRequested(int fragmentId, String fragmentClassName);

        public void OnFragmentRequested(int fragmentId, Fragment fragment);

    }

    private static TelephonyManager sTelManager;
    private static List<Frontend> frontends = new ArrayList<Frontend>();
    private static Frontend selectedFrontend;
    private static final String MYTHTV_FRONTEND_TYPE = "_mythfrontend._tcp.local.";
    private static final String HOSTNAME = "mythandroid";
    private static JmDNS zeroConf = null;
    private static MulticastLock mLock = null;
    private static boolean isAway = false;

    private boolean isConnected = false;
    private FrontendAdapter adapter = null;
    private ContentFragmentRequestedListener mContentFragmentRequestedListener;

    private LocationProfile mLocationProfile;

    private OnCheckedChangeListener homeAwayCheckedChanged = new OnCheckedChangeListener() {

        /* (non-Javadoc)
         * @see android.widget.CompoundButton.OnCheckedChangeListener#onCheckedChanged(android.widget.CompoundButton, boolean)
         */
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

            // set if profile is home/away
            isAway = isChecked;

            if (!isChecked) { // isChecked - false - home

                if (null == mLocationProfileDaoHelper.findSelectedHomeProfile(getActivity())) {

                    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                    builder.setTitle(R.string.location_alert_error_title);
                    builder.setNeutralButton(R.string.btn_ok, new DialogInterface.OnClickListener() {

                        public void onClick(DialogInterface dialog, int which) {
                        }

                    });
                    builder.setMessage(R.string.location_alert_error_home_message);
                    builder.show();

                } else {
                    // LocationProfile profile =
                    // mLocationProfileDaoHelper.findSelectedHomeProfile(
                    // getActivity() );
                    // mLocationProfileDaoHelper.setConnectedLocationProfile(
                    // getActivity(), (long) profile.getId() );

                    // here i think we need to re-start ourself and do not need
                    // to fire this intent
                    // This intent was used in LocationDashboardFragment to
                    // start HomeActivity
                    getActivity().startService(new Intent(MythtvService.ACTION_CONNECT));
                }

            } else { // ischecked - true - away

                if (null == mLocationProfileDaoHelper.findSelectedAwayProfile(getActivity())) {

                    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                    builder.setTitle(R.string.location_alert_error_title);
                    builder.setNeutralButton(R.string.btn_ok, new DialogInterface.OnClickListener() {

                        public void onClick(DialogInterface dialog, int which) {
                        }

                    });
                    builder.setMessage(R.string.location_alert_error_away_message);
                    builder.show();

                } else {
                    // LocationProfile profile =
                    // mLocationProfileDaoHelper.findSelectedAwayProfile(
                    // getActivity() );
                    // mLocationProfileDaoHelper.setConnectedLocationProfile(
                    // getActivity(), (long) profile.getId() );

                    // here i think we need to re-start ourself and do not need
                    // to fire this intent
                    // This intent was used in LocationDashboardFragment to
                    // start AwayActivity
                    getActivity().startService(new Intent(MythtvService.ACTION_CONNECT));
                }
            }

            // show/hide frontend selection
            LinearLayout linearLayoutFrontends = (LinearLayout) getActivity()
                    .findViewById(R.id.linear_layout_frontend_spinner);
            if (null != linearLayoutFrontends) {
                linearLayoutFrontends.setVisibility(isChecked ? View.GONE : View.VISIBLE);
            }
        }

    };

    private OnClickListener preferenceButtonOnClick = new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
                startActivity(new Intent(getActivity(), MythtvPreferenceActivity.class));
            } else {
                startActivity(new Intent(getActivity(), MythtvPreferenceActivity.class));
            }
        }
    };

    private OnClickListener mythmoteButtonOnClick = new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (NetworkHelper.getInstance().isNetworkConnected(getActivity())
                    && !getActivity().getClass().equals(MythmoteActivity.class)) {
                startActivity(new Intent(getActivity(), MythmoteActivity.class));
            }
        }
    };

    private OnClickListener activityButtonOnClick = new OnClickListener() {
        @Override
        public void onClick(View v) {

            // get button text
            Button btn = (Button) v;
            String btnTxt = btn.getText().toString();

            // check if we're connected to a backend
            if (!isConnected) {
                Toast.makeText(getActivity(), "No connection to backend or no backend profile is selected.",
                        Toast.LENGTH_SHORT).show();
                return;
            }

            // find button action based on the display string
            if (getString(R.string.btn_guide).equals(btnTxt)) {
                requestContentFragment(R.id.fragment_dvr_guide, GuideFragment.class.getName());
            } else if (getString(R.string.btn_music).equals(btnTxt)) {
                Toast.makeText(getActivity(), "Music - Coming Soon!", Toast.LENGTH_SHORT).show();
            } else if (getString(R.string.btn_pictures).equals(btnTxt)) {
                Toast.makeText(getActivity(), "Pictures - Coming Soon!", Toast.LENGTH_SHORT).show();
            } else if (getString(R.string.btn_recording_rules).equals(btnTxt)) {
                requestContentFragment(R.id.fragment_dvr_recording_rules, RecordingRulesFragment.class.getName());
            } else if (getString(R.string.btn_recordings).equals(btnTxt)) {
                requestContentFragment(R.id.fragment_dvr_recordings_parent,
                        RecordingsParentFragment.class.getName());
            } else if (getString(R.string.btn_upcoming).equals(btnTxt)) {
                requestContentFragment(R.id.fragment_dvr_upcoming, UpcomingPagerFragment.class.getName());
            } else if (getString(R.string.btn_videos).equals(btnTxt)) {
                Toast.makeText(getActivity(), "Video - Coming Soon!", Toast.LENGTH_SHORT).show();
            }
        }
    };

    /**
     * Returns the list of known frontends
     * 
     * @return
     */
    public static List<Frontend> GetFrontends() {
        return frontends;
    }

    /**
     * 
     * @return
     */
    public static Frontend getSelectedFrontend() {
        return selectedFrontend;
    }

    /**
     * 
     * @return
     */
    public static boolean isAway() {
        return isAway;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mLocationProfile = mLocationProfileDaoHelper.findConnectedProfile(getActivity());

        adapter = new FrontendAdapter(getActivity(), R.layout.frontend_row, MainMenuFragment.GetFrontends());

        sTelManager = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
        sTelManager.listen(new PhoneStateListener() {

            /* (non-Javadoc)
             * @see android.telephony.PhoneStateListener#onCallStateChanged(int, java.lang.String)
             */
            @Override
            public void onCallStateChanged(int state, String incomingNumber) {

                if (state == TelephonyManager.CALL_STATE_RINGING) {

                    final Frontend fe = selectedFrontend;

                    if (null == fe)
                        return;

                    SendMessageTask sendMessageTask = new SendMessageTask(getActivity(), mLocationProfile);
                    sendMessageTask.execute(fe.getUrl(), "Incoming Call From: " + incomingNumber);
                }

                super.onCallStateChanged(state, incomingNumber);
            }
        }, PhoneStateListener.LISTEN_CALL_STATE);
    }

    /*
     * (non-Javadoc)
     * 
     * @see android.support.v4.app.Fragment#onStart()
     */
    @Override
    public void onStart() {
        Log.v(TAG, "onStart : enter");
        super.onStart();

        IntentFilter channelDownloadFilter = new IntentFilter();
        channelDownloadFilter.addAction(ChannelDownloadService.ACTION_COMPLETE);
        getActivity().registerReceiver(channelDownloadReceiver, channelDownloadFilter);

        Log.v(TAG, "onStart : exit");
    }

    /*
     * (non-Javadoc)
     * 
     * @see android.support.v4.app.Fragment#onStop()
     */
    @Override
    public void onStop() {
        Log.v(TAG, "onStop : enter");
        super.onStop();

        // Unregister for broadcast
        if (null != channelDownloadReceiver) {
            try {
                getActivity().unregisterReceiver(channelDownloadReceiver);
                // channelDownloadReceiver = null;
            } catch (IllegalArgumentException e) {
                Log.e(TAG, "onStop : error", e);
            }
        }

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View mainMenuView = inflater.inflate(R.layout.fragment_main_menu, container, false);

        ToggleButton toggleIsAway = (ToggleButton) mainMenuView.findViewById(R.id.toggleButtonIsAway);
        toggleIsAway.setOnCheckedChangeListener(this.homeAwayCheckedChanged);

        // set version string - we only do this to prepend the 'V'
        TextView tView = (TextView) mainMenuView.findViewById(R.id.textview_mainmenu_version);
        tView.setText("V" + this.getString(R.string.app_version));

        // set frontends spinner
        Spinner spinner = (Spinner) mainMenuView.findViewById(R.id.spinner_frontends);
        spinner.setAdapter(adapter);
        spinner.setOnItemSelectedListener(this);

        // set preference button click listener
        ImageButton prefButton = (ImageButton) mainMenuView.findViewById(R.id.imagebutton_main_menu_preferences);
        prefButton.setOnClickListener(this.preferenceButtonOnClick);

        // set mythmote button click listener
        ImageButton mmButton = (ImageButton) mainMenuView.findViewById(R.id.imagebutton_main_menu_mythmote);
        mmButton.setOnClickListener(this.mythmoteButtonOnClick);

        // set activities button click listeners
        Button aBtn = (Button) mainMenuView.findViewById(R.id.button_main_menu_guide);
        aBtn.setOnClickListener(this.activityButtonOnClick);
        aBtn = (Button) mainMenuView.findViewById(R.id.button_main_menu_music);
        aBtn.setOnClickListener(this.activityButtonOnClick);
        aBtn = (Button) mainMenuView.findViewById(R.id.button_main_menu_pictures);
        aBtn.setOnClickListener(this.activityButtonOnClick);
        aBtn = (Button) mainMenuView.findViewById(R.id.button_main_menu_recording_rules);
        aBtn.setOnClickListener(this.activityButtonOnClick);
        aBtn = (Button) mainMenuView.findViewById(R.id.button_main_menu_recordings);
        aBtn.setOnClickListener(this.activityButtonOnClick);
        aBtn = (Button) mainMenuView.findViewById(R.id.button_main_menu_upcoming);
        aBtn.setOnClickListener(this.activityButtonOnClick);
        aBtn = (Button) mainMenuView.findViewById(R.id.button_main_menu_videos);
        aBtn.setOnClickListener(this.activityButtonOnClick);

        return mainMenuView;
    }

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

        // get connected location profile
        LocationProfile profile = this.mLocationProfileDaoHelper.findConnectedProfile(this.getActivity());

        // check if we have a connected profile
        if (null == profile) {

            // auto connected to the first profile found
            List<LocationProfile> profiles = mLocationProfileDaoHelper.findAll(getActivity());
            if (profiles.size() > 0) {
                profile = profiles.get(0);

                // mLocationProfileDaoHelper.setConnectedLocationProfile(
                // getActivity(), profile.getId() );
                isConnected = true;
            } else {
                isConnected = false;
            }
        } else {
            isConnected = true;
        }

        // get away/home toggle
        ToggleButton toggleIsAway = (ToggleButton) this.getActivity().findViewById(R.id.toggleButtonIsAway);
        toggleIsAway.setOnCheckedChangeListener(this.homeAwayCheckedChanged);

        // set away/home toggle based on the connected location profile
        if (null != profile && null != toggleIsAway) {
            toggleIsAway.setChecked(profile.getType() == LocationProfile.LocationType.AWAY);
        }

        // if frontend list is empty start a scan.
        if (frontends.isEmpty()) {
            scanForFrontends();
        }

        if (isConnected) {

            DateTime etag = mEtagDaoHelper.findDateByEndpointAndDataId(getActivity(), profile,
                    ChannelEndpoint.GET_CHANNEL_INFO_LIST.name(), "");
            if (null != etag) {

                DateTime now = DateUtils.convertUtc(new DateTime(System.currentTimeMillis()));
                if (now.getMillis() - etag.getMillis() > 86400000) {
                    if (!mRunningServiceHelper.isServiceRunning(getActivity(),
                            "org.mythtv.service.channel.ChannelDownloadService")) {
                        getActivity().startService(new Intent(ChannelDownloadService.ACTION_DOWNLOAD));
                    }
                }

            } else {
                if (!mRunningServiceHelper.isServiceRunning(getActivity(),
                        "org.mythtv.service.channel.ChannelDownloadService")) {
                    getActivity().startService(new Intent(ChannelDownloadService.ACTION_DOWNLOAD));
                }
            }

        }
    }

    @Override
    public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
        Log.d(TAG, "Frontend Spinner Item Selected");

        // leave if we don't have frontends, should never happen
        if (MainMenuFragment.GetFrontends().size() <= 0) {
            Log.e(TAG, "Frontend selected but no frontends in ArrayList");
            return;
        }

        // set selected frontend
        if (arg2 >= 0 && arg2 < MainMenuFragment.GetFrontends().size()) {
            selectedFrontend = MainMenuFragment.GetFrontends().get(arg2);
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> arg0) {
        Log.d(TAG, "Frontend Spinner Item Nothing Selected");
    }

    /**
     * 
     */
    public void scanForFrontends() {
        Log.v(TAG, "scanForFrontends : enter");

        new ScanFrontendsTask().execute();

        Log.v(TAG, "scanForFrontends : exit");
    }

    private class ScanFrontendsTask extends AsyncTask<Void, Void, Void> {

        private Exception e = null;

        @Override
        protected Void doInBackground(Void... params) {
            Log.v(TAG, "doInBackground : enter");

            try {
                Log.v(TAG, "doInBackground : startProbe");

                startProbe();
            } catch (Exception e) {
                Log.v(TAG, "doInBackground : error");

                this.e = e;
            }

            Log.v(TAG, "doInBackground : exit");
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            Log.v(TAG, "onPostExecute : enter");

            if (null != e && null != getActivity()) {
                Log.e(TAG, "error getting programs", e);

                AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                if (null != builder) {
                    builder.setTitle(getString(R.string.frontends_scan_error_title));
                    builder.setNeutralButton(R.string.btn_ok, new DialogInterface.OnClickListener() {

                        public void onClick(DialogInterface dialog, int which) {

                        }

                    });
                    builder.setMessage(getString(R.string.frontends_scan_error_message));
                    builder.show();
                }
            }

            Log.v(TAG, "onPostExecute : exit");
        }
    }

    /**
     * @throws IOException
     */
    private void startProbe() throws IOException {
        Log.v(TAG, "startProbe : enter");

        if (zeroConf != null) {
            stopProbe();
        }

        // figure out our wifi address, otherwise bail
        WifiManager wifi = (WifiManager) getActivity().getSystemService(Context.WIFI_SERVICE);

        WifiInfo wifiinfo = wifi.getConnectionInfo();
        int intaddr = wifiinfo.getIpAddress();

        byte[] byteaddr = new byte[] { (byte) (intaddr & 0xff), (byte) (intaddr >> 8 & 0xff),
                (byte) (intaddr >> 16 & 0xff), (byte) (intaddr >> 24 & 0xff) };
        InetAddress addr = InetAddress.getByAddress(byteaddr);
        Log.d(TAG, "startProbe : wifi address=" + addr.toString());

        // start multicast lock
        mLock = wifi.createMulticastLock("mythtv_lock");
        mLock.setReferenceCounted(true);
        mLock.acquire();

        zeroConf = JmDNS.create(addr, HOSTNAME);
        zeroConf.addServiceListener(MYTHTV_FRONTEND_TYPE, this);

        Log.v(TAG, "startProbe : exit");
    }

    /**
     * @throws IOException
     */
    private void stopProbe() throws IOException {
        Log.v(TAG, "stopProbe : enter");

        zeroConf.removeServiceListener(MYTHTV_FRONTEND_TYPE, this);
        zeroConf.close();
        zeroConf = null;

        mLock.release();
        mLock = null;

        Log.v(TAG, "stopProbe : exit");
    }

    /**
     * Call this to tell the containing activity it's time to close the menu
     * 
     * @param fragmentId
     * @param fragmentClassName
     */
    private void requestContentFragment(int fragmentId, String fragmentClassName) {
        if (null != mContentFragmentRequestedListener)
            mContentFragmentRequestedListener.OnFragmentRequested(fragmentId, fragmentClassName);
    }

    /**
     * 
     * @param fragment
     */
    private void requestContentFragment(int fragmentId, Fragment fragment) {
        if (null != mContentFragmentRequestedListener)
            mContentFragmentRequestedListener.OnFragmentRequested(fragmentId, fragment);
    }

    /**
     * 
     * @param listener
     */
    public void setContentFragmentRequestedListener(ContentFragmentRequestedListener listener) {
        mContentFragmentRequestedListener = listener;
    }

    // ***************************************
    // JMDNS ServiceListener methods
    // ***************************************

    /*
     * (non-Javadoc)
     * 
     * @see javax.jmdns.ServiceListener#serviceAdded(javax.jmdns.ServiceEvent)
     */
    @SuppressWarnings("deprecation")
    public void serviceAdded(ServiceEvent event) {
        Log.v(TAG, "serviceAdded : enter");

        Log.v(TAG, "serviceAdded : " + event.getDNS().getServiceInfo(event.getType(), event.getName()).toString());

        final String hostname = event.getDNS().getServiceInfo(event.getType(), event.getName()).getInet4Address()
                .getHostAddress();
        final int port = event.getDNS().getServiceInfo(event.getType(), event.getName()).getPort();
        Log.v(TAG, "serviceAdded : masterbackend=" + ("http://" + hostname + ":" + port + "/"));

        // Dont' do both adds
        final Frontend fe = new Frontend(event.getName(), hostname, port);

        this.getActivity().runOnUiThread(new Runnable() {

            @Override
            public void run() {
                // frontends.add(fe);
                adapter.add(fe);
            }
        });

        Log.v(TAG, "serviceAdded : exit");
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.jmdns.ServiceListener#serviceRemoved(javax.jmdns.ServiceEvent)
     */
    public void serviceRemoved(ServiceEvent event) {
        // Log.v( TAG, "serviceRemoved : enter" );
        //
        // Log.v( TAG, "serviceRemoved : event=" + event.toString() );
        //
        // Log.v( TAG, "serviceRemoved : exit" );
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * javax.jmdns.ServiceListener#serviceResolved(javax.jmdns.ServiceEvent)
     */
    public void serviceResolved(ServiceEvent event) {
        // Log.v( TAG, "serviceResolved : enter" );
        //
        // Log.v( TAG, "serviceResolved : event=" + event.toString() );
        //
        // Log.v( TAG, "serviceResolved : exit" );
    }

    private class FrontendAdapter extends ArrayAdapter<Frontend> {

        private final String TAG = FrontendAdapter.class.getSimpleName();

        private int layoutResourceId;
        private List<Frontend> frontends = null;

        FrontendAdapter(Context context, int layoutResourceId, List<Frontend> frontends) {
            super(context, layoutResourceId, frontends);
            Log.v(TAG, "initialize : enter");

            this.layoutResourceId = layoutResourceId;
            this.frontends = frontends;

            Log.v(TAG, "initialize : exit");
        }

        // @Override
        // public int getCount() {
        // return frontends.size();
        // }
        //
        // @Override
        // public Frontend getItem( int position ) {
        // return frontends.get( position );
        // }
        //
        // @Override
        // public long getItemId( int position ) {
        // return position;
        // }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return getFrontendView(position, convertView, parent);
        }

        @Override
        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            return getFrontendView(position, convertView, parent);
        }

        private View getFrontendView(int position, View convertView, ViewGroup parent) {
            Log.v(TAG, "getFrontendView : enter");

            View row = convertView;
            FrontendHolder holder = null;

            if (row == null) {
                Log.v(TAG, "getFrontendView : row is null");

                LayoutInflater inflater = getActivity().getLayoutInflater();

                row = inflater.inflate(layoutResourceId, parent, false);

                holder = new FrontendHolder();
                holder.name = (TextView) row.findViewById(R.id.frontend_name);
                holder.url = (TextView) row.findViewById(R.id.frontend_url);

                row.setTag(holder);
            } else {
                holder = (FrontendHolder) row.getTag();
            }

            Frontend frontend = frontends.get(position);

            holder.name.setText(frontend.getName());
            holder.url.setText(frontend.getUrl());

            Log.v(TAG, "getFrontendView : exit");
            return row;
        }

        class FrontendHolder {
            TextView name;
            TextView url;
        }

    }

    private class ChannelDownloadReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {

            if (intent.getAction().equals(ChannelDownloadService.ACTION_PROGRESS)) {
                Log.i(TAG, "ProgramGuideDownloadReceiver.onReceive : progress="
                        + intent.getStringExtra(ProgramGuideDownloadService.EXTRA_PROGRESS));
            }

            if (intent.getAction().equals(ChannelDownloadService.ACTION_COMPLETE)) {
                Log.i(TAG, "ProgramGuideDownloadReceiver.onReceive : "
                        + intent.getStringExtra(ProgramGuideDownloadService.EXTRA_COMPLETE));

                Toast.makeText(getActivity(), "Channels Updated!", Toast.LENGTH_SHORT).show();

                // if( !mRunningServiceHelper.isServiceRunning(
                // "org.mythtv.service.guide.ProgramGuideDownloadService" ) ) {
                // startService( new Intent(
                // ProgramGuideDownloadService.ACTION_DOWNLOAD ) );
                // }

            }

        }

    }

}