com.shadowmaps.example.GpsTestActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.shadowmaps.example.GpsTestActivity.java

Source

/*
 * 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.shadowmaps.example;

import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.hardware.GeomagneticField;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.Surface;
import android.view.View;
import android.widget.Toast;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.actionbarsherlock.view.Window;
import com.shadowmaps.example.data.Mode;
import com.shadowmaps.example.service.ShadowMapsService;
import com.shadowmaps.example.util.GpsTestUtil;
import com.shadowmaps.example.view.ViewPagerMapBevelScroll;
import com.shadowmaps.shadowgps.R;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

import org.codehaus.jackson.map.ObjectMapper;

import java.util.ArrayList;

public class GpsTestActivity extends SherlockFragmentActivity
        implements LocationListener, GpsStatus.Listener, ActionBar.TabListener, SensorEventListener {

    protected void onStart() {
        super.onStart();
    }

    protected void onStop() {
        super.onStop();
    }

    private static final String TAG = "GpsTestActivity";

    private static final int SECONDS_TO_MILLISECONDS = 1000;

    static boolean mIsLargeScreen = false;

    private static GpsTestActivity sInstance;

    private ObjectMapper objectMapper = new ObjectMapper();

    private OkHttpClient client = new OkHttpClient();

    public final String URL_TO_POST = "https://api.shadowmaps.com/v1/mode/";
    // Holds sensor data
    private static float[] mRotationMatrix = new float[16];

    private static float[] mRemappedMatrix = new float[16];

    private static float[] mValues = new float[3];

    private static float[] mTruncatedRotationVector = new float[4];

    private static boolean mTruncateVector = false;

    boolean mStarted;

    boolean mFaceTrueNorth;

    boolean recording;

    String mTtff;

    private Menu menu;

    SectionsPagerAdapter mSectionsPagerAdapter;

    ViewPagerMapBevelScroll mViewPager;

    private LocationManager mService;

    private LocationProvider mProvider;

    private GpsStatus mStatus;

    private ArrayList<GpsTestListener> mGpsTestListeners = new ArrayList<GpsTestListener>();

    private Location mLastLocation;
    private MediaRecorder mRecorder;

    private GeomagneticField mGeomagneticField;

    private long minTime; // Min Time between location updates, in milliseconds

    private float minDistance; // Min Distance between location updates, in meters

    private SensorManager mSensorManager;

    static GpsTestActivity getInstance() {
        return sInstance;
    }

    void addListener(GpsTestListener listener) {
        mGpsTestListeners.add(listener);
    }

    private synchronized void gpsStart() {

        if (!mStarted) {
            mService.requestLocationUpdates(mProvider.getName(), minTime, minDistance, this);
            mStarted = true;

            // Reset the options menu to trigger updates to action bar menu items
            invalidateOptionsMenu();
        }
        for (GpsTestListener listener : mGpsTestListeners) {
            listener.gpsStart();
        }
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        setTheme(com.actionbarsherlock.R.style.Theme_Sherlock);
        super.onCreate(savedInstanceState);
        sInstance = this;

        // Set the default values from the XML file if this is the first
        // execution of the app
        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
        recording = false;
        mService = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        mProvider = mService.getProvider(LocationManager.GPS_PROVIDER);
        if (mProvider == null) {
            Log.e(TAG, "Unable to get GPS_PROVIDER");
            Toast.makeText(this, getString(R.string.gps_not_supported), Toast.LENGTH_SHORT).show();
            finish();
            return;
        }
        mService.addGpsStatusListener(this);

        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

        // Request use of spinner for showing indeterminate progress, to show
        // the user something is going on during long-running operations
        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);

        // If we have a large screen, show all the fragments in one layout
        if (GpsTestUtil.isLargeScreen(this)) {
            setContentView(R.layout.activity_main_large_screen);
            mIsLargeScreen = true;
        } else {
            setContentView(R.layout.activity_main);
        }

        initActionBar(savedInstanceState);

        SharedPreferences settings = Application.getPrefs();

        double tempMinTime = Double.valueOf(settings.getString(getString(R.string.pref_key_gps_min_time),
                getString(R.string.pref_gps_min_time_default_sec)));
        minTime = (long) (tempMinTime * SECONDS_TO_MILLISECONDS);
        minDistance = Float.valueOf(settings.getString(getString(R.string.pref_key_gps_min_distance),
                getString(R.string.pref_gps_min_distance_default_meters)));

        if (settings.getBoolean(getString(R.string.pref_key_auto_start_gps), true)) {
            gpsStart();
        }
        if (!isShadowServiceRunning(ShadowMapsService.class)) {
            final String STARTUP_EXTRA = "com.shadowmaps.example.start";
            Intent i = new Intent(this, ShadowMapsService.class);
            i.putExtra(STARTUP_EXTRA, true);
            startService(i);
            Log.v("SERVICE", "Starting ShadowService in activity onCreate");
        }
    }

    private boolean isShadowServiceRunning(Class<?> serviceClass) {
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                return true;
            }
        }
        return false;
    }

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

        SharedPreferences settings = Application.getPrefs();
        if (GpsTestUtil.isRotationVectorSensorSupported(this)) {
            // Use the modern rotation vector sensors
            Sensor vectorSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
            mSensorManager.registerListener(this, vectorSensor, 16000); // ~60hz
        } else {
            // Use the legacy orientation sensors
            Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
            if (sensor != null) {
                mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
            }
        }

        if (!mService.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            promptEnableGps();
        }

        /**
         * Check preferences to see how they should be initialized
         */
        checkKeepScreenOn(settings);

        checkTimeAndDistance(settings);

        checkTrueNorth(settings);
    }

    @Override
    protected void onPause() {
        mSensorManager.unregisterListener(this);
        super.onPause();
        if (mRecorder != null) {
            mRecorder.release();
            mRecorder = null;
        }

    }

    /**
     * Ask the user if they want to enable GPS
     */
    private void promptEnableGps() {
        new AlertDialog.Builder(this).setMessage(getString(R.string.enable_gps_message))
                .setPositiveButton(getString(R.string.enable_gps_positive_button),
                        new DialogInterface.OnClickListener() {

                            public void onClick(DialogInterface dialog, int which) {
                                Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                                startActivity(intent);
                            }
                        })
                .setNegativeButton(getString(R.string.enable_gps_negative_button),
                        new DialogInterface.OnClickListener() {

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

    private void checkTimeAndDistance(SharedPreferences settings) {
        double tempMinTimeDouble = Double
                .valueOf(settings.getString(getString(R.string.pref_key_gps_min_time), "1"));
        long minTimeLong = (long) (tempMinTimeDouble * SECONDS_TO_MILLISECONDS);

        if (minTime != minTimeLong || minDistance != Float
                .valueOf(settings.getString(getString(R.string.pref_key_gps_min_distance), "0"))) {
            // User changed preference values, get the new ones
            minTime = minTimeLong;
            minDistance = Float.valueOf(settings.getString(getString(R.string.pref_key_gps_min_distance), "0"));
            // If the GPS is started, reset the location listener with the new values
            if (mStarted) {
                mService.requestLocationUpdates(mProvider.getName(), minTime, minDistance, this);
                Toast.makeText(this,
                        String.format(getString(R.string.gps_set_location_listener),
                                String.valueOf(tempMinTimeDouble), String.valueOf(minDistance)),
                        Toast.LENGTH_SHORT).show();
            }
        }
    }

    private void checkKeepScreenOn(SharedPreferences settings) {
        if (mViewPager != null) {
            if (settings.getBoolean(getString(R.string.pref_key_keep_screen_on), true)) {
                mViewPager.setKeepScreenOn(true);
            } else {
                mViewPager.setKeepScreenOn(false);
            }
        } else {
            View v = findViewById(R.id.large_screen_layout);
            if (v != null && mIsLargeScreen) {
                if (settings.getBoolean(getString(R.string.pref_key_keep_screen_on), true)) {
                    v.setKeepScreenOn(true);
                } else {
                    v.setKeepScreenOn(false);
                }
            }
        }
    }

    private void checkTrueNorth(SharedPreferences settings) {
        mFaceTrueNorth = settings.getBoolean(getString(R.string.pref_key_true_north), true);
    }

    @Override
    protected void onDestroy() {
        mService.removeGpsStatusListener(this);
        mService.removeUpdates(this);
        super.onDestroy();
    }

    public String switch_modes(String mode) {
        String API_KEY = Application.getPrefs().getString("API_KEY", "YOUR_API_KEY");
        String id = Settings.Secure.getString(getApplicationContext().getContentResolver(),
                Settings.Secure.ANDROID_ID);
        Mode json_mode_object = new Mode(id, API_KEY, mode);
        try {
            String toSend = objectMapper.writeValueAsString(json_mode_object);
            RequestBody body = RequestBody.create(Application.JSON, toSend);
            Request request = new Request.Builder().url(URL_TO_POST).post(body).build();
            Response response = client.newCall(request).execute();
            Log.v("MODE", "Posting mode change request to " + URL_TO_POST);
            String mode_response = response.body().string();
            String to_ret = "error";
            if (mode_response.contains("pedestrian")) {
                to_ret = "pedestrian";
            } else if (mode_response.contains("vehicular")) {
                to_ret = "vehicular";
            }
            return to_ret;
        } catch (Exception e) {
            Log.v("MODE", "Error switching modes");
            e.printStackTrace();
            return "error";
        }
    }

    private class ChangeModeTask extends AsyncTask<String, Integer, String> {
        protected String doInBackground(String... mode) {
            String mode_str = mode[0];
            return switch_modes(mode_str);
        }

        protected void onProgressUpdate(Integer... progress) {
            Log.v("MODE", " Progress at " + progress[0]);
            return;
        }

        protected void onPostExecute(String result) {
            Log.v("MODE", "Got mode change result: " + result + " from server.");
            if (result.contains("pedestrian") || result.contains("vehicular")) {
                Toast.makeText(getApplicationContext(), "Switched mobility model to " + result + ".",
                        Toast.LENGTH_LONG).show();
            } else {
                Log.v("MODE", "Could not switch mobility model.");
            }
            return;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(com.actionbarsherlock.view.Menu menu) {
        getSupportMenuInflater().inflate(R.menu.gps_menu, menu);
        this.menu = menu;
        MenuItem item = menu.getItem(0);
        boolean pedestrian = Application.getPrefs().getBoolean("pedestrian", true);
        if (pedestrian) {
            item.setTitle("Mode: Pedestrian");
            item.setIcon(R.drawable.ic_directions_walk_white_24dp);
        } else {
            item.setTitle("Mode: Vehicular");
            item.setIcon(R.drawable.ic_directions_car_white_24dp);
        }
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(com.actionbarsherlock.view.Menu menu) {
        return true;
    }

    public void onLocationChanged(Location location) {
        //Log.v("GpsTestActivity", "onLocChanged");
        mLastLocation = location;

        updateGeomagneticField();

        // Reset the options menu to trigger updates to action bar menu items
        invalidateOptionsMenu();

        for (GpsTestListener listener : mGpsTestListeners) {
            listener.onLocationChanged(location);
        }
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {
        Log.v("GpsTestActivity", "onStatusChanged");

        for (GpsTestListener listener : mGpsTestListeners) {
            listener.onStatusChanged(provider, status, extras);
        }
    }

    public void onProviderEnabled(String provider) {
        for (GpsTestListener listener : mGpsTestListeners) {
            listener.onProviderEnabled(provider);
        }
    }

    public void onProviderDisabled(String provider) {
        for (GpsTestListener listener : mGpsTestListeners) {
            listener.onProviderDisabled(provider);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        boolean success;
        // Handle menu item selection
        switch (item.getItemId()) {
        case R.id.mode_switch:
            Log.i("MENU", "Mode Switch");
            Log.i("MENU", item.getTitle().toString());
            if (item.getTitle().equals("Mode: Vehicular")) {
                item.setTitle("Mode: Pedestrian");
                item.setIcon(R.drawable.ic_directions_walk_white_24dp);
                Application.getPrefs().edit().putBoolean("pedestrian", true).commit();
                new ChangeModeTask().execute("pedestrian");

            } else {
                item.setTitle("Mode: Vehicular");
                item.setIcon(R.drawable.ic_directions_car_white_24dp);
                Application.getPrefs().edit().putBoolean("pedestrian", false).commit();
                new ChangeModeTask().execute("vehicular");
            }
            return true;
        case R.id.map_circles:
            Log.i("MENU", "Circles");
            if (item.isChecked()) {
                Toast.makeText(this, "68% Confidence Circles Disabled", Toast.LENGTH_SHORT).show();
                Application.getPrefs().edit().putBoolean("circles", false).commit();
                Application.circles = false;
                item.setChecked(false);
            } else {
                Toast.makeText(this, "68% Confidence Circles Enabled", Toast.LENGTH_SHORT).show();
                Application.getPrefs().edit().putBoolean("circles", true).commit();
                Application.circles = true;
                item.setChecked(true);
            }
            return true;
        case R.id.menu_settings:
            startActivity(new Intent(this, Preferences.class));

        default:
            Log.v("MENU", "Something else clicked!");
            return super.onOptionsItemSelected(item);
        }
    }

    public void onGpsStatusChanged(int event) {
        //Log.v("GpsTestActivity", "onGpsStatusChanged");

        mStatus = mService.getGpsStatus(mStatus);

        switch (event) {
        case GpsStatus.GPS_EVENT_STARTED:
            break;
        case GpsStatus.GPS_EVENT_STOPPED:
            break;
        case GpsStatus.GPS_EVENT_FIRST_FIX:
            int ttff = mStatus.getTimeToFirstFix();
            if (ttff == 0) {
                mTtff = "";
            } else {
                ttff = (ttff + 500) / 1000;
                mTtff = Integer.toString(ttff) + " sec";
            }
            break;
        case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
            // Stop progress bar after the first status information is obtained
            setSupportProgressBarIndeterminateVisibility(Boolean.FALSE);
            break;
        }

        for (GpsTestListener listener : mGpsTestListeners) {
            listener.onGpsStatusChanged(event, mStatus);
        }
    }

    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    @Override
    public void onSensorChanged(SensorEvent event) {

        double orientation = Double.NaN;
        double tilt = Double.NaN;

        switch (event.sensor.getType()) {
        case Sensor.TYPE_ROTATION_VECTOR:
            // Modern rotation vector sensors
            if (!mTruncateVector) {
                try {
                    SensorManager.getRotationMatrixFromVector(mRotationMatrix, event.values);
                } catch (IllegalArgumentException e) {
                    // On some Samsung devices, an exception is thrown if this vector > 4 (see #39)
                    // Truncate the array, since we can deal with only the first four values
                    Log.e(TAG, "Samsung device error? Will truncate vectors - " + e);
                    mTruncateVector = true;
                    // Do the truncation here the first time the exception occurs
                    getRotationMatrixFromTruncatedVector(event.values);
                }
            } else {
                // Truncate the array to avoid the exception on some devices (see #39)
                getRotationMatrixFromTruncatedVector(event.values);
            }

            int rot = getWindowManager().getDefaultDisplay().getRotation();
            switch (rot) {
            case Surface.ROTATION_0:
                // No orientation change, use default coordinate system
                SensorManager.getOrientation(mRotationMatrix, mValues);
                // Log.d(TAG, "Rotation-0");
                break;
            case Surface.ROTATION_90:
                // Log.d(TAG, "Rotation-90");
                SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_Y,
                        SensorManager.AXIS_MINUS_X, mRemappedMatrix);
                SensorManager.getOrientation(mRemappedMatrix, mValues);
                break;
            case Surface.ROTATION_180:
                // Log.d(TAG, "Rotation-180");
                SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_X,
                        SensorManager.AXIS_MINUS_Y, mRemappedMatrix);
                SensorManager.getOrientation(mRemappedMatrix, mValues);
                break;
            case Surface.ROTATION_270:
                // Log.d(TAG, "Rotation-270");
                SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_Y,
                        SensorManager.AXIS_X, mRemappedMatrix);
                SensorManager.getOrientation(mRemappedMatrix, mValues);
                break;
            default:
                // This shouldn't happen - assume default orientation
                SensorManager.getOrientation(mRotationMatrix, mValues);
                // Log.d(TAG, "Rotation-Unknown");
                break;
            }
            orientation = Math.toDegrees(mValues[0]); // azimuth
            tilt = Math.toDegrees(mValues[1]);
            break;
        case Sensor.TYPE_ORIENTATION:
            // Legacy orientation sensors
            orientation = event.values[0];
            break;
        default:
            // A sensor we're not using, so return
            return;
        }

        // Correct for true north, if preference is set
        if (mFaceTrueNorth && mGeomagneticField != null) {
            orientation += mGeomagneticField.getDeclination();
        }

        for (GpsTestListener listener : mGpsTestListeners) {
            listener.onOrientationChanged(orientation, tilt);
        }
    }

    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    private void getRotationMatrixFromTruncatedVector(float[] vector) {
        System.arraycopy(vector, 0, mTruncatedRotationVector, 0, 4);
        SensorManager.getRotationMatrixFromVector(mRotationMatrix, mTruncatedRotationVector);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }

    private void updateGeomagneticField() {
        mGeomagneticField = new GeomagneticField((float) mLastLocation.getLatitude(),
                (float) mLastLocation.getLongitude(), (float) mLastLocation.getAltitude(), mLastLocation.getTime());
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        // When the given tab is selected, switch to the corresponding page in
        // the ViewPager.
        if (mViewPager != null) {
            mViewPager.setCurrentItem(tab.getPosition());
        }
    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }

    private void initActionBar(Bundle savedInstanceState) {
        // Set up the action bar.
        final com.actionbarsherlock.app.ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode(com.actionbarsherlock.app.ActionBar.NAVIGATION_MODE_TABS);
        actionBar.setTitle(getApplicationContext().getText(R.string.app_name));

        // If we don't have a large screen, set up the tabs using the ViewPager
        if (!mIsLargeScreen) {
            //  page adapter contains all the fragment registrations
            mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());

            // Set up the ViewPager with the sections adapter.
            mViewPager = (ViewPagerMapBevelScroll) findViewById(R.id.pager);
            mViewPager.setAdapter(mSectionsPagerAdapter);
            mViewPager.setOffscreenPageLimit(2);

            // When swiping between different sections, select the corresponding
            // tab. We can also use ActionBar.Tab#select() to do this if we have a
            // reference to the Tab.
            mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
                @Override
                public void onPageSelected(int position) {
                    actionBar.setSelectedNavigationItem(position);
                }
            });
            // For each of the sections in the app, add a tab to the action bar.
            for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
                // Create a tab with text corresponding to the page title defined by
                // the adapter. Also specify this Activity object, which implements
                // the TabListener interface, as the listener for when this tab is
                // selected.
                actionBar.addTab(
                        actionBar.newTab().setText(mSectionsPagerAdapter.getPageTitle(i)).setTabListener(this));
            }
        }
    }

    interface GpsTestListener extends LocationListener {

        public void gpsStart();

        public void gpsStop();

        public void onGpsStatusChanged(int event, GpsStatus status);

        public void onOrientationChanged(double x, double y);

    }

    /**
     * A {@link FragmentStatePagerAdapter} that returns a fragment corresponding to
     * one of the primary sections of the app.
     */
    public class SectionsPagerAdapter extends FragmentStatePagerAdapter {

        public static final int NUMBER_OF_TABS = 2; // Used to set up TabListener

        // Constants for the different fragments that will be displayed in tabs, in numeric order
        public static final int GPS_SKY_FRAGMENT = 0;
        public static final int GPS_MAP_FRAGMENT = 1;

        // Maintain handle to Fragments to avoid recreating them if one already
        // exists
        Fragment gpsMap, gpsSky;

        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int i) {
            switch (i) {
            case GPS_MAP_FRAGMENT:
                if (gpsMap == null) {
                    gpsMap = new GpsMapFragment();
                }
                return gpsMap;
            case GPS_SKY_FRAGMENT:
                if (gpsSky == null) {
                    gpsSky = new GpsSkyFragment();
                }
                return gpsSky;
            }
            return null; // This should never happen
        }

        @Override
        public int getCount() {
            return NUMBER_OF_TABS;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            switch (position) {
            case GPS_MAP_FRAGMENT:
                return getString(R.string.gps_map_tab);
            case GPS_SKY_FRAGMENT:
                return getString(R.string.gps_sky_tab);
            }
            return null; // This should never happen
        }
    }
}