org.otempo.view.StationActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.otempo.view.StationActivity.java

Source

/*
 * Copyright (C) 2010-2011 Ruben Lopez
 *
 * This file is part of OTempo - Galician Weather
 *
 * 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.otempo.view;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.arch.lifecycle.ProcessLifecycleOwner;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.location.Criteria;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import org.otempo.R;
import org.otempo.StationManager;
import org.otempo.favorites.FavoritesDB;
import org.otempo.model.AlphabeticStationComparator;
import org.otempo.model.FavoritesStationComparator;
import org.otempo.model.Station;
import org.otempo.model.StationMediumTermPrediction;
import org.otempo.model.StationPrediction;
import org.otempo.model.StationPredictionVisitor;
import org.otempo.model.StationShortTermPrediction;
import org.otempo.service.FetchWorker;
import org.otempo.service.UpdateWorker;
import org.otempo.util.BitmapUtils;
import org.otempo.util.DateUtils;
import org.otempo.util.LayoutUtils;
import org.otempo.util.Nullness;
import org.otempo.util.ResourceUtils;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;

/**
 * Actividad principal, donde se muestran los datos de la estacin actual
 */
public class StationActivity extends Activity implements OnSharedPreferenceChangeListener, StationManager.Listener,
        ActivityCompat.OnRequestPermissionsResultCallback {

    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.gc();
        lastUpdateFormat = new SimpleDateFormat(getString(R.string.predicted_at), Locale.getDefault());
        PeriodicWorkRequest updateWork = new PeriodicWorkRequest.Builder(UpdateWorker.class, updatePeriodMs(),
                TimeUnit.MILLISECONDS)
                        .setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.UNMETERED)
                                .setRequiresBatteryNotLow(true).build())
                        .build();
        // Make sure there is nothing else running at this point. Not even the widget. The attention
        // of the user is on the activity.
        WorkManager.getInstance().cancelAllWork();
        // Now start polling in the background
        WorkManager.getInstance().enqueueUniquePeriodicWork("SyncStations", ExistingPeriodicWorkPolicy.KEEP,
                updateWork);

        setContentView(R.layout.main);

        if (_favoritesDB == null) {
            _favoritesDB = new FavoritesDB(getApplicationContext());
        }
        Spinner stationSpinner = findViewById(R.id.stationSpinner);
        _stationAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);
        initStationManager();
        fillStationAdapter();
        _stationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        stationSpinner.setAdapter(_stationAdapter);
        if (_stationManager.getStation() != null) {
            stationSpinner.setSelection(_stationAdapter.getPosition(_stationManager.getStation()));
        }
        stationSpinner.setOnItemSelectedListener(new OnStationSelectedListener());
        PreferenceManager.getDefaultSharedPreferences(getBaseContext())
                .registerOnSharedPreferenceChangeListener(this);

        reloadPreferences();

        final Button predictionHelpButton = findViewById(R.id.predictionHelpButton);
        if (predictionHelpButton != null) { // en landscape no hay
            predictionHelpButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    showDialog(DIALOG_PREDICTION_EXPLAIN_ID);
                    // Marcando la preferencia
                    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
                    Editor editor = prefs.edit();
                    editor.putBoolean(Preferences.PREF_PREDICTED_TIME_WHY_CLICKED, true);
                    editor.commit();
                }
            });
        }

        final Button hamburgerButton = findViewById(R.id.hamburgerButton);
        final DrawerLayout drawerLayout = findViewById(R.id.drawerLayout);
        final ListView sideMenu = findViewById(R.id.sideMenu);
        if (hamburgerButton != null && drawerLayout != null && sideMenu != null) {
            hamburgerButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    drawerLayout.openDrawer(sideMenu);
                }
            });
            fillSideMenu(sideMenu, drawerLayout);
        }
    }

    int updatePeriodMs() {
        return Integer.valueOf(PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
                .getString(Preferences.PREF_UPDATE_PERIOD, "3600000"));
    }

    static class SideMenuItem {
        final int id;
        final int title_id;
        final int icon_id;

        SideMenuItem(int id, int title_id, int icon_id) {
            this.id = id;
            this.title_id = title_id;
            this.icon_id = icon_id;
        }
    }

    static class SideMenuListAdapter extends ArrayAdapter<SideMenuItem> {
        SideMenuListAdapter(Context context, int resource, List<SideMenuItem> items) {
            super(context, resource, items);
        }

        @NonNull
        @Override
        public View getView(int position, View convertView, @NonNull ViewGroup parent) {
            View v = convertView;
            if (v == null) {
                LayoutInflater vi;
                vi = LayoutInflater.from(getContext());
                v = vi.inflate(R.layout.side_menu_item, null);
            }

            SideMenuItem item = getItem(position);
            if (item != null) {
                ImageView icon = v.findViewById(R.id.side_menu_item_icon);
                if (icon != null) {
                    icon.setImageResource(item.icon_id);
                }
                TextView title = v.findViewById(R.id.side_menu_item_title);
                if (title != null) {
                    title.setText(item.title_id);
                }
            }
            return v;
        }
    }

    private void fillSideMenu(ListView sideMenu, final DrawerLayout drawerLayout) {
        final List<SideMenuItem> items = new ArrayList<>();
        items.add(new SideMenuItem(R.id.settings, R.string.settings, android.R.drawable.ic_menu_preferences));
        items.add(new SideMenuItem(R.id.syncNow, R.string.syncMenu, R.drawable.menu_update));
        items.add(new SideMenuItem(R.id.shareFbook, R.string.shareMenu, android.R.drawable.ic_menu_share));
        items.add(new SideMenuItem(R.id.source, R.string.source, android.R.drawable.ic_menu_info_details));
        items.add(new SideMenuItem(R.id.myLocation, R.string.my_location, android.R.drawable.ic_menu_mylocation));
        items.add(new SideMenuItem(R.id.changelogMenu, R.string.changelogMenu,
                android.R.drawable.ic_menu_recent_history));
        sideMenu.setAdapter(new SideMenuListAdapter(this, R.layout.side_menu_item, items));

        sideMenu.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int pos, long id) {
                drawerLayout.closeDrawers();
                onMenuItemSelected(items.get(pos).id);
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        maybeRequestLocationPermission();
        Station station = _stationManager.getStation();
        if (station != null) {
            fetchThenShow(station, false);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        PreferenceManager.getDefaultSharedPreferences(getBaseContext())
                .unregisterOnSharedPreferenceChangeListener(this);
    }

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

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        onMenuItemSelected(item.getItemId());
        return true;
    }

    private void onMenuItemSelected(int id) {
        if (id == R.id.settings) {
            showPreferences();
        } else if (id == R.id.source) {
            gotoMeteogalicia();
        } else if (id == R.id.myLocation) {
            _stationManager.setClosestStation();
        } else if (id == R.id.changelogMenu) {
            showChangeLog();
        } else if (id == R.id.syncNow) {
            Station station = _stationManager.getStation();
            if (station != null) {
                fetchThenShow(station, true);
            }
        } else if (id == R.id.shareFbook) {
            Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
            shareIntent.setType("text/plain");
            shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.share_subject));
            shareIntent.putExtra(android.content.Intent.EXTRA_TEXT, "http://www.facebook.com/otempo");
            startActivity(Intent.createChooser(shareIntent, getString(R.string.share_chooser_title)));
        }
    }

    /**
     * Lanza la actividad del registro de cambios
     */
    private void showChangeLog() {
        Intent i = new Intent(this, ChangeLog.class);
        startActivity(i);

    }

    /**
     * Lanza el navegador en la web de meteogalicia
     */
    private void gotoMeteogalicia() {
        Station station = _stationManager.getStation();
        if (station != null) {
            Intent i = new Intent(Intent.ACTION_VIEW,
                    Uri.parse("http://www.meteogalicia.es/web/predicion/localidades/localidadesIndex.action?idZona="
                            + station.getId()));
            startActivity(i);
        } else {
            Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.meteogalicia.es"));
            startActivity(i);
        }
    }

    /**
     * Recarga las preferencias, por si han cambiado
     */
    private void reloadPreferences() {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
        String stationOrdering = prefs.getString(Preferences.PREF_STATION_ORDERING,
                Preferences.DEFAULT_STATION_ORDERING);
        if (stationOrdering.equals("favorites")) {
            Station.sortStations(new FavoritesStationComparator());
        } else if (stationOrdering.equals("alphabetic")) {
            Station.sortStations(new AlphabeticStationComparator());
        }
        _stationManager.setAutoSortStations(stationOrdering.equals("distance"));
        fillStationAdapter();
        Spinner stationSpinner = findViewById(R.id.stationSpinner);
        stationSpinner.setSelection(_stationAdapter.getPosition(_stationManager.getStation()));
        String background = prefs.getString(Preferences.PREF_BACKGROUND, "default");
        boolean mustUpdate = !_background.equals(background);
        _background = background;
        if (_background.equals("black")) {
            findViewById(R.id.scrollView).setBackgroundColor(Color.BLACK);
        } else if (_background.equals("default")) {
            if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
                findViewById(R.id.scrollView).setBackgroundResource(R.drawable.background_land);
            } else {
                findViewById(R.id.scrollView).setBackgroundResource(R.drawable.background);
            }
        } else if (_background.equals("user_image")) {
            maybeRequestStoragePermission();
            String fileName = prefs.getString(Preferences.PREF_BACKGROUND_USER_IMAGE, "");
            // No permitimos imgenes de ms de 1000 pixeles de ancho o alto, para no petar la memoria con esta imagen
            int maxW = Math.max((int) (getResources().getDisplayMetrics().widthPixels * 1.5), 1000);
            int maxH = Math.max((int) (getResources().getDisplayMetrics().heightPixels * 1.5), 1000);
            Bitmap bitmap = BitmapUtils.safeDecodeFile(new File(fileName), maxW, maxH);
            if (bitmap != null) {
                BitmapDrawable image = new BitmapDrawable(getResources(), bitmap);
                findViewById(R.id.scrollView).setBackground(image);
            }
        }
        if (mustUpdate) {
            updateLayout();
        }
        boolean predictedTimeWhyClicked = prefs.getBoolean(Preferences.PREF_PREDICTED_TIME_WHY_CLICKED, false);
        if (predictedTimeWhyClicked) {
            View predButton = findViewById(R.id.predictionHelpButton);
            if (predButton != null) {
                predButton.setVisibility(View.GONE);
            }
        }
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences preferences, String pref) {
        reloadPreferences();
    }

    /**
     * Devuelve un TextView con el nombre de un da para poner sobre las predicciones
     */
    private TextView getDayName(Calendar date) {
        TextView dayName = new TextView(this);
        if (date != null) {
            dayName.setText(DateUtils.weekDayFormat.format(date.getTime()));

        } else {
            dayName.setText("???");
        }
        dayName.setGravity(Gravity.CENTER_HORIZONTAL);
        if (_background.equals("black")) {
            dayName.setTextColor(Color.rgb(255, 255, 255));
        } else {
            dayName.setTextColor(Color.rgb(0, 0, 0));

        }
        return dayName;
    }

    /**
     * @param predictionTime Fecha de la ltima actualizacin
     * @return Devuelve el string que indica la fecha de la ltima actualizacin
     */
    private String getPredictionTimeString(Calendar predictionTime) {
        if (predictionTime == null) {
            return "";
        } else {
            return lastUpdateFormat.format(predictionTime.getTime());
        }
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        Dialog dialog;
        switch (id) {
        case DIALOG_LOADING_ID: {
            // No usar nunca esta forma, peta en 1.5, porque el show se hace dos veces:
            //dialog = ProgressDialog.show(this, "", getString(R.string.loading_data), true);
            ProgressDialog progress = new ProgressDialog(this);
            progress.setMessage(getString(R.string.loading_data));
            progress.setIndeterminate(true);
            dialog = progress;
            _dialogLoadingShown = true;
            break;
        }
        case DIALOG_PREDICTION_EXPLAIN_ID: {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage(getString(R.string.predicted_time_explain)).setCancelable(false)
                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int id) {
                            dialog.cancel();
                        }
                    });
            dialog = builder.create();
            break;
        }
        default:
            if (id >= DIALOG_DAY_COMMENT_MASK) {
                Station station = _stationManager.getStation();
                if (station != null) {
                    int day = id - DIALOG_DAY_COMMENT_MASK - station.getId() * MAX_PREDICTED_DAYS;
                    if (day >= station.getPredictions().size()) {
                        // Avoid overflow if tapping past the last prediction.
                        return null;
                    }
                    StationPrediction prediction = station.getPredictions().get(day);
                    AlertDialog.Builder builder = new AlertDialog.Builder(this);
                    Calendar predictionDate = prediction.getDate();
                    String header = "";
                    if (predictionDate != null) {
                        header = DateUtils.weekDayFormat.format(predictionDate.getTime());
                    }
                    builder.setMessage(header + ":\n\n" + prediction.createDescription(this)).setCancelable(false)
                            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int id) {
                                    dialog.cancel();
                                }
                            });
                    return builder.create();
                } else {
                    AlertDialog.Builder builder = new AlertDialog.Builder(this);
                    builder.setMessage(getString(R.string.loading_data)).setCancelable(false)
                            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int id) {
                                    dialog.cancel();
                                }
                            });
                    return builder.create();
                }
            }
            return super.onCreateDialog(id);
        }
        return dialog;
    }

    /**
     * Actualiza la parte del layout que visualiza predicciones
     */
    private void updateLayout() {

        final LinearLayout scrolled = findViewById(R.id.scrolled);
        scrolled.removeAllViews();
        final LinearLayout predictedGroup = findViewById(R.id.predictedGroup);
        if (predictedGroup != null) { // en landscape no hay
            predictedGroup.setVisibility(LinearLayout.INVISIBLE);
        }
        final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutUtils.dips(75, this),
                LinearLayout.LayoutParams.WRAP_CONTENT);
        Station currentStation = _stationManager.getStation();
        if (currentStation == null) {
            // Si no hay estacin elegida, nada ms que hacer...
            return;
        }
        final int currentStationId = currentStation.getId();
        if (currentStation.getPredictions().size() > 0) {
            removeDialog(DIALOG_LOADING_ID);
            if (predictedGroup != null) { // en landscape no hay
                final TextView predictionTime = findViewById(R.id.predictionTime);
                predictionTime.setText(getPredictionTimeString(currentStation.getLastCreationDate()));
                predictedGroup.setVisibility(LinearLayout.VISIBLE);
            }
            currentStation.acceptPredictionVisitor(new StationPredictionVisitor() {
                @Override
                public void apply(@NonNull StationShortTermPrediction shortPred, final int index) {
                    Calendar predictionDate = shortPred.getDate();
                    if (predictionDate == null || !DateUtils.isFromToday(predictionDate)) {
                        return;
                    }
                    LinearLayout day = new LinearLayout(StationActivity.this);
                    day.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View arg0) {
                            showDialog(DIALOG_DAY_COMMENT_MASK + index + currentStationId * MAX_PREDICTED_DAYS);
                        }
                    });
                    day.setOrientation(LinearLayout.VERTICAL);
                    TextView dayName = getDayName(shortPred.getDate());
                    day.addView(dayName);
                    ImageView morningIcon = new ImageView(StationActivity.this);
                    morningIcon.setImageResource(ResourceUtils.getResource(shortPred.getSkyStateMorning(), true));
                    day.addView(morningIcon);
                    ImageView afternoonIcon = new ImageView(StationActivity.this);
                    afternoonIcon
                            .setImageResource(ResourceUtils.getResource(shortPred.getSkyStateAfternoon(), true));
                    day.addView(afternoonIcon);
                    ImageView nightIcon = new ImageView(StationActivity.this);
                    nightIcon.setImageResource(ResourceUtils.getResource(shortPred.getSkyStateNight(), false));
                    day.addView(nightIcon);
                    TextView temps = new TextView(StationActivity.this);
                    temps.setText(shortPred.getMinTemp() + " - " + shortPred.getMaxTemp() + " C");
                    temps.setGravity(Gravity.CENTER_HORIZONTAL);
                    day.addView(temps);
                    if (DateUtils.isToday(predictionDate)) {
                        day.setBackgroundResource(R.drawable.today_bg);
                        temps.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
                        dayName.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
                    }
                    scrolled.addView(day, params);
                }

                @Override
                public void apply(@NonNull StationMediumTermPrediction medPred, final int index) {
                    Calendar predictionDate = medPred.getDate();
                    if (predictionDate == null || !DateUtils.isFromToday(predictionDate)) {
                        return;
                    }
                    LinearLayout day = new LinearLayout(StationActivity.this);
                    day.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View arg0) {
                            showDialog(DIALOG_DAY_COMMENT_MASK + index + currentStationId * MAX_PREDICTED_DAYS);
                        }
                    });
                    day.setOrientation(LinearLayout.VERTICAL);
                    TextView dayName = getDayName(medPred.getDate());
                    day.addView(dayName);
                    ImageView morningIcon = new ImageView(StationActivity.this);
                    morningIcon.setImageResource(ResourceUtils.getResource(medPred.getSkyState(), true));
                    day.addView(morningIcon);
                    TextView temps = new TextView(StationActivity.this);
                    temps.setText(medPred.getMinTemp() + " - " + medPred.getMaxTemp() + " C");
                    temps.setGravity(Gravity.CENTER_HORIZONTAL);
                    temps.setTextColor(Color.WHITE);
                    day.addView(temps);
                    scrolled.addView(day, params);
                }
            });
        } else {
            // Ahora mostramos el dilogo si hace falta, y sino borramos la marca de skip
            if (_skipDialog) {
                _skipDialog = false;
            } else {
                if (!this.isFinishing()) {
                    showDialog(DIALOG_LOADING_ID);
                    fetchThenShow(currentStation, false);
                }
            }
        }
    }

    /**
     * Inicializa la estacin elegida, mirando en el GPS y sino pillando la primera.
     */
    private void initStationManager() {
        final Object data = getLastNonConfigurationInstance();
        if (data == null) {
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
            String defaultStationPreference = prefs.getString(Preferences.PREF_DEFAULT_STATION,
                    Preferences.DEFAULT_DEFAULT_STATION);
            int defaultStationFixed = Integer.valueOf(prefs.getString(Preferences.PREF_DEFAULT_STATION_FIXED, "1"));
            LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
            assert locationManager != null;
            _stationManager = new StationManager(locationManager, defaultStationPreference, defaultStationFixed);
            if (locationManager.getBestProvider(new Criteria(), false) == null) {
                Toast.makeText(getApplicationContext(), R.string.gps_unavailable, Toast.LENGTH_LONG).show();
            }
        } else {
            final Object[] list = (Object[]) data;
            _stationManager = (StationManager) list[0];
            _skipDialog = true;
        }
        _stationManager.setListener(this);
    }

    /**
     * Llamado por el manager cuando cambiamos de estacin por GPS
     */
    @Override
    public void onStationChanged(final @NonNull Station station) {
        Log.d("OTempo", "onStationChanged");
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                fillStationAdapter();
                Spinner stationSpinner = findViewById(R.id.stationSpinner);
                stationSpinner.setSelection(_stationAdapter.getPosition(station));
                updateLayout();
            }
        });
    }

    /**
     * Rellena un adaptador de android con las estaciones, para el combo
     */
    private void fillStationAdapter() {
        _stationAdapter.clear();
        if (_stationManager.getStation() == null) {
            _stationAdapter.add(new Station(getString(R.string.waiting_gps), -1, 0, 0));
        }
        for (Station station : Station.getKnownStations()) {
            _stationAdapter.add(station);
        }
    }

    /**
     * Utilidad para mostrar la actividad con las preferencias
     */
    private void showPreferences() {
        Intent settingsActivity = new Intent(getBaseContext(), Preferences.class);
        startActivity(settingsActivity);
    }

    /**
     * Listener del combo de estaciones
     */
    private class OnStationSelectedListener implements OnItemSelectedListener {

        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
            Station selected = (Station) parent.getItemAtPosition(pos);
            if (selected.getId() != -1 && selected != _stationManager.getStation()) {
                _stationManager.setStaticStation(selected);
                selected.setAccessCount(selected.getAccessCount() + 1);
                selected.setLastAccess(new Date());
                _favoritesDB.updateFavorite(selected);
            }
        }

        @Override
        public void onNothingSelected(AdapterView<?> arg0) {
            // Nada que hacer
        }
    }

    private void fetchThenShow(final Station station, final boolean checkIfWasAlreadyLatest) {
        final Calendar previousPredictionTime = station.getLastCreationDate();
        Log.d("OTempo", "Starting to fetch activity data");
        FetchWorker.run(station, ProcessLifecycleOwner.get(), new FetchWorker.ResultListener() {
            @Override
            public void success() {
                Log.d("OTempo", "Success fetching activity data");
                updateLayout();
                if (checkIfWasAlreadyLatest
                        && Nullness.equals(previousPredictionTime, station.getLastCreationDate())) {
                    removeDialog(DIALOG_LOADING_ID);
                    _dialogLoadingShown = false;
                    Toast.makeText(getApplicationContext(), R.string.up_to_date, Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void error() {
                Log.e("OTempo", "Unable to fetch activity data");
                removeDialog(DIALOG_LOADING_ID);
                _dialogLoadingShown = false;
                Toast.makeText(getApplicationContext(), R.string.internet_error, Toast.LENGTH_LONG).show();
            }
        });
    }

    /**
     * @return Un objeto con el estado para recuperarlo tras la rotacin de pantalla
     */
    @Override
    public Object onRetainNonConfigurationInstance() {
        final Object[] list = new Object[1];
        list[0] = _stationManager;
        return list;
    }

    private void maybeRequestLocationPermission() {
        if (Build.VERSION.SDK_INT < 23) {
            return;
        }
        if (ContextCompat.checkSelfPermission(this,
                android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[] { android.Manifest.permission.ACCESS_COARSE_LOCATION }, 0);
        }
    }

    private void maybeRequestStoragePermission() {
        if (Build.VERSION.SDK_INT < 23) {
            return;
        }
        if (ContextCompat.checkSelfPermission(this,
                android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }, 0);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        for (int i = 0; i < permissions.length; i++) {
            if (permissions[i].equals(android.Manifest.permission.ACCESS_COARSE_LOCATION)
                    && grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                Log.d("OTempo", "Got GPS permission");
                initStationManager();
                if (_stationManager.getStation() != null) {
                    Spinner stationSpinner = findViewById(R.id.stationSpinner);
                    stationSpinner.setSelection(_stationAdapter.getPosition(_stationManager.getStation()));
                }
            } else if (permissions[i].equals(android.Manifest.permission.READ_EXTERNAL_STORAGE)
                    && grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                reloadPreferences();
            }
        }
    }

    private String _background = "default";
    /// La actividad necesita un manager propio por si cambias de estacion a mano, no le sirve el del servicio
    private StationManager _stationManager = null;
    private ArrayAdapter<Station> _stationAdapter = null;
    private static FavoritesDB _favoritesDB = null;

    private final static int DIALOG_LOADING_ID = 0;
    private final static int DIALOG_PREDICTION_EXPLAIN_ID = 1;
    private final static int DIALOG_DAY_COMMENT_MASK = 1024; // por encima de 1024, el dilogo es el comentario de un da
    private final static int MAX_PREDICTED_DAYS = 20; // Cualquier nmero mayor a la cantidad de das que mostramos estar bien, preferiblemente no muy grande.

    // Lo tenemos aqu y no en dateutils porque necesita los strings para estar traducido
    private static SimpleDateFormat lastUpdateFormat = null;

    /// Usamos esto para no intentar mostrar el dilogo en los cambios de orientacin
    private boolean _skipDialog = false;

    /// Marca para saber si el usuario est esperando por el UpdateServiceLegacy y debemos notificar cualquier evento que suceda
    private boolean _dialogLoadingShown = false;
}