/** * Copyright 2010 Mark Wyszomierski */ package com.joelapenna.foursquared; import com.joelapenna.foursquare.error.FoursquareException; import com.joelapenna.foursquared.util.NotificationsUtil; import android.app.Activity; import android.app.ProgressDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.DialogInterface.OnCancelListener; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.widget.Toast; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; /** * Handles fetching an image from the web, then writes it to a temporary file on * the sdcard so we can hand it off to a view intent. This activity can be * styled with a custom transparent-background theme, so that it appears like a * simple progress dialog to the user over whichever activity launches it, * instead of two seaparate activities before they finally get to the * image-viewing activity. The only required intent extra is the URL to download * the image, the others are optional: * <ul> * <li>IMAGE_URL - String, url of the image to download, either jpeg or png.</li> * <li>CONNECTION_TIMEOUT_IN_SECONDS - int, optional, max timeout wait for * download, in seconds.</li> * <li>READ_TIMEOUT_IN_SECONDS - int, optional, max timeout wait for read of * image, in seconds.</li> * <li>PROGRESS_BAR_TITLE - String, optional, title of the progress bar during * download.</li> * <li>PROGRESS_BAR_MESSAGE - String, optional, message body of the progress bar * during download.</li> * </ul> * * @date February 25, 2010 * @author Mark Wyszomierski (markww@gmail.com), foursquare. */ public class FetchImageForViewIntent extends Activity { private static final String TAG = "FetchImageForViewIntent"; private static final boolean DEBUG = FoursquaredSettings.DEBUG; private static final String TEMP_FILE_NAME = "tmp_fsq"; public static final String IMAGE_URL = Foursquared.PACKAGE_NAME + ".FetchImageForViewIntent.IMAGE_URL"; public static final String CONNECTION_TIMEOUT_IN_SECONDS = Foursquared.PACKAGE_NAME + ".FetchImageForViewIntent.CONNECTION_TIMEOUT_IN_SECONDS"; public static final String READ_TIMEOUT_IN_SECONDS = Foursquared.PACKAGE_NAME + ".FetchImageForViewIntent.READ_TIMEOUT_IN_SECONDS"; public static final String PROGRESS_BAR_TITLE = Foursquared.PACKAGE_NAME + ".FetchImageForViewIntent.PROGRESS_BAR_TITLE"; public static final String PROGRESS_BAR_MESSAGE = Foursquared.PACKAGE_NAME + ".FetchImageForViewIntent.PROGRESS_BAR_MESSAGE"; private StateHolder mStateHolder; private ProgressDialog mDlgProgress; private BroadcastReceiver mLoggedOutReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "onReceive: " + intent); finish(); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (DEBUG) Log.d(TAG, "onCreate()"); setContentView(R.layout.fetch_image_for_view_intent_activity); registerReceiver(mLoggedOutReceiver, new IntentFilter(Foursquared.INTENT_ACTION_LOGGED_OUT)); Object retained = getLastNonConfigurationInstance(); if (retained != null && retained instanceof StateHolder) { mStateHolder = (StateHolder) retained; mStateHolder.setActivity(this); } else { String url = null; if (getIntent().getExtras().containsKey(IMAGE_URL)) { url = getIntent().getExtras().getString(IMAGE_URL); } else { Log.e(TAG, "FetchImageForViewIntent requires intent extras parameter 'IMAGE_URL'."); finish(); return; } if (DEBUG) Log.d(TAG, "Fetching image: " + url); // Grab the extension of the file that should be present at the end // of the url. We can do a better job of this, and could even check // that the extension is of an expected type. int posdot = url.lastIndexOf("."); if (posdot < 0) { Log.e(TAG, "FetchImageForViewIntent requires a url to an image resource with a file extension in its name."); finish(); return; } String progressBarTitle = getIntent().getStringExtra(PROGRESS_BAR_TITLE); if (progressBarTitle == null) { progressBarTitle = "Fetching Image"; } String progressBarMessage = getIntent().getStringExtra(PROGRESS_BAR_MESSAGE); if (progressBarMessage == null) { progressBarMessage = "Fetching image..."; } mStateHolder = new StateHolder(); mStateHolder.startTask( FetchImageForViewIntent.this, url, url.substring(posdot), progressBarTitle, progressBarMessage, getIntent().getIntExtra(CONNECTION_TIMEOUT_IN_SECONDS, 20), getIntent().getIntExtra(READ_TIMEOUT_IN_SECONDS, 20)); } } @Override public void onResume() { super.onResume(); if (mStateHolder.getIsRunning()) { startProgressBar(mStateHolder.getProgressTitle(), mStateHolder.getProgressMessage()); } } @Override public void onPause() { super.onPause(); stopProgressBar(); } @Override public void onDestroy() { super.onDestroy(); unregisterReceiver(mLoggedOutReceiver); } @Override public Object onRetainNonConfigurationInstance() { mStateHolder.setActivity(null); return mStateHolder; } private void startProgressBar(String title, String message) { if (mDlgProgress == null) { mDlgProgress = ProgressDialog.show(this, title, message); mDlgProgress.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dlg) { mStateHolder.cancel(); finish(); } }); mDlgProgress.setCancelable(true); } mDlgProgress.setTitle(title); mDlgProgress.setMessage(message); } private void stopProgressBar() { if (mDlgProgress != null && mDlgProgress.isShowing()) { mDlgProgress.dismiss(); } mDlgProgress = null; } private void onFetchImageTaskComplete(Boolean result, String path, String extension, Exception ex) { try { // If successful, start an intent to view the image. if (result != null && result.equals(Boolean.TRUE)) { // If the image can't be loaded or an intent can't be found to // view it, launchViewIntent() will create a toast with an error // message. launchViewIntent(path, extension); } else { NotificationsUtil.ToastReasonForFailure(FetchImageForViewIntent.this, ex); } } finally { // Whether download worked or not, we finish ourselves now. If an // error occurred, the toast should remain on the calling activity. mStateHolder.setIsRunning(false); stopProgressBar(); finish(); } } private boolean launchViewIntent(String outputPath, String extension) { Foursquared foursquared = (Foursquared) getApplication(); if (foursquared.getUseNativeImageViewerForFullScreenImages()) { // Try to open the file now to create the uri we'll hand to the intent. Uri uri = null; try { File file = new File(outputPath); uri = Uri.fromFile(file); } catch (Exception ex) { Log.e(TAG, "Error opening downloaded image from temp location: ", ex); Toast.makeText(this, "No application could be found to diplay the full image.", Toast.LENGTH_SHORT); return false; } // Try to start an intent to view the image. It's possible that the user // may not have any intents to handle the request. try { Intent intent = new Intent(android.content.Intent.ACTION_VIEW); intent.setDataAndType(uri, "image/" + extension); startActivity(intent); } catch (Exception ex) { Log.e(TAG, "Error starting intent to view image: ", ex); Toast.makeText(this, "There was an error displaying the image.", Toast.LENGTH_SHORT); return false; } } else { Intent intent = new Intent(this, FullSizeImageActivity.class); intent.putExtra(FullSizeImageActivity.INTENT_EXTRA_IMAGE_PATH, outputPath); startActivity(intent); } return true; } /** * Handles fetching the image from the net and saving it to disk in a task. */ private static class FetchImageTask extends AsyncTask<Void, Void, Boolean> { private FetchImageForViewIntent mActivity; private final String mUrl; private String mExtension; private final String mOutputPath; private final int mConnectionTimeoutInSeconds; private final int mReadTimeoutInSeconds; private Exception mReason; public FetchImageTask(FetchImageForViewIntent activity, String url, String extension, int connectionTimeoutInSeconds, int readTimeoutInSeconds) { mActivity = activity; mUrl = url; mExtension = extension; mOutputPath = Environment.getExternalStorageDirectory() + "/" + TEMP_FILE_NAME; mConnectionTimeoutInSeconds = connectionTimeoutInSeconds; mReadTimeoutInSeconds = readTimeoutInSeconds; } public void setActivity(FetchImageForViewIntent activity) { mActivity = activity; } @Override protected Boolean doInBackground(Void... params) { try { saveImage(mUrl, mOutputPath, mConnectionTimeoutInSeconds, mReadTimeoutInSeconds); return Boolean.TRUE; } catch (Exception e) { if (DEBUG) Log.d(TAG, "FetchImageTask: Exception while fetching image.", e); mReason = e; } return Boolean.FALSE; } @Override protected void onPostExecute(Boolean result) { if (DEBUG) Log.d(TAG, "FetchImageTask: onPostExecute()"); if (mActivity != null) { mActivity.onFetchImageTaskComplete(result, mOutputPath, mExtension, mReason); } } @Override protected void onCancelled() { if (mActivity != null) { mActivity.onFetchImageTaskComplete(null, null, null, new FoursquareException("Image download cancelled.")); } } } public static void saveImage(String urlImage, String savePath, int connectionTimeoutInSeconds, int readTimeoutInSeconds) throws Exception { URL url = new URL(urlImage); URLConnection conn = url.openConnection(); conn.setConnectTimeout(connectionTimeoutInSeconds * 1000); conn.setReadTimeout(readTimeoutInSeconds * 1000); int contentLength = conn.getContentLength(); InputStream raw = conn.getInputStream(); InputStream in = new BufferedInputStream(raw); byte[] data = new byte[contentLength]; int bytesRead = 0; int offset = 0; while (offset < contentLength) { bytesRead = in.read(data, offset, data.length - offset); if (bytesRead == -1) { break; } offset += bytesRead; } in.close(); if (offset != contentLength) { Log.e(TAG, "Error fetching image, only read " + offset + " bytes of " + contentLength + " total."); throw new FoursquareException("Error fetching full image, please try again."); } // This will fail if the user has no sdcard present, catch it specifically // to alert user. try { FileOutputStream out = new FileOutputStream(savePath); out.write(data); out.flush(); out.close(); } catch (Exception ex) { Log.e(TAG, "Error saving fetched image to disk.", ex); throw new FoursquareException("Error opening fetched image, make sure an sdcard is present."); } } /** Maintains state between rotations. */ private static class StateHolder { FetchImageTask mTaskFetchImage; boolean mIsRunning; String mProgressTitle; String mProgressMessage; public StateHolder() { mIsRunning = false; } public void startTask(FetchImageForViewIntent activity, String url, String extension, String progressBarTitle, String progressBarMessage, int connectionTimeoutInSeconds, int readTimeoutInSeconds) { mIsRunning = true; mProgressTitle = progressBarTitle; mProgressMessage = progressBarMessage; mTaskFetchImage = new FetchImageTask(activity, url, extension, connectionTimeoutInSeconds, readTimeoutInSeconds); mTaskFetchImage.execute(); } public void setActivity(FetchImageForViewIntent activity) { if (mTaskFetchImage != null) { mTaskFetchImage.setActivity(activity); } } public void setIsRunning(boolean isRunning) { mIsRunning = isRunning; } public boolean getIsRunning() { return mIsRunning; } public String getProgressTitle() { return mProgressTitle; } public String getProgressMessage() { return mProgressMessage; } public void cancel() { if (mTaskFetchImage != null) { mTaskFetchImage.cancel(true); mIsRunning = false; } } } }