package edu.vanderbilt.cs282.feisele.assignment7;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.app.Activity;
import android.content.ContentUris;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import edu.vanderbilt.cs282.feisele.assignment7.DownloadContentProviderSchema.ImageTable;
/**
* The Fragment is the android user interface component. Fragments can have a
* lifetime which spans the demise of its parent activity. In this particular
* case the fragment is attached to an effective clone of its original activity.
* <p>
* A fragment does not need to persist its view elements but in this
* implementation it does.
* <p>
* There is a some concern of the bitmap being updated concurrently so there is
* protection around the bitmap and its image view.
* <p>
* The following indicate the tolerated changes.
* <dl>
* <dt>orientation</dt>
* <dt>startActivity</dt>
* <dd>configuration change doesn't handle properly</dt>
* <dt>keyboard</dt>
* </dl>
* <p>
*
* @author "Fred Eisele" <phreed@gmail.com>
*
*/
public class DownloadFragment extends LLFragment {
static private final Logger logger = LoggerFactory
.getLogger("class.fragment.download");
/** my oldest daughter */
static private final String[] DEFAULT_IMAGE_SET = new String[] {
"raquel_eisele_port_2012.jpg", "raquel_eisele_land_2012.jpg" };
private Bitmap bitmap = null;
private int ordinal = 0;
private ImageView bitmapImage = null;
private Context context = null;
/**
* This ensures that the controlling activity implements the callback
* interface.
* <p>
* The intents could be implicit... <code>
final Intent syncIntent = new Intent(DownloadCall.class.getName());
final Intent asyncIntent = new Intent(DownloadRequest.class.getName());
</code> This would also require changes
* to the AndroidManifest.xml
* <p>
* <code>
<intent-filter>
<action android:name="edu.vanderbilt.cs282.feisele.DownloadCall" />
</intent-filter>
</code> ...and... <code>
<intent-filter>
<action android:name="edu.vanderbilt.cs282.feisele.DownloadRequest" />
</intent-filter>
</code> ... but in this case we will be explicit.
*/
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.context = activity.getApplicationContext();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
/**
* Disable the ability to receiver download completion messages.
*/
@Override
public void onDetach() {
super.onDetach();
}
/**
* The bitmap field serves double duty. It serves to hold the downloaded
* bitmap image and, when null, it acts as a flag to indicate that the
* default image should be used.
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState, true);
this.setRetainInstance(true);
final View result = inflater.inflate(R.layout.downloaded_image,
container, false);
this.bitmapImage = (ImageView) result.findViewById(R.id.current_image);
if (this.bitmap == null) {
this.resetImage(this.ordinal);
} else {
this.bitmapImage.setImageBitmap(this.bitmap);
}
return result;
}
/**
* A new bitmap image has been obtained. Update the bitmap.
*
* @param result
*/
@Override
public void setArguments(Bundle bundle) {
try {
final String url = bundle.getString(ImageTable.URI.title);
if (url == null || url.isEmpty() || url.length() < 1) {
this.ordinal = bundle.getInt(ImageTable.ORDINAL.title);
this.bitmap = null;
return;
}
this.loadBitmap(bundle.getInt(ImageTable.ID.title));
} catch (FileNotFoundException ex) {
logger.error("could not load file {}", bundle, ex);
} catch (IOException ex) {
logger.error("could not close file {}", bundle, ex);
}
}
/**
* Load the default image from the assets. Just for fun a different asset is
* loaded depending on the screen orientation.
*
* @param view
*/
public void resetImage(int ordinal) {
logger.debug("loading a default image {}", ordinal);
final AssetManager am = this.getActivity().getAssets();
final InputStream is;
try {
is = am.open(DEFAULT_IMAGE_SET[ordinal % DEFAULT_IMAGE_SET.length]);
} catch (IOException ex) {
Toast.makeText(this.getActivity(),
R.string.error_opening_default_image, Toast.LENGTH_LONG)
.show();
return;
}
try {
this.bitmap = null;
final Bitmap bitmap = BitmapFactory.decodeStream(is);
this.bitmapImage.setImageBitmap(bitmap);
} finally {
try {
is.close();
} catch (IOException ex) {
logger.error("cannot load a bitmap asset");
}
}
}
/**
* Load the bitmap from the content provider.
*
* @param tupleId
* @throws IOException
*/
public void loadBitmap(int tupleId) throws IOException {
logger.debug("tuple id=<{}>", tupleId);
InputStream fileStream = null;
try {
final Uri tupleUri = ContentUris.withAppendedId(
ImageTable.CONTENT_URI, tupleId);
fileStream = this.context.getContentResolver().openInputStream(
tupleUri);
final Bitmap bitmap = BitmapFactory.decodeStream(fileStream);
if (bitmap == null) {
logger.error("null bitmap returned {}", tupleUri);
return;
}
logger.trace("bitmap meta-data {}x{}", bitmap.getHeight(),
bitmap.getWidth());
this.bitmap = bitmap;
} finally {
if (fileStream != null)
fileStream.close();
}
}
}