org.dicadeveloper.runnerapp.TrackListActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.dicadeveloper.runnerapp.TrackListActivity.java

Source

/*
 * Copyright 2008 Google Inc.
 *
 * 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 org.dicadeveloper.runnerapp;

import org.dicadeveloper.runnerapp.content.MyTracksProviderUtils;
import org.dicadeveloper.runnerapp.content.Track;
import org.dicadeveloper.runnerapp.content.TracksColumns;
import org.dicadeveloper.runnerapp.fragments.ChooseAccountDialogFragment;
import org.dicadeveloper.runnerapp.fragments.ChooseAccountDialogFragment.ChooseAccountCaller;
import org.dicadeveloper.runnerapp.fragments.ConfirmSyncDialogFragment;
import org.dicadeveloper.runnerapp.fragments.ConfirmSyncDialogFragment.ConfirmSyncCaller;
import org.dicadeveloper.runnerapp.fragments.EulaDialogFragment;
import org.dicadeveloper.runnerapp.fragments.EulaDialogFragment.EulaCaller;
import org.dicadeveloper.runnerapp.fragments.FileTypeDialogFragment;
import org.dicadeveloper.runnerapp.fragments.FileTypeDialogFragment.FileTypeCaller;
import org.dicadeveloper.runnerapp.fragments.PlayMultipleDialogFragment;
import org.dicadeveloper.runnerapp.fragments.PlayMultipleDialogFragment.PlayMultipleCaller;
import org.dicadeveloper.runnerapp.io.file.TrackFileFormat;
import org.dicadeveloper.runnerapp.io.file.exporter.SaveActivity;
import org.dicadeveloper.runnerapp.io.file.importer.ImportActivity;
import org.dicadeveloper.runnerapp.io.sync.SyncUtils;
import org.dicadeveloper.runnerapp.services.ITrackRecordingService;
import org.dicadeveloper.runnerapp.services.MyTracksLocationManager;
import org.dicadeveloper.runnerapp.services.TrackRecordingServiceConnection;
import org.dicadeveloper.runnerapp.settings.SettingsActivity;
import org.dicadeveloper.runnerapp.util.AnalyticsUtils;
import org.dicadeveloper.runnerapp.util.ApiAdapterFactory;
import org.dicadeveloper.runnerapp.util.EulaUtils;
import org.dicadeveloper.runnerapp.util.GoogleLocationUtils;
import org.dicadeveloper.runnerapp.util.IntentUtils;
import org.dicadeveloper.runnerapp.util.ListItemUtils;
import org.dicadeveloper.runnerapp.util.PreferencesUtils;
import org.dicadeveloper.runnerapp.util.StringUtils;
import org.dicadeveloper.runnerapp.util.TrackIconUtils;
import org.dicadeveloper.runnerapp.util.TrackRecordingServiceConnectionUtils;
import org.dicadeveloper.runnerapp.util.TrackUtils;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import org.dicadeveloper.runnerapp.BuildConfig;
import org.dicadeveloper.runnerapp.R;

import android.accounts.Account;
import android.app.Dialog;
import android.app.SearchManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Looper;
import android.os.Parcelable;
import android.os.RemoteException;
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.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;

import java.util.Locale;

/**
 * An activity displaying a list of tracks.
 * 
 * @author Leif Hendrik Wilden
 */
