Java tutorial
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); } } }