zack.yovel.clear.controller.foreground.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for zack.yovel.clear.controller.foreground.MainActivity.java

Source

/*
 * Copyright (c) 2014 Yehezkel (Zack) Yovel
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package zack.yovel.clear.controller.foreground;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;

import java.util.ArrayList;
import java.util.Locale;

import zack.yovel.clear.R;
import zack.yovel.clear.infrastructure.model.datapoints.WeatherReport;
import zack.yovel.clear.infrastructure.model.geonames.GeoName;
import zack.yovel.clear.infrastructure.networking.geocoding.GeoCoderTask;
import zack.yovel.clear.infrastructure.networking.geocoding.LatLng;
import zack.yovel.clear.infrastructure.networking.geonames.GeoNamesApiClientFactory;
import zack.yovel.clear.infrastructure.networking.weather.WeatherApiClientFactory;
import zack.yovel.clear.infrastructure.networking.weather.WeatherApiClientInterface;
import zack.yovel.clear.infrastructure.persist.AWeatherPersistAgent;
import zack.yovel.clear.infrastructure.persist.IWeatherPersist;
import zack.yovel.clear.util.Constants;

/*
 * TODO: Check this dilemma:
 * Implementing so many interfaces seems to be a violation of the single responsibility
 * prinsiple. It may be good to provide separate managing instances instead. Still, since
 * android can be a limited-resources environment, it me be inevitable to mix responsibility
 * in order to cut down on resource consumption. Needs further research.
 */
