Latest update: August 2013
In this tutorial, we will show you how to download content from your FlashAir device. We will use command.cgi to do this. This tutorial builds off of Android Tutorial 2: Getting A List of Contents.
We will fetch a list of the current contents of your FlashAir, parse the list, and display
the files
in a
ListView
. If you click any folder in the list, it will open and show its
contents. If
you click on an image, it will download the image to your Android device as well as display
it
in the application.
We will set up a layout file so that the name of the current directory and the number of
items in
that directory will be displayed above the list of directory contents. We will also create
a
Button
that will allow us to move back to the parent directory of the
directory we are
currently in (ex. if we are in 'DCIM/106
_05/', pressing the back button would take us back to 'DCIM/').
The content list will be displayed like this:
If you click on an image file in the list, the image will be displayed like this:
As you are viewing the image, the image file will download to your Android device.
In order to make this application, we will create the following files:
Important: Please note that your project contains a
file called
AndroidManifest.xml. This file gives your application particular permissions. By
default,
applications are not permitted to access the internet. The path to this file should look
something
like:
[Project_Folder]/AndroidManifest.xml
You will need to add the following lines of code into your
AndroidManifest.xml in order for this application to work:
<uses-permission android:name="android.permission.INTERNET" />
First, we will write the activity_main.xml file that determines the layout of our Android App. This can be found in your layout folder. The path to this file should look something like: [Project_Folder]/res/layout/activity_main.xml
You want the activity_main.xml file to look like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cacheColorHint="#00000000"
android:text="Back"
android:textColor="@android:color/white"
android:textSize="20sp" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:scaleType="centerInside"
android:text="Directory Name"
android:textAlignment="center"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:scaleType="centerInside"
android:text="Number of files"
android:textAlignment="center"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="20sp" />
<ListView
android:id="@+id/listView1"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:cacheColorHint="#00000000"
android:clickable="true"
android:headerDividersEnabled="false" />
</LinearLayout>
Next, we will edit the activity_image_view.xml file that determines the layout of our image viewing screen. This can also be found in your layout folder. The path to this file should look something like: [Project_Folder]/res/layout/activity_image_view.xml
You want the activity_image_view.xml file to look like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ImageViewActivity" >
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cacheColorHint="#00000000"
android:text="Back"
android:textColor="@android:color/white"
android:textSize="20sp" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:scaleType="centerInside"
android:contentDescription="view image"
android:text="image would be here" />
</LinearLayout>
Next, we will edit the AndroidManifest.xml file that adds the activity of our image viewing screen. The path to this file should look something like: [Project_Folder]/AndroidManifest.xml
You want the
<application>
tag in the
AndroidManifest.xml file to look like this:
<activity
android:name="com.example.android_tutorial_03.ImageViewActivity"
android:label="@string/app_name" >
</activity>
Now we will modify the MainActivity.java file. It should look like this by default:
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
We will start by changing the class declaration, since we want to include a list that contains clickable items.
public class MainActivity extends Activity implements AdapterView.OnItemClickListener {
We will start by declaring the series of views that we are planning to use, other
class
variables, and the formatting of the screen. We will also override the
onCreate(Bundle savedInstanceState)
function that initializes the Activity
class. We want
the initialization function to set up the default screen layout for our list of contents as
well
as set a click listener for the
Button
that we will use to navigate to the parent directory.
ListView listView;
ImageView imageView;
TextView currentDirText;
TextView numFilesText;
Button backButton;
String rootDir = "DCIM";
String directoryName = rootDir; // Initialize to rootDirectory
ArrayAdapter<String> listAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set buttons
getWindow().setTitleColor(Color.rgb(65, 183, 216));
backButton = (Button)findViewById(R.id.button1);
backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(directoryName.equals(rootDir)) {
listRootDirectory();
}
else {
int index = directoryName.lastIndexOf("/");
directoryName = directoryName.substring(0, index);
listDirectory(directoryName);
}
}
});
backButton.setEnabled(false); // Disable in root directory
listRootDirectory();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
Button
that will allow the user to navigate to the parent directory. This
feature
is disabled when
MainActivity
is initialized because the app will start in the root
directory and there
would be no parent directory to navigate to. It is worth noting that we have set the root directory to be the "DCIM" folder (line 6). You do not have to set this as your root directory. Since the "DCIM" folder is the location that most digital cameras store photos, it is set as the root directory for this tutorial.
The
listRootDirectory()
and
listDirectory(directoryName)
functions called in the function above are the
functions
that handle requesting and fetching content data from the FlashAir device. The
listRootDirectory()
function merely sets the
directoryName
class variable to the root directory for the FlashAir and then
calls
listDirectory(String dir)
on it:
public void listRootDirectory() {
directoryName = rootDir;
listDirectory(directoryName);
}
We will set the back button to be disabled, when viewing contents of the home directory of the FlashAir.
public void listDirectory(String dir)
{ // Prepare command directory path
if(dir.equals(rootDir)) {
backButton.setEnabled(false);
}
else {
backButton.setEnabled(true);
}
Note that in lines 3-8, we set the back button to be either enabled or disabled. When viewing contents of the home directory of the FlashAir, the back button will be greyed out and unclickable as there is no further parent directory that will be traversed.
Therefore, the home directory screen will look like this:
In all other directories, the back button will be completely visible and clickable.
We will use the following CGI command to get the number of items in a directory:
command.cgi
with
op=101
and the directory as a parameter
http://flashair/command.cgi?op=101&DIR=/DCIM
<NumberofItems>
We will use the following CGI command to retrieve a list of contents in a directory:
command.cgi
with
op=100
and the directory as a parameter
http://flashair/command.cgi?op=100&DIR=/DCIM
<Directory>,<Filename>,<Size>,<Attribute>,<Date>,<Time>
To execute this CGI command, we will reuse the FlashAirRequest.java file from Android Tutorial 2: Getting A List of Contents:
currentDirText = (TextView)findViewById(R.id.textView1);
currentDirText.setText(dir + "/");
// Fetch number of items in directory and display in a TextView
dir = "/" + dir;
ArrayList <NameValuePair> httpParams = new ArrayList <NameValuePair> ();
httpParams.add(new BasicNameValuePair("DIR", dir));
dir = URLEncodedUtils.format (httpParams, "UTF-8" );
numFilesText = (TextView)findViewById(R.id.textView2);
// Fetch number of items in directory and display in a TextView
new AsyncTask<String, Void, String>(){
@Override
protected String doInBackground(String... params) {
String dir = params[0];
String fileCount = FlashAirRequest.getString("http://flashair/command.cgi?op=101&" + dir);
return fileCount;
}
@Override
protected void onPostExecute(String fileCount) {
numFilesText.setText("Items Found: " + fileCount);
}
}.execute(dir);
The command
command.cgi
with
op=100
will return the following information about each item in the directory:
<Directory>,<Filename>,<Size>,<Attribute>,<Date>,<Time>
However, we only are interested in listing the names of each item in the directory.
After this
CGI command is executed, we will parse the data so it stores only the file names.
Again, since the class will keep track of the current and root directories, we will not need to manually type the folder name into the CGI command, but instead will use the directory name that was passed into the function.
// Fetch list of items in directory and display in a ListView
new AsyncTask<String, Void, ListAdapter>(){
@Override
protected ListAdapter doInBackground(String... params) {
String dir = params[0];
ArrayList <String> fileNames = new ArrayList <String>();
String files = FlashAirRequest.getString("http://flashair/command.cgi?op=100&" + dir);
String[] allFiles = files.split("([,\n])"); // split by newline or comma
for(int i = 2; i < allFiles.length; i= i + 6) {
if(allFiles[i].contains(".")) {
// File
fileNames.add(allFiles[i]);
}
else { // Directory, append "/"
fileNames.add(allFiles[i] + "/");
}
}
listAdapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, fileNames);
return listAdapter;
}
@Override
protected void onPostExecute(ListAdapter listAdapter) {
// Set the file list to a widget
listView = (ListView)findViewById(R.id.listView1);
ColorDrawable divcolor = new ColorDrawable(Color.rgb(17, 19, 58));
listView.setDivider(divcolor);
listView.setDividerHeight(1);
listView.setAdapter(listAdapter);
listView.setOnItemClickListener(MainActivity.this);
}
}.execute(dir);
onItemClick()
function below, we will check for the "/" at the end of the
item name
in order to determine its click behavior.The
MainActivity
class will now be able to draw a list of contents of the FlashAir
to the
screen in a
ListView
, but the list is currently not clickable. We need to implement the
OnItemClickListener
function from the
MainActivity
class declaration. This particular click listener will identify
not only
that the list was clicked, but specifically which list item was clicked.
We will set the list in the following manner:
ImageView
The original
onItemClick
function takes
< AdapterView<?> l, View v, int position, long id >
as arguments.
In order
to override the original function, our new function will also pass in those arguments.
@Override
public void onItemClick(AdapterView<?> l, View v, int position, long id) {
Object downloadFile = l.getItemAtPosition(position); // get item at clicked position in list of files
if(downloadFile.toString().endsWith("/")) { // Directory, remove "/" and show content list
String dirName = downloadFile.toString().substring(0, downloadFile.toString().length()-1); // all but the "/"
directoryName = directoryName + "/" + dirName;
listDirectory(directoryName);
}
else if( downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpg") || downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg")
|| downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpe") || downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".png") )
{ // Image file, download using ImageViewActivity
Intent viewImageIntent = new Intent(this, ImageViewActivity.class);
viewImageIntent.putExtra("downloadFile", downloadFile.toString());
viewImageIntent.putExtra("directoryName", directoryName);
MainActivity.this.startActivity(viewImageIntent);
}
}
} // End MainActivity class
As the image is actually downloaded and viewed using a second class (
class ImageViewActivity
), we have set an
Intent
to start this activity (line 12).
We need
class ImageViewActivity
to be able to access the name of the file (stored in
downloadFile.toString()
) and the directory path (stored in
directoryName
) to the file that we wish to view.
In lines 13-14, we save these values as extra
Bundle
data within the
Intent
we just created. This allows us to send these values to
ImageViewActivity
when it is initialized (below).
This class will also be an extension of the
Activity
class. We will set the
ImageView
that we use to show the image and
Button
that allows us to go back to the content list to be class variables.
Our class
declaration will look like this:
public class ImageViewActivity extends Activity {
ImageView imageView;
Button backButton;
We will also need to override the
onCreate(Bundle savedInstanceState)
and
onCreateOptionsMenu(Menu menu)
functions. In the
onCreate(Bundle savedInstanceState)
function, we will set the behavior of the
back button.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_view);
getIntent();
imageView = (ImageView)findViewById(R.id.imageView1);
backButton = (Button)findViewById(R.id.button2);
getWindow().setTitleColor(Color.rgb(65, 183, 216));
backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
Bundle extrasData = getIntent().getExtras();
String fileName = extrasData.getString("downloadFile");
String directory = extrasData.getString("directoryName");
downloadFile(fileName, directory);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.image_view, menu);
return true;
}
class MainActivity
when we declared this
Intent
. This allows us to access the name of the file to download, as well
as its
directory path. We save these imported values in lines 11 and 12.
Next, we will write the function that will download the file data from the FlashAir device.
We will need to add the following lines of function into the FlashAirRequest.java file from Android Tutorial 2: Getting A List of Contents in order for the file to view:
static public Bitmap getBitmap(String command) {
Bitmap resultBitmap = null;
try{
URL url = new URL(command);
URLConnection urlCon = url.openConnection();
urlCon.connect();
InputStream inputStream = urlCon.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] byteChunk = new byte[1024];
int bytesRead = 0;
while( (bytesRead = inputStream.read(byteChunk)) != -1) {
byteArrayOutputStream.write(byteChunk, 0, bytesRead);
}
byte[] byteArray = byteArrayOutputStream.toByteArray();
BitmapFactory.Options bfOptions = new BitmapFactory.Options();
bfOptions.inPurgeable = true;
resultBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, bfOptions);
byteArrayOutputStream.close();
inputStream.close();
}catch(MalformedURLException e) {
Log.e("ERROR", "ERROR: " + e.toString());
e.printStackTrace();
}
catch(IOException e) {
Log.e("ERROR", "ERROR: " + e.toString());
e.printStackTrace();
}
return resultBitmap;
}
Lines 15-16 will give us permission to purge the
Bitmap
that we create (in line 20). This will allow us to manage memory more
efficiently,
as the memory allocated can be freed later if needed.
We will use the file name and directory that was passed through the
Intent
to reconstruct the path to the file. Using this information, we can
retrieve the
file data.
We will use the
getBitmap()
that we just added:
void downloadFile(String downloadFile, String directory) {
final ProgressDialog waitDialog;
// Setting ProgressDialog
waitDialog = new ProgressDialog(this);
waitDialog.setMessage("Now downloading...");
waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
waitDialog.show();
// Download file
new AsyncTask<String, Void, Bitmap>(){
@Override
protected Bitmap doInBackground(String... params) {
String fileName = params[0];
return FlashAirRequest.getBitmap(fileName);
}
@Override
protected void onPostExecute(Bitmap resultBitmap) {
waitDialog.dismiss();
viewImage(resultBitmap);
}
}.execute("http://flashair/" + directory + "/" + downloadFile.toString());
}
The call to
viewImage()
in the function above is what displays the image to the screen
after the image
Bitmap
is fetched. This function will display the image in an
ImageView
.
void viewImage(Bitmap imageBitmap) {
// Show image in ImageView
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ImageViewActivity.this.finish();
}
});
if (imageBitmap == null) {
imageView.setImageResource(R.drawable.ic_launcher);
}
else {
imageView.setImageBitmap(imageBitmap);
}
}
} // End ImageViewActivity class
The clicked image will be drawn to the screen like this:
android_tutorial_03.zip (539KB)
All sample code on this page is licensed under BSD 2-Clause License