edu.wpi.khufnagle.lighthousenavigator.PhotographsActivity.java Source code

Java tutorial

Introduction

Here is the source code for edu.wpi.khufnagle.lighthousenavigator.PhotographsActivity.java

Source

package edu.wpi.khufnagle.lighthousenavigator;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Locale;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBar.OnNavigationListener;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapClickListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;

import edu.wpi.khufnagle.lighthousenavigator.data.Lighthouse;
import edu.wpi.khufnagle.lighthousenavigator.data.Photograph;
import edu.wpi.khufnagle.lighthousenavigator.list.ActivityVisibilityList.ActivityVisible;
import edu.wpi.khufnagle.lighthousenavigator.list.ContentActivityList.ContentActivity;
import edu.wpi.khufnagle.lighthousenavigator.list.CreativeCommonsLicenseTypeList.CreativeCommonsLicenseType;
import edu.wpi.khufnagle.lighthousenavigator.util.FlickrDownloadHandler;
import edu.wpi.khufnagle.lighthousenavigator.util.FlickrParseOutputXMLThread;
import edu.wpi.khufnagle.lighthousenavigator.util.FlickrPhotoDownloadThread;
import edu.wpi.khufnagle.lighthousenavigator.util.LighthouseDataParser;
import edu.wpi.khufnagle.lighthousenavigator.view.LighthouseImageAdapter;

/**
 * @author Kevin Hufnagle
 * @date March 20, 2014
 * @description Displays the "photographs" screen for the application, which
 *              contains a "main" photo (along with its photographer and
 *              licensing information), a map depicting the relative locations
 *              of all images, and thumbnails showing nearby pictures of the
 *              same lighthouse.
 */
// Version history:
// 3/20/14 - Added pop-up dialog which allows users to select photograph
// filtering operations for the application to perform
//
// 3/18/14 - Fixed miscellaneous Flickr downloading bugs
//
// 2/25/14 - Added support for downloading from Flickr
//
// 2/13/14 - Initial release
public class PhotographsActivity extends ActionBarActivity implements OnNavigationListener {
    private Lighthouse currentLighthouse;
    private FlickrParseOutputXMLThread parseOutputThread;
    private boolean userConnectedToInternet;
    private boolean photosFromFlickr;
    private GridView gv;

    // "Fake enum" for background thread handler message codes
    private static final int XML_DOWNLOAD_COMPLETE = 0;
    // XML_PARSING_COMPLETE = 1 not used in this context
    private static final int INTERRUPT = 2;

    private static boolean downloadingThreadStarted = false;

