osu.appclub.corvallisbus.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for osu.appclub.corvallisbus.MainActivity.java

Source

package osu.appclub.corvallisbus;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.location.Location;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationServices;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.regex.Pattern;

import osu.appclub.corvallisbus.dataaccess.CorvallisBusAPIClient;
import osu.appclub.corvallisbus.dataaccess.CorvallisBusPreferences;
import osu.appclub.corvallisbus.models.BusStaticData;
import osu.appclub.corvallisbus.models.BusStop;
import osu.appclub.corvallisbus.widget.CorvallisBusWidgetProvider;

public class MainActivity extends AppCompatActivity
        implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
        LocationProvider, BusStopSelectionQueue, ViewPager.OnPageChangeListener, ActivityRunningMonitor,
        SearchView.OnQueryTextListener, StopsSearchTask.Listener {
    public static final String VIEW_STOP_ACTION = "osu.appclub.corvallisbus.VIEW_STOP_ACTION";
    public static final String EXTRA_STOP_ID = "osu.appclub.corvallisbus.EXTRA_STOP_ID";

    private GoogleApiClient apiClient;
    private Toolbar toolbar;
    private ViewPager pager;
    private SearchView searchView;

    private static final class LoadStaticDataTask extends AsyncTask<Void, Void, BusStaticData> {
        @Override
        protected BusStaticData doInBackground(Void... params) {
            return CorvallisBusAPIClient.getStaticData();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Kick off static data download task so it can be accessed quickly later
        new LoadStaticDataTask().execute();

        // initial UI and Toolbar setup
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        toolbar = (Toolbar) findViewById(R.id.toolbar);
        toolbar.setTitle("Favorites");
        setSupportActionBar(toolbar);

        pager = (ViewPager) findViewById(R.id.viewPager);
        pager.setAdapter(new CorvallisBusPagerAdapter(getSupportFragmentManager()));
        pager.addOnPageChangeListener(this);

        TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout);
        tabLayout.setupWithViewPager(pager);

        TabLayout.Tab favoritesTab = tabLayout.getTabAt(0);
        assert favoritesTab != null;
        favoritesTab.setIcon(R.drawable.favorites_24dp);

        TabLayout.Tab browseTab = tabLayout.getTabAt(1);
        assert browseTab != null;
        browseTab.setIcon(R.drawable.ic_directions_bus_white_24dp);

        TabLayout.Tab alertsTab = tabLayout.getTabAt(2);
        assert alertsTab != null;
        alertsTab.setIcon(R.drawable.ic_warning_white_24dp);

        apiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this).addApi(LocationServices.API).build();
    }

    // region ViewPager.OnPageChangeListener
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {
        switch (position) {
        case 0:
            toolbar.setTitle("Favorites");
            break;
        case 1:
            toolbar.setTitle("Browse Stops");
            break;
        case 2:
            toolbar.setTitle("Service Alerts");
            break;
        default:
            throw new UnsupportedOperationException("Unsupported position selected");
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }
    // endregion

    @Override
    protected void onStart() {
        super.onStart();
        if (apiClient != null) {
            apiClient.connect();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (apiClient != null) {
            apiClient.disconnect();
        }
    }

    private boolean isActivityRunning = false;

    @Override
    protected void onResume() {
        super.onResume();
        isActivityRunning = true;
        for (ActivityRunningMonitor.Listener listener : new ArrayList<>(activityRunningListeners)) {
            listener.onResume();
        }

        Intent intent = getIntent();
        int stopID = intent.getIntExtra(EXTRA_STOP_ID, -1);
        if (stopID != -1) {
            intent.removeExtra(EXTRA_STOP_ID);
            enqueueBusStop(stopID);
        }
    }

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

        Intent updateWidgetItn = new Intent(this, CorvallisBusWidgetProvider.class);
        updateWidgetItn.setAction(CorvallisBusWidgetProvider.UPDATE_ACTION);
        sendBroadcast(updateWidgetItn);

        isActivityRunning = false;
        for (ActivityRunningMonitor.Listener listener : new ArrayList<>(activityRunningListeners)) {
            listener.onPause();
        }
    }

    // region ActivityRunningMonitor
    List<ActivityRunningMonitor.Listener> activityRunningListeners = new ArrayList<>();

    @Override
    public boolean isActivityRunning() {
        return isActivityRunning;
    }

    @Override
    public void addActivityRunningListener(ActivityRunningMonitor.Listener listener) {
        activityRunningListeners.add(listener);
    }

    @Override
    public void removeActivityRunningListener(ActivityRunningMonitor.Listener listener) {
        activityRunningListeners.remove(listener);
    }
    // endregion

    // region LocationProvider
    @Override
    public boolean isLocationResolved() {
        int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
        boolean hasPermission = permission == PackageManager.PERMISSION_GRANTED;
        boolean wasLocationRequested = CorvallisBusPreferences.getWasLocationRequested(this);
        if (!hasPermission) {
            if (wasLocationRequested) {
                fireOnLocationResolved();
            } else {
                ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_FINE_LOCATION },
                        0);
            }
        }

        return apiClient != null && apiClient.isConnected() && hasPermission;
    }

    @Nullable
    @Override
    public Location getUserLocation() {
        int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
        if (permission == PackageManager.PERMISSION_GRANTED) {
            return LocationServices.FusedLocationApi.getLastLocation(apiClient);
        } else {
            return null;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        if (grantResults.length != 0 && apiClient != null && apiClient.isConnected()) {
            // Continue regardless of whether location permissions are granted or not
            CorvallisBusPreferences.setWasLocationRequested(this, true);
            fireOnLocationResolved();
        }
    }

    private final List<LocationAvailableListener> locationListeners = new ArrayList<>();

    @Override
    public void addLocationResolutionListener(LocationAvailableListener listener) {
        locationListeners.add(listener);
    }

    @Override
    public void removeLocationResolutionListener(LocationAvailableListener listener) {
        locationListeners.remove(listener);
    }

    void fireOnLocationResolved() {
        // Prevents ConcurrentModificationException because listeners can remove themselves
        ArrayList<LocationAvailableListener> iterableListeners = new ArrayList<>(locationListeners);
        for (LocationAvailableListener listener : iterableListeners) {
            listener.onLocationResolved(this);
        }
    }
    // endregion

    // region GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener
    @Override
    public void onConnected(Bundle bundle) {
        int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
        boolean hasPermission = permission == PackageManager.PERMISSION_GRANTED;
        if (hasPermission || CorvallisBusPreferences.getWasLocationRequested(this)) {
            fireOnLocationResolved();
        }
    }

    @Override
    public void onConnectionSuspended(int i) {

    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.d("osu.appclub", "API client connection failed: " + connectionResult);
    }
    // endregion

    // TODO: add license info and maybe settings options items
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);

        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        assert searchManager != null;

        searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
        searchView.setOnQueryTextListener(this);
        searchView.setSubmitButtonEnabled(false);
        searchView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                Log.d("osu.appclub", "Focus changed to " + hasFocus);
            }
        });
        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        return true;
    }

    private StopsSearchTask searchTask;

    @Override
    public boolean onQueryTextChange(String newText) {
        if (searchTask != null) {
            searchTask.cancel(true);
        }

        if (newText.isEmpty()) {
            searchView.setSuggestionsAdapter(null);
        } else {
            searchTask = new StopsSearchTask(new WeakReference<>(this), newText, getUserLocation());
            searchTask.execute();
        }
        return true;
    }

    @Override
    public void searchComplete(Cursor cursor) {
        Log.d("osu.appclub", "Found " + cursor.getCount() + " results");
        if (cursor.getCount() == 0) {
            MatrixCursor placeholder = new MatrixCursor(new String[] { "_id" });
            placeholder.addRow(new Object[] { 0 });
            searchView.setSuggestionsAdapter(new CursorAdapter(this, placeholder, false) {
                @Override
                public View newView(Context context, Cursor cursor, ViewGroup parent) {
                    LayoutInflater inflater = (LayoutInflater) context
                            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    View view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
                    return view;
                }

                @Override
                public void bindView(View view, Context context, Cursor cursor) {
                    TextView text = (TextView) view.findViewById(android.R.id.text1);
                    text.setText(R.string.search_no_results);
                    view.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            // no-op
                        }
                    });
                }
            });
        } else {
            searchView.setSuggestionsAdapter(new CursorAdapter(this, cursor, false) {
                @Override
                public View newView(Context context, Cursor cursor, ViewGroup parent) {
                    LayoutInflater inflater = (LayoutInflater) context
                            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    View view = inflater.inflate(android.R.layout.simple_list_item_2, parent, false);
                    return view;
                }

                @SuppressLint("DefaultLocale")
                @Override
                public void bindView(View view, Context context, Cursor cursor) {
                    final int stopID = cursor.getInt(0);
                    view.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            searchView.clearFocus();
                            enqueueBusStop(stopID);
                        }
                    });

                    TextView textStopName = (TextView) view.findViewById(android.R.id.text1);
                    textStopName.setText(cursor.getString(1));

                    TextView textDistance = (TextView) view.findViewById(android.R.id.text2);
                    if (cursor.getType(2) == Cursor.FIELD_TYPE_NULL) {
                        textDistance.setText("");
                    } else {
                        textDistance.setText(String.format("%.1f miles", cursor.getDouble(2)));
                    }

                }
            });
        }
    }

    @Override
    public boolean onSearchRequested() {
        return super.onSearchRequested();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_about) {
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://rikkigibson.github.io/corvallisbus"));
            startActivity(intent);
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    public void displayStopsFragment() {
        pager.setCurrentItem(1);
    }

    // region BusStopSelectionQueue
    BusStopSelectionQueue.Listener stopDetailsListener;

    @Override
    public void setStopDetailsQueueListener(@Nullable BusStopSelectionQueue.Listener listener) {
        stopDetailsListener = listener;
    }

    private Queue<Integer> busStopQueue = new ArrayBlockingQueue<>(1);

    @Override
    public void enqueueBusStop(int stopId) {
        busStopQueue.offer(stopId);
        displayStopsFragment();
        if (stopDetailsListener != null) {
            stopDetailsListener.onEnqueueBusStop(this);
        }
    }

    @Nullable
    public Integer dequeueBusStopId() {
        return busStopQueue.poll();
    }

    @Nullable
    public Integer peekBusStopId() {
        return busStopQueue.peek();
    }
    // endregion
}