com.glandorf1.joe.wsprnetviewer.app.WsprFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.glandorf1.joe.wsprnetviewer.app.WsprFragment.java

Source

/*
 * Copyright (C) 2014 Joseph D. Glandorf
 *
 * 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.glandorf1.joe.wsprnetviewer.app;

import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
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.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.glandorf1.joe.wsprnetviewer.app.data.WsprNetContract;
import com.glandorf1.joe.wsprnetviewer.app.sync.WsprNetViewerSyncAdapter;

/**
 * Encapsulates getting the wspr data and displaying it in a ListView layout.
 */
public class WsprFragment extends Fragment implements LoaderCallbacks<Cursor> {
    private final String LOG_TAG = MainActivity.class.getSimpleName();
    private static final String SELECTED_KEY = "selected_position";
    private WsprAdapter mWsprAdapter;
    private static final int WSPR_LOADER = 0;
    private String mSelection, mGridsquare, mFilterTxCallsign, mFilterRxCallsign, mFilterTxGridsquare,
            mFilterRxGridsquare;
    private boolean mFilterAnd, mFiltered;
    private int mLastNumItems = -1;
    private ListView mListView;
    private TextView mTVGridCallHeader;
    private int mPosition = ListView.INVALID_POSITION; // selected item's position
    private boolean mDualPane; // provision for putting the details fragment next to this fragment for wider screens

    // For the wspr view, show only a subset of the stored data.
    // Specify the columns we need; this is the 'projection' parameter passed to query().
    private static final String[] WSPR_COLUMNS = {
            // Fully qualify the id with a table name in case the content provider joins the
            // gridsquare & wspr tables in the background--both have an _id column.
            WsprNetContract.SignalReportEntry.TABLE_NAME + "." + WsprNetContract.SignalReportEntry._ID,
            WsprNetContract.SignalReportEntry.COLUMN_TIMESTAMPTEXT,
            WsprNetContract.SignalReportEntry.COLUMN_TX_CALLSIGN,
            WsprNetContract.SignalReportEntry.COLUMN_TX_FREQ_MHZ, WsprNetContract.SignalReportEntry.COLUMN_RX_SNR,
            WsprNetContract.SignalReportEntry.COLUMN_RX_DRIFT,
            WsprNetContract.SignalReportEntry.COLUMN_TX_GRIDSQUARE,
            WsprNetContract.SignalReportEntry.COLUMN_TX_POWER, WsprNetContract.SignalReportEntry.COLUMN_RX_CALLSIGN,
            WsprNetContract.SignalReportEntry.COLUMN_RX_GRIDSQUARE,
            WsprNetContract.SignalReportEntry.COLUMN_DISTANCE, WsprNetContract.SignalReportEntry.COLUMN_AZIMUTH };

    // These indices are tied to WSPR_COLUMNS.  If WSPR_COLUMNS changes, these must change.
    public static final int COL_WSPR_ID = 0;
    public static final int COL_WSPR_TIMESTAMP = 1;
    public static final int COL_WSPR_TX_CALLSIGN = 2;
    public static final int COL_WSPR_TX_FREQ_MHZ = 3;
    public static final int COL_WSPR_RX_SNR = 4;
    public static final int COL_WSPR_RX_DRIFT = 5;
    public static final int COL_WSPR_TX_GRIDSQUARE = 6;
    public static final int COL_WSPR_TX_POWER = 7;
    public static final int COL_WSPR_RX_CALLSIGN = 8;
    public static final int COL_WSPR_RX_GRIDSQUARE = 9;
    public static final int COL_WSPR_DISTANCE = 10;
    public static final int COL_WSPR_AZIMUTH = 11;
    public static final int COL_WSPR_CITY_NAME = 12;
    public static final int COL_WSPR_COUNTRY_NAME = 13;
    public static final int COL_WSPR_COORD_LAT = 14;
    public static final int COL_WSPR_COORD_LONG = 15;

    /**
     * A callback interface that all activities containing this fragment must
     * implement. This mechanism allows activities to be notified of item
     * selections.
     */
    public interface Callback {
        /**
         * DetailFragmentCallback for when an item has been selected.
         */
        public void onItemSelected(String timestamp);
    }