    /**
     * Display the UI defined in activity_photographs.xml upon activity start.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_photographs);

        /*
         * Add "up" button functionality in the left of application icon in
         * top-left corner, allowing user to return to "welcome" screen
         */
        final ActionBar ab = this.getSupportActionBar();
        ab.setDisplayHomeAsUpEnabled(true);
        ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);

        /*
         * Create "navigation spinner" in activity action bar that allows user to
         * view a different content screen
         */
        final ArrayAdapter<CharSequence> navDropDownAdapter = ArrayAdapter.createFromResource(this,
                R.array.ab_content_activities_names, R.layout.ab_navigation_dropdown_item);
        ab.setListNavigationCallbacks(navDropDownAdapter, this);
        ab.setSelectedNavigationItem(ContentActivity.PHOTOGRAPHS.getABDropDownListIndex());

        // Retrieve name of lighthouse that user is viewing
        final Bundle extras = this.getIntent().getExtras();
        final String lighthouseNameSelected = extras.getString("lighthousename");

        // Lighthouse data guaranteed to exist ("Welcome" activity performs data
        // existence validation)
        final LighthouseDataParser lighthouseInformationParser = new LighthouseDataParser(
                this.getApplicationContext(), lighthouseNameSelected);
        lighthouseInformationParser.parseLighthouseData();
        this.currentLighthouse = lighthouseInformationParser.getLighthouse();

        /*
         * Complete Flickr downloading task only if a network connection exists
         * _and_ the cache is empty or contains information about a lighthouse
         * other than the "desired" one
         */
        this.userConnectedToInternet = this.networkConnectionExists();
        FlickrDownloadHandler downloadHandler = null;

        if (this.userConnectedToInternet) {
            if (!this.photoCacheExists()) {
                if (!PhotographsActivity.downloadingThreadStarted) {
                    Log.d("LIGHTNAVDEBUG", "Creating dialog...");

                    // Display dialog informing user about download progress
                    final ProgressDialog flickrPhotoDownloadDialog = new ProgressDialog(this,
                            ProgressDialog.STYLE_SPINNER);
                    flickrPhotoDownloadDialog.setTitle("Downloading Flickr photos");
                    flickrPhotoDownloadDialog.setMessage("Downloading photographs from Flickr...");
                    flickrPhotoDownloadDialog.setCancelable(true);

                    flickrPhotoDownloadDialog.show();

                    final HashSet<Photograph> flickrPhotos = new HashSet<Photograph>();

                    /*
                     * Start background thread that will complete actual downloading
                     * process
                     */
                    PhotographsActivity.downloadingThreadStarted = true;
                    downloadHandler = new FlickrDownloadHandler(this, this.currentLighthouse, flickrPhotos,
                            flickrPhotoDownloadDialog);

                    final FlickrPhotoDownloadThread downloadThread = new FlickrPhotoDownloadThread(
                            this.getApplicationContext(), this.currentLighthouse, downloadHandler,
                            PhotographsActivity.XML_DOWNLOAD_COMPLETE, PhotographsActivity.INTERRUPT);
                    Log.d("LIGHTNAVDEBUG", "Flickr download XML thread started");
                    downloadThread.start();

                    /*
                     * Interrupt (stop) currently-running thread if user cancels
                     * downloading operation explicitly
                     */
                    flickrPhotoDownloadDialog.setOnCancelListener(new OnCancelListener() {
                        @Override
                        public void onCancel(DialogInterface di) {
                            if (downloadThread.isAlive()) {
                                downloadThread.interrupt();
                            } else if (PhotographsActivity.this.parseOutputThread != null
                                    && PhotographsActivity.this.parseOutputThread.isAlive()) {
                                PhotographsActivity.this.parseOutputThread.interrupt();
                            } else {
                                // Do nothing
                            }
                        }
                    });
                }
            } else {

                final ArrayList<Photograph> lighthousePhotos = this.currentLighthouse.getAlbum()
                        .get(ActivityVisible.PHOTOGRAPHS);
                lighthousePhotos.clear();
                final File downloadCacheDir = new File(this.getFilesDir() + "/download-cache/");
                final File[] downloadCacheDirContents = downloadCacheDir.listFiles();
                for (int i = 0; i < downloadCacheDirContents.length; i++) {
                    final String downloadCacheDirContentName = downloadCacheDirContents[i].getName();
                    final String[] downloadCacheDirContentPieces = downloadCacheDirContentName.split("_");
                    final Long photoID = Long.parseLong(downloadCacheDirContentPieces[2]);
                    final String ownerName = downloadCacheDirContentPieces[3].replace("~", " ");
                    final String creativeCommonsLicenseIDString = downloadCacheDirContentPieces[4].substring(0, 1);
                    final int creativeCommonsLicenseID = Integer.parseInt(creativeCommonsLicenseIDString);
                    final CreativeCommonsLicenseType licenseType = CreativeCommonsLicenseType
                            .convertToLicenseEnum(creativeCommonsLicenseID);
                    lighthousePhotos.add(new Photograph(photoID, ownerName, licenseType,
                            "/download-cache/" + downloadCacheDirContentName));
                }
                this.currentLighthouse.getAlbum().put(ActivityVisible.PHOTOGRAPHS, lighthousePhotos);
            }
        }

        if (!this.userConnectedToInternet || this.userConnectedToInternet && this.photoCacheExists()
                || downloadHandler != null && downloadHandler.getFlickrOperationsComplete()) {
            // Display first photograph as "selected" photograph in top-left corner
            // by default
            final ArrayList<Photograph> photoSet = this.currentLighthouse.getAlbum()
                    .get(ActivityVisible.PHOTOGRAPHS);
            final Photograph currentPhotoOnLeftSide = photoSet.get(0);

            final ImageView currentPhotoView = (ImageView) this.findViewById(R.id.iv_photographs_current_image);
            if (this.userConnectedToInternet) {
                currentPhotoView.setImageDrawable(Drawable.createFromPath(
                        PhotographsActivity.this.getFilesDir() + currentPhotoOnLeftSide.getInternalStorageLoc()));
            } else {
                currentPhotoView.setImageResource(currentPhotoOnLeftSide.getResID());
            }

            // Add credits for "default" main photograph
            final TextView owner = (TextView) this.findViewById(R.id.tv_photographs_photographer);
            final TextView licenseType = (TextView) this.findViewById(R.id.tv_photographs_license);
            owner.setText("Uploaded by: " + currentPhotoOnLeftSide.getOwner());
            licenseType.setText("License: " + currentPhotoOnLeftSide.getLicenseTypeAbbreviation());

            /*
             * Display Google Map as a SupportMapFragment (instead of MapFragment
             * for compatibility with Android 2.x)
             */
            /*
             * BIG thanks to http://www.truiton.com/2013/05/
             * android-supportmapfragment-example/ for getting this part of the app
             * working
             */
            final FragmentManager fm = this.getSupportFragmentManager();
            final Fragment mapFragment = fm.findFragmentById(R.id.frag_photographs_map);
            final SupportMapFragment smf = (SupportMapFragment) mapFragment;
            smf.getMap();
            final GoogleMap supportMap = smf.getMap();

            /*
             * Center map at lighthouse location, at a zoom level of 12 with no
             * tilt, facing due north
             */
            final LatLng lighthouseLocation = new LatLng(this.currentLighthouse.getCoordinates().getLatitude(),
                    this.currentLighthouse.getCoordinates().getLongitude());
            final float zoomLevel = 12.0f;
            final float tiltAngle = 0.0f;
            final float bearing = 0.0f;
            final CameraPosition cameraPos = new CameraPosition(lighthouseLocation, zoomLevel, tiltAngle, bearing);
            supportMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPos));

            /*
             * Allows user to open a maps application (such as Google Maps) upon
             * selecting the map fragment
             */
            /*
             * Courtesy of:
             * http://stackoverflow.com/questions/6205827/how-to-open-standard
             * -google-map-application-from-my-application
             */
            supportMap.setOnMapClickListener(new OnMapClickListener() {

                @Override
                public void onMapClick(LatLng lighthouseCoordinates) {
                    final String uri = String.format(Locale.US, "geo:%f,%f", lighthouseCoordinates.latitude,
                            lighthouseCoordinates.longitude);
                    final Intent googleMapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
                    PhotographsActivity.this.startActivity(googleMapsIntent);
                }
            });

            this.gv = (GridView) this.findViewById(R.id.gv_photographs_thumbnails);

            /*
             * Photographs in the "current lighthouse album" are from Flickr if the
             * album is empty or if the first (or _any_) of the elements in the
             * album do not have a "proper" static resource ID
             */
            this.photosFromFlickr = this.currentLighthouse.getAlbum().get(ActivityVisible.PHOTOGRAPHS).isEmpty()
                    || this.currentLighthouse.getAlbum().get(ActivityVisible.PHOTOGRAPHS).iterator().next()
                            .getResID() == 0;

            final LighthouseImageAdapter thumbnailAdapter = new LighthouseImageAdapter(this,
                    this.currentLighthouse.getAlbum().get(ActivityVisible.PHOTOGRAPHS), this.photosFromFlickr);

            this.gv.setAdapter(thumbnailAdapter);

            this.gv.setOnItemClickListener(new OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
                    // Determine which photograph from the list was selected...
                    final Photograph photoClicked = (Photograph) thumbnailAdapter.getItem(position);
                    final int photoClickedResID = photoClicked.getResID();

                    // ...and change the information in the top-left corner
                    // accordingly
                    if (PhotographsActivity.this.userConnectedToInternet) {
                        currentPhotoView.setImageDrawable(Drawable.createFromPath(
                                PhotographsActivity.this.getFilesDir() + photoClicked.getInternalStorageLoc()));
                    } else {
                        currentPhotoView.setImageResource(photoClickedResID);
                    }
                    owner.setText("Uploaded by: " + photoClicked.getOwner());
                    licenseType.setText("License: " + photoClicked.getLicenseTypeAbbreviation());
                }
            });
        }
    }

    /**
     * Determines whether the user can connect to the Internet using a Wi-Fi
     * network or over a cellular network.
     * @return True if the user has either type of Internet connection
     */
    /*
     * Courtesy of
     * http://stackoverflow.com/questions/4238921/android-detect-whether
     * -there-is-an-internet-connection-available
     */
    private boolean networkConnectionExists() {
        boolean wifiConnectionExists = false;
        boolean mobileConnectionExists = false;

        final ConnectivityManager connManager = (ConnectivityManager) this
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        final NetworkInfo[] connSources = connManager.getAllNetworkInfo();
        for (final NetworkInfo connSource : connSources) {
            if (connSource.getTypeName().equalsIgnoreCase("WIFI")) {
                if (connSource.isConnected()) {
                    wifiConnectionExists = true;
                }
            }

            if (connSource.getTypeName().equalsIgnoreCase("MOBILE")) {
                if (connSource.isConnected()) {
                    mobileConnectionExists = true;
                }
            }
        }

        return wifiConnectionExists || mobileConnectionExists;
    }

    private boolean photoCacheExists() {
        boolean cacheForDesiredLighthouseExists = true;
        final File cacheDirPath = new File(this.getFilesDir() + "/download-cache");

        // Create download cache directory if it doesn't already exist
        if (!cacheDirPath.exists()) {
            cacheDirPath.mkdir();
        }

        final File[] cacheDirContents = cacheDirPath.listFiles();

        /*
         * Cache for desired lighthouse certainly doesn't exist if cache is empty
         */
        if (cacheDirContents.length == 0) {
            cacheForDesiredLighthouseExists = false;
        }

        /*
         * If first element does not contain the "directory-friendly" version of
         * the name of the desired lighthouse, the rest of them don't either, so
         * can safely assume that the lighthouse photos are _not_ in the cache
         */
        else {
            final String[] cacheDirFirstFileNamePieces = cacheDirContents[0].getName().split("_");

            /*
             * Directory-friendly version of name is in second "segment" of
             * underscore-separated string
             */
            if (!cacheDirFirstFileNamePieces[1].equals(
                    Lighthouse.constructDirectoryFriendlyLighthouseName(this.currentLighthouse.getName()))) {
                cacheForDesiredLighthouseExists = false;
            }
        }

        return cacheForDesiredLighthouseExists;
    }

    /**
     * Display icon buttons for navigating to "photo actions" activities.
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        final MenuInflater inflater = this.getMenuInflater();
        inflater.inflate(R.menu.photographs_activity_actions, menu);
        return super.onCreateOptionsMenu(menu);
    }

    /**
     * Loads a new activity based on the one that the user chooses from the
     * drop-down list after selecting the activity title in the action bar.
     * @param position The placement of the selected activity name within the
     *        list of names
     * @param id The ID value of the selected activity name selected within the
     *        list of names
     * @return True if the user selected a valid activity name (should always
     *         occur)
     */
    @Override
    public boolean onNavigationItemSelected(int position, long id) {
        final ContentActivity activityNameSelected = ContentActivity.values()[position];
        switch (activityNameSelected) {
        case INFORMATION:
            Intent nextActivity = new Intent(PhotographsActivity.this, InformationActivity.class);
            nextActivity.putExtra("lighthousename", this.currentLighthouse.getName());
            this.startActivity(nextActivity);
            return true;
        case PHOTOGRAPHS:
            // No need to reload current activity
            return true;
        case HISTORY:
            nextActivity = new Intent(PhotographsActivity.this, HistoryActivity.class);
            nextActivity.putExtra("lighthousename", this.currentLighthouse.getName());
            this.startActivity(nextActivity);
            return true;
        case REVIEWS:
            nextActivity = new Intent(PhotographsActivity.this, ReviewsActivity.class);
            nextActivity.putExtra("lighthousename", this.currentLighthouse.getName());
            this.startActivity(nextActivity);
            return true;
        default:
            return false;
        }
    }

    /**
     * Responds to a user's selection of a bottom action bar button.
     * @param item The resource ID of the button that the user has selected
     * @return True if the button press is processed correctly (should always
     *         occur)
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.ab_photographs_upload:
            // Show a "coming soon" message
            Toast.makeText(this, "\"Upload photo\" functionality coming soon", Toast.LENGTH_SHORT).show();
            return true;

        case R.id.ab_photographs_filter:
            final ArrayList<Integer> selectedFilters = new ArrayList<Integer>();

            // Create the dialog itself
            final AlertDialog.Builder filterPhotosDialogBuilder = new AlertDialog.Builder(this);

            // Set up UI of dialog
            // This section of code courtesy of:
            // http://developer.android.com/guide/topics/ui/dialogs.html
            filterPhotosDialogBuilder.setTitle(R.string.photographs_filter_dialog_title);

            filterPhotosDialogBuilder.setMultiChoiceItems(R.array.photograph_filter_options, null,
                    new DialogInterface.OnMultiChoiceClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                            /*
                             * If user enables a check box, add it to list of selected
                             * filters
                             */
                            if (isChecked) {
                                selectedFilters.add(Integer.valueOf(which));
                            }

                            /*
                             * If user disables a check box, remove it from the list of
                             * selected filters
                             */
                            else if (selectedFilters.contains(Integer.valueOf(which))) {
                                selectedFilters.remove(Integer.valueOf(which));
                            } else {
                                // Do nothing
                            }
                        }
                    });

            filterPhotosDialogBuilder.setPositiveButton(R.string.dialog_positive, new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {

                    /*
                     * Determine the filtering criterion/criteria that the user
                     * has/have selected
                     */
                    /*
                     * final Set<PhotographFilter.Criteria> filterCriteria =
                     * new HashSet<PhotographFilter.Criteria>(); for (final int
                     * filter : selectedFilters) {
                     * filterCriteria.add(PhotographFilter
                     * .Criteria.convertToCriteriaEnum(filter)); } final
                     * PhotographFilter photoFilter = new PhotographFilter(
                     * PhotographsActivity.this.getApplicationContext(),
                     * filterCriteria); try { /* Display placeholder
                     * "toast text" message indicating that, if ImageJ could
                     * integrate with this application correctly, this is where
                     * the actual filtering operation would take place
                     */
                    /*
                     * Toast.makeText(PhotographsActivity.this,
                     * "Filtering operation coming soon", Toast.LENGTH_SHORT)
                     * .show();
                     */

                    // Perform actual filtering operation

                    /*
                     * final ArrayList<Photograph> filteredPhotos =
                     * photoFilter.
                     * filterPhotos(PhotographsActivity.this.currentLighthouse
                     * .getAlbum() .get( ActivityVisible.PHOTOGRAPHS));
                     */

                    /*
                     * "Reset" the grid view on the right-hand side of the
                     * "Photographs" activity to show only the photographs that
                     * "passed through" each of the filters that the user
                     * specified within the dialog
                     */

                    /*
                     * final LighthouseImageAdapter filteredThumbnailAdapter =
                     * new LighthouseImageAdapter(
                     * PhotographsActivity.this.getApplicationContext(),
                     * filteredPhotos,
                     * PhotographsActivity.this.photosFromFlickr);
                     * PhotographsActivity
                     * .this.gv.setAdapter(filteredThumbnailAdapter);
                     */

                }

                /*
                 * catch (final PhotographFilterException pfe) {
                 * System.err.println
                 * ("An error occurred while applying photograph filters!");
                 * pfe.printStackTrace(); } catch (final
                 * ImageManipulatorException ime) { System.err.println(
                 * "An image does not exist at a file path used during filtering!"
                 * ); ime.printStackTrace(); } }
                 */
            });

            /*
             * If user cancels filtering selection, just close dialog (and do
             * nothing else)
             */
            filterPhotosDialogBuilder.setNegativeButton(R.string.dialog_negative, new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            });

            // Dialog ready to be displayed to user
            final AlertDialog filterPhotosDialog = filterPhotosDialogBuilder.create();
            filterPhotosDialog.show();

            return true;

        default:
            // "Do nothing" if a button other than the specified action buttons
            // is selected
            return super.onOptionsItemSelected(item);
        }
    }
}