public class MainActivity extends ActionBarActivity
        implements ActionBar.TabListener, IWeatherPersist.IWeatherInflatedListener, SearchView.OnQueryTextListener,
        SuggestionsDialogFragment.OnSelectionMade, GooglePlayServicesClient.ConnectionCallbacks,
        GooglePlayServicesClient.OnConnectionFailedListener, GeoCoderTask.OnAddressReady, LocationListener,
        WeatherFragment.IDataProvider<WeatherReport> {

    private static final int REQUEST_PGS_ERROR_DIALOG = 9000;
    private static final int REQUEST_GPS_CONNECTION_RESOLUTION = 9001;
    private static final String TAG = "MainActivity";

    /**
     * The {@link android.support.v4.view.PagerAdapter} that will provide
     * fragments for each of the sections. We use a
     * {@link android.support.v4.app.FragmentPagerAdapter} derivative, which will keep every
     * loaded fragment in memory. If this becomes too memory intensive, it
     * may be best to switch to a
     * {@link android.support.v4.app.FragmentStatePagerAdapter}.
     */
    SectionsPagerAdapter mSectionsPagerAdapter;

    /**
     * The {@link android.support.v4.view.ViewPager} that will host the section contents.
     */
    ViewPager mViewPager;
    Handler handler = new Handler(Looper.getMainLooper()) {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.arg1) {
            case Constants.Flags.SUCCESS:
                handlerReportsSuccess((ArrayList<GeoName>) msg.obj);
                break;
            case Constants.Flags.FAILURE:
                handlerReportsFailure(msg.arg2);
            }
        }
    };
    private ArrayList<IWeatherPersist.IWeatherInflatedListener> listeners = new ArrayList<IWeatherPersist.IWeatherInflatedListener>();
    private WeatherReport weatherReport;
    private LocationClient locationClient;
    private ProgressDialog progress;
    private boolean needCurrentLocation;

    private void handlerReportsFailure(int status) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.title_server_error).setMessage(getString(R.string.message_server_error) + status);
        builder.create().show();
    }

    private void handlerReportsSuccess(ArrayList<GeoName> suggestions) {
        // This is a bit dangerous. Throughout method definitions this
        // object has been up-casted to List, but knowing that it originated
        // as an array list I use an array list here. I don't know of any usage
        // of non-array lists, but if such a thing happens a class cast exception
        // may rise here. I do not catch it because this should not remain un-fixed
        // at run-time, this should be noticed and taken care of by the developer.
        // Yes, that huge comment referred to the above line only.
        Bundle args = new Bundle(2);
        SuggestionsDialogFragment suggestionsDialog = new SuggestionsDialogFragment();
        args.putString(SuggestionsDialogFragment.EXTRA_TITLE, getString(R.string.title_suggestions_dialog));
        args.putParcelableArrayList(SuggestionsDialogFragment.EXTRA_SUGGESTIONS_LIST, suggestions);
        suggestionsDialog.setArguments(args);
        suggestionsDialog.show(getSupportFragmentManager(), Constants.Tags.SUGGESTIONS);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setLocationClient();
        initTitle();
        final ActionBar actionBar = initActionBar();
        initViewPager(actionBar);
        initTabs(actionBar);
        registerReceiverForPersistingCompleteAction();
        attemptReadWeatherReport();
    }

    private void registerReceiverForPersistingCompleteAction() {
        // Register a broadcast receiver to listen to weather report persisting complete events.
        LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                attemptReadWeatherReport();
            }
        }, new IntentFilter(IWeatherPersist.ACTION_PERSISTING_COMPLETE));
    }

    private void initTabs(ActionBar actionBar) {
        // For each of the sections in the app, add a tab to the action bar.
        for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
            // Create a tab with text corresponding to the page title defined by
            // the adapter. Also specify this Activity object, which implements
            // the TabListener interface, as the callback (listener) for when
            // this tab is selected.
            actionBar
                    .addTab(actionBar.newTab().setText(mSectionsPagerAdapter.getPageTitle(i)).setTabListener(this));
        }
    }

    private void initViewPager(final ActionBar actionBar) {
        // Create the adapter that will return a fragment for each of the three
        // primary sections of the activity.
        mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());

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

        // When swiping between different sections, select the corresponding
        // tab. We can also use ActionBar.Tab#select() to do this if we have
        // a reference to the Tab.
        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                actionBar.setSelectedNavigationItem(position);
            }
        });
    }

    private ActionBar initActionBar() {
        // Set up the action bar.
        final ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        return actionBar;
    }

    private void initTitle() {
        // Set page title to the chosen location.
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        String defValue = getString(R.string.app_name);
        String locationName = preferences.getString(Constants.Keys.PREF_LOCATION_NAME, defValue);
        if (locationName.equals(defValue)) {
            setLocationToMyLocation();
        }
        setTitle(locationName);
    }

    private void setLocationClient() {
        // Connect location client
        locationClient = new LocationClient(this, this, this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        locationClient.connect();
    }

    @Override
    protected void onStop() {
        locationClient.disconnect();
        super.onStop();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
    }

    private void attemptReadWeatherReport() {
        // Trigger the read WeatherUpdate inflation process
        IWeatherPersist persistenceAgent = AWeatherPersistAgent.getInstance();
        persistenceAgent.readWeatherReport(this, this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);

        SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search));
        searchView.setQueryHint(getString(R.string.hint_search));
        searchView.setOnQueryTextListener(this);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        switch (id) {
        case R.id.action_my_location:
            setLocationToMyLocation();
            break;
        case R.id.action_help:
            showHelpDialogFragment();
            break;
        }
        return super.onOptionsItemSelected(item);
    }

    private void showHelpDialogFragment() {
        HelpFragment fragment = new HelpFragment();
        fragment.show(getSupportFragmentManager(), Constants.Tags.HELP);
    }

    private void setLocationToMyLocation() {
        showCheckingLocationProgressDialog();
        int available = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        if (available == ConnectionResult.SUCCESS) {
            if (locationClient.isConnected()) {
                findCurrentLocation();
            } else {
                needCurrentLocation = true;
            }
        } else {
            launchGpsErrorDialog(available);
        }
    }

    private void findCurrentLocation() {
        Location lastLocation = locationClient.getLastLocation();
        if (lastLocation != null) {
            setNewLocation(lastLocation);
        } else {
            LocationRequest request = LocationRequest.create();
            request.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
            int expirationDuration = 30 * 1000;
            request.setExpirationDuration(expirationDuration);
            request.setInterval(1000);
            request.setNumUpdates(1);
            locationClient.requestLocationUpdates(request, this);

            scheduleProgressDismiss(expirationDuration);
        }
    }

    private void showCheckingLocationProgressDialog() {
        String title = getString(R.string.title_checking_location);
        String message = getString(R.string.message_checking_location);
        progress = ProgressDialog.show(this, title, message, true, true);
    }

    private void scheduleProgressDismiss(int expirationDuration) {
        Handler dismissHandler;
        try {
            dismissHandler = getWindow().getDecorView().getHandler();
        } catch (NullPointerException e) {
            Log.e(TAG, "scheduleProgressDismiss failed with error: " + e.getMessage());
            dismissHandler = handler;
        }

        if (dismissHandler != null) {
            dismissHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (progress != null && progress.isShowing()) {
                        progress.dismiss();
                        progress = null;
                        Log.d("test", "scheduleProgressDismiss");
                        getCannotFindLocationDialog().show();
                    }
                }
            }, expirationDuration);
        }
    }

    private void setNewLocation(Location location) {
        dismissProgressDialog();
        if (null != location) {
            String newLatLng = location.getLatitude() + "," + location.getLongitude();
            triggerWeatherUpdate(newLatLng);
            GeoCoderTask geoCoderTask = new GeoCoderTask(this, this);
            geoCoderTask.execute(new LatLng(location));
            removeSetting(Constants.Keys.PREF_LOCATION_NAME, Constants.Keys.PREF_LATLNG);
        } else {
            Log.d("test", "setNewLocation");
            getCannotFindLocationDialog().show();
        }
    }

    private void dismissProgressDialog() {
        if (progress != null) {
            progress.dismiss();
            progress = null;
        }
    }

    private Dialog getCannotFindLocationDialog() {
        Dialog result;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.title_cannot_find_location).setMessage(R.string.message_cannot_find_location)
                .setIcon(R.drawable.sad);
        result = builder.create();
        return result;
    }

    private void removeSetting(String... keys) {
        SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
        for (String key : keys) {
            editor.remove(key);
        }
        editor.apply();
    }

    private void launchGpsErrorDialog(int available) {
        Dialog dialog = GooglePlayServicesUtil.getErrorDialog(available, this, REQUEST_PGS_ERROR_DIALOG);
        GPSErrorDialogFragment errorDialogFragment = new GPSErrorDialogFragment();
        errorDialogFragment.setDialog(dialog);
        errorDialogFragment.show(getSupportFragmentManager(), Constants.Tags.GPSERROR);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_PGS_ERROR_DIALOG && resultCode == RESULT_OK) {
            setLocationToMyLocation();
        }
    }

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

    @Override
    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
    }

    @Override
    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
    }

    /**
     * Adds a listener to the WeatherReport ready event, returns the index of this listener in the list.
     *
     * @param listener
     * @return
     */
    public int addListener(IWeatherPersist.IWeatherInflatedListener listener) {
        int position = listeners.size();
        listeners.add(listener);
        return position;
    }

    public void removeListener(IWeatherPersist.IWeatherInflatedListener listener) {
        listeners.remove(listener);
    }

    @Override
    public void onWeatherReportReady(WeatherReport weatherReport) {
        if (weatherReport != null) {
            for (IWeatherPersist.IWeatherInflatedListener listener : listeners) {
                listener.onWeatherReportReady(weatherReport);
            }
            this.weatherReport = weatherReport;
        }
    }

    @Override
    public void onReadFailed() {
        triggerWeatherUpdate();
    }

    private void triggerWeatherUpdate(String latlng) {
        WeatherApiClientInterface client = WeatherApiClientFactory.getInstance();
        if (latlng != null) {
            client.makeAsyncRequest(this, latlng);
        }
    }

    private void triggerWeatherUpdate() {
        String latlng = PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.Keys.PREF_LATLNG,
                null);
        triggerWeatherUpdate(latlng);
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        GeoNamesApiClientFactory.getInstance().makeAsyncRequest(MainActivity.this, query, handler);
        return false;
    }

    @Override
    public boolean onQueryTextChange(String newText) {
        return false;
    }

    public void setNewLocation(String locationName, String latlng) {
        if (TextUtils.isEmpty(locationName)) {
            locationName = getString(R.string.app_name);
        }
        setTitle(locationName);
        changeSetting(Constants.Keys.PREF_LOCATION_NAME, locationName);
        changeSetting(Constants.Keys.PREF_LATLNG, latlng);
    }

    // Only get the editor
    private void changeSetting(String key, String value) {
        SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
        editor.putString(key, value);
        editor.apply();
    }

    @Override
    public void onSelectionMade(String locationName, String latlng) {
        triggerWeatherUpdate(latlng);
        setNewLocation(locationName, latlng);
    }

    @Override
    public void onConnected(Bundle bundle) {
        Log.i(TAG, "Connected to location services.");
        if (needCurrentLocation) {
            findCurrentLocation();
        }
    }

    @Override
    public void onDisconnected() {
        Log.d(TAG, "Disconnected from location services.");
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        if (connectionResult.hasResolution()) {
            try {
                connectionResult.startResolutionForResult(this, REQUEST_GPS_CONNECTION_RESOLUTION);
            } catch (IntentSender.SendIntentException e) {
                e.printStackTrace();
            }
        } else {
            GPSErrorDialogFragment errorDialogFragment = new GPSErrorDialogFragment();
            if (getTitle().equals(getString(R.string.app_name))) {
                errorDialogFragment.setDialog(getCannotFindLocationDialog());
                errorDialogFragment.show(getSupportFragmentManager(), Constants.Tags.CONNECTION_FAILED);
            }
        }
    }

    @Override
    public void onAddressReady(String address) {
        setTitle(address);
    }

    @Override
    public void onLocationChanged(Location location) {
        setNewLocation(location);
    }

    @Override
    public WeatherReport getData() {
        return weatherReport;
    }

    /**
     * A {@link android.support.v4.app.FragmentPagerAdapter} that returns a fragment corresponding to
     * one of the sections/tabs/pages.
     */
    public class SectionsPagerAdapter extends FragmentPagerAdapter {

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

        @Override
        public Fragment getItem(int position) {
            // getItem is called to instantiate the fragment for the given page.
            // Return a PlaceholderFragment (defined as a static inner class below).
            switch (position) {
            case 0:
                return new CurrentFragment();
            case 1:
                return new HourlyFragment();
            case 2:
                return new DailyFragment();
            default:
                return new CurrentFragment();
            }
        }

        @Override
        public int getCount() {
            // Show 3 total pages.
            return 3;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            Locale l = Locale.getDefault();
            switch (position) {
            case 0:
                return getString(R.string.title_currently).toUpperCase(l);
            case 1:
                return getString(R.string.title_hourly).toUpperCase(l);
            case 2:
                return getString(R.string.title_daily).toUpperCase(l);
            }
            return null;
        }
    }
}