public class TrackListActivity extends AbstractSendToGoogleActivity
        implements EulaCaller, FileTypeCaller, PlayMultipleCaller, ChooseAccountCaller, ConfirmSyncCaller {

    private static final String TAG = TrackListActivity.class.getSimpleName();
    private static final String[] PROJECTION = new String[] { TracksColumns._ID, TracksColumns.NAME,
            TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME, TracksColumns.TOTALDISTANCE,
            TracksColumns.TOTALTIME, TracksColumns.ICON, TracksColumns.SHAREDWITHME, TracksColumns.SHAREDOWNER };

    // Callback when the trackRecordingServiceConnection binding changes.
    private final Runnable bindChangedCallback = new Runnable() {
        @Override
        public void run() {
            /*
             * After binding changes (e.g., becomes available), update the total time
             * in trackController.
             */
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    trackController.update(recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT,
                            recordingTrackPaused);
                }
            });

            if (!startGps && !startNewRecording) {
                return;
            }

            ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
            if (service == null) {
                Log.d(TAG, "service not available to start gps or a new recording");
                return;
            }
            if (startNewRecording) {
                startGps = false;
                try {
                    long trackId = service.startNewTrack();
                    startNewRecording = false;
                    Intent intent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
                            .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
                    startActivity(intent);
                    Toast.makeText(TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT)
                            .show();
                } catch (RemoteException e) {
                    Toast.makeText(TrackListActivity.this, R.string.track_list_record_error, Toast.LENGTH_LONG)
                            .show();
                    Log.e(TAG, "Unable to start a new recording.", e);
                }
            }
            if (startGps) {
                try {
                    service.startGps();
                    startGps = false;
                } catch (RemoteException e) {
                    Toast.makeText(TrackListActivity.this, R.string.gps_starting_error, Toast.LENGTH_LONG).show();
                    Log.e(TAG, "Unable to start gps");
                }
            }
        }
    };

    /*
     * Note that sharedPreferenceChangeListenr cannot be an anonymous inner class.
     * Anonymous inner class will get garbage collected.
     */
    private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
            if (key == null
                    || key.equals(PreferencesUtils.getKey(TrackListActivity.this, R.string.stats_units_key))) {
                metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
            }
            if (key == null || key
                    .equals(PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_id_key))) {
                recordingTrackId = PreferencesUtils.getLong(TrackListActivity.this,
                        R.string.recording_track_id_key);
                if (key != null && recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
                    trackRecordingServiceConnection.startAndBind();
                }
            }
            if (key == null || key
                    .equals(PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_paused_key))) {
                recordingTrackPaused = PreferencesUtils.getBoolean(TrackListActivity.this,
                        R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
            }
            if (key == null
                    || key.equals(PreferencesUtils.getKey(TrackListActivity.this, R.string.drive_sync_key))) {
                driveSync = PreferencesUtils.getBoolean(TrackListActivity.this, R.string.drive_sync_key,
                        PreferencesUtils.DRIVE_SYNC_DEFAULT);
            }
            if (key != null) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        ApiAdapterFactory.getApiAdapter().invalidMenu(TrackListActivity.this);
                        getSupportLoaderManager().restartLoader(0, null, loaderCallbacks);
                        boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
                        trackController.update(isRecording, recordingTrackPaused);
                    }
                });
            }
        }
    };

    // Callback when an item is selected in the contextual action mode
    private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {

        @Override
        public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
            boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
            boolean isSingleSelection = ids.length == 1;
            boolean isSingleSelectionShareWithMe;
            if (isSingleSelection) {
                Track track = myTracksProviderUtils.getTrack(ids[0]);
                isSingleSelectionShareWithMe = track.isSharedWithMe();
            } else {
                isSingleSelectionShareWithMe = false;
            }

            // Not recording
            menu.findItem(R.id.list_context_menu_play).setVisible(!isRecording);
            // Not recording, one item, not sharedWithMe item
            menu.findItem(R.id.list_context_menu_share)
                    .setVisible(!isRecording && isSingleSelection && !isSingleSelectionShareWithMe);
            // Always disable
            menu.findItem(R.id.list_context_menu_show_on_map).setVisible(false);
            // One item, not sharedWithMe item
            menu.findItem(R.id.list_context_menu_edit)
                    .setVisible(isSingleSelection && !isSingleSelectionShareWithMe);
            // delete is always enabled
            menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
        }

        @Override
        public boolean onClick(int itemId, int[] positions, long[] ids) {
            return handleContextItem(itemId, ids);
        };
    };

    private final OnClickListener recordListener = new OnClickListener() {
        public void onClick(View v) {
            if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
                // Not recording -> Recording
                AnalyticsUtils.sendPageViews(TrackListActivity.this, AnalyticsUtils.ACTION_RECORD_TRACK);
                updateMenuItems(false, true);
                startRecording();
            } else {
                if (recordingTrackPaused) {
                    // Paused -> Resume
                    AnalyticsUtils.sendPageViews(TrackListActivity.this, AnalyticsUtils.ACTION_RESUME_TRACK);
                    updateMenuItems(false, true);
                    TrackRecordingServiceConnectionUtils.resumeTrack(trackRecordingServiceConnection);
                    trackController.update(true, false);
                } else {
                    // Recording -> Paused
                    AnalyticsUtils.sendPageViews(TrackListActivity.this, AnalyticsUtils.ACTION_PAUSE_TRACK);
                    updateMenuItems(false, true);
                    TrackRecordingServiceConnectionUtils.pauseTrack(trackRecordingServiceConnection);
                    trackController.update(true, true);
                }
            }
        }
    };

    private final OnClickListener stopListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            AnalyticsUtils.sendPageViews(TrackListActivity.this, AnalyticsUtils.ACTION_STOP_RECORDING);
            updateMenuItems(false, false);
            TrackRecordingServiceConnectionUtils.stopRecording(TrackListActivity.this,
                    trackRecordingServiceConnection, true);
        }
    };

    private final LoaderCallbacks<Cursor> loaderCallbacks = new LoaderCallbacks<Cursor>() {
        @Override
        public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
            return new CursorLoader(TrackListActivity.this, TracksColumns.CONTENT_URI, PROJECTION, null, null,
                    TrackUtils.TRACK_SORT_ORDER);
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
            sectionResourceCursorAdapter.swapCursor(cursor);
        }

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

    // The following are set in onCreate
    private MyTracksProviderUtils myTracksProviderUtils;
    private SharedPreferences sharedPreferences;
    private TrackRecordingServiceConnection trackRecordingServiceConnection;
    private TrackController trackController;
    private ListView listView;
    private SectionResourceCursorAdapter sectionResourceCursorAdapter;

    // Preferences
    private boolean metricUnits = true;
    private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
    private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
    private boolean driveSync = PreferencesUtils.DRIVE_SYNC_DEFAULT;

    // Menu items
    private MenuItem searchMenuItem;
    private MenuItem startGpsMenuItem;
    private MenuItem playMultipleItem;
    private MenuItem syncNowMenuItem;
    private MenuItem aggregatedStatisticsMenuItem;
    private MenuItem exportAllMenuItem;
    private MenuItem importAllMenuItem;
    private MenuItem deleteAllMenuItem;

    private boolean startGps = false; // true to start gps
    private boolean startNewRecording = false; // true to start a new recording

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

        if (BuildConfig.DEBUG) {
            ApiAdapterFactory.getApiAdapter().enableStrictMode();
        }

        myTracksProviderUtils = MyTracksProviderUtils.Factory.get(this);
        sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);

        trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
        trackController = new TrackController(this, trackRecordingServiceConnection, true, recordListener,
                stopListener);

        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
        // Show trackController when search dialog is dismissed
        SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
        searchManager.setOnDismissListener(new SearchManager.OnDismissListener() {
            @Override
            public void onDismiss() {
                trackController.show();
            }
        });

        listView = (ListView) findViewById(R.id.track_list);
        listView.setEmptyView(findViewById(R.id.track_list_empty_view));
        listView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
                        .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, id);
                startActivity(newIntent);
            }
        });
        sectionResourceCursorAdapter = new SectionResourceCursorAdapter(this, R.layout.list_item, null, 0) {
            @Override
            public void bindView(View view, Context context, Cursor cursor) {
                int idIndex = cursor.getColumnIndex(TracksColumns._ID);
                int iconIndex = cursor.getColumnIndex(TracksColumns.ICON);
                int nameIndex = cursor.getColumnIndex(TracksColumns.NAME);
                int sharedOwnerIndex = cursor.getColumnIndex(TracksColumns.SHAREDOWNER);
                int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
                int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
                int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
                int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY);
                int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION);

                long trackId = cursor.getLong(idIndex);
                boolean isRecording = trackId == recordingTrackId;
                String icon = cursor.getString(iconIndex);
                int iconId = TrackIconUtils.getIconDrawable(icon);
                String name = cursor.getString(nameIndex);
                String sharedOwner = cursor.getString(sharedOwnerIndex);
                String totalTime = StringUtils.formatElapsedTime(cursor.getLong(totalTimeIndex));
                String totalDistance = StringUtils.formatDistance(TrackListActivity.this,
                        cursor.getDouble(totalDistanceIndex), metricUnits);
                int markerCount = myTracksProviderUtils.getWaypointCount(trackId);
                long startTime = cursor.getLong(startTimeIndex);
                String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
                String description = cursor.getString(descriptionIndex);

                ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused, iconId,
                        R.string.image_track, name, sharedOwner, totalTime, totalDistance, markerCount, startTime,
                        true, category, description, null);
            }
        };
        listView.setAdapter(sectionResourceCursorAdapter);
        ApiAdapterFactory.getApiAdapter().configureListViewContextualMenu(this, listView,
                contextualActionModeCallback);

        getSupportLoaderManager().initLoader(0, null, loaderCallbacks);
        showStartupDialogs();
    }

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

        // Register shared preferences listener
        sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);

        // Update shared preferences
        sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);

        // Update track recording service connection
        TrackRecordingServiceConnectionUtils.startConnection(this, trackRecordingServiceConnection);

        AnalyticsUtils.sendPageViews(this, AnalyticsUtils.PAGE_TRACK_LIST);
    }

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

        // Update UI
        ApiAdapterFactory.getApiAdapter().invalidMenu(this);
        getSupportLoaderManager().restartLoader(0, null, loaderCallbacks);
        boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
        trackController.onResume(isRecording, recordingTrackPaused);
    }

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

        // Update UI
        trackController.onPause();
    }

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

        // Unregister shared preferences listener
        sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);

        trackRecordingServiceConnection.unbind();

        AnalyticsUtils.dispatch();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == GOOGLE_PLAY_SERVICES_REQUEST_CODE) {
            checkGooglePlayServices();
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    protected int getLayoutResId() {
        return R.layout.track_list;
    }

    @Override
    protected boolean configureActionBarHomeAsUp() {
        return false;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.track_list, menu);

        menu.findItem(R.id.track_list_help_feedback).setTitle(
                ApiAdapterFactory.getApiAdapter().isGoogleFeedbackAvailable() ? R.string.menu_help_feedback
                        : R.string.menu_help);

        searchMenuItem = menu.findItem(R.id.track_list_search);
        ApiAdapterFactory.getApiAdapter().configureSearchWidget(this, searchMenuItem, trackController);

        startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
        playMultipleItem = menu.findItem(R.id.track_list_play_mutliple);
        syncNowMenuItem = menu.findItem(R.id.track_list_sync_now);
        aggregatedStatisticsMenuItem = menu.findItem(R.id.track_list_aggregated_statistics);
        exportAllMenuItem = menu.findItem(R.id.track_list_export_all);
        importAllMenuItem = menu.findItem(R.id.track_list_import_all);
        deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        boolean isGpsStarted = TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(this);
        boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
        updateMenuItems(isGpsStarted, isRecording);
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Intent intent;
        switch (item.getItemId()) {
        case R.id.track_list_search:
            return ApiAdapterFactory.getApiAdapter().handleSearchMenuSelection(this);
        case R.id.track_list_start_gps:
            MyTracksLocationManager myTracksLocationManager = new MyTracksLocationManager(this, Looper.myLooper(),
                    false);
            if (!myTracksLocationManager.isGpsProviderEnabled()) {
                intent = GoogleLocationUtils.newLocationSettingsIntent(TrackListActivity.this);
                startActivity(intent);
            } else {
                startGps = !TrackRecordingServiceConnectionUtils.isRecordingServiceRunning(this);

                // Show toast
                Toast toast = Toast.makeText(this, startGps ? R.string.gps_starting : R.string.gps_stopping,
                        Toast.LENGTH_SHORT);
                toast.setGravity(Gravity.CENTER, 0, 0);
                toast.show();

                // Invoke trackRecordingService
                if (startGps) {
                    trackRecordingServiceConnection.startAndBind();
                    bindChangedCallback.run();
                } else {
                    ITrackRecordingService trackRecordingService = trackRecordingServiceConnection
                            .getServiceIfBound();
                    if (trackRecordingService != null) {
                        try {
                            trackRecordingService.stopGps();
                        } catch (RemoteException e) {
                            Log.e(TAG, "Unable to stop gps.", e);
                        }
                    }
                    trackRecordingServiceConnection.unbindAndStop();
                }

                // Update menu after starting or stopping gps
                ApiAdapterFactory.getApiAdapter().invalidMenu(this);
            }
            myTracksLocationManager.close();
            return true;
        case R.id.track_list_play_mutliple:
            PlayMultipleDialogFragment.newInstance(-1L).show(getSupportFragmentManager(),
                    PlayMultipleDialogFragment.PLAY_MULTIPLE_DIALOG_TAG);
            return true;
        case R.id.track_list_sync_now:
            if (driveSync) {
                SyncUtils.syncNow(this);
            } else {
                new ChooseAccountDialogFragment().show(getSupportFragmentManager(),
                        ChooseAccountDialogFragment.CHOOSE_ACCOUNT_DIALOG_TAG);
            }
            return true;
        case R.id.track_list_aggregated_statistics:
            intent = IntentUtils.newIntent(this, AggregatedStatsActivity.class);
            startActivity(intent);
            return true;
        case R.id.track_list_export_all:
            FileTypeDialogFragment.newInstance(R.id.track_list_export_all, R.string.export_all_title,
                    R.string.export_all_option, 4)
                    .show(getSupportFragmentManager(), FileTypeDialogFragment.FILE_TYPE_DIALOG_TAG);
            return true;
        case R.id.track_list_import_all:
            FileTypeDialogFragment
                    .newInstance(R.id.track_list_import_all, R.string.import_selection_title,
                            R.string.import_selection_option, 2)
                    .show(getSupportFragmentManager(), FileTypeDialogFragment.FILE_TYPE_DIALOG_TAG);
            return true;
        case R.id.track_list_delete_all:
            deleteTracks(new long[] { -1L });
            return true;
        case R.id.track_list_settings:
            intent = IntentUtils.newIntent(this, SettingsActivity.class);
            startActivity(intent);
            return true;
        case R.id.track_list_help_feedback:
            intent = IntentUtils.newIntent(this, HelpActivity.class);
            startActivity(intent);
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        getMenuInflater().inflate(R.menu.list_context_menu, menu);

        AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
        contextualActionModeCallback.onPrepare(menu, new int[] { info.position }, new long[] { info.id }, false);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        if (handleContextItem(item.getItemId(), new long[] { info.id })) {
            return true;
        }
        return super.onContextItemSelected(item);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) {
            if (ApiAdapterFactory.getApiAdapter().handleSearchKey(searchMenuItem)) {
                return true;
            }
        }
        return super.onKeyUp(keyCode, event);
    }

    @Override
    public boolean onSearchRequested() {
        // Hide trackController when search dialog is shown
        trackController.hide();
        return super.onSearchRequested();
    };

    @Override
    protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
        return trackRecordingServiceConnection;
    }

    @Override
    protected void onDeleted() {
        // Do nothing
    }

    @Override
    public void onFileTypeDone(int menuId, TrackFileFormat trackFileFormat) {
        Intent intent;
        switch (menuId) {
        case R.id.track_list_export_all:
            AnalyticsUtils.sendPageViews(this,
                    AnalyticsUtils.ACTION_EXPORT_ALL_PREFIX + trackFileFormat.getExtension());
            intent = IntentUtils.newIntent(this, SaveActivity.class)
                    .putExtra(SaveActivity.EXTRA_TRACK_IDS, new long[] { -1L })
                    .putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat);
            startActivity(intent);
            break;
        case R.id.track_list_import_all:
            AnalyticsUtils.sendPageViews(this,
                    AnalyticsUtils.ACTION_IMPORT_ALL_PREFIX + trackFileFormat.getExtension());
            intent = IntentUtils.newIntent(this, ImportActivity.class)
                    .putExtra(ImportActivity.EXTRA_IMPORT_ALL, true)
                    .putExtra(ImportActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat);
            startActivity(intent);
            break;
        default:
        }
    }

    /**
     * Shows start up dialogs.
     */
    public void showStartupDialogs() {
        if (!EulaUtils.hasAcceptedEula(this)) {
            Fragment fragment = getSupportFragmentManager().findFragmentByTag(EulaDialogFragment.EULA_DIALOG_TAG);
            if (fragment == null) {
                EulaDialogFragment.newInstance(false).show(getSupportFragmentManager(),
                        EulaDialogFragment.EULA_DIALOG_TAG);
            }
        } else {
            // If stats_units_key is undefined, set it
            if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
                String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial
                        : R.string.stats_units_metric);
                PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
            }
            checkGooglePlayServices();
        }
    }

    @Override
    public void onEulaDone() {
        if (EulaUtils.hasAcceptedEula(this)) {
            showStartupDialogs();
            return;
        }
        finish();
    }

    private void checkGooglePlayServices() {
        int code = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        if (code != ConnectionResult.SUCCESS) {
            Dialog dialog = GooglePlayServicesUtil.getErrorDialog(code, this, GOOGLE_PLAY_SERVICES_REQUEST_CODE,
                    new DialogInterface.OnCancelListener() {

                        @Override
                        public void onCancel(DialogInterface dialogInterface) {
                            finish();
                        }
                    });
            if (dialog != null) {
                dialog.show();
                return;
            }
        }
    }

    /**
     * Updates the menu items.
     * 
     * @param isGpsStarted true if gps is started
     * @param isRecording true if recording
     */
    private void updateMenuItems(boolean isGpsStarted, boolean isRecording) {
        boolean hasTrack = listView != null && listView.getCount() != 0;
        if (startGpsMenuItem != null) {
            startGpsMenuItem.setVisible(!isRecording);
            if (!isRecording) {
                startGpsMenuItem.setTitle(isGpsStarted ? R.string.menu_stop_gps : R.string.menu_start_gps);
                startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_menu_stop_gps : R.drawable.ic_menu_start_gps);
                TrackIconUtils.setMenuIconColor(startGpsMenuItem);
            }
        }
        if (playMultipleItem != null) {
            playMultipleItem.setVisible(hasTrack);
        }
        if (syncNowMenuItem != null) {
            syncNowMenuItem.setTitle(driveSync ? R.string.menu_sync_now : R.string.menu_sync_drive);
        }
        if (aggregatedStatisticsMenuItem != null) {
            aggregatedStatisticsMenuItem.setVisible(hasTrack);
        }
        if (exportAllMenuItem != null) {
            exportAllMenuItem.setVisible(hasTrack && !isRecording);
        }
        if (importAllMenuItem != null) {
            importAllMenuItem.setVisible(!isRecording);
        }
        if (deleteAllMenuItem != null) {
            deleteAllMenuItem.setVisible(hasTrack && !isRecording);
        }
    }

    /**
     * Starts a new recording.
     */
    private void startRecording() {
        startNewRecording = true;
        trackRecordingServiceConnection.startAndBind();

        /*
         * If the binding has happened, then invoke the callback to start a new
         * recording. If the binding hasn't happened, then invoking the callback
         * will have no effect. But when the binding occurs, the callback will get
         * invoked.
         */
        bindChangedCallback.run();
    }

    /**
     * Handles a context item selection.
     * 
     * @param itemId the menu item id
     * @param trackIds the track ids
     * @return true if handled.
     */
    private boolean handleContextItem(int itemId, long[] trackIds) {
        switch (itemId) {
        case R.id.list_context_menu_play:
            playTracks(trackIds);
            return true;
        case R.id.list_context_menu_share:
            shareTrack(trackIds[0]);
            return true;
        case R.id.list_context_menu_edit:
            Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
                    .putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
            startActivity(intent);
            return true;
        case R.id.list_context_menu_delete:
            if (trackIds.length > 1 && trackIds.length == listView.getCount()) {
                trackIds = new long[] { -1L };
            }
            deleteTracks(trackIds);
            return true;
        case R.id.list_context_menu_select_all:
            int size = listView.getCount();
            for (int i = 0; i < size; i++) {
                listView.setItemChecked(i, true);
            }
            return false;
        default:
            return false;
        }
    }

    @Override
    public void onPlayMultipleDone(long[] trackIds) {
        playTracks(trackIds);
    }

    @Override
    public void onChooseAccountDone(String account) {
        PreferencesUtils.setString(this, R.string.google_account_key, account);
        if (PreferencesUtils.GOOGLE_ACCOUNT_DEFAULT.equals(account)) {
            return;
        } else {
            new ConfirmSyncDialogFragment().show(getSupportFragmentManager(),
                    ConfirmSyncDialogFragment.CONFIRM_SYNC_DIALOG_TAG);
        }
    }

    @Override
    public void onConfirmSyncDone(boolean enable) {
        if (enable) {
            String googleAccount = PreferencesUtils.getString(this, R.string.google_account_key,
                    PreferencesUtils.GOOGLE_ACCOUNT_DEFAULT);
            enableSync(new Account(googleAccount, Constants.ACCOUNT_TYPE));
        }
    }
}