/* * Copyright (C) 2008-2013 The Android Open Source Project, * Sean J. Barbeau * * 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.android.gpstest; import com.android.gpstest.util.GnssType; import com.android.gpstest.util.GpsTestUtil; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.location.GnssMeasurementsEvent; import android.location.GnssStatus; import android.location.GpsSatellite; import android.location.GpsStatus; import android.location.Location; import android.os.Build; import android.os.Bundle; import android.support.annotation.RequiresApi; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.TextView; import java.text.SimpleDateFormat; import java.util.Iterator; public class GpsStatusFragment extends Fragment implements GpsTestListener { private final static String TAG = "GpsStatusFragment"; private static final int PRN_COLUMN = 0; private static final int FLAG_IMAGE_COLUMN = 1; private static final int SNR_COLUMN = 2; private static final int ELEVATION_COLUMN = 3; private static final int AZIMUTH_COLUMN = 4; private static final int FLAGS_COLUMN = 5; private static final int COLUMN_COUNT = 6; private static final String EMPTY_LAT_LONG = " "; SimpleDateFormat mDateFormat = new SimpleDateFormat("hh:mm:ss.SS a"); private Resources mRes; private TextView mLatitudeView, mLongitudeView, mFixTimeView, mTTFFView, mAltitudeView, mAltitudeMslView, mAccuracyView, mSpeedView, mBearingView, mNumSats, mPdopLabelView, mPdopView, mHvdopLabelView, mHvdopView; private SvGridAdapter mAdapter; private int mSvCount, mPrns[], mConstellationType[], mUsedInFixCount; private float mSnrCn0s[], mSvElevations[], mSvAzimuths[]; private String mSnrCn0Title; private boolean mHasEphemeris[], mHasAlmanac[], mUsedInFix[]; private long mFixTime; private boolean mNavigating, mGotFix; private Drawable mFlagUsa, mFlagRussia, mFlagJapan, mFlagChina, mFlagGalileo; public void onLocationChanged(Location location) { if (!mGotFix) { mTTFFView.setText(GpsTestActivity.getInstance().mTtff); mGotFix = true; } mLatitudeView.setText(getString(R.string.gps_latitude_value, location.getLatitude())); mLongitudeView.setText(getString(R.string.gps_longitude_value, location.getLongitude())); mFixTime = location.getTime(); if (location.hasAltitude()) { mAltitudeView.setText(getString(R.string.gps_altitude_value, location.getAltitude())); } else { mAltitudeView.setText(""); } if (location.hasAccuracy()) { mAccuracyView.setText(getString(R.string.gps_accuracy_value, location.getAccuracy())); } else { mAccuracyView.setText(""); } if (location.hasSpeed()) { mSpeedView.setText(getString(R.string.gps_speed_value, location.getSpeed())); } else { mSpeedView.setText(""); } if (location.hasBearing()) { mBearingView.setText(getString(R.string.gps_bearing_value, location.getBearing())); } else { mBearingView.setText(""); } updateFixTime(); } public void onStatusChanged(String provider, int status, Bundle extras) { } public void onProviderEnabled(String provider) { } public void onProviderDisabled(String provider) { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mRes = getResources(); View v = inflater.inflate(R.layout.gps_status, container, false); mLatitudeView = (TextView) v.findViewById(R.id.latitude); mLongitudeView = (TextView) v.findViewById(R.id.longitude); mFixTimeView = (TextView) v.findViewById(R.id.fix_time); mTTFFView = (TextView) v.findViewById(R.id.ttff); mAltitudeView = (TextView) v.findViewById(R.id.altitude); mAltitudeMslView = (TextView) v.findViewById(R.id.altitude_msl); mAccuracyView = (TextView) v.findViewById(R.id.accuracy); mSpeedView = (TextView) v.findViewById(R.id.speed); mBearingView = (TextView) v.findViewById(R.id.bearing); mNumSats = (TextView) v.findViewById(R.id.num_sats); mPdopLabelView = (TextView) v.findViewById(R.id.pdop_label); mPdopView = (TextView) v.findViewById(R.id.pdop); mHvdopLabelView = (TextView) v.findViewById(R.id.hvdop_label); mHvdopView = (TextView) v.findViewById(R.id.hvdop); mLatitudeView.setText(EMPTY_LAT_LONG); mLongitudeView.setText(EMPTY_LAT_LONG); mFlagUsa = getResources().getDrawable(R.drawable.ic_flag_usa); mFlagRussia = getResources().getDrawable(R.drawable.ic_flag_russia); mFlagJapan = getResources().getDrawable(R.drawable.ic_flag_japan); mFlagChina = getResources().getDrawable(R.drawable.ic_flag_china); mFlagGalileo = getResources().getDrawable(R.drawable.ic_flag_galileo); GridView gridView = (GridView) v.findViewById(R.id.sv_grid); mAdapter = new SvGridAdapter(getActivity()); gridView.setAdapter(mAdapter); gridView.setFocusable(false); gridView.setFocusableInTouchMode(false); GpsTestActivity.getInstance().addListener(this); return v; } private void setStarted(boolean navigating) { if (navigating != mNavigating) { if (navigating) { } else { mLatitudeView.setText(EMPTY_LAT_LONG); mLongitudeView.setText(EMPTY_LAT_LONG); mFixTime = 0; updateFixTime(); mTTFFView.setText(""); mAltitudeView.setText(""); mAltitudeMslView.setText(""); mAccuracyView.setText(""); mSpeedView.setText(""); mBearingView.setText(""); mNumSats.setText(""); mPdopView.setText(""); mHvdopView.setText(""); mSvCount = 0; mAdapter.notifyDataSetChanged(); } mNavigating = navigating; } } private void updateFixTime() { if (mFixTime == 0 || !GpsTestActivity.getInstance().mStarted) { mFixTimeView.setText(""); } else { mFixTimeView.setText(mDateFormat.format(mFixTime)); } } @Override public void onResume() { super.onResume(); GpsTestActivity gta = GpsTestActivity.getInstance(); setStarted(gta.mStarted); } public void onGpsStarted() { setStarted(true); } public void onGpsStopped() { setStarted(false); } @SuppressLint("NewApi") public void gpsStart() { //Reset flag for detecting first fix mGotFix = false; } public void gpsStop() { } @Deprecated public void onGpsStatusChanged(int event, GpsStatus status) { switch (event) { case GpsStatus.GPS_EVENT_STARTED: setStarted(true); break; case GpsStatus.GPS_EVENT_STOPPED: setStarted(false); break; case GpsStatus.GPS_EVENT_FIRST_FIX: break; case GpsStatus.GPS_EVENT_SATELLITE_STATUS: updateLegacyStatus(status); break; } } @Override public void onGnssFirstFix(int ttffMillis) { } @RequiresApi(api = Build.VERSION_CODES.N) @Override public void onSatelliteStatusChanged(GnssStatus status) { updateGnssStatus(status); } @Override public void onGnssStarted() { setStarted(true); } @Override public void onGnssStopped() { setStarted(false); } @RequiresApi(api = Build.VERSION_CODES.N) @Override public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) { // No-op } @Override public void onOrientationChanged(double orientation, double tilt) { } @Override public void onNmeaMessage(String message, long timestamp) { if (!isAdded()) { // Do nothing if the Fragment isn't added return; } if (message.startsWith("$GPGGA") || message.startsWith("$GNGNS")) { Double altitudeMsl = GpsTestUtil.getAltitudeMeanSeaLevel(message); if (altitudeMsl != null && mNavigating) { mAltitudeMslView.setText(getString(R.string.gps_altitude_msl_value, altitudeMsl)); } } if (message.startsWith("$GNGSA") || message.startsWith("$GPGSA")) { DilutionOfPrecision dop = GpsTestUtil.getDop(message); if (dop != null && mNavigating) { showDopViews(); mPdopView.setText(String.valueOf(dop.getPositionDop())); mHvdopView.setText( getString(R.string.hvdop_value, dop.getHorizontalDop(), dop.getVerticalDop())); } } } private void showDopViews() { mPdopLabelView.setVisibility(View.VISIBLE); mPdopView.setVisibility(View.VISIBLE); mHvdopLabelView.setVisibility(View.VISIBLE); mHvdopView.setVisibility(View.VISIBLE); } @RequiresApi(api = Build.VERSION_CODES.N) private void updateGnssStatus(GnssStatus status) { setStarted(true); updateFixTime(); mSnrCn0Title = mRes.getString(R.string.gps_cn0_column_label); if (mPrns == null) { /** * We need to allocate arrays big enough so we don't overflow them. Per * https://developer.android.com/reference/android/location/GnssStatus.html#getSvid(int) * 255 should be enough to contain all known satellites world-wide. */ final int MAX_LENGTH = 255; mPrns = new int[MAX_LENGTH]; mSnrCn0s = new float[MAX_LENGTH]; mSvElevations = new float[MAX_LENGTH]; mSvAzimuths = new float[MAX_LENGTH]; mConstellationType = new int[MAX_LENGTH]; mHasEphemeris = new boolean[MAX_LENGTH]; mHasAlmanac = new boolean[MAX_LENGTH]; mUsedInFix = new boolean[MAX_LENGTH]; } final int length = status.getSatelliteCount(); mSvCount = 0; mUsedInFixCount = 0; while (mSvCount < length) { int prn = status.getSvid(mSvCount); mPrns[mSvCount] = prn; mConstellationType[mSvCount] = status.getConstellationType(mSvCount); mSnrCn0s[mSvCount] = status.getCn0DbHz(mSvCount); mSvElevations[mSvCount] = status.getElevationDegrees(mSvCount); mSvAzimuths[mSvCount] = status.getAzimuthDegrees(mSvCount); mHasEphemeris[mSvCount] = status.hasEphemerisData(mSvCount); mHasAlmanac[mSvCount] = status.hasAlmanacData(mSvCount); mUsedInFix[mSvCount] = status.usedInFix(mSvCount); if (status.usedInFix(mSvCount)) { mUsedInFixCount++; } mSvCount++; } mNumSats.setText(getString(R.string.gps_num_sats_value, mUsedInFixCount, mSvCount)); mAdapter.notifyDataSetChanged(); } @Deprecated private void updateLegacyStatus(GpsStatus status) { setStarted(true); updateFixTime(); mSnrCn0Title = mRes.getString(R.string.gps_snr_column_label); Iterator<GpsSatellite> satellites = status.getSatellites().iterator(); if (mPrns == null) { int length = status.getMaxSatellites(); mPrns = new int[length]; mSnrCn0s = new float[length]; mSvElevations = new float[length]; mSvAzimuths = new float[length]; // Constellation type isn't used, but instantiate it to avoid NPE in legacy devices mConstellationType = new int[length]; mHasEphemeris = new boolean[length]; mHasAlmanac = new boolean[length]; mUsedInFix = new boolean[length]; } mSvCount = 0; mUsedInFixCount = 0; while (satellites.hasNext()) { GpsSatellite satellite = satellites.next(); int prn = satellite.getPrn(); mPrns[mSvCount] = prn; mSnrCn0s[mSvCount] = satellite.getSnr(); mSvElevations[mSvCount] = satellite.getElevation(); mSvAzimuths[mSvCount] = satellite.getAzimuth(); mHasEphemeris[mSvCount] = satellite.hasEphemeris(); mHasAlmanac[mSvCount] = satellite.hasAlmanac(); mUsedInFix[mSvCount] = satellite.usedInFix(); if (satellite.usedInFix()) { mUsedInFixCount++; } mSvCount++; } mNumSats.setText(getString(R.string.gps_num_sats_value, mUsedInFixCount, mSvCount)); mAdapter.notifyDataSetChanged(); } private class SvGridAdapter extends BaseAdapter { private Context mContext; public SvGridAdapter(Context c) { mContext = c; } public int getCount() { // add 1 for header row return (mSvCount + 1) * COLUMN_COUNT; } public Object getItem(int position) { Log.d(TAG, "getItem(" + position + ")"); return "foo"; } public long getItemId(int position) { return position; } public View getView(int position, View convertView, ViewGroup parent) { TextView textView = null; ImageView imageView = null; int row = position / COLUMN_COUNT; int column = position % COLUMN_COUNT; if (convertView != null) { if (convertView instanceof ImageView) { imageView = (ImageView) convertView; } else if (convertView instanceof TextView) { textView = (TextView) convertView; } } CharSequence text = null; if (row == 0) { switch (column) { case PRN_COLUMN: text = mRes.getString(R.string.gps_prn_column_label); break; case FLAG_IMAGE_COLUMN: text = mRes.getString(R.string.gps_flag_image_label); break; case SNR_COLUMN: text = mSnrCn0Title; break; case ELEVATION_COLUMN: text = mRes.getString(R.string.gps_elevation_column_label); break; case AZIMUTH_COLUMN: text = mRes.getString(R.string.gps_azimuth_column_label); break; case FLAGS_COLUMN: text = mRes.getString(R.string.gps_flags_column_label); break; } } else { row--; switch (column) { case PRN_COLUMN: text = Integer.toString(mPrns[row]); break; case FLAG_IMAGE_COLUMN: if (imageView == null) { imageView = new ImageView(mContext); imageView.setScaleType(ImageView.ScaleType.FIT_START); } GnssType type; if (GpsTestUtil.isGnssStatusListenerSupported()) { type = GpsTestUtil.getGnssConstellationType(mConstellationType[row]); } else { type = GpsTestUtil.getGnssType(mPrns[row]); } switch (type) { case NAVSTAR: imageView.setImageDrawable(mFlagUsa); break; case GLONASS: imageView.setImageDrawable(mFlagRussia); break; case QZSS: imageView.setImageDrawable(mFlagJapan); break; case BEIDOU: imageView.setImageDrawable(mFlagChina); break; case GALILEO: imageView.setImageDrawable(mFlagGalileo); break; } return imageView; case SNR_COLUMN: if (mSnrCn0s[row] != 0.0f) { text = Float.toString(mSnrCn0s[row]); } else { text = ""; } break; case ELEVATION_COLUMN: if (mSvElevations[row] != 0.0f) { text = getString(R.string.gps_elevation_column_value, Float.toString(mSvElevations[row])); } else { text = ""; } break; case AZIMUTH_COLUMN: if (mSvAzimuths[row] != 0.0f) { text = getString(R.string.gps_azimuth_column_value, Float.toString(mSvAzimuths[row])); } else { text = ""; } break; case FLAGS_COLUMN: char[] flags = new char[3]; flags[0] = !mHasEphemeris[row] ? ' ' : 'E'; flags[1] = !mHasAlmanac[row] ? ' ' : 'A'; flags[2] = !mUsedInFix[row] ? ' ' : 'U'; text = new String(flags); break; } } if (textView == null) { textView = new TextView(mContext); } textView.setText(text); return textView; } } }