public class LogCollectorService extends Service {
    private static final String PREFIX = LogCollectorService.class.getName() + ".";
    public static final String ACTION_START_COLLECTING = PREFIX + "ACTION_START_COLLECTING";
    public static final String ACTION_STOP_COLLECTING = PREFIX + "ACTION_STOP_COLLECTING";

    private static final int NOTIFICATION_ID = 1;
    private Uri mCollectingRideUri;
    protected Location mLastLocation;
    private Float mLastCadence;
    private Integer mLastHeartRate;
    private SharedPreferences mPreferences;
    private AndroidWearSender mAndroidWearSender = null;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("intent=" + StringUtil.toString(intent));
        if (intent == null) return Service.START_STICKY;
        String action = intent.getAction();
        if (ACTION_START_COLLECTING.equals(action)) {
            startCollecting(intent.getData());
        } else if (ACTION_STOP_COLLECTING.equals(action)) {
            stopCollecting(intent.getData());
        }
        return Service.START_STICKY;
    }

    private void startCollecting(Uri rideUri) {
        Context context = getApplicationContext();
        Schedulers.io().scheduleDirect(() -> {
            // Smartwatches support (if enabled in prefs)
            if (mPreferences.getBoolean(Constants.PREF_ANDROID_WEAR, Constants.PREF_ANDROID_WEAR_DEFAULT)) {
                mAndroidWearSender = new AndroidWearSender();
                mAndroidWearSender.startSending(context);
            }

            // First, pause current ride if any
            if (mCollectingRideUri != null) {
                RideManager.get().pause(mCollectingRideUri);
            }

            // Check if the ride still exists (it may have been deleted)
            boolean rideExists = RideManager.get().isExistingRide(rideUri);
            Log.d("rideExists=" + rideExists);
            if (!rideExists) {
                stopSelf();
                return;
            }

            // Save the ride as the current one
            RideManager.get().setCurrentRide(rideUri);

            // Now collect for the new current ride
            mCollectingRideUri = rideUri;
            RideManager.get().activate(mCollectingRideUri);

            // Show notification
            Notification notification = createNotification();
            startForeground(NOTIFICATION_ID, notification);

            // Start recording location
            LocationManager.get().addLocationListener(mLocationListener);

            // Start monitoring cadence (if enabled in the prefs)
            if (mPreferences.getBoolean(Constants.PREF_RECORD_CADENCE, Constants.PREF_RECORD_CADENCE_DEFAULT)) {
                CadenceManager.get().addListener(mCadenceListener);
            }

            // Start listening to pref changes (to enable / disable cadence recording accordingly)
            mPreferences.registerOnSharedPreferenceChangeListener(mOnSharedPreferenceChangeListener);

            // Start recording heart rate
            HeartRateManager.get().addListener(mHeartRateListener);
        });
    }

    private void stopCollecting(Uri rideUri) {
        Schedulers.io().scheduleDirect(() -> RideManager.get().pause(rideUri));

        // Dismiss notification
        dismissNotification();

        LocationManager.get().removeLocationListener(mLocationListener);
        CadenceManager.get().removeListener(mCadenceListener);
        HeartRateManager.get().removeListener(mHeartRateListener);

        mCollectingRideUri = null;
        stopSelf();
    }

    /*
     * Location listener.
     */

    private LocationListener mLocationListener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            Schedulers.io().scheduleDirect(() -> {
                LogManager.get().add(mCollectingRideUri, location, mLastLocation, mLastCadence, mLastHeartRate);
                mLastLocation = location;
            });
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {}

        @Override
        public void onProviderEnabled(String provider) {}

        @Override
        public void onProviderDisabled(String provider) {}
    };


    /*
     * Cadence listener.
     */

    private CadenceListener mCadenceListener = new CadenceListener() {
        @Override
        public void onCadenceChanged(Float cadence, float[][] rawData) {
            mLastCadence = cadence;
        }
    };


    /*
     * Pref listener.
     */

    protected OnSharedPreferenceChangeListener mOnSharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
            if (mPreferences.getBoolean(Constants.PREF_RECORD_CADENCE, Constants.PREF_RECORD_CADENCE_DEFAULT)) {
                // Start monitoring cadence
                CadenceManager.get().addListener(mCadenceListener);
            } else {
                // Stop monitoring cadence
                CadenceManager.get().removeListener(mCadenceListener);
            }

            // TODO check for smartwatch preferences and start / stop sending values
        }
    };


    /*
     * Heart rate listener.
     */

    private HeartRateListener mHeartRateListener = new HeartRateListener() {
        @Override
        public void onConnecting() {}

        @Override
        public void onHeartRateChange(int bpm) {
            mLastHeartRate = bpm;
        }

        @Override
        public void onDisconnected() {
            mLastHeartRate = null;
        }

        @Override
        public void onConnected() {}

        @Override
        public void onError() {
            onDisconnected();
        }
    };


    /*
     * Notification.
     */

    private Notification createNotification() {
        NotificationCompat.Builder mainNotifBuilder = new NotificationCompat.Builder(this);
        mainNotifBuilder.setOngoing(true);
        mainNotifBuilder.setSmallIcon(R.drawable.ic_stat_collecting);
        mainNotifBuilder.setTicker(getString(R.string.service_notification_ticker));
        mainNotifBuilder.setContentTitle(getString(R.string.app_name));
        mainNotifBuilder.setContentText(getString(R.string.service_notification_text));
        mainNotifBuilder.setPriority(NotificationCompat.PRIORITY_MAX);

        //        Intent intent = new Intent(this, DisplayActivity.class).setData(mCollectingRideUri);
        //        builder.setContentIntent(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));

        TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(this);
        taskStackBuilder.addParentStack(DisplayActivity.class);
        Log.d("mCollectingRideUri=" + mCollectingRideUri);
        Intent intent = new Intent(this, DisplayActivity.class).setData(mCollectingRideUri);
        taskStackBuilder.addNextIntent(intent);
        mainNotifBuilder.setContentIntent(taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT));

        //TODO
        //        builder.addAction(R.drawable.ic_action_stop, getString(R.string.service_notification_action_stop),
        //                PendingIntent.getBroadcast(this, 0, new Intent(ACTION_DISABLE), PendingIntent.FLAG_CANCEL_CURRENT));
        //        builder.addAction(R.drawable.ic_action_logs, getString(R.string.service_notification_action_logs),
        //                PendingIntent.getActivity(this, 0, new Intent(this, LogActivity.class), PendingIntent.FLAG_UPDATE_CURRENT));

        Notification notification = mainNotifBuilder.build();
        return notification;
    }

    private void dismissNotification() {
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.cancel(NOTIFICATION_ID);
    }


    @Override
    public void onDestroy() {
        // Disconnect smartwatch senders
        if (mAndroidWearSender != null) mAndroidWearSender.stopSending();

        // Unregister pref listener
        PreferenceManager.getDefaultSharedPreferences(LogCollectorService.this).unregisterOnSharedPreferenceChangeListener(mOnSharedPreferenceChangeListener);
        super.onDestroy();
    }
} public class DisplayActivity extends BaseFragmentActivity {
    private static final long DELAY_HIDE_CONTROLS = 4500;

    private Handler mHandler = new Handler();

    private ImageView mImgGpsStatus;
    private CheckableRelativeLayout mChkRecord;
    private TextView mChkRecordText;
    private Animator mChkRecordTextAnimator;
    private View mConTabsA;
    private View mConTabsB;
    private View mConFragments;
    private TextView mTxtTitle;

    private boolean mNavigationBarHiding = false;
    private Uri mRideUri;
    private FragmentCycler mFragmentCycler;
    private boolean mControlsVisible = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);// TODO only if setting says so

        mRideUri = getIntent().getData();

        setContentView(R.layout.display);

        mChkRecord = (CheckableRelativeLayout) findViewById(R.id.chkRecord);
        mChkRecord.setEnabled(false);
        mChkRecordText = (TextView) findViewById(R.id.chkRecord_text);
        mChkRecordTextAnimator = AnimatorInflater.loadAnimator(this, R.animator.blink);
        mChkRecordTextAnimator.setTarget(mChkRecordText);
        mImgGpsStatus = (ImageView) findViewById(R.id.imgGpsStatus);
        ((AnimationDrawable) mImgGpsStatus.getDrawable()).start();
        findViewById(R.id.vieFragmentCycle).setOnTouchListener(mFragmentCycleOnTouchListener);
        mConTabsA = findViewById(R.id.conTabsA);
        mConTabsB = findViewById(R.id.conTabsB);
        mConFragments = findViewById(R.id.conFragments);
        mTxtTitle = (TextView) findViewById(R.id.txtTitle);

        setupFragments(savedInstanceState == null ? 0 : savedInstanceState.getInt("mFragmentCycler.currentVisibleIndex"));

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            setupNavigationBarHiding();
        }
        scheduleHideControls();

        setupFragmentContainer();
    }

    private void setupFragmentContainer() {
        mConFragments.post(() -> {
            PointF shrinkPercents = getShrinkPercents();
            mConFragments.setScaleX(shrinkPercents.x);
            mConFragments.setScaleY(shrinkPercents.y);

            mTxtTitle.setAlpha(0);
        });
    }

    private void toggleRecordingIfActive() {
        Single.fromCallable(() -> RideManager.get().getState(mRideUri))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(rideState -> {
                    switch (rideState) {
                        case CREATED:
                            mChkRecord.setChecked(false, false);
                            mChkRecordText.setText(R.string.display_chkRecord_created);
                            break;

                        case ACTIVE:
                            mChkRecord.setChecked(true, false);
                            mChkRecordText.setText(R.string.display_chkRecord_active);
                            if (!mChkRecordTextAnimator.isStarted()) mChkRecordTextAnimator.start();
                            break;

                        case PAUSED:
                            mChkRecord.setChecked(false, false);
                            mChkRecordText.setText(R.string.display_chkRecord_paused);
                            if (mChkRecordTextAnimator.isStarted()) mChkRecordTextAnimator.cancel();
                            mChkRecordText.setAlpha(1f);

                            break;
                    }

                    mChkRecord.setEnabled(true);
                    mChkRecord.setOnCheckedChangeListener(mRecordingOnCheckedChangeListener);

                });
    }

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

        toggleRecordingIfActive();

        // GPS status
        LocationManager.get().addStatusListener(mGpsStatusListener);

        // Ride
        RideManager.get().addListener(mRideListener);
    }

    @Override
    protected void onStop() {
        // GPS status
        LocationManager.get().removeStatusListener(mGpsStatusListener);

        // Ride
        RideManager.get().removeListener(mRideListener);

        super.onStop();
    }

    private void setupFragments(int currentVisibleIndex) {
        long updateTitleDelay = getResources().getInteger(R.integer.animation_controls_showHide);
        int tabColorEnabled = getResources().getColor(R.color.bright_foreground_dark);
        int tabColorDisabled = getResources().getColor(R.color.bright_foreground_disabled_dark);
        mFragmentCycler = new FragmentCycler(R.id.conFragments, mTxtTitle, updateTitleDelay, tabColorEnabled, tabColorDisabled);
        mFragmentCycler.setCurrentVisibleIndex(currentVisibleIndex);
        mFragmentCycler.add(this, SpeedDisplayFragment.newInstance(), R.id.chkTabSpeed, R.string.display_title_speed);
        mFragmentCycler.add(this, ElapsedTimeDisplayFragment.newInstance(), R.id.chkTabDuration, R.string.display_title_duration);
        mFragmentCycler.add(this, TotalDistanceDisplayFragment.newInstance(), R.id.chkTabDistance, R.string.display_title_distance);
        mFragmentCycler.add(this, HeartRateDisplayFragment.newInstance(), R.id.chkHeartRate, R.string.display_title_heartRate);
        mFragmentCycler.add(this, CompassDisplayFragment.newInstance(), R.id.chkTabCompass, R.string.display_title_compass);
        mFragmentCycler.add(this, CurrentTimeDisplayFragment.newInstance(), R.id.chkTabCurrentTime, R.string.display_title_currentTime);

        HeartRateManager heartRateManager = HeartRateManager.get();
        mFragmentCycler.setEnabled(this, HeartRateDisplayFragment.class, heartRateManager.isConnected() || heartRateManager.isConnecting());

        mFragmentCycler.show(this);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putInt("mFragmentCycler.currentVisibleIndex", mFragmentCycler.getCurrentVisibleIndex());
        super.onSaveInstanceState(outState);
    }

    private OnTouchListener mFragmentCycleOnTouchListener = new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                mFragmentCycler.cycle(thiz);
            }
            return true;
        }
    };


    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private void setupNavigationBarHiding() {
        findViewById(android.R.id.content).setOnSystemUiVisibilityChangeListener(visibility -> {
            Log.d("visibility=" + visibility);
            if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) {
                Log.d("Navigation bar showing");
                if (!isPaused()) mFragmentCycler.cycle(thiz);
                scheduleHideNavigationBar();
                showControls();
                scheduleHideControls();
            }
        });
        scheduleHideNavigationBar();
        mNavigationBarHiding = true;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mNavigationBarHiding) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    Log.d("up");
                    scheduleHideNavigationBar();
                    scheduleHideControls();
                    break;

                default:
                    mHandler.removeCallbacks(mHideNavigationBarRunnable);
                    mHandler.removeCallbacks(mHideControlsRunnable);
                    showControls();
                    break;
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    private Runnable mHideNavigationBarRunnable = this::hideNavigationBar;

    private void scheduleHideNavigationBar() {
        mHandler.removeCallbacks(mHideNavigationBarRunnable);
        mHandler.postDelayed(mHideNavigationBarRunnable, DELAY_HIDE_CONTROLS + 1000);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private void hideNavigationBar() {
        Log.d();
        findViewById(android.R.id.content).setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
    }

    private OnCheckedChangeListener mRecordingOnCheckedChangeListener = new OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            Log.d("isChecked=" + isChecked);
            if (isChecked) {
                startService(new Intent(LogCollectorService.ACTION_START_COLLECTING, mRideUri, thiz, LogCollectorService.class));
                mChkRecordText.setText(R.string.display_chkRecord_active);
                //                mChkRecordTextAnimator.start();
            } else {
                startService(new Intent(LogCollectorService.ACTION_STOP_COLLECTING, mRideUri, thiz, LogCollectorService.class));
                mChkRecordText.setText(R.string.display_chkRecord_paused);
                //                mChkRecordTextAnimator.cancel();
                //                mChkRecordText.setAlpha(1f);
            }
        }
    };

    private StatusListener mGpsStatusListener = new StatusListener() {
        @Override
        public void onStatusChanged(boolean active) {
            if (active) {
                mImgGpsStatus.setVisibility(View.GONE);
            } else {
                mImgGpsStatus.setVisibility(View.VISIBLE);
            }
        }
    };

    public Uri getRideUri() {
        return mRideUri;
    }


    /*
     * Ride listener.
     */

    private RideListener mRideListener = new RideListener() {
        @Override
        public void onActivated(Uri rideUri) {
            if (!rideUri.equals(mRideUri)) return;
            mChkRecord.setChecked(true, false);
            mChkRecordText.setText(R.string.display_chkRecord_active);
            if (!mChkRecordTextAnimator.isStarted()) mChkRecordTextAnimator.start();
        }

        @Override
        public void onPaused(Uri rideUri) {
            if (!rideUri.equals(mRideUri)) return;
            mChkRecord.setChecked(false, false);
            mChkRecordText.setText(R.string.display_chkRecord_paused);
            if (mChkRecordTextAnimator.isStarted()) mChkRecordTextAnimator.cancel();
            mChkRecordText.setAlpha(1f);
        }
    };


    /*
     * Controls show / hide.
     */

    private void hideControls() {
        mControlsVisible = false;
        int duration = getResources().getInteger(R.integer.animation_controls_showHide);
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            // Landscape: tab containers slide to left / right
            mConTabsA.animate().alpha(0).translationX(-mConTabsA.getWidth()).setInterpolator(new AccelerateInterpolator()).setDuration(duration);
            mConTabsB.animate().alpha(0).translationX(mConTabsB.getWidth()).setInterpolator(new AccelerateInterpolator()).setDuration(duration);
        } else {
            // Portrait: tab containers slide to top / bottom
            mConTabsA.animate().alpha(0).translationY(-mConTabsA.getHeight()).setInterpolator(new AccelerateInterpolator()).setDuration(duration);
            mConTabsB.animate().alpha(0).translationY(mConTabsB.getHeight()).setInterpolator(new AccelerateInterpolator()).setDuration(duration);
        }

        // 'Uncompress' the main fragment
        mConFragments.animate().scaleX(1f).scaleY(1f).setInterpolator(new AccelerateInterpolator()).setDuration(duration);

        // Record button
        mChkRecord.animate().alpha(0).translationY(-mChkRecord.getHeight()).setInterpolator(new AccelerateInterpolator()).setDuration(duration);

        // Show the title
        mTxtTitle.animate().alpha(1f).setDuration(duration).setStartDelay(duration);
    }

    private PointF getShrinkPercents() {
        float percentX;
        float percentY;
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            int fragmentWidth = mConFragments.getWidth();
            percentX = (fragmentWidth - 2 * getResources().getDimension(R.dimen.display_tabs_width)) / fragmentWidth;
            // Remove 5% because it looks better
            percentX -= .05f;
            percentY = percentX + (1f - percentX) / 2f;
        } else {
            int fragmentHeight = mConFragments.getHeight();
            percentY = (fragmentHeight - 2 * getResources().getDimension(R.dimen.display_tabs_width)) / fragmentHeight;
            // Remove 5% because it looks better
            percentY -= .05f;
            percentX = percentY + (1f - percentY) / 2f;
        }
        return new PointF(percentX, percentY);
    }

    private void showControls() {
        if (mControlsVisible) return;
        mControlsVisible = true;
        int duration = getResources().getInteger(R.integer.animation_controls_showHide);
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            // Landscape: tab containers slide from left / right
            mConTabsA.animate().alpha(1).translationX(0).setInterpolator(new DecelerateInterpolator()).setDuration(duration);
            mConTabsB.animate().alpha(1).translationX(0).setInterpolator(new DecelerateInterpolator()).setDuration(duration);
        } else {
            // Portrait: tab containers slide from top / bottom
            mConTabsA.animate().alpha(1).translationY(0).setInterpolator(new DecelerateInterpolator()).setDuration(duration);
            mConTabsB.animate().alpha(1).translationY(0).setInterpolator(new DecelerateInterpolator()).setDuration(duration);
        }

        // 'Compress' the main fragment, to make space for the tab containers
        PointF shrinkPercents = getShrinkPercents();
        mConFragments.animate().scaleX(shrinkPercents.x).scaleY(shrinkPercents.y).setInterpolator(new DecelerateInterpolator()).setDuration(duration);

        // Record button
        mChkRecord.animate().alpha(1).translationY(0).setInterpolator(new DecelerateInterpolator()).setDuration(duration);

        mTxtTitle.animate().alpha(0).setDuration(duration).setStartDelay(0);
    }

    private Runnable mHideControlsRunnable = this::hideControls;

    private void scheduleHideControls() {
        mHandler.removeCallbacks(mHideControlsRunnable);
        mHandler.postDelayed(mHideControlsRunnable, DELAY_HIDE_CONTROLS);
    }
} public class PreferenceActivity extends BaseAppCompatActivity
        implements PreferenceCallbacks, AlertDialogListener {
    private static final int REQUEST_PICK_FILE_FOR_IMPORT = 0;
    private static final int REQUEST_SCAN_HEART_RATE_MONITOR = 1;

    private static final int DIALOG_RECORD_CADENCE = 0;
    private static final int DIALOG_DISCONNECT_HEART_RATE = 1;
    private static final int DIALOG_RECONNECT_HEART_RATE = 2;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);

        getFragmentManager().beginTransaction().replace(android.R.id.content, new MainPreferenceFragment()).commit();

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }


    /*
     * Cadence confirmation.
     */

    @Override
    public void showRecordCadenceConfirmDialog() {
        AlertDialogFragment dialog = AlertDialogFragment.newInstance(DIALOG_RECORD_CADENCE);
        dialog.title(R.string.preference_recordCadence_confirmDialog_title);
        dialog.message(R.string.preference_recordCadence_confirmDialog_message);
        dialog.positiveButton(R.string.common_yes);
        dialog.negativeButton(R.string.common_no);
        dialog.show(this);
    }


    /*
     * Database import / export.
     */

    @Override
    public void startExport() {
        new TaskFragment(new Task<PreferenceActivity>() {
            DbExporter mExporter = new DbExporter();

            @Override
            protected void doInBackground() throws Throwable {
                mExporter.export();
            }

            @Override
            protected void onPostExecuteOk() {
                File exportedFile = mExporter.getExportFile();

                Intent sendIntent = new Intent();
                sendIntent.setAction(Intent.ACTION_SEND);
                sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.export_subject));
                String messageBody = getString(R.string.export_body);
                sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + exportedFile.getAbsolutePath()));
                sendIntent.setType("application/bikey");
                sendIntent.putExtra(Intent.EXTRA_TEXT, messageBody);

                startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.preference_export_title)));
            }
        }.toastFail(R.string.export_failToast)).execute(getSupportFragmentManager());
    }

    @Override
    public void startImport() {
        Intent importIntent = new Intent(Intent.ACTION_GET_CONTENT);
        String contentType = "application/octet-stream";
        importIntent.setType(contentType);
        importIntent.addCategory(Intent.CATEGORY_OPENABLE);

        if (getPackageManager().resolveActivity(importIntent, 0) == null) {
            // No file manager found, try the Samsung specific one.
            importIntent = new Intent("com.sec.android.app.myfiles.PICK_DATA");
            importIntent.putExtra("CONTENT_TYPE", contentType);
        }

        startActivityForResult(Intent.createChooser(importIntent, getString(R.string.preference_importDialog_title)), REQUEST_PICK_FILE_FOR_IMPORT);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case REQUEST_PICK_FILE_FOR_IMPORT:
                if (resultCode != RESULT_OK) return;
                importRides(data.getData());
                break;
        }
    }

    private void importRides(Uri ridesFile) {
        new TaskFragment(new Task<PreferenceActivity>() {
            @Override
            protected void doInBackground() throws Throwable {
                DatabaseImporter.importDatabase(thiz, ridesFile);
            }
        }.toastFail(R.string.preference_import_failToast).toastOk(R.string.preference_import_successToast)).execute(getSupportFragmentManager());
    }


    /*
     * Heart rate monitor.
     */

    @Override
    public void startHeartRateMonitorScan() {
        Intent intent = new Intent(this, HeartRateMonitorScanActivity.class);
        startActivityForResult(intent, REQUEST_SCAN_HEART_RATE_MONITOR);
    }

    @Override
    public void disconnectHeartRateMonitor() {
        Log.d();
        AlertDialogFragment dialog = AlertDialogFragment.newInstance(DIALOG_DISCONNECT_HEART_RATE);
        dialog.title(R.string.preference_heartRate_disconnect_confirmDialog_title);
        dialog.message(R.string.preference_heartRate_disconnect_confirmDialog_message);
        dialog.positiveButton(R.string.preference_heartRate_disconnect_confirmDialog_positive);
        dialog.negativeButton(R.string.preference_heartRate_disconnect_confirmDialog_negative);
        dialog.cancelIsNegative(false);
        dialog.show(this);
    }

    @Override
    public void tryToReconnectHeartRateMonitorOrGiveUp() {
        Log.d();
        AlertDialogFragment dialog = AlertDialogFragment.newInstance(DIALOG_RECONNECT_HEART_RATE);
        dialog.title(R.string.preference_heartRate_reconnect_confirmDialog_title);
        dialog.message(R.string.preference_heartRate_reconnect_confirmDialog_message);
        dialog.positiveButton(R.string.preference_heartRate_reconnect_confirmDialog_positive);
        dialog.negativeButton(R.string.preference_heartRate_reconnect_confirmDialog_negative);
        dialog.cancelIsNegative(false);
        dialog.show(this);
    }

    @Override
    public void syncWithGoogleDrive() {
        Log.d();
        Intent intent = new Intent(this, GoogleDriveSyncActivity.class);
        startActivity(intent);
    }


    /*
     * AlertDialogListener.
     */

    @Override
    public void onDialogClickNegative(int tag, Object payload) {
        switch (tag) {
            case DIALOG_RECORD_CADENCE:
                PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(Constants.PREF_RECORD_CADENCE, false).commit();
                break;

            case DIALOG_DISCONNECT_HEART_RATE:
            case DIALOG_RECONNECT_HEART_RATE:
                // Disconnect
                HeartRateManager.get().disconnect();
                break;
        }
    }

    @Override
    public void onDialogClickPositive(int tag, Object payload) {
        switch (tag) {
            case DIALOG_DISCONNECT_HEART_RATE:
                // Disconnect
                HeartRateManager.get().disconnect();

                // Turn off bluetooth
                BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
                if (mBluetoothAdapter.isEnabled()) {
                    mBluetoothAdapter.disable();
                }
                break;

            case DIALOG_RECONNECT_HEART_RATE:
                // Disconnect and go back to scanning
                HeartRateManager.get().disconnect();
                startHeartRateMonitorScan();
                break;
        }
    }

    @Override
    public void onDialogClickListItem(int tag, int index, Object payload) {}
} public class RideDetailActivity extends BaseAppCompatActivity implements AlertDialogListener {
    private static final String FRAGMENT_RETAINED_STATE = "FRAGMENT_RETAINED_STATE";

    private static final int POINTS_TO_GRAPH = 100;

    private static final int DIALOG_CONFIRM_DELETE = 0;
    private static final int DIALOG_SHARE = 1;

    private RideDetailBinding mBinding;

    private Uri mRideUri;
    private RideDetailStateFragment mState;
    private GoogleMap mMap;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.ride_detail);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        mRideUri = getIntent().getData();

        restoreState();
    }

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

    private void restoreState() {
        mState = (RideDetailStateFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_RETAINED_STATE);
        if (mState == null) {
            mState = new RideDetailStateFragment();
            getSupportFragmentManager().beginTransaction().add(mState, FRAGMENT_RETAINED_STATE).commit();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.ride_detail, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;

            case R.id.action_display:
                finish();
                Intent intent = new Intent(this, DisplayActivity.class);
                intent.setData(mRideUri);
                startActivity(intent);
                return true;

            case R.id.action_delete:
                showDeleteDialog();
                return true;

            case R.id.action_share:
                showShareDialog();
                return true;

            case R.id.action_edit:
                startActivity(new Intent(this, RideEditActivity.class).setData(mRideUri));
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void loadData() {
        new TaskFragment(new Task<RideDetailActivity>() {
            private String mName;
            private Date mCreatedDate;
            private long mDuration;
            private float mDistance;
            private float mAverageMovingSpeed;
            private float mMaxSpeed;
            private Date mFirstActivatedDate;
            private Long mMovingDuration;
            private Float mAverageCadence;
            private float mMaxCadence;
            private List<LatLng> mLatLngArray;
            private float[] mSpeedArray;
            private float[] mCadenceArray;
            private float mMinHeartRate;
            private float mMaxHeartRate;
            private Float mAverageHeartRate;
            private float[] mHeartRateArray;

            @Override
            protected void onPreExecute() {
                mBinding.pgbLoading.setVisibility(View.VISIBLE);
                mBinding.conRoot.setVisibility(View.INVISIBLE);
            }

            @Override
            protected void doInBackground() throws Throwable {
                RideManager rideManager = RideManager.get();
                Uri rideUri = getActivity().mRideUri;
                RideCursor rideCursor = rideManager.query(rideUri);
                mName = rideCursor.getName();
                mCreatedDate = rideCursor.getCreatedDate();
                mFirstActivatedDate = rideCursor.getFirstActivatedDate();
                mDuration = rideCursor.getDuration();
                mDistance = rideCursor.getDistance();
                rideCursor.close();

                LogManager logManager = LogManager.get();
                mAverageMovingSpeed = logManager.getAverageMovingSpeed(rideUri);
                mMaxSpeed = logManager.getMaxSpeed(rideUri);
                mMovingDuration = logManager.getMovingDuration(rideUri);
                mAverageCadence = logManager.getAverageCadence(rideUri);
                mMaxCadence = logManager.getMaxCadence(rideUri);
                mMinHeartRate = logManager.getMinHeartRate(rideUri);
                mMaxHeartRate = logManager.getMaxHeartRate(rideUri);
                mAverageHeartRate = logManager.getAverageHeartRate(rideUri);

                mLatLngArray = logManager.getLatLngArray(rideUri, POINTS_TO_GRAPH);

                List<Float> speedList = logManager.getSpeedArray(rideUri, POINTS_TO_GRAPH);
                mSpeedArray = CollectionUtil.unwrap(speedList.toArray(new Float[speedList.size()]));
                mSpeedArray = MathUtil.getMovingAverage(mSpeedArray, mSpeedArray.length / 10);

                List<Float> cadenceList = logManager.getCadenceArray(rideUri, POINTS_TO_GRAPH);
                mCadenceArray = CollectionUtil.unwrap(cadenceList.toArray(new Float[cadenceList.size()]));
                mCadenceArray = MathUtil.getMovingAverage(mCadenceArray, mCadenceArray.length / 10);

                List<Float> heartRateList = logManager.getHeartRateArray(rideUri, POINTS_TO_GRAPH);
                mHeartRateArray = CollectionUtil.unwrap(heartRateList.toArray(new Float[heartRateList.size()]));
                mHeartRateArray = MathUtil.getMovingAverage(mHeartRateArray, mHeartRateArray.length / 10);

                // Make sure the map is actually available (this is blocking for a few seconds)
                getMap();
            }

            @Override
            protected void onPostExecuteOk() {
                RideDetailActivity a = getActivity();

                if (a.mMap == null) return;

                a.mBinding.pgbLoading.setVisibility(View.GONE);
                a.mBinding.conRoot.setVisibility(View.VISIBLE);

                if (mName != null) a.setTitle(mName);
                a.mBinding.txtDateTimeDate
                        .setText(DateUtils.formatDateTime(a, mCreatedDate.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
                        | DateUtils.FORMAT_SHOW_YEAR));

                if (mLatLngArray.isEmpty()) {
                    // Special case: we have no points. Show empty screen.
                    a.mBinding.conMap.setVisibility(View.GONE);
                    a.mBinding.conDetailedInfo.setVisibility(View.GONE);
                    a.mBinding.txtEmpty.setVisibility(View.VISIBLE);
                    return;
                }

                if (mFirstActivatedDate != null) {
                    mBinding.txtDateTimeStart.setText(DateUtils.formatDateTime(a, mFirstActivatedDate.getTime(), DateUtils.FORMAT_SHOW_TIME));
                    mBinding.txtDateTimeFinish.setText(DateUtils.formatDateTime(a, mFirstActivatedDate.getTime() + mDuration, DateUtils.FORMAT_SHOW_TIME));
                }
                if (mMovingDuration != null) mBinding.txtDurationMoving.setText(DateTimeUtil.formatDuration(a, mMovingDuration));
                mBinding.txtDurationTotal.setText(DateTimeUtil.formatDuration(a, mDuration));
                mBinding.txtDistanceTotal.setText(UnitUtil.formatDistance(mDistance, true, .85f, false));

                mBinding.txtSpeedAverage.setText(UnitUtil.formatSpeed(mAverageMovingSpeed, true, .85f, false));
                mBinding.txtSpeedMax.setText(UnitUtil.formatSpeed(mMaxSpeed, true, .85f, false));

                // Cadence
                if (mAverageCadence == null) {
                    mBinding.txtCadenceSectionTitle.setVisibility(View.GONE);
                    mBinding.txtCadenceAverage.setVisibility(View.GONE);
                    mBinding.txtCadenceMax.setVisibility(View.GONE);
                    mBinding.grpCadence.setVisibility(View.GONE);
                } else {
                    mBinding.txtCadenceSectionTitle.setVisibility(View.VISIBLE);
                    mBinding.txtCadenceAverage.setVisibility(View.VISIBLE);
                    mBinding.txtCadenceAverage.setText(UnitUtil.formatCadence(mAverageCadence, true));
                    mBinding.txtCadenceMax.setVisibility(View.VISIBLE);
                    mBinding.txtCadenceMax.setText(UnitUtil.formatCadence(mMaxCadence, true));
                    mBinding.grpCadence.setVisibility(View.VISIBLE);
                    mBinding.grpCadence.setColor(0, a.getResources().getColor(R.color.graph_line));
                    mBinding.grpCadence.setValues(0, mCadenceArray);
                }

                // Map
                a.getMap().getUiSettings().setMapToolbarEnabled(false);
                if (mLatLngArray.size() > 0) {
                    // Polyline
                    PolylineOptions polylineOptions = new PolylineOptions().addAll(mLatLngArray);
                    polylineOptions.color(getResources().getColor(R.color.map_polyline));
                    a.getMap().addPolyline(polylineOptions);

                    // Start / finish markers
                    a.getMap().addMarker(new MarkerOptions()
                            .position(mLatLngArray.get(0)).icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)));

                    a.getMap().addMarker(new MarkerOptions()
                            .position(mLatLngArray.get(mLatLngArray.size() - 1)).icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)));

                    // Calculate bounds
                    LatLngBounds bounds = new LatLngBounds(mLatLngArray.get(0), mLatLngArray.get(0));
                    for (LatLng latLng : mLatLngArray) {
                        bounds = bounds.including(latLng);
                    }
                    int padding = getResources().getDimensionPixelSize(R.dimen.ride_detail_map_padding);
                    a.getMap().moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, padding));

                    a.mBinding.conMap.setVisibility(View.VISIBLE);
                }

                // Speed graph
                mBinding.grpSpeed.setColor(0, a.getResources().getColor(R.color.graph_line));
                mBinding.grpSpeed.setValues(0, mSpeedArray);

                // Heart rate
                if (mAverageHeartRate == null) {
                    mBinding.txtHeartRateSectionTitle.setVisibility(View.GONE);
                    mBinding.txtHeartRateAverage.setVisibility(View.GONE);
                    mBinding.txtHeartRateMin.setVisibility(View.GONE);
                    mBinding.txtHeartRateMax.setVisibility(View.GONE);
                    mBinding.grpHeartRate.setVisibility(View.GONE);
                } else {
                    mBinding.txtHeartRateSectionTitle.setVisibility(View.VISIBLE);
                    mBinding.txtHeartRateAverage.setVisibility(View.VISIBLE);
                    mBinding.txtHeartRateAverage.setText(UnitUtil.formatHeartRate(mAverageHeartRate.intValue(), true));
                    mBinding.txtHeartRateMin.setVisibility(View.VISIBLE);
                    mBinding.txtHeartRateMin.setText(UnitUtil.formatHeartRate((int) mMinHeartRate, true));
                    mBinding.txtHeartRateMax.setVisibility(View.VISIBLE);
                    mBinding.txtHeartRateMax.setText(UnitUtil.formatHeartRate((int) mMaxHeartRate, true));
                    mBinding.grpHeartRate.setVisibility(View.VISIBLE);
                    mBinding.grpHeartRate.setColor(0, a.getResources().getColor(R.color.graph_line));
                    mBinding.grpHeartRate.setValues(0, mHeartRateArray);
                }
            }
        }).execute(getSupportFragmentManager(), false);
    }


    /*
     * Map.
     */

    /**
     * Blocks until the map is actually available.
     */
    @WorkerThread
    private GoogleMap getMap() {
        if (mMap == null) {
            CountDownLatch latch = new CountDownLatch(1);
            HandlerUtil.runOnUiThread(() -> {
                SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
                if (mapFragment == null) {
                    latch.countDown();
                    return;
                }
                mapFragment.getMapAsync(googleMap -> {
                    mMap = googleMap;
                    latch.countDown();
                });
            });

            try {
                latch.await(2, TimeUnit.SECONDS);
            } catch (InterruptedException ignored) {}
        }
        return mMap;
    }

    public void onMapClicked(View view) {
        Log.d();
        Intent intent = new Intent(this, RideMapActivity.class);
        intent.setData(mRideUri);
        startActivity(intent);
    }


    /*
     * Delete.
     */

    private void showDeleteDialog() {
        AlertDialogFragment dialog = AlertDialogFragment.newInstance(DIALOG_CONFIRM_DELETE);
        dialog.title(R.string.preference_heartRate_disconnect_confirmDialog_title);
        dialog.message(R.string.ride_detail_deleteDialog_message);
        dialog.positiveButton(android.R.string.ok);
        dialog.negativeButton(android.R.string.cancel);
        dialog.show(this);
    }

    private void delete() {
        long[] ids = {ContentUris.parseId(mRideUri)};
        new TaskFragment(new Task<RideDetailActivity>() {
            @Override
            protected void doInBackground() throws Throwable {
                RideManager.get().delete(ids);
            }

            @Override
            protected void onPostExecuteOk() {
                finish();
            }
        }).execute(getSupportFragmentManager());
    }


    /*
     * Share.
     */

    public void showShareDialog() {
        AlertDialogFragment dialog = AlertDialogFragment.newInstance(DIALOG_SHARE);
        dialog.title(R.string.ride_list_shareDialog_title);
        dialog.items(R.array.export_choices);
        dialog.show(this);
    }

    @Override
    public void onDialogClickListItem(int tag, int index, Object payload) {
        switch (index) {
            case 0:
                // Gpx
                mState.mExporter = new GpxExporter(mRideUri);
                break;
            case 1:
                // Kml
                mState.mExporter = new KmlExporter(mRideUri);
                break;
            case 2:
                // Genymotion script
                mState.mExporter = new GenymotionExporter(mRideUri);
                break;
            case 3:
                // Bikey
                mState.mExporter = new BikeyExporter(mRideUri);
                break;
        }
        startExport();
    }

    private void startExport() {
        new TaskFragment(new Task<RideDetailActivity>() {
            @Override
            protected void doInBackground() throws Throwable {
                getActivity().mState.mExporter.export();
            }

            @Override
            protected void onPostExecuteOk() {
                File exportedFile = getActivity().mState.mExporter.getExportFile();

                Intent sendIntent = new Intent();
                sendIntent.setAction(Intent.ACTION_SEND);
                sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.export_subject));
                String messageBody = getString(R.string.export_body);
                sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + exportedFile.getAbsolutePath()));
                sendIntent.setType("application/bikey");
                sendIntent.putExtra(Intent.EXTRA_TEXT, messageBody);

                startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.ride_list_action_share)));

            }
        }.toastFail(R.string.export_failToast)).execute(getSupportFragmentManager());
    }


    /*
     * Dialog callbacks.
     */

    @Override
    public void onDialogClickPositive(int tag, Object payload) {
        switch (tag) {
            case DIALOG_CONFIRM_DELETE:
                delete();
                break;
        }
    }

    @Override
    public void onDialogClickNegative(int tag, Object payload) {}
} public class RideEditActivity extends BaseAppCompatActivity {
    private EditText mEdtName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.ride_edit);
        mEdtName = (EditText) findViewById(R.id.edtName);
        mEdtName.setOnEditorActionListener((v, actionId, event) -> {
            saveRide();
            return true;
        });
        populateViews();
        setupActionBar();
    }

    private void populateViews() {
        Uri rideUri = getIntent().getData();
        if (rideUri == null) return;
        new TaskFragment(new Task<RideEditActivity>() {
            private String mRideName;

            @Override
            protected void doInBackground() throws Throwable {
                mRideName = RideManager.get().getName(rideUri);
            }

            @Override
            protected void onPostExecuteOk() {
                if (mRideName != null) mEdtName.append(mRideName);
            }
        }).execute(getSupportFragmentManager());
    }

    private void setupActionBar() {
        ActionBar actionBar = getSupportActionBar();
        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
        View customActionBarView = getLayoutInflater().inflate(R.layout.ride_edit_actionbar, null);
        actionBar.setCustomView(customActionBarView, new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

        View btnDone = customActionBarView.findViewById(R.id.actionbar_done);
        btnDone.setOnClickListener(v -> saveRide());

        View btnDiscard = customActionBarView.findViewById(R.id.actionbar_discard);
        btnDiscard.setOnClickListener(v -> finish());
    }

    private void saveRide() {
        new TaskFragment(new Task<RideEditActivity>() {
            private Uri mRideUri;

            @Override
            protected void doInBackground() throws Throwable {
                String name = getActivity().mEdtName.getText().toString().trim();
                Uri rideUri = getIntent().getData();
                if (rideUri == null) {
                    // Create
                    mRideUri = RideManager.get().create(name);

                    // Save the new ride as the current one
                    RideManager.get().setCurrentRide(mRideUri);
                } else {
                    mRideUri = rideUri;
                    RideManager.get().updateName(rideUri, name);
                }
            }

            @Override
            protected void onPostExecuteOk() {
                setResult(RESULT_OK, new Intent(null, mRideUri));
                getActivity().finish();
            }
        }).execute(getSupportFragmentManager());
    }
} public class BikeyExporter extends Exporter {
    private static final long NEW_SEGMENT_DURATION = 5 * 60 * 1000;

    public BikeyExporter(Uri rideUri) {
        super(rideUri);
    }

    @Override
    protected String getExportedFileName() {
        return FileUtil.getValidFileName(RideManager.get().getDisplayName(getRideUri()) + ".ride");
    }

    @Override
    @WorkerThread
    public void export() throws IOException {
        PrintWriter out = new PrintWriter(new BufferedOutputStream(getOutputStream()));
        // Header
        String appVersion = null;
        try {
            appVersion = String.valueOf(getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), 0).versionCode);
        } catch (PackageManager.NameNotFoundException e) {
            // Can never happen
        }
        String creationDate = DateTimeUtil.toIso8601(System.currentTimeMillis(), true);

        // Query
        long rideId = ContentUris.parseId(getRideUri());
        LogSelection logSelection = new LogSelection();
        logSelection.rideId(rideId);
        LogCursor logCursor = logSelection.query(getContext(), LogColumns.ALL_COLUMNS);

        int logCount = logCursor.getCount();
        out.println(getString(R.string.export_bikey_begin, appVersion, creationDate, logCount));

        RideCursor rideCursor = RideManager.get().query(getRideUri());
        exportCursorRow(rideCursor, out);
        rideCursor.close();

        // Logs
        out.println(getString(R.string.export_bikey_logs_begin));

        while (logCursor.moveToNext()) {
            out.println(getString(R.string.export_bikey_log_begin));
            exportCursorRow(logCursor, out);
            out.println(getString(R.string.export_bikey_log_end));
        }
        logCursor.close();

        // End
        out.println(getString(R.string.export_bikey_logs_end));
        out.println(getString(R.string.export_bikey_end));
        out.flush();
        IoUtil.closeSilently(out);

    }

    private static void exportCursorRow(Cursor cursor, PrintWriter out) {
        int rideCursorColumnCount = cursor.getColumnCount();
        for (int i = 0; i < rideCursorColumnCount; i++) {
            String rideColumnName = cursor.getColumnName(i);
            int rideColumnType = cursor.getType(i);
            out.print("<" + rideColumnName + " type=\"" + rideColumnType + "\">");
            String value = null;
            switch (rideColumnType) {
                case Cursor.FIELD_TYPE_NULL:
                    value = "null";
                    break;

                case Cursor.FIELD_TYPE_STRING:
                    value = cursor.getString(i);
                    break;

                case Cursor.FIELD_TYPE_FLOAT:
                    value = String.valueOf(cursor.getDouble(i));
                    break;

                case Cursor.FIELD_TYPE_INTEGER:
                    value = String.valueOf(cursor.getLong(i));
                    break;
            }
            out.print(value);
            out.println("</" + rideColumnName + ">");
        }
    }
} public class GenymotionExporter extends Exporter {
    public GenymotionExporter(Uri rideUri) {
        super(rideUri);
    }

    @Override
    protected String getExportedFileName() {
        return FileUtil.getValidFileName(RideManager.get().getDisplayName(getRideUri()) + ".gm");
    }

    @Override
    @WorkerThread
    public void export() throws IOException {
        PrintWriter out = new PrintWriter(getOutputStream());
        long rideId = ContentUris.parseId(getRideUri());
        String selection = LogColumns.RIDE_ID + "=?";
        String[] selectionArgs = { String.valueOf(rideId) };
        LogCursor c = new LogCursor(getContext().getContentResolver().query(LogColumns.CONTENT_URI, null, selection, selectionArgs, null));
        try {
            while (c.moveToNext()) {
                String lat = String.valueOf(c.getLat());
                String lon = String.valueOf(c.getLon());
                String ele = String.valueOf(c.getEle());
                out.println("gps setlatitude " + lat);
                out.println("gps setlongitude " + lon);
                out.println("gps setaltitude " + ele);
                out.println("pause 1");
            }
        } finally {
            c.close();
        }
        IoUtil.closeSilently(out);
    }
} public class GpxExporter extends Exporter {
    private static final long NEW_SEGMENT_DURATION = 5 * 60 * 1000;

    public GpxExporter(Uri rideUri) {
        super(rideUri);
    }

    @Override
    protected String getExportedFileName() {
        return FileUtil.getValidFileName(RideManager.get().getDisplayName(getRideUri()) + ".gpx");
    }

    @Override
    @WorkerThread
    public void export() throws IOException {
        PrintWriter out = new PrintWriter(getOutputStream());
        // Header
        String appName = getString(R.string.app_name);
        String rideName = RideManager.get().getDisplayName(getRideUri());
        out.println(getString(R.string.export_gpx_begin, appName, rideName));

        long rideId = ContentUris.parseId(getRideUri());
        String selection = LogColumns.RIDE_ID + "=?";
        String[] selectionArgs = { String.valueOf(rideId) };
        Long previousRecordedDate = null;
        LogCursor c = new LogCursor(getContext().getContentResolver().query(LogColumns.CONTENT_URI, null, selection, selectionArgs, null));
        try {
            while (c.moveToNext()) {
                long recordedDate = c.getRecordedDate().getTime();
                // Track segment
                if (previousRecordedDate == null) {
                    out.println(getString(R.string.export_gpx_trackSegment_begin));
                } else if (recordedDate - previousRecordedDate > NEW_SEGMENT_DURATION) {
                    out.println(getString(R.string.export_gpx_trackSegment_end));
                    out.println(getString(R.string.export_gpx_trackSegment_begin));
                }

                // Track point
                String lat = String.valueOf(c.getLat());
                String lon = String.valueOf(c.getLon());
                String ele = String.valueOf(c.getEle());
                String dateTime = DateTimeUtil.toIso8601(recordedDate, true);
                out.println(getString(R.string.export_gpx_trackPoint, lat, lon, ele, dateTime));

                previousRecordedDate = recordedDate;
            }
        } finally {
            c.close();
        }
        out.println(getString(R.string.export_gpx_trackSegment_end));
        out.println(getString(R.string.export_gpx_end));
        IoUtil.closeSilently(out);
    }
} public class KmlExporter extends Exporter {

    public KmlExporter(Uri rideUri) {
        super(rideUri);
    }

    @Override
    protected String getExportedFileName() {
        return FileUtil.getValidFileName(RideManager.get().getDisplayName(getRideUri()) + ".kml");
    }

    @Override
    @WorkerThread
    public void export() throws IOException {
        PrintWriter out = new PrintWriter(getOutputStream());
        Uri rideUri = getRideUri();
        // Header
        out.println(getString(R.string.export_kml_document_begin));
        String appName = getString(R.string.app_name);
        String rideName = RideManager.get().getDisplayName(rideUri);
        out.println(getString(R.string.export_kml_name, appName + ": " + rideName));

        String timestampNow = new Date().toString();
        String created = getString(R.string.export_kml_created, timestampNow);
        out.println(getString(R.string.export_kml_timestamp, created));
        long rideId = ContentUris.parseId(rideUri);
        String selection = LogColumns.RIDE_ID + "=?";
        String[] selectionArgs = { String.valueOf(rideId) };
        LogCursor c = new LogCursor(getContext().getContentResolver().query(LogColumns.CONTENT_URI, null, selection, selectionArgs, null));
        try {
            c.moveToFirst();
            // Write the LookAt element, which contains the start and end timestamps, and the first coordinate.
            long rideBeginDate = c.getRecordedDate().getTime();
            long rideEndDate = rideBeginDate + RideManager.get().getDuration(rideUri);
            String timestampBegin = DateTimeUtil.toIso8601(rideBeginDate, false);
            String timestampEnd = DateTimeUtil.toIso8601(rideEndDate, false);
            double firstLatitude = c.getLat();
            double firstLongitude = c.getLon();
            double range = 500;
            out.println(getString(R.string.export_kml_look_at, timestampBegin, timestampEnd, firstLongitude, firstLatitude, range));

            // Write the KML elements leading up to the list of track points.
            out.println(getString(R.string.export_kml_style));
            out.println(getString(R.string.export_kml_folder_begin, getString(R.string.export_kml_folder_name)));

            // Write out the Placemark for the track.
            c.moveToPosition(-1);
            writeTrackPlacemark(c, out, timestampBegin);

            // Write out the Placemark for the LineString.
            c.moveToPosition(-1);
            writeLineStringPlacemark(c, out, timestampBegin);

            // Write out the Placemark for the end Point.
            c.moveToPosition(-1);
            String placemarkName = getString(R.string.export_kml_point_name);
            RideExtendedData rideExtendedData = new RideExtendedData(getContext(), rideUri);
            c.moveToLast();
            writePointPlacemark(rideExtendedData, c, out, placemarkName, Style.DEFAULT);

            // Write out the cadence as a set of Placemarks
            writeCadence(rideId, out);

            // Write out the heart rate logs as a set of Placemarks
            writeHeartRate(rideId, out);

            // Write the KML elements to close the document.
            out.println(getString(R.string.export_kml_folder_end));
            out.println(getString(R.string.export_kml_document_end));
        } finally {
            c.close();
        }
        IoUtil.closeSilently(out);
    }

    /**
     * Write a Placemark which contains a gx:Track element
     */
    private void writeTrackPlacemark(LogCursor c, PrintWriter out, String timestampBegin) {
        Log.d();
        out.println(getString(R.string.export_kml_placemark_begin));
        String trackName = getString(R.string.export_kml_track_name, timestampBegin);
        out.println(getString(R.string.export_kml_name, trackName));
        out.println(getString(R.string.export_kml_track_style_url));
        out.println(getString(R.string.export_kml_track_begin));

        // Write the timestamps for each track point
        while (c.moveToNext()) {
            long recordedDate = c.getRecordedDate().getTime();
            String dateTime = DateTimeUtil.toIso8601(recordedDate, true);
            out.println(getString(R.string.export_kml_when, dateTime));
        }

        // Write the coordinates for each track point
        c.moveToPosition(-1);
        while (c.moveToNext()) {
            double latitude = c.getLat();
            double longitude = c.getLon();
            double elevation = c.getEle();
            out.println(getString(R.string.export_kml_coord, longitude, latitude, elevation));
        }

        out.println(getString(R.string.export_kml_track_end));
        out.println(getString(R.string.export_kml_placemark_end));
    }

    /**
     * Write a Placemark which contains a LineString element.
     */
    private void writeLineStringPlacemark(LogCursor c, PrintWriter out, String timestampBegin) {
        Log.d();
        out.println(getString(R.string.export_kml_placemark_begin));
        String linestringName = getString(R.string.export_kml_linestring_name, timestampBegin);
        out.println(getString(R.string.export_kml_name, linestringName));
        out.println(getString(R.string.export_kml_track_style_url));
        out.println(getString(R.string.export_kml_linestring_begin));
        c.moveToPosition(-1);
        while (c.moveToNext()) {
            double latitude = c.getLat();
            double longitude = c.getLon();
            double elevation = c.getEle();
            out.println(longitude + "," + latitude + "," + elevation + " ");
        }
        out.println(getString(R.string.export_kml_linestring_end));
        out.println(getString(R.string.export_kml_placemark_end));
    }

    /**
     * Write a Placemark which contains a Point element corresponding to the last track point.
     */
    private void writePointPlacemark(RideExtendedData rideExtendedData, LogCursor c, PrintWriter out, String placemarkName, Style style) {
        Log.d();
        out.println(getString(R.string.export_kml_placemark_begin));
        out.println(getString(R.string.export_kml_name, placemarkName));
        if (style != Style.DEFAULT) out.println(getString(style.getResId()));
        out.println(getString(R.string.export_kml_point_begin));
        double latitude = c.getLat();
        double longitude = c.getLon();
        double elevation = c.getEle();
        out.println(longitude + "," + latitude + "," + elevation + " ");
        out.println(getString(R.string.export_kml_point_end));
        if (rideExtendedData != null) out.println(rideExtendedData.toString());
        out.println(getString(R.string.export_kml_placemark_end));
    }

    /**
     * Write a folder containing all the cadence points
     */
    private void writeCadence(long rideId, PrintWriter out) {
        Log.d();
        String selection = LogColumns.RIDE_ID + "=? AND " + LogColumns.CADENCE + " NOT NULL";
        String[] selectionArgs = { String.valueOf(rideId) };
        LogCursor c = new LogCursor(getContext().getContentResolver().query(LogColumns.CONTENT_URI, null, selection, selectionArgs, LogColumns.RECORDED_DATE));
        if (c != null) {
            try {
                // Only write out cadence if we have enough values.
                if (c.getCount() < 5) return;
                out.println(getString(R.string.export_kml_folder_begin, getString(R.string.export_kml_cadence_folder_name)));
                int previousCadence = 0;
                while (c.moveToNext()) {
                    int cadence = c.getCadence().intValue();
                    if (cadence != previousCadence) {
                        Style style = Style.RED;
                        if (cadence > 80) style = Style.GREEN;
                        else if (cadence >= 60) style = Style.YELLOW;
                        writePointPlacemark(null, c, out, String.valueOf(cadence), style);
                        previousCadence = cadence;
                    }
                }
                out.println(getString(R.string.export_kml_folder_end));
            } finally {
                c.close();
            }
        }

    }

    /**
     * Write a folder containing all the heart rate points
     */
    private void writeHeartRate(long rideId, PrintWriter out) {
        Log.d();
        String selection = LogColumns.RIDE_ID + "=? AND " + LogColumns.HEART_RATE + " NOT NULL";
        String[] selectionArgs = { String.valueOf(rideId) };
        LogCursor c = new LogCursor(getContext().getContentResolver().query(LogColumns.CONTENT_URI, null, selection, selectionArgs, LogColumns.RECORDED_DATE));
        if (c != null) {
            try {
                // Only write out heart rate if we have enough values.
                if (c.getCount() < 5) return;
                out.println(getString(R.string.export_kml_folder_begin, getString(R.string.export_kml_heart_rate_folder_name)));
                int previousHeartRate = 0;
                while (c.moveToNext()) {
                    int heartRate = c.getHeartRate();
                    if (heartRate != previousHeartRate) {
                        Style style = Style.RED;
                        if (heartRate > 100) style = Style.GREEN;
                        else if (heartRate >= 60) style = Style.YELLOW;
                        writePointPlacemark(null, c, out, String.valueOf(heartRate), style);
                        previousHeartRate = heartRate;
                    }
                }
                out.println(getString(R.string.export_kml_folder_end));
            } finally {
                c.close();
            }
        }

    }
} public class RideManager {
    private static final RideManager INSTANCE = new RideManager();

    public static RideManager get() {
        return INSTANCE;
    }

    private final Context mContext;
    private Listeners<RideListener> mListeners = Listeners.newInstance();

    private RideManager() {
        mContext = Application.getApplication();
    }

    @WorkerThread
    public Uri create(String name) {
        RideContentValues values = new RideContentValues();
        values.putUuid(UUID.randomUUID().toString());
        values.putCreatedDate(new Date());
        if (!TextUtils.isEmpty(name)) {
            values.putName(name);
        }
        values.putState(RideState.CREATED);
        values.putDuration(0L);
        values.putDistance(0f);
        return values.insert(mContext);
    }

    @WorkerThread
    public int delete(long[] ids) {
        // First pause any active rides in the list
        pauseRides(ids);

        // Mark rides as deleted
        RideSelection rideSelection = new RideSelection();
        rideSelection.id(ids);
        RideContentValues rideContentValues = new RideContentValues();
        rideContentValues.putState(RideState.DELETED);
        int res = rideContentValues.update(mContext, rideSelection);

        // Delete logs
        LogSelection logSelection = new LogSelection();
        logSelection.rideId(ids);
        logSelection.delete(mContext);

        // If we just deleted the current ride, select another ride to be the current ride (if any).
        Uri currentRideUri = getCurrentRide();
        if (currentRideUri != null) {
            long currentRideId = Long.valueOf(currentRideUri.getLastPathSegment());
            for (long id : ids) {
                if (currentRideId == id) {
                    Uri nextRideUri = getMostRecentRide();
                    setCurrentRide(nextRideUri);
                    break;
                }
            }
        }
        return res;
    }

    @WorkerThread
    public void merge(long[] ids) {
        // First pause any active rides in the list
        pauseRides(ids);

        // Choose the master ride (the one with the earliest creation date)
        String[] projection = {RideColumns._ID};
        RideSelection rideSelection = new RideSelection();
        rideSelection.id(ids);
        rideSelection.orderByCreatedDate();
        ContentResolver contentResolver = mContext.getContentResolver();
        RideCursor rideCursor = rideSelection.query(contentResolver, projection);
        long masterRideId;
        try {
            rideCursor.moveToNext();
            masterRideId = rideCursor.getId();
        } finally {
            rideCursor.close();
        }

        // Calculate the total duration
        projection = new String[] {"sum(" + RideColumns.DURATION + ")"};
        Cursor c = contentResolver.query(RideColumns.CONTENT_URI, projection, rideSelection.sel(), rideSelection.args(), null);
        long totalDuration = 0;
        try {
            if (c.moveToNext()) {
                totalDuration = c.getLong(0);
            }
        } finally {
            c.close();
        }

        // Merge
        for (long mergedRideId : ids) {
            if (mergedRideId == masterRideId) continue;

            // Update logs
            LogSelection logSelection = new LogSelection();
            logSelection.rideId(mergedRideId);
            LogContentValues values = new LogContentValues();
            values.putRideId(masterRideId);
            values.update(contentResolver, logSelection);

            // Delete merged ride
            rideSelection = new RideSelection();
            rideSelection.id(mergedRideId);
            // Do not notify yet
            Uri contentUri = BikeyProvider.notify(RideColumns.CONTENT_URI, false);
            contentResolver.delete(contentUri, rideSelection.sel(), rideSelection.args());
        }

        // Rename master ride
        Uri masterRideUri = ContentUris.withAppendedId(RideColumns.CONTENT_URI, masterRideId);
        String name = getName(masterRideUri);
        if (name == null) {
            name = mContext.getString(R.string.ride_list_mergedRide);
        } else {
            name = mContext.getString(R.string.ride_list_mergedRide_append, name);
        }
        RideContentValues values = new RideContentValues();
        values.putName(name);
        contentResolver.update(masterRideUri, values.values(), null, null);

        // Update master ride total distance
        float distance = LogManager.get().getTotalDistance(masterRideUri);
        updateTotalDistance(masterRideUri, distance);

        // Update master ride total duration
        updateDuration(masterRideUri, totalDuration);
    }

    private void pauseRides(long[] ids) {
        for (long rideId : ids) {
            Uri rideUri = ContentUris.withAppendedId(RideColumns.CONTENT_URI, rideId);
            RideState state = getState(rideUri);
            if (state == RideState.ACTIVE) {
                mContext.startService(new Intent(LogCollectorService.ACTION_STOP_COLLECTING, rideUri, mContext, LogCollectorService.class));
                break;
            }
        }
    }

    @WorkerThread
    public void activate(@NonNull Uri rideUri) {
        // Get first activated date
        Date firstActivatedDate = getFirstActivatedDate(rideUri);

        // Update state
        RideContentValues values = new RideContentValues();
        values.putState(RideState.ACTIVE);
        // Update activated date
        Date now = new Date();
        values.putActivatedDate(now);
        // Update first activated date, only if first time
        if (firstActivatedDate == null) {
            values.putFirstActivatedDate(now);
        }
        mContext.getContentResolver().update(rideUri, values.values(), null, null);

        // Dispatch to listeners
        mListeners.dispatch(listener -> listener.onActivated(rideUri));
    }

    @WorkerThread
    public void updateTotalDistance(@NonNull Uri rideUri, float distance) {
        RideContentValues values = new RideContentValues();
        values.putDistance(distance);
        mContext.getContentResolver().update(rideUri, values.values(), null, null);
    }

    @WorkerThread
    private void updateDuration(@NonNull Uri rideUri, long duration) {
        RideContentValues values = new RideContentValues();
        values.putDuration(duration);
        mContext.getContentResolver().update(rideUri, values.values(), null, null);
    }

    @WorkerThread
    public void updateName(@NonNull Uri rideUri, String name) {
        RideContentValues values = new RideContentValues();
        if (TextUtils.isEmpty(name)) {
            values.putNameNull();
        } else {
            values.putName(name);
        }
        mContext.getContentResolver().update(rideUri, values.values(), null, null);
    }

    @WorkerThread
    public void pause(@NonNull Uri rideUri) {
        // Get current activated date / duration
        String[] projection = {RideColumns.ACTIVATED_DATE, RideColumns.DURATION};
        RideCursor c = new RideCursor(mContext.getContentResolver().query(rideUri, projection, null, null, null));
        try {
            if (!c.moveToNext()) {
                Log.w("Could not pause ride, uri " + rideUri + " not found");
                return;
            }
            long activatedDate = c.getActivatedDate().getTime();
            long duration = c.getDuration();

            // Update duration, state, and reset activated date
            duration += System.currentTimeMillis() - activatedDate;

            RideContentValues values = new RideContentValues();
            values.putState(RideState.PAUSED);
            values.putDuration(duration);
            values.putActivatedDate(0l);
            mContext.getContentResolver().update(rideUri, values.values(), null, null);

            // Dispatch to listeners
            mListeners.dispatch(listener -> listener.onPaused(rideUri));
        } finally {
            c.close();
        }
    }

    /**
     * Queries all the columns for the given ride.
     * Do not forget to call {@link Cursor#close()} on the returned Cursor.
     */
    public RideCursor query(@NonNull Uri rideUri) {
        if (rideUri == null) throw new IllegalArgumentException("null rideUri");
        Cursor c = mContext.getContentResolver().query(rideUri, null, null, null, null);
        if (!c.moveToNext()) {
            c.close();
            throw new IllegalArgumentException(rideUri + " not found");
        }
        return new RideCursor(c);
    }

    @WorkerThread
    @Nullable
    public Uri getCurrentRide() {
        String currentRideUriStr = PreferenceManager.getDefaultSharedPreferences(mContext).getString(Constants.PREF_CURRENT_RIDE_URI, null);
        if (!TextUtils.isEmpty(currentRideUriStr)) {
            Uri currentRideUri = Uri.parse(currentRideUriStr);
            return currentRideUri;
        }
        return null;
    }

    @WorkerThread
    public void setCurrentRide(@NonNull Uri rideUri) {
        PreferenceManager.getDefaultSharedPreferences(mContext).edit().putString(Constants.PREF_CURRENT_RIDE_URI, rideUri.toString()).apply();
    }

    @WorkerThread
    private Uri getMostRecentRide() {
        String[] projection = {RideColumns._ID};
        // Return a ride, prioritizing ACTIVE ones first, then sorting by creation date.
        Cursor c = mContext.getContentResolver().query(RideColumns.CONTENT_URI, projection, null, null,
                RideColumns.STATE + ", " + RideColumns.CREATED_DATE + " DESC");
        try {
            if (!c.moveToNext()) return null;
            long id = c.getLong(0);
            return ContentUris.withAppendedId(RideColumns.CONTENT_URI, id);
        } finally {
            c.close();
        }
    }

    @WorkerThread
    public Date getActivatedDate(@NonNull Uri rideUri) {
        RideCursor c = query(rideUri);
        try {
            return c.getActivatedDate();
        } finally {
            c.close();
        }
    }

    @WorkerThread
    private Date getFirstActivatedDate(@NonNull Uri rideUri) {
        RideCursor c = query(rideUri);
        try {
            return c.getFirstActivatedDate();
        } finally {
            c.close();
        }
    }

    @WorkerThread
    public long getDuration(@NonNull Uri rideUri) {
        RideCursor c = query(rideUri);
        try {
            return c.getDuration();
        } finally {
            c.close();
        }
    }

    @WorkerThread
    public RideState getState(@NonNull Uri rideUri) {
        RideCursor c = query(rideUri);
        try {
            return c.getState();
        } finally {
            c.close();
        }
    }

    @WorkerThread
    public String getDisplayName(@NonNull Uri rideUri) {
        RideCursor c = query(rideUri);
        try {
            String name = c.getName();
            long createdDateLong = c.getCreatedDate().getTime();
            String createdDateTimeStr = DateUtils.formatDateTime(mContext, createdDateLong, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
            if (name == null) {
                return createdDateTimeStr;
            }
            return name + " (" + createdDateTimeStr + ")";
        } finally {
            c.close();
        }
    }

    @WorkerThread
    public String getName(@NonNull Uri rideUri) {
        RideCursor c = query(rideUri);
        try {
            return c.getName();
        } finally {
            c.close();
        }
    }

    @WorkerThread
    public boolean isExistingRide(@NonNull Uri rideUri) {
        Cursor c = mContext.getContentResolver().query(rideUri, null, null, null, null);
        try {
            return c.moveToNext();
        } finally {
            c.close();
        }
    }


    /*
     * Listeners.
     */

    public void addListener(@NonNull RideListener listener) {
        mListeners.add(listener);
    }

    public void removeListener(@NonNull RideListener listener) {
        mListeners.remove(listener);
    }
} public class MediaButtonUtil {
    public static void registerMediaButtonEventReceiverAccordingToPreferences(Context context) {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

        // Only care if the preference is checked
        if (sharedPreferences.getBoolean(Constants.PREF_LISTEN_TO_HEADSET_BUTTON, Constants.PREF_LISTEN_TO_HEADSET_BUTTON_DEFAULT)) {
            registerMediaButtonEventReceiver(context);
        } else {
            unregisterMediaButtonEventReceiver();
        }
    }

    public static void registerMediaButtonEventReceiver(Context context) {
        MediaButtonHandler.get().start(context);
        TextToSpeechManager.get().start();
    }

    public static void unregisterMediaButtonEventReceiver() {
        MediaButtonHandler.get().stop();
        TextToSpeechManager.get().stop();
    }
}
import java.io.File;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import org.jraf.android.bikey.BuildConfig;
import org.jraf.android.bikey.R;
import org.jraf.android.bikey.app.collect.LogCollectorService;
import org.jraf.android.bikey.app.display.DisplayActivity;
import org.jraf.android.bikey.app.preference.PreferenceActivity;
import org.jraf.android.bikey.app.ride.detail.RideDetailActivity;
import org.jraf.android.bikey.app.ride.edit.RideEditActivity;
import org.jraf.android.bikey.backend.export.bikey.BikeyExporter;
import org.jraf.android.bikey.backend.export.genymotion.GenymotionExporter;
import org.jraf.android.bikey.backend.export.gpx.GpxExporter;
import org.jraf.android.bikey.backend.export.kml.KmlExporter;
import org.jraf.android.bikey.backend.provider.ride.RideState;
import org.jraf.android.bikey.backend.ride.RideManager;
import org.jraf.android.bikey.util.MediaButtonUtil;
import org.jraf.android.util.about.AboutActivityIntentBuilder;
import org.jraf.android.util.app.base.BaseAppCompatActivity;
import org.jraf.android.util.async.Task;
import org.jraf.android.util.async.TaskFragment;
import org.jraf.android.util.dialog.AlertDialogFragment;
import org.jraf.android.util.dialog.AlertDialogListener;
/*
 * This source is part of the
 *      _____  ___   ____
 *  __ / / _ \/ _ | / __/___  _______ _
 * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/
 * \___/_/|_/_/ |_/_/ (_)___/_/  \_, /
 *                              /___/
 * repository.
 *
 * Copyright (C) 2013 Benoit 'BoD' Lubek (BoD@JRAF.org)
 *
 * 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 org.jraf.android.bikey.app.ride.list;




public class RideListActivity extends BaseAppCompatActivity implements AlertDialogListener, RideListCallbacks {
    private static final String FRAGMENT_RETAINED_STATE = "FRAGMENT_RETAINED_STATE";

    private static final String PREFIX = RideListActivity.class.getName() + ".";
    public static final String ACTION_QUICK_START = PREFIX + "ACTION_QUICK_START";

    private static final int DIALOG_CONFIRM_DELETE = 0;
    private static final int DIALOG_SHARE = 1;
    private static final int DIALOG_CONFIRM_MERGE = 2;

    private static final int REQUEST_ADD_RIDE = 0;

    private RideListStateFragment mState;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.ride_list);
        findViewById(R.id.btnQuickStart).setOnClickListener(v -> quickStart());
        restoreState();

        MediaButtonUtil.registerMediaButtonEventReceiverAccordingToPreferences(this);

        // Quick start
        if (ACTION_QUICK_START.equals(getIntent().getAction())) {
            quickStart();
        }
    }

    private void restoreState() {
        mState = (RideListStateFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_RETAINED_STATE);
        if (mState == null) {
            mState = new RideListStateFragment();
            getSupportFragmentManager().beginTransaction().add(mState, FRAGMENT_RETAINED_STATE).commit();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.ride_list, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_add:
                startActivityForResult(new Intent(this, RideEditActivity.class), REQUEST_ADD_RIDE);
                return true;

            case R.id.action_about:
                AboutActivityIntentBuilder builder = new AboutActivityIntentBuilder();
                builder.setAppName(getString(R.string.app_name));
                builder.setBuildDate(BuildConfig.BUILD_DATE);
                builder.setGitSha1(BuildConfig.GIT_SHA1);
                builder.setAuthorCopyright(getString(R.string.about_txtInfo2));
                builder.setLicense(getString(R.string.about_txtInfo3));
                builder.setShareTextSubject(getString(R.string.about_shareText_subject));
                builder.setShareTextBody(getString(R.string.about_shareText_body));
                builder.setBackgroundResId(R.drawable.about_bg);
                builder.addLink(getString(R.string.about_email_uri), getString(R.string.about_email_text));
                builder.addLink(getString(R.string.about_web_uri), getString(R.string.about_web_text));
                builder.addLink(getString(R.string.about_sources_uri), getString(R.string.about_sources_text));
                builder.addLink(getString(R.string.about_gplusCommunity_uri), getString(R.string.about_gplusCommunity_text));
                builder.setIsLightIcons(true);
                startActivity(builder.build(this));
                return true;

            case R.id.action_settings:
                startActivity(new Intent(this, PreferenceActivity.class));
                return true;
        }
        return super.onOptionsItemSelected(item);
    }


    /*
     * Ride selected.
     */

    @Override
    public void onRideSelected(Uri rideUri, RideState state) {
        switch (state) {
            case CREATED:
            case ACTIVE:
                // Go to the display
                startActivity(new Intent(null, rideUri, this, DisplayActivity.class));