com.nadmm.airports.afd.AirportDetailsFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.nadmm.airports.afd.AirportDetailsFragment.java

Source

/*
 * FlightIntel for Pilots
 *
 * Copyright 2011-2016 Nadeem Hasan <nhasan@nadmm.com>
 *
 * 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.nadmm.airports.afd;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.nadmm.airports.Application;
import com.nadmm.airports.FragmentBase;
import com.nadmm.airports.PreferencesActivity;
import com.nadmm.airports.R;
import com.nadmm.airports.aeronav.ClassBService;
import com.nadmm.airports.aeronav.DafdService;
import com.nadmm.airports.aeronav.DtppActivity;
import com.nadmm.airports.data.DatabaseManager;
import com.nadmm.airports.data.DatabaseManager.Aff3;
import com.nadmm.airports.data.DatabaseManager.Airports;
import com.nadmm.airports.data.DatabaseManager.Attendance;
import com.nadmm.airports.data.DatabaseManager.Awos1;
import com.nadmm.airports.data.DatabaseManager.Dafd;
import com.nadmm.airports.data.DatabaseManager.DafdCycle;
import com.nadmm.airports.data.DatabaseManager.Dtpp;
import com.nadmm.airports.data.DatabaseManager.LocationColumns;
import com.nadmm.airports.data.DatabaseManager.Remarks;
import com.nadmm.airports.data.DatabaseManager.Runways;
import com.nadmm.airports.data.DatabaseManager.Tower1;
import com.nadmm.airports.data.DatabaseManager.Tower3;
import com.nadmm.airports.data.DatabaseManager.Tower6;
import com.nadmm.airports.data.DatabaseManager.Tower7;
import com.nadmm.airports.data.DatabaseManager.Tower8;
import com.nadmm.airports.data.DatabaseManager.Wxs;
import com.nadmm.airports.donate.DonateActivity;
import com.nadmm.airports.notams.AirportNotamActivity;
import com.nadmm.airports.tfr.TfrListActivity;
import com.nadmm.airports.utils.ClassBUtils;
import com.nadmm.airports.utils.CursorAsyncTask;
import com.nadmm.airports.utils.DataUtils;
import com.nadmm.airports.utils.FormatUtils;
import com.nadmm.airports.utils.GeoUtils;
import com.nadmm.airports.utils.NetworkUtils;
import com.nadmm.airports.utils.SystemUtils;
import com.nadmm.airports.utils.TimeUtils;
import com.nadmm.airports.utils.UiUtils;
import com.nadmm.airports.wx.Metar;
import com.nadmm.airports.wx.MetarService;
import com.nadmm.airports.wx.NearbyWxCursor;
import com.nadmm.airports.wx.NoaaService;
import com.nadmm.airports.wx.WxDetailActivity;
import com.nadmm.airports.wx.WxUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.TimeZone;

public final class AirportDetailsFragment extends FragmentBase {

    private final HashSet<TextView> mAwosViews = new HashSet<>();
    private final HashSet<TextView> mRunwayViews = new HashSet<>();
    private final int MAX_WX_STATIONS = 5;

    private BroadcastReceiver mBcastReceiver;
    private IntentFilter mBcastFilter;
    private Location mLocation;
    private float mDeclination;
    private String mIcaoCode;
    private int mRadius;
    private int mWxUpdates = 0;
    private String mHome;
    private String mSiteNumber;

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

        mBcastFilter = new IntentFilter();
        mBcastFilter.addAction(NoaaService.ACTION_GET_METAR);
        mBcastFilter.addAction(DafdService.ACTION_GET_AFD);
        mBcastFilter.addAction(ClassBService.ACTION_GET_CLASSB_GRAPHIC);
        mBcastReceiver = new BroadcastReceiver() {

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

        };

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
        mRadius = Integer.valueOf(prefs.getString(PreferencesActivity.KEY_LOCATION_NEARBY_RADIUS, "30"));
        mHome = prefs.getString(PreferencesActivity.KEY_HOME_AIRPORT, "");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mAwosViews.clear();
        View view = inflater.inflate(R.layout.airport_detail_view, container, false);
        return createContentView(view);
    }

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

        setActionBarTitle("Airport Data", "");

        Bundle args = getArguments();
        String siteNumber = args.getString(Airports.SITE_NUMBER);
        setBackgroundTask(new AirportDetailsTask()).execute(siteNumber);
    }

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

        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(getActivity());
        bm.registerReceiver(mBcastReceiver, mBcastFilter);
        requestMetars(false);
    }

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

        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(getActivity());
        bm.unregisterReceiver(mBcastReceiver);
    }

    @Override
    public boolean isRefreshable() {
        return true;
    }

    @Override
    public void requestDataRefresh() {
        requestMetars(true);
    }

    protected void handleBroadcast(Intent intent) {
        String action = intent.getAction();
        if (action.equals(MetarService.ACTION_GET_METAR)) {
            Metar metar = (Metar) intent.getSerializableExtra(NoaaService.RESULT);
            if (metar != null && metar.rawText != null) {
                showWxInfo(metar);
                if (isRefreshing()) {
                    setRefreshing(false);
                }
            }
        } else if (action.equals(DafdService.ACTION_GET_AFD)) {
            String path = intent.getStringExtra(DafdService.PDF_PATH);
            if (path != null) {
                SystemUtils.startPDFViewer(getActivity(), path);
            }
        } else if (action.equals(ClassBService.ACTION_GET_CLASSB_GRAPHIC)) {
            String path = intent.getStringExtra(ClassBService.PDF_PATH);
            if (path != null) {
                SystemUtils.startPDFViewer(getActivity(), path);
            }
        }
    }

    protected void getAfdPage(String afdCycle, String pdfName) {
        Intent service = new Intent(getActivity(), DafdService.class);
        service.setAction(DafdService.ACTION_GET_AFD);
        service.putExtra(DafdService.CYCLE_NAME, afdCycle);
        service.putExtra(DafdService.PDF_NAME, pdfName);
        getActivity().startService(service);
    }

    protected void getClassBGraphic(String faaCode) {
        Intent service = new Intent(getActivity(), ClassBService.class);
        service.setAction(ClassBService.ACTION_GET_CLASSB_GRAPHIC);
        service.putExtra(Airports.FAA_CODE, faaCode);
        getActivity().startService(service);
    }

    protected void showDetails(Cursor[] result) {
        Cursor apt = result[0];

        showAirportTitle(apt);

        showCommunicationsDetails(result);
        showRunwayDetails(result);
        showRemarks(result);
        showAwosDetails(result);
        showHomeDistance(result);
        showNearbyFacilities(result);
        showNotamAndTfr();
        showCharts(result);
        showOperationsDetails(result);
        showAeroNavDetails(result);
        showServicesDetails(result);
        showOtherDetails();

        requestMetars(false);

        setFragmentContentShown(true);
    }

    protected void showCommunicationsDetails(Cursor[] result) {
        Cursor apt = result[0];

        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_comm_layout);

        String ctaf = apt.getString(apt.getColumnIndex(Airports.CTAF_FREQ));
        addRow(layout, "CTAF", !ctaf.isEmpty() ? ctaf : "None");
        String unicom = apt.getString(apt.getColumnIndex(Airports.UNICOM_FREQS));
        if (!unicom.isEmpty()) {
            addRow(layout, "Unicom", unicom);
        }

        Cursor twr3 = result[4];
        if (twr3.moveToFirst()) {
            HashMap<String, ArrayList<Float>> freqMap = new HashMap<>();
            do {
                String freqUse = twr3.getString(twr3.getColumnIndex(Tower3.MASTER_AIRPORT_FREQ_USE));
                String value = twr3.getString(twr3.getColumnIndex(Tower3.MASTER_AIRPORT_FREQ));
                if (freqUse.contains("LCL") || freqUse.contains("LC/P")) {
                    addFrequencyToMap(freqMap, "Tower", value);
                } else if (freqUse.contains("GND")) {
                    addFrequencyToMap(freqMap, "Ground", value);
                } else if (freqUse.contains("ATIS")) {
                    addFrequencyToMap(freqMap, "ATIS", value);
                }
            } while (twr3.moveToNext());

            if (freqMap.size() > 0) {
                for (String key : freqMap.keySet()) {
                    ArrayList<Float> freqs = freqMap.get(key);
                    // Do not show here if multiple frequencies are listed
                    if (freqs.size() == 1) {
                        addRow(layout, key, FormatUtils.formatFreq(freqs.get(0)));
                    }
                }
            }
        }

        addClickableRow(layout, "More...", CommunicationsFragment.class, getArguments());
    }

    protected void addFrequencyToMap(HashMap<String, ArrayList<Float>> freqMap, String key, String value) {
        ArrayList<Float> freqs = freqMap.get(key);
        if (freqs == null) {
            freqs = new ArrayList<>();
        }
        int i = 0;
        while (i < value.length()) {
            char c = value.charAt(i);
            if ((c >= '0' && c <= '9') || c == '.') {
                ++i;
                continue;
            }
            value = value.substring(0, i);
            break;
        }
        Float freq = Float.valueOf(value);
        if (freq <= 136 && !freqs.contains(freq)) {
            // Add VHF frequencies only
            freqs.add(freq);
        }
        freqMap.put(key, freqs);
    }

    protected void showRunwayDetails(Cursor[] result) {
        LinearLayout rwyLayout = (LinearLayout) findViewById(R.id.detail_rwy_layout);
        LinearLayout heliLayout = (LinearLayout) findViewById(R.id.detail_heli_layout);
        TextView tv;
        int rwyNum = 0;
        int heliNum = 0;

        Cursor rwy = result[1];
        if (rwy.moveToFirst()) {
            do {
                String rwyId = rwy.getString(rwy.getColumnIndex(Runways.RUNWAY_ID));
                if (rwyId.startsWith("H")) {
                    // This is a helipad
                    addRunwayRow(heliLayout, rwy);
                    ++heliNum;
                } else {
                    // This is a runway
                    addRunwayRow(rwyLayout, rwy);
                    ++rwyNum;
                }
            } while (rwy.moveToNext());
        }

        if (rwyNum == 0) {
            // No runways so remove the section
            tv = (TextView) findViewById(R.id.detail_rwy_label);
            tv.setVisibility(View.GONE);
            rwyLayout.setVisibility(View.GONE);
        }
        if (heliNum == 0) {
            // No helipads so remove the section
            tv = (TextView) findViewById(R.id.detail_heli_label);
            tv.setVisibility(View.GONE);
            heliLayout.setVisibility(View.GONE);
        }
    }

    protected void showRemarks(Cursor[] result) {
        int row = 0;
        TextView label = (TextView) findViewById(R.id.detail_remarks_label);
        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_remarks_layout);
        Cursor rmk = result[2];
        if (rmk.moveToFirst()) {
            do {
                String remark = rmk.getString(rmk.getColumnIndex(Remarks.REMARK_TEXT));
                addBulletedRow(layout, remark);
                ++row;
            } while (rmk.moveToNext());
        }

        Cursor twr1 = result[3];
        Cursor twr7 = result[5];
        if (twr1.moveToFirst()) {
            String facilityType = twr1.getString(twr1.getColumnIndex(Tower1.FACILITY_TYPE));
            if (facilityType.equals("NON-ATCT") && twr7.getCount() == 0) {
                // Show remarks, if any, since there are no frequencies listed
                Cursor twr6 = result[6];
                if (twr6.moveToFirst()) {
                    do {
                        String remark = twr6.getString(twr6.getColumnIndex(Tower6.REMARK_TEXT));
                        addBulletedRow(layout, remark);
                        ++row;
                    } while (twr6.moveToNext());
                }
            }
        }

        if (row == 0) {
            label.setVisibility(View.GONE);
            layout.setVisibility(View.GONE);
        }
    }

    protected void showAwosDetails(Cursor[] result) {
        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_awos_layout);
        Cursor awos1 = result[7];
        if (awos1.moveToFirst()) {
            do {
                if (awos1.getPosition() == MAX_WX_STATIONS) {
                    break;
                }
                String icaoCode = awos1.getString(awos1.getColumnIndex(Wxs.STATION_ID));
                String sensorId = awos1.getString(awos1.getColumnIndex(Awos1.WX_SENSOR_IDENT));
                if (icaoCode == null || icaoCode.isEmpty()) {
                    icaoCode = "K" + sensorId;
                }
                String type = awos1.getString(awos1.getColumnIndex(Awos1.WX_SENSOR_TYPE));
                String freq = awos1.getString(awos1.getColumnIndex(Awos1.STATION_FREQUENCY));
                if (freq == null || freq.isEmpty()) {
                    freq = awos1.getString(awos1.getColumnIndex(Awos1.SECOND_STATION_FREQUENCY));
                }
                String phone = awos1.getString(awos1.getColumnIndex(Awos1.STATION_PHONE_NUMBER));
                String name = awos1.getString(awos1.getColumnIndex(Wxs.STATION_NAME));
                float distance = awos1.getFloat(awos1.getColumnIndex("DISTANCE"));
                float bearing = awos1.getFloat(awos1.getColumnIndex("BEARING"));

                final Bundle extras = new Bundle();
                extras.putString(NoaaService.STATION_ID, icaoCode);
                extras.putString(Awos1.WX_SENSOR_IDENT, sensorId);

                Runnable runnable = new Runnable() {

                    @Override
                    public void run() {
                        cacheMetars();
                        Intent intent = new Intent(getActivity(), WxDetailActivity.class);
                        intent.putExtras(extras);
                        startActivity(intent);
                    }
                };
                addAwosRow(layout, icaoCode, name, type, freq, phone, distance, bearing, runnable);
            } while (awos1.moveToNext());

            if (!awos1.isAfterLast()) {
                Intent intent = new Intent(getActivity(), NearbyWxActivity.class);
                intent.putExtra(LocationColumns.LOCATION, mLocation);
                intent.putExtra(LocationColumns.RADIUS, mRadius);
                addClickableRow(layout, "More...", intent);
            }
        } else {
            addRow(layout, "No Wx stations found nearby.");
        }
    }

    protected void showHomeDistance(Cursor[] result) {
        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_home_layout);
        Cursor home = result[14];
        if (home == null) {
            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    Intent prefs = new Intent(getActivity(), PreferencesActivity.class);
                    startActivity(prefs);
                    getActivity().finish();
                }
            };
            addClickableRow(layout, "Tap here to set home airport", runnable);
        } else if (home.moveToFirst()) {
            String siteNumber = home.getString(home.getColumnIndex(Airports.SITE_NUMBER));
            if (siteNumber.equals(mSiteNumber)) {
                addRow(layout, mHome + " is your home airport");
            } else {
                double lat = home.getDouble(home.getColumnIndex(Airports.REF_LATTITUDE_DEGREES));
                double lon = home.getDouble(home.getColumnIndex(Airports.REF_LONGITUDE_DEGREES));
                float[] results = new float[3];
                Location.distanceBetween(lat, lon, mLocation.getLatitude(), mLocation.getLongitude(), results);
                float distance = results[0] / GeoUtils.METERS_PER_NAUTICAL_MILE;
                if (distance >= 100) {
                    distance = Math.round(distance);
                }
                int initialBearing = Math.round((results[1] + mDeclination + 360) % 360);
                int finalBearing = Math.round((results[2] + mDeclination + 360) % 360);

                addRow(layout, "Distance from " + mHome, String.format(Locale.US, "%s %s",
                        FormatUtils.formatNauticalMiles(distance), GeoUtils.getCardinalDirection(initialBearing)));
                addRow(layout, "Initial bearing", FormatUtils.formatDegrees(initialBearing) + " M");
                if (Math.abs(finalBearing - initialBearing) >= 10) {
                    addRow(layout, "Final bearing", FormatUtils.formatDegrees(finalBearing) + " M");
                }
            }
        } else {
            addRow(layout, "Home airport '" + mHome + "' not found");
        }
    }

    protected void showNearbyFacilities(Cursor[] result) {
        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_nearby_layout);

        Bundle args = new Bundle();
        args.putParcelable(LocationColumns.LOCATION, mLocation);
        args.putInt(LocationColumns.RADIUS, mRadius);
        args.putString(Airports.ICAO_CODE, mIcaoCode);
        addClickableRow(layout, "Airports", NearbyAirportsFragment.class, args);
        addClickableRow(layout, "FSS outlets", FssCommFragment.class, getArguments());
        addClickableRow(layout, "Navaids", NearbyNavaidsFragment.class, getArguments());
    }

    private void showNotamAndTfr() {
        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_notam_faa_layout);
        Intent intent = new Intent(getActivity(), AirportNotamActivity.class);
        intent.putExtra(Airports.SITE_NUMBER, mSiteNumber);
        addClickableRow(layout, "View NOTAMs", intent);
        intent = new Intent(getActivity(), TfrListActivity.class);
        addClickableRow(layout, "View TFRs", intent);
    }

    private void showCharts(Cursor[] result) {
        Cursor apt = result[0];
        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_charts_layout);
        String sectional = apt.getString(apt.getColumnIndex(Airports.SECTIONAL_CHART));
        if (sectional == null || sectional.isEmpty()) {
            sectional = "N/A";
        }
        String lat = apt.getString(apt.getColumnIndex(Airports.REF_LATTITUDE_DEGREES));
        String lon = apt.getString(apt.getColumnIndex(Airports.REF_LONGITUDE_DEGREES));
        if (!lat.isEmpty() && !lon.isEmpty()) {
            // Link to the sectional at VFRMAP if location is available
            Uri uri = Uri.parse(
                    String.format(Locale.US, "http://vfrmap.com/?type=vfrc&lat=%s&lon=%s&zoom=10", lat, lon));
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            addClickableRow(layout, sectional + " Sectional VFR", null, intent);
            uri = Uri.parse(
                    String.format(Locale.US, "http://vfrmap.com/?type=ifrlc&lat=%s&lon=%s&zoom=10", lat, lon));
            intent = new Intent(Intent.ACTION_VIEW, uri);
            addClickableRow(layout, "Low-altitude IFR", intent);
            uri = Uri
                    .parse(String.format(Locale.US, "http://vfrmap.com/?type=ehc&lat=%s&lon=%s&zoom=10", lat, lon));
            intent = new Intent(Intent.ACTION_VIEW, uri);
            addClickableRow(layout, "High-altitude IFR", intent);
        } else {
            addRow(layout, "Sectional chart", sectional);
        }

        Cursor classb = result[15];
        if (classb.moveToFirst()) {
            HashSet<String> seen = new HashSet<>();
            do {
                String faaCode = classb.getString(classb.getColumnIndex(Airports.FAA_CODE));
                String classBName = ClassBUtils.getClassBName(faaCode);
                if (!seen.contains(classBName)) {
                    View row = addClickableRow(layout, classBName + " Class B airspace", null);
                    row.setTag(faaCode);
                    row.setOnClickListener(new OnClickListener() {

                        @Override
                        public void onClick(View v) {
                            String faaCode = (String) v.getTag();
                            getClassBGraphic(faaCode);
                        }
                    });
                    seen.add(classBName);
                }
            } while (classb.moveToNext());
        }
    }

    protected void showOperationsDetails(Cursor[] result) {
        Cursor apt = result[0];
        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_operations_layout);
        String use = apt.getString(apt.getColumnIndex(Airports.FACILITY_USE));
        addRow(layout, "Operation", DataUtils.decodeFacilityUse(use));
        String faaCode = apt.getString(apt.getColumnIndex(Airports.FAA_CODE));
        addRow(layout, "FAA code", faaCode);
        String timezoneId = apt.getString(apt.getColumnIndex(Airports.TIMEZONE_ID));
        if (!timezoneId.isEmpty()) {
            TimeZone tz = TimeZone.getTimeZone(timezoneId);
            addRow(layout, "Local time zone", TimeUtils.getTimeZoneAsString(tz));
        }
        String activation = apt.getString(apt.getColumnIndex(Airports.ACTIVATION_DATE));
        if (!activation.isEmpty()) {
            addRow(layout, "Activation date", activation);
        }
        Cursor twr8 = result[9];
        if (twr8.moveToFirst()) {
            String airspace = twr8.getString(twr8.getColumnIndex(Tower8.AIRSPACE_TYPES));
            String value = DataUtils.decodeAirspace(airspace);
            String hours = twr8.getString(twr8.getColumnIndex(Tower8.AIRSPACE_HOURS));
            addRow(layout, "Airspace", value, hours);
        }
        String tower = apt.getString(apt.getColumnIndex(Airports.TOWER_ON_SITE));
        addRow(layout, "Control tower", tower.equals("Y") ? "Yes" : "No");
        String windIndicator = apt.getString(apt.getColumnIndex(Airports.WIND_INDICATOR));
        addRow(layout, "Wind indicator", DataUtils.decodeWindIndicator(windIndicator));
        String circle = apt.getString(apt.getColumnIndex(Airports.SEGMENTED_CIRCLE));
        addRow(layout, "Segmented circle", circle.equals("Y") ? "Yes" : "No");
        String beacon = apt.getString(apt.getColumnIndex(Airports.BEACON_COLOR));
        addRow(layout, "Beacon", DataUtils.decodeBeacon(beacon));
        String lighting;
        lighting = apt.getString(apt.getColumnIndex(Airports.LIGHTING_SCHEDULE));
        if (!lighting.isEmpty()) {
            addRow(layout, "Airport lighting", lighting);
        }
        try {
            lighting = apt.getString(apt.getColumnIndex(Airports.BEACON_LIGHTING_SCHEDULE));
            if (!lighting.isEmpty()) {
                addRow(layout, "Beacon lighting", lighting);
            }
        } catch (Exception ignored) {
        }
        String landingFee = apt.getString(apt.getColumnIndex(Airports.LANDING_FEE));
        addRow(layout, "Landing fee", landingFee.equals("Y") ? "Yes" : "No");
        String dir = apt.getString(apt.getColumnIndex(Airports.MAGNETIC_VARIATION_DIRECTION));
        if (!dir.isEmpty()) {
            int variation = apt.getInt(apt.getColumnIndex(Airports.MAGNETIC_VARIATION_DEGREES));
            String year = apt.getString(apt.getColumnIndex(Airports.MAGNETIC_VARIATION_YEAR));
            if (!year.isEmpty()) {
                addRow(layout, "Magnetic variation",
                        String.format(Locale.US, "%d\u00B0 %s (%s)", variation, dir, year));
            } else {
                addRow(layout, "Magnetic variation", String.format(Locale.US, "%d\u00B0 %s", variation, dir));
            }
        } else {
            int variation = Math.round(GeoUtils.getMagneticDeclination(mLocation));
            dir = (variation >= 0) ? "W" : "E";
            addRow(layout, "Magnetic variation",
                    String.format(Locale.US, "%d\u00B0 %s (actual)", Math.abs(variation), dir));
        }
        String intlEntry = apt.getString(apt.getColumnIndex(Airports.INTL_ENTRY_AIRPORT));
        if (intlEntry != null && intlEntry.equals("Y")) {
            addRow(layout, "International entry", "Yes");
        }
        String customs = apt.getString(apt.getColumnIndex(Airports.CUSTOMS_LANDING_RIGHTS_AIRPORT));
        if (customs != null && customs.equals("Y")) {
            addRow(layout, "Customs landing rights", "Yes");
        }
        String jointUse = apt.getString(apt.getColumnIndex(Airports.CIVIL_MILITARY_JOINT_USE));
        if (jointUse != null && jointUse.equals("Y")) {
            addRow(layout, "Civil/military joint use", "Yes");
        }
        String militaryRights = apt.getString(apt.getColumnIndex(Airports.MILITARY_LANDING_RIGHTS));
        if (militaryRights != null && militaryRights.equals("Y")) {
            addRow(layout, "Military landing rights", "Yes");
        }
        String medical = apt.getString(apt.getColumnIndex(Airports.MEDICAL_USE));
        if (medical != null && medical.equals("Y")) {
            addRow(layout, "Medical use", "Yes");
        }
    }

    protected void showAeroNavDetails(Cursor[] result) {
        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_aeronav_layout);
        if (Application.sDonationDone) {
            Cursor apt = result[0];
            String siteNumber = apt.getString(apt.getColumnIndex(Airports.SITE_NUMBER));
            Cursor cycle = result[12];
            if (cycle != null && cycle.moveToFirst()) {
                String afdCycle = cycle.getString(cycle.getColumnIndex(DafdCycle.AFD_CYCLE));
                Cursor dafd = result[13];
                if (dafd.moveToFirst()) {
                    String pdfName = dafd.getString(dafd.getColumnIndex(Dafd.PDF_NAME));
                    View row = addClickableRow(layout, "A/FD page", null);
                    row.setTag(R.id.DAFD_CYCLE, afdCycle);
                    row.setTag(R.id.DAFD_PDF_NAME, pdfName);
                    row.setOnClickListener(new OnClickListener() {

                        @Override
                        public void onClick(View v) {
                            String afdCycle = (String) v.getTag(R.id.DAFD_CYCLE);
                            String pdfName = (String) v.getTag(R.id.DAFD_PDF_NAME);
                            getAfdPage(afdCycle, pdfName);
                        }
                    });
                } else {
                    addRow(layout, "A/FD page is not available for this airport");
                }
            } else {
                addRow(layout, "d-A/FD data not found");
            }
            Cursor dtpp = result[11];
            if (dtpp != null) {
                if (dtpp.moveToFirst()) {
                    Intent intent = new Intent(getActivity(), DtppActivity.class);
                    intent.putExtra(Airports.SITE_NUMBER, siteNumber);
                    addClickableRow(layout, "Instrument procedures", intent);
                } else {
                    addRow(layout, "No instrument procedures available");
                }
            } else {
                addRow(layout, "d-TPP data not found");
            }
        } else {
            Intent intent = new Intent(getActivity(), DonateActivity.class);
            addClickableRow(layout, "Please donate to enable this section", intent);
        }
    }

    protected void showServicesDetails(Cursor[] result) {
        Cursor apt = result[0];
        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_services_layout);
        String fuelTypes = DataUtils.decodeFuelTypes(apt.getString(apt.getColumnIndex(Airports.FUEL_TYPES)));
        if (fuelTypes.isEmpty()) {
            fuelTypes = "No";
        }
        addRow(layout, "Fuel available", fuelTypes);
        String repair = apt.getString(apt.getColumnIndex(Airports.AIRFRAME_REPAIR_SERVICE));
        if (repair.isEmpty()) {
            repair = "No";
        }
        addRow(layout, "Airframe repair", repair);
        repair = apt.getString(apt.getColumnIndex(Airports.POWER_PLANT_REPAIR_SERVICE));
        if (repair.isEmpty()) {
            repair = "No";
        }
        addRow(layout, "Power plant repair", repair);
        addClickableRow(layout, "Other services", ServicesFragment.class, getArguments());
    }

    protected void showOtherDetails() {
        Bundle args = getArguments();
        LinearLayout layout = (LinearLayout) findViewById(R.id.detail_other_layout);
        addClickableRow(layout, "Ownership and contact", OwnershipFragment.class, args);
        addClickableRow(layout, "Aircraft operations", AircraftOpsFragment.class, args);
        addClickableRow(layout, "Additional remarks", RemarksFragment.class, args);
        addClickableRow(layout, "Attendance", AttendanceFragment.class, args);
        addClickableRow(layout, "Sunrise and sunset", AlmanacFragment.class, args);
    }

    protected void addAwosRow(LinearLayout layout, String id, String name, String type, String freq, String phone,
            float distance, float bearing, final Runnable runnable) {
        StringBuilder sb = new StringBuilder();
        sb.append(id);
        if (name != null && !name.isEmpty()) {
            sb.append(" - ");
            sb.append(name);
        }
        String label1 = sb.toString();

        sb.setLength(0);
        if (freq != null && !freq.isEmpty()) {
            try {
                sb.append(FormatUtils.formatFreq(Float.valueOf(freq)));
            } catch (NumberFormatException ignored) {
            }
        }
        String value1 = sb.toString();

        sb.setLength(0);
        sb.append(type);
        if (mIcaoCode.equals(id)) {
            sb.append(", On-site");
        } else {
            sb.append(String.format(Locale.US, ", %.1f NM %s", distance, GeoUtils.getCardinalDirection(bearing)));
        }
        String label2 = sb.toString();

        View row = addClickableRow(layout, label1, value1, label2, phone, runnable);

        TextView tv = (TextView) row.findViewById(R.id.item_label);
        tv.setTag(id);
        mAwosViews.add(tv);
        // Make phone number clickable
        tv = (TextView) row.findViewById(R.id.item_extra_value);
        if (tv.getText().length() > 0) {
            makeClickToCall(tv);
        }
    }

    protected void addRunwayRow(LinearLayout layout, Cursor c) {
        String siteNumber = c.getString(c.getColumnIndex(Runways.SITE_NUMBER));
        String runwayId = c.getString(c.getColumnIndex(Runways.RUNWAY_ID));
        int length = c.getInt(c.getColumnIndex(Runways.RUNWAY_LENGTH));
        int width = c.getInt(c.getColumnIndex(Runways.RUNWAY_WIDTH));
        String surfaceType = c.getString(c.getColumnIndex(Runways.SURFACE_TYPE));
        String baseId = c.getString(c.getColumnIndex(Runways.BASE_END_ID));
        String reciprocalId = c.getString(c.getColumnIndex(Runways.RECIPROCAL_END_ID));
        String baseRP = c.getString(c.getColumnIndex(Runways.BASE_END_RIGHT_TRAFFIC));
        String reciprocalRP = c.getString(c.getColumnIndex(Runways.RECIPROCAL_END_RIGHT_TRAFFIC));

        String rp = null;
        if (baseRP.equals("Y") && reciprocalRP.equals("Y")) {
            rp = " (RP)";
        } else if (baseRP.equals("Y")) {
            rp = " (RP " + baseId + ")";
        } else if (reciprocalRP.equals("Y")) {
            rp = " (RP " + reciprocalId + ")";
        }

        int heading = c.getInt(c.getColumnIndex(Runways.BASE_END_HEADING));
        if (heading > 0) {
            heading = (int) GeoUtils.applyDeclination(heading, mDeclination);
        } else {
            // Actual heading is not available, try to deduce it from runway id
            heading = DataUtils.getRunwayHeading(runwayId);
        }

        RelativeLayout row = (RelativeLayout) inflate(R.layout.runway_detail_item);

        TextView tv = (TextView) row.findViewById(R.id.runway_id);
        tv.setText(runwayId);
        UiUtils.setRunwayDrawable(getActivity(), tv, runwayId, length, heading);

        if (rp != null) {
            tv = (TextView) row.findViewById(R.id.runway_rp);
            tv.setText(rp);
            tv.setVisibility(View.VISIBLE);
        }

        tv = (TextView) row.findViewById(R.id.runway_size);
        tv.setText(
                String.format(Locale.US, "%s x %s", FormatUtils.formatFeet(length), FormatUtils.formatFeet(width)));

        tv = (TextView) row.findViewById(R.id.runway_surface);
        tv.setText(DataUtils.decodeSurfaceType(surfaceType));

        if (!runwayId.startsWith("H")) {
            // Save the textview and runway info for later use
            tv = (TextView) row.findViewById(R.id.runway_wind_info);
            Bundle tag = new Bundle();
            tag.putString(Runways.BASE_END_ID, baseId);
            tag.putString(Runways.RECIPROCAL_END_ID, reciprocalId);
            tag.putInt(Runways.BASE_END_HEADING, heading);
            tv.setTag(tag);
            mRunwayViews.add(tv);
        }

        Bundle args = new Bundle();
        args.putString(Runways.SITE_NUMBER, siteNumber);
        args.putString(Runways.RUNWAY_ID, runwayId);
        addClickableRow(layout, row, RunwaysFragment.class, args);
    }

    protected void cacheMetars() {
        requestMetars(NoaaService.ACTION_CACHE_METAR, false, false);
    }

    protected void requestMetars(boolean force) {
        boolean cacheOnly = !NetworkUtils.canDownloadData(getActivity());
        requestMetars(NoaaService.ACTION_GET_METAR, force, cacheOnly);
    }

    protected void requestMetars(String action, boolean force, boolean cacheOnly) {
        if (mAwosViews.size() == 0) {
            return;
        }

        ArrayList<String> stationIds = new ArrayList<>();
        for (TextView tv : mAwosViews) {
            String stationId = (String) tv.getTag();
            stationIds.add(stationId);
        }
        Intent service = new Intent(getActivity(), MetarService.class);
        service.setAction(action);
        service.putExtra(NoaaService.STATION_IDS, stationIds);
        service.putExtra(NoaaService.TYPE, NoaaService.TYPE_TEXT);
        if (force) {
            service.putExtra(NoaaService.FORCE_REFRESH, true);
        } else if (cacheOnly) {
            service.putExtra(NoaaService.CACHE_ONLY, true);
        }
        getActivity().startService(service);
    }

    protected void showWxInfo(Metar metar) {
        if (metar.stationId == null) {
            return;
        }

        if (metar.isValid && mIcaoCode != null && mIcaoCode.equals(metar.stationId)
                && WxUtils.isWindAvailable(metar)) {
            showRunwayWindInfo(metar);
        }

        for (TextView tv : mAwosViews) {
            String icaoCode = (String) tv.getTag();
            if (icaoCode.equals(metar.stationId)) {
                WxUtils.setColorizedWxDrawable(tv, metar, mDeclination);
                break;
            }
        }
    }

    protected void showRunwayWindInfo(Metar metar) {
        for (TextView tv : mRunwayViews) {
            Bundle tag = (Bundle) tv.getTag();
            String id = tag.getString(Runways.BASE_END_ID);
            long rwyHeading = tag.getInt(Runways.BASE_END_HEADING);
            long windDir = GeoUtils.applyDeclination(metar.windDirDegrees, mDeclination);
            long headWind = WxUtils.getHeadWindComponent(metar.windSpeedKnots, windDir, rwyHeading);

            if (headWind < 0) {
                // If this is a tail wind, use the other end
                id = tag.getString(Runways.RECIPROCAL_END_ID);
                rwyHeading = (rwyHeading + 180) % 360;
            }
            long crossWind = WxUtils.getCrossWindComponent(metar.windSpeedKnots, windDir, rwyHeading);
            String side = (crossWind >= 0) ? "right" : "left";
            crossWind = Math.abs(crossWind);
            StringBuilder windInfo = new StringBuilder();
            if (crossWind > 0) {
                windInfo.append(String.format(Locale.US, "%d %s %s x-wind", crossWind,
                        crossWind > 1 ? "knots" : "knot", side));
            } else {
                windInfo.append("no x-wind");
            }
            if (metar.windGustKnots < Integer.MAX_VALUE) {
                double gustFactor = (metar.windGustKnots - metar.windSpeedKnots) / 2;
                windInfo.append(String.format(Locale.US, ", %d knots gust factor", Math.round(gustFactor)));
            }
            tv.setText(String.format(Locale.US, "Rwy %s: %s", id, windInfo.toString()));
            tv.setVisibility(View.VISIBLE);
        }
    }

    private final class AirportDetailsTask extends CursorAsyncTask {

        @Override
        protected Cursor[] doInBackground(String... params) {
            mSiteNumber = params[0];

            SQLiteDatabase db = getDatabase(DatabaseManager.DB_FADDS);
            Cursor[] cursors = new Cursor[16];

            Cursor apt = getAirportDetails(mSiteNumber);
            cursors[0] = apt;

            String faaCode = apt.getString(apt.getColumnIndex(Airports.FAA_CODE));
            double lat = apt.getDouble(apt.getColumnIndex(Airports.REF_LATTITUDE_DEGREES));
            double lon = apt.getDouble(apt.getColumnIndex(Airports.REF_LONGITUDE_DEGREES));
            int elev_msl = apt.getInt(apt.getColumnIndex(Airports.ELEVATION_MSL));
            mIcaoCode = apt.getString(apt.getColumnIndex(Airports.ICAO_CODE));
            if (mIcaoCode == null || mIcaoCode.isEmpty()) {
                mIcaoCode = "K" + apt.getString(apt.getColumnIndex(Airports.FAA_CODE));
            }

            mLocation = new Location("");
            mLocation.setLatitude(lat);
            mLocation.setLongitude(lon);
            mLocation.setAltitude(elev_msl);

            mDeclination = GeoUtils.getMagneticDeclination(mLocation);

            SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
            builder.setTables(Runways.TABLE_NAME);
            Cursor c = builder.query(db,
                    new String[] { Runways.SITE_NUMBER, Runways.RUNWAY_ID, Runways.RUNWAY_LENGTH,
                            Runways.RUNWAY_WIDTH, Runways.SURFACE_TYPE, Runways.BASE_END_HEADING,
                            Runways.BASE_END_ID, Runways.RECIPROCAL_END_ID, Runways.BASE_END_RIGHT_TRAFFIC,
                            Runways.RECIPROCAL_END_RIGHT_TRAFFIC },
                    Runways.SITE_NUMBER + "=? AND " + Runways.RUNWAY_LENGTH + " > 0", new String[] { mSiteNumber },
                    null, null, null, null);
            cursors[1] = c;

            builder = new SQLiteQueryBuilder();
            builder.setTables(Remarks.TABLE_NAME);
            c = builder.query(db, new String[] { Remarks.REMARK_TEXT },
                    Runways.SITE_NUMBER + "=?" + "AND " + Remarks.REMARK_NAME
                            + " in ('E147', 'A3', 'A24', 'A70', 'A75', 'A82')",
                    new String[] { mSiteNumber }, null, null, null, null);
            cursors[2] = c;

            builder = new SQLiteQueryBuilder();
            builder.setTables(Tower1.TABLE_NAME);
            c = builder.query(db, new String[] { "*" }, Tower1.SITE_NUMBER + "=?", new String[] { mSiteNumber },
                    null, null, null, null);
            cursors[3] = c;

            builder = new SQLiteQueryBuilder();
            builder.setTables(Tower3.TABLE_NAME);
            c = builder.query(db, new String[] { "*" }, Tower3.FACILITY_ID + "=?", new String[] { faaCode }, null,
                    null, null, null);
            cursors[4] = c;

            builder = new SQLiteQueryBuilder();
            builder.setTables(Tower7.TABLE_NAME);
            c = builder.query(db, new String[] { "*" }, Tower7.SATELLITE_AIRPORT_SITE_NUMBER + "=?",
                    new String[] { mSiteNumber }, null, null, null, null);
            cursors[5] = c;

            if (!c.moveToFirst()) {
                builder = new SQLiteQueryBuilder();
                builder.setTables(Tower6.TABLE_NAME);
                c = builder.query(db, new String[] { "*" }, Tower6.FACILITY_ID + "=?", new String[] { faaCode },
                        null, null, Tower6.ELEMENT_NUMBER, null);
                cursors[6] = c;
            }

            cursors[7] = new NearbyWxCursor(db, mLocation, mRadius);

            String faa_code = apt.getString(apt.getColumnIndex(Airports.FAA_CODE));
            builder = new SQLiteQueryBuilder();
            builder.setTables(Aff3.TABLE_NAME);
            c = builder.query(db, new String[] { "*" }, Aff3.IFR_FACILITY_ID + "=?", new String[] { faa_code },
                    null, null, null, null);
            cursors[8] = c;

            builder = new SQLiteQueryBuilder();
            builder.setTables(Tower8.TABLE_NAME);
            c = builder.query(db, new String[] { "*" }, Tower8.FACILITY_ID + "=? ", new String[] { faaCode }, null,
                    null, null, null);
            cursors[9] = c;

            builder = new SQLiteQueryBuilder();
            builder.setTables(Attendance.TABLE_NAME);
            c = builder.query(db, new String[] { Attendance.ATTENDANCE_SCHEDULE }, Attendance.SITE_NUMBER + "=?",
                    new String[] { mSiteNumber }, null, null, Attendance.SEQUENCE_NUMBER, null);
            cursors[10] = c;

            db = getDatabase(DatabaseManager.DB_DTPP);
            if (db != null) {
                builder = new SQLiteQueryBuilder();
                builder.setTables(Dtpp.TABLE_NAME);
                c = builder.query(db, new String[] { "*" }, Dtpp.FAA_CODE + "=? ", new String[] { faaCode }, null,
                        null, null, null);
                cursors[11] = c;
            }

            db = getDatabase(DatabaseManager.DB_DAFD);
            if (db != null) {
                builder = new SQLiteQueryBuilder();
                builder.setTables(DafdCycle.TABLE_NAME);
                c = builder.query(db, new String[] { "*" }, null, null, null, null, null, null);
                cursors[12] = c;

                builder = new SQLiteQueryBuilder();
                builder.setTables(Dafd.TABLE_NAME);
                c = builder.query(db, new String[] { "*" }, Dafd.FAA_CODE + "=? ", new String[] { faaCode }, null,
                        null, null, null);
                cursors[13] = c;
            }

            db = getDatabase(DatabaseManager.DB_FADDS);

            if (!mHome.isEmpty()) {
                builder = new SQLiteQueryBuilder();
                builder.setTables(Airports.TABLE_NAME);
                c = builder.query(db,
                        new String[] { Airports.SITE_NUMBER, Airports.REF_LATTITUDE_DEGREES,
                                Airports.REF_LONGITUDE_DEGREES },
                        Airports.FAA_CODE + "=? OR " + Airports.ICAO_CODE + "=?", new String[] { mHome, mHome },
                        null, null, null, null);
                cursors[14] = c;
            }

            // Get nearby Class B airports
            String selection = " AND " + Airports.FAA_CODE + " IN (" + ClassBUtils.getClassBFacilityList() + ")";
            cursors[15] = new NearbyAirportsCursor(db, mLocation, 50, selection);

            return cursors;
        }

        @Override
        protected boolean onResult(Cursor[] result) {
            showDetails(result);
            return true;
        }

    }

}