    public WsprFragment() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Enable this fragment to handle menu events.
        setHasOptionsMenu(true);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.wsprfragment, menu);
        if (BuildConfig.DEBUG == true) {
            menu.findItem(R.id.action_debug).setVisible(true);
            menu.findItem(R.id.action_debug).setEnabled(true);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        // The action_debug menu (refresh, dump_db, clear_db) is only turned on for BuildConfig.DEBUG.
        if (id == R.id.action_refresh) {
            updateWspr();
            return true;
        }
        if (id == R.id.action_dumpdb) {
            Utility.exportDB(getActivity());
            return true;
        }
        if (id == R.id.action_cleardb) {
            Utility.deleteAllRecords(getActivity());
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (mPosition != ListView.INVALID_POSITION) {
            outState.putInt(SELECTED_KEY, mPosition);
        }
        super.onSaveInstanceState(outState);
    }

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

        // The ArrayAdapter will take data from a source and
        // use it to populate the ListView it's attached to.
        mWsprAdapter = new WsprAdapter(getActivity(), null, 0);

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

        // Get a reference to the grid/call header
        mTVGridCallHeader = (TextView) rootView.findViewById(R.id.textview_list_header_gridsquare);

        // Get a reference to the ListView, and attach this adapter to it.
        mListView = (ListView) rootView.findViewById(R.id.listview_wspr);
        mListView.setAdapter(mWsprAdapter);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                Cursor cursor = mWsprAdapter.getCursor();
                if (cursor != null && cursor.moveToPosition(position)) {
                    ((Callback) getActivity()).onItemSelected(cursor.getString(COL_WSPR_ID)); // TODO: change to record ID!!
                }
                mPosition = position;
            }
        });

        // Get previously-selected position from bundle, restore list selection during onLoadFinished.
        if (savedInstanceState != null && savedInstanceState.containsKey(SELECTED_KEY)) {
            mPosition = savedInstanceState.getInt(SELECTED_KEY);
        }
        mWsprAdapter.setUseDualPane(mDualPane);
        return rootView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        getLoaderManager().initLoader(WSPR_LOADER, null, this);
        super.onActivityCreated(savedInstanceState);
    }

    private void updateWspr() {
        // invoke the sync adapter service
        WsprNetViewerSyncAdapter.syncImmediately(getActivity());
    }

    @Override
    public void onResume() {
        super.onResume();
        boolean filterOn = Utility.isFiltered(getActivity());
        // Restart the loader if some of the preferences have changed.
        if (filterOn // and filter settings have changed
                && (((mFilterTxCallsign != null)
                        && !mFilterTxCallsign.equals((Utility.getFilterCallsign(getActivity(), true))))
                        || ((mFilterRxCallsign != null)
                                && !mFilterRxCallsign.equals((Utility.getFilterCallsign(getActivity(), false))))
                        || ((mFilterTxGridsquare != null)
                                && !mFilterTxGridsquare.equals((Utility.getFilterGridsquare(getActivity(), true))))
                        || ((mFilterRxGridsquare != null)
                                && !mFilterRxGridsquare.equals((Utility.getFilterGridsquare(getActivity(), false))))
                        || (mFilterAnd != Utility.isFilterAnd(getActivity())))
                || (mFiltered != Utility.isFiltered(getActivity())) // or filter on/off has changed
                || (mWsprAdapter.mainDisplayFormat != Utility.getMainDisplayPreference(getActivity())) // or layout has changed
        ) {
            mLastNumItems = -1; // reset so that notification will appear
            getLoaderManager().restartLoader(WSPR_LOADER, null, this);
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created.  This
        // fragment only uses one loader, so we don't care about checking the id.

        // TODO: Filter the query to only return data from today and the previous N days.
        //        Calendar cal = Calendar.getInstance();
        //        cal.setTime(new Date());
        //        cal.add(Calendar.DATE, -7); // N= 7
        //        Date cutoffDate = cal.getTime();
        //        String startTimestamp = WsprNetContract.getDbTimestampString(cutoffDate);

        // Sort order:  Descending, by timestamp.
        String sortOrder = WsprNetContract.SignalReportEntry.COLUMN_TIMESTAMPTEXT + " DESC";
        String txCall = Utility.getFilterCallsign(getActivity(), true),
                rxCall = Utility.getFilterCallsign(getActivity(), false);
        String txGridsquare = Utility.getFilterGridsquare(getActivity(), true),
                rxGridsquare = Utility.getFilterGridsquare(getActivity(), false);
        txCall = Utility.filterCleanupForSQL(txCall);
        rxCall = Utility.filterCleanupForSQL(rxCall);
        txGridsquare = Utility.filterCleanupForSQL(txGridsquare);
        rxGridsquare = Utility.filterCleanupForSQL(rxGridsquare);
        mSelection = "";
        if (Utility.isFiltered(getActivity())) {
            // When adding filters, be sure to update onResume(), and save the preference value below, too.
            String prefAndOr = Utility.isFilterAnd(getActivity()) ? " and " : " or ";
            String sAndOr = " ";
            if (txCall.length() > 0) {
                sAndOr = (mSelection.length() > 0) ? prefAndOr : "";
                mSelection += sAndOr + "(" + WsprNetContract.SignalReportEntry.COLUMN_TX_CALLSIGN + " like '"
                        + txCall + "')";
            }
            if (rxCall.length() > 0) {
                sAndOr = (mSelection.length() > 0) ? prefAndOr : "";
                mSelection += sAndOr + "(" + WsprNetContract.SignalReportEntry.COLUMN_RX_CALLSIGN + " like '"
                        + rxCall + "')";
            }
            if (txGridsquare.length() > 0) {
                sAndOr = (mSelection.length() > 0) ? prefAndOr : "";
                mSelection += sAndOr + "(" + WsprNetContract.SignalReportEntry.COLUMN_TX_GRIDSQUARE + " like '"
                        + txGridsquare + "')";
            }
            if (rxGridsquare.length() > 0) {
                sAndOr = (mSelection.length() > 0) ? prefAndOr : "";
                mSelection += sAndOr + "(" + WsprNetContract.SignalReportEntry.COLUMN_RX_GRIDSQUARE + " like '"
                        + rxGridsquare + "')";
            }
            // Examples of resulting 'selection' clause:
            //   tx_callsign like 'D%'
            //   (tx_gridsquare like 'D%') and (rx_callsign like 'N%')
            //   (tx_gridsquare like 'D%') or (rx_callsign like 'N%')
            if (mSelection.length() > 0) {
                // Remind user that items are filtered, in case the result is not what they expect.
                Toast.makeText(getActivity(), getActivity().getString(R.string.toast_filter_items),
                        Toast.LENGTH_LONG).show();
            }
        }

        int mainDisplayPreference = Utility.getMainDisplayPreference(getActivity());
        if (mWsprAdapter.mainDisplayFormat != mainDisplayPreference) {
            // Update the gridsquare/callsign heading text based on the display layout.
            switch (mainDisplayPreference) {
            case Utility.MAIN_DISPLAY_CALLSIGN: // fit everything into 2 lines of display
                mTVGridCallHeader.setText(getActivity().getString(R.string.callsign));
                mTVGridCallHeader.setTextColor(getResources().getColor(R.color.wspr_brown));
                break;
            case Utility.MAIN_DISPLAY_GRIDCALL: // fit everything into 4 lines of display
                String g = getActivity().getString(R.string.grid);
                String c = getActivity().getString(R.string.call);
                Spannable s = new SpannableString(g + "/" + c);
                // In "Grid/Call", make "grid" black, "Call" brown, and "/" somewhere between.
                // Release: For "Grid", set the span to be 1 extra character.
                s.setSpan(new ForegroundColorSpan(Color.BLACK), 0, g.length() + 0,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                // Debug: For the "/", set the span to be 2 characters; it will won't display in the color if the span is only 1 character.
                s.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.wspr_brown2)), g.length() + 0,
                        g.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                s.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.wspr_brown)), g.length() + 1,
                        s.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                mTVGridCallHeader.setText(s);
                break;
            case Utility.MAIN_DISPLAY_GRIDSQUARE: // fit everything into 2 lines of display
            default:
                mTVGridCallHeader.setText(getActivity().getString(R.string.gridsquare));
                mTVGridCallHeader.setTextColor(Color.BLACK);
            }

        }

        // Save some of the preferences to detect if they've changed in onResume().
        mGridsquare = Utility.getPreferredGridsquare(getActivity());
        mWsprAdapter.mainDisplayFormat = mainDisplayPreference;
        mFilterTxCallsign = Utility.getFilterCallsign(getActivity(), true);
        mFilterRxCallsign = Utility.getFilterCallsign(getActivity(), false);
        mFilterTxGridsquare = Utility.getFilterGridsquare(getActivity(), true);
        mFilterRxGridsquare = Utility.getFilterGridsquare(getActivity(), false);
        mFilterAnd = Utility.isFilterAnd(getActivity());
        mFiltered = Utility.isFiltered(getActivity());
        Uri wsprUri;
        wsprUri = WsprNetContract.SignalReportEntry.buildWspr();

        // Create and return a CursorLoader that will take care of creating a Cursor for the data being displayed.
        return new CursorLoader(getActivity(), // context
                wsprUri, // URI
                WSPR_COLUMNS, // String[] projection
                mSelection, // String selection
                null, // String[] selectionArgs
                sortOrder // String sortOrder
        );
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        Integer n = data.getCount();
        Log.v(LOG_TAG, "WsprFragment: onLoadFinished:  data.getCount()= " + n.toString());
        if ((n == 0) && (mLastNumItems != 0)) {
            // only do this once if there are no items
            Toast.makeText(getActivity(), getActivity().getString(R.string.toast_no_items), Toast.LENGTH_LONG)
                    .show();
            WsprNetViewerSyncAdapter.syncImmediately(getActivity());
        } else if (n > 0) {
            String msg = getActivity().getString(R.string.toast_num_items, n);
            Toast.makeText(getActivity(), msg, Toast.LENGTH_LONG).show();
        }
        mWsprAdapter.swapCursor(data);
        if (mPosition != ListView.INVALID_POSITION) {
            mListView.smoothScrollToPosition(mPosition);
        }
        mLastNumItems = n;

    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        mWsprAdapter.swapCursor(null);
    }

    public void setDualPane(boolean dualPane) {
        mDualPane = dualPane;
        if (mWsprAdapter != null) {
            mWsprAdapter.setUseDualPane(mDualPane);
        }
    }
}