Previous  | Next  | Home

Accessing the File System


 

Android devices are computers, with file systems that can be accessed using many of the same methods as for standard computers (see Transferring Files). In this project we give two examples of using the Android file system.

  1. Writing to files on the SD card or other external media.

  2. Reading data at runtime from static (read-only) files that are packaged with an application.

In later projects we shall use these methods and others to give additional examples of storing and retrieving information.

 

Creating the Project in Android Studio

Following the general procedure in Creating a New Project, either choose Start a new Android Studio project from the Android Studio homepage, or from the Android Studio interface choose File > New > New Project. Fill out the fields in the resulting screens as follows,


Application Name: WriteSDCard
Company Domain:< YourNamespace >
Package Name: <YourNamespace> . writesdcard
Project Location: <ProjectPath> WriteSDCard
Target Devices: Phone and Tablet; Min SDK API 15
Add an Activity: Empty Activity
Activity Name: MainActivity (check the Generate Layout File box)
Layout Name: activity_main

where you should substitute your namespace for <YourNamespace> (com.lightcone in my case) and <ProjectPath> is the path to the directory where you will store this Android Studio Project (/home/guidry/StudioProjects/ in my case). If you have chosen to use version control for your projects, go ahead and commit this project to version control.

 

Filling out the Code

We first insert all the Java and XML that we shall need and then we shall explain its functionality.

 

The XML Files

Edit the res/values/strings.xml file to read


<resources> <string name="app_name">WriteSDCard</string> <string name="action_settings">Settings</string> <string name="hello">FILE SYSTEM I/O</string> </resources>

and edit the res/layout/activity_main.xml file so that it reads:


<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <TextView android:id="@+id/TextView01" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="15sp" android:textColor="@color/colorPrimary" android:text="@string/hello" /> </RelativeLayout>

 

The Manifest File

Next, we must modify the manifest file because we are going to need explicit permission to write to external files. Open AndroidManifest.xml and edit it to read


<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="<YourNamespace>.writesdcard"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

where the added permission WRITE_EXTERNAL_STORAGE is highlighted in red.


Before Android 4.4 (API 19), reading files on the external file system required the READ_EXTERNAL_STORAGE permission, and writing to external storage required the WRITE_EXTERNAL_STORAGE. (If you needed to do both, the read permission is implicit in the write permission, so it was only necessary to acquire the write permission in that case.)

Beginning with Android 4.4, these permissions are not required if you are are reading or writing only files that are private to your app. This is why the android:maxSdkVersion="18" attribute is included in the permission request in the manifest file. We shall explain this app-specific storage that does not require write permission further below . You should be aware that there are some limitations associated with this convenience. For example, if the user uninstalls your app, all of these app-specific data directories are erased, and the system media scanner does not scan these directories, making them inaccessible to the MediaStore content provider (see saving files that are app-private).

Hence you should not use these app-specific directories for media that belongs ultimately to the user. Examples: user photos captured or edited with your app, or music the user has purchased with your app. In that case, the files should be saved in the shared public directories (see saving files that can be shared with other apps for further information). This project will concentrate on how to save and read files in the data area specific to this app.

 

The Java Class Files

Next, open the file java/<YourNamespace>.writesdcard/MainActivity.java and edit it to read


package <YourNamespace>.writesdcard; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import android.os.Environment; import android.util.Log; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private static final String TAG = "MEDIA"; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.TextView01); checkExternalMedia(); writeToSDFile(); readRaw(); } /** * Method to check whether external media available and writable. This is adapted from * http://developer.android.com/guide/topics/data/data-storage.html#filesExternal */ private void checkExternalMedia() { boolean mExternalStorageAvailable = false; boolean mExternalStorageWriteable = false; String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { // Can read and write the media mExternalStorageAvailable = mExternalStorageWriteable = true; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { // Can only read the media mExternalStorageAvailable = true; mExternalStorageWriteable = false; } else { // Can't read or write mExternalStorageAvailable = mExternalStorageWriteable = false; } tv.append("\n\nEXTERNAL MEDIA: readable=" + mExternalStorageAvailable + " writable=" + mExternalStorageWriteable); } /** * Method to write ascii text characters to file on SD card. In earlier versions of Android a * WRITE_EXTERNAL_STORAGE permission must be added to the manifest file or this method will throw * a FileNotFound Exception because you won't have write permission. But not true after * API 18 for files in storage area of app (then no write permission required). */ private void writeToSDFile() { // Root of the external file system File root0 = android.os.Environment.getExternalStorageDirectory(); /* Now find the root of the external storage for this app (where the app can place * persistent files that it owns internal to the application and not typically visible * to the user as media). See * * http://developer.android.com/guide/topics/data/data-storage.html#filesExternal * * The method getExternalFilesDir (string) returns the user storage associated with the * app, which doesn't require write permissions after API 18. The string argument specifies various * regions of this storage. For example, * * - null specifies the root of the storage for this app * - Environment.DIRECTORY_NOTIFICATIONS specifies the Notifications directory of app storage * - Environvment.DIRECTORY_DOWNLOADS specifies standard directory for files downloaded by user * - Environment.DIRECTORY_PICTURES specifies standard directory for pictures available to the user * - Environment.DIRECTORY_DOCUMENTS specifies standard directory for documents produced by user * etc. * * See the fields of the Environment class at * https://developer.android.com/reference/android/os/Environment.html * for other possibilities. For example, on my phone (running Android 6.0.1) the root of * the user storage for this specific app is found at * * /storage/emulated/0/Android/data/com.lightcone.writesdcard/files * */ // Root of the data directories Documents subdirectory specific to this app, for which no write // permission is required for Android 4.4 and later. File root = this.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS); tv.append("\n\nEXTERNAL FILE SYSTEM ROOT DIRECTORY:\n" + root0); tv.append("\n\nEXTERNAL APP DATA ROOT DIRECTORY:\n" + root); // Create a Documents/download subdirectory in the data area for this app // See http://stackoverflow.com/questions/3551821/android-write-to-sd-card-folder File dir = new File(root.getAbsolutePath() + "/download"); dir.mkdirs(); File file = new File(dir, "myData.txt"); // Must catch FileNotFoundException and IOException try { FileOutputStream f = new FileOutputStream(file); PrintWriter pw = new PrintWriter(f); pw.println("Howdy do to you,"); pw.println("and the horse you rode in on."); pw.flush(); pw.close(); f.close(); } catch (FileNotFoundException e) { e.printStackTrace(); Log.i(TAG, "File not found"); } catch (IOException e) { e.printStackTrace(); Log.i(TAG, "I/O exception"); } tv.append("\n\nFILE WRITTEN TO:\n" + file); } /** * Method to read in a text file placed in the res/raw directory of the application. The * method reads in all lines of the file sequentially. */ private void readRaw() { tv.append("\n\nDATA READ FROM res/raw/textfile.txt:\n"); InputStream is = this.getResources().openRawResource(R.raw.textfile); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr, 8192); // 2nd arg is buffer size // More efficient (less readable) implementation of above is the composite expression // BufferedReader br = new BufferedReader(new InputStreamReader( // this.getResources().openRawResource(R.raw.textfile)), 8192); try { String test; while (true) { test = br.readLine(); // readLine() returns null if no more lines in the file if (test == null) break; tv.append("\n" + " " + test); } isr.close(); is.close(); br.close(); } catch (IOException e) { e.printStackTrace(); } tv.append("\n\nTHAT IS ALL"); } }

 

Adding Resources

Finally, to test part of our application we need to create the res/raw directory and place a file containing some lines of text in it.

  1. If res/raw does not exist in the project, right click on the res directory in the left panel of Android Studio and select New > Directory. In the resulting window, give the folder the name raw and click OK.

  2. Then, right-click on the new res/raw folder and select New > File. In the resulting window give the new file the name textfile.txt (with the .txt extension) and click OK.

  3. Double click on the new res/raw/textfile.txt to open it in the editor and add several lines of text to it for testing purposes. For this example, I inserted into res/raw/textfile.txt
        Now is the time
        for all good men
        to come to the aid
        of their country.
    
    but you can put whatever you wish.

That completes our application. Now let's test it and explain what it does.

 

Trying it Out

Execution of the application on a phone or emulator should produce a display like the following figure



indicating that several tasks have been carried out:

  1. The external file system (generically the SD card) has been checked for whether it is mounted and readable/writable.

  2. The root directory of the external file system has been identified.

  3. The root directory of the data directory for this app in the external file system has been identified.

  4. A data file was written to

    /storage/emulated/0/Android/data/com.lightcone.writesdcard/files/Documents/download/myData.txt

    on the external file system.

  5. Data were read in and displayed from a file res/raw/textfile.txt.

Let us now explain how the code in our XML and Java files carries this out.

 

Overview of Basic Functionality

First, we set up the ability to output results to our main display:

  1. In a manner that should by now be familiar, a TextView object tv is defined by using FindViewByID().

  2. Then, at various places, the append(CharSequence text) method of TextView is used to append additional information to that already displayed on the screen.

Besides the onCreate() method, there are three methods in MainActivity.java. Each implements a basic functionality:

  1. The method checkExternalMedia() checks whether the device has external media installed and whether it is readable and writable.

  2. The method writeToSDFile() writes some output to a file on the external SD card.

  3. The method readRaw() reads input from a file installed with the application in the res/raw directory.

Let us now explain in turn how each of these methods works.

 

Checking External Media

To check the status of external storage media in the method checkExternalMedia(), we first invoke the getExternalStorageState() method of Environment to return a string that is stored in the variable state. This string is then compared, using the equals() method of the String class, with various String constants of the Environment class to determine the state of the external media.


Notice that in Android, as in Java more generally, the logical comparison operator == cannot be used to compare two strings. Instead we must use an operator like the equals() method to return a boolean indicating whether two strings are equivalent.

If the check is successful, we display the line on the screen

   EXTERNAL MEDIA: readable=true writable=true

Assuming this to be the case, in the next section we shall write to a file on the external storage medium.


"External" means media/shared storage. It is a filesystem with relatively large capacity that does not enforce permissions (so it can be shared across applications). Traditionally this is an SD card, but it might also be built-in storage that is distinct from the protected internal storage and can be mounted as a filesystem on the device. For a more extensive discussion of data storage in Android, see the Storage Options document.

 

Writing to a File on the SD Card

Assuming that we have a writable external medium, the method writeSDCard() illustrates how to output a file to that storage.

 

Finding the Root of External Storage

First, we use the getExternalStorageDirectory() method of Environment to return the root of the file system for the external medium, assigning the returned string to the Java File object root.


A File is an abstract representation of a filesystem entity that is identified by a path. It can be a normal file, but it can also be a directory or some other entity. For our usage it will typically be a file or directory that either exists or that we wish to create.

For example, when executed on a Nexus 6P phone running Android 6.0.1, the line

   EXTERNAL FILE SYSTEM ROOT DIRECTORY: /storage/emulated/0

was written to the screen, indicating that the external medium was mounted and writable, with the root of the filesystem at /storage/emulated/0.


Some earlier discussions of Android programming assume that the root of the external storage will be /sdcard (since it commonly was on earlier devices) and give examples of hardwiring that into an application. It is more bullet-proof to use the getExternalStorageDirectory() method, as described above, to have the device itself tell you the filesystem root. This is particularly true for the Android file system beginning with Jelly Bean (Android 4.2) and following, which introduced the option of multiple users for the same device. This required the introduction of various Linux symbolic links (symlinks) that produce symbolic directories pointing to real physical directories. For the most part this is transparent to the ordinary user, but for applications such as those discussed here where we are programatically manipulating the file system it becomes more relevant. This will be discussed further below.

But what is more relevant to the present application is the location of the user-writable storage allocated for this app (where the app can place persistent files that it owns internal to the application and not typically visible to the user as media). This is obtained using the getExternalFilesDir(String string) method of Context to return the root directory for writing app-specific data to the external medium, assigning the returned string to the Java File object root.


The method getExternalFilesDir(String string) returns the user storage associated with the app, which doesn't require write permissions after API 18. The string argument specifies various regions of this storage. For example,
  • null specifies the root of the storage for this app.

  • Environment.DIRECTORY_NOTIFICATIONS specifies the Notifications directory of app storage

  • Environvment.DIRECTORY_DOWNLOADS specifies a standard directory for files downloaded by user

  • Environment.DIRECTORY_PICTURES specifies a standard directory for images available to the user

  • Environment.DIRECTORY_DOCUMENTS specifies a standard directory for documents produced by user
and so on. See the public static String fields of the Environment class for other options. For example, on my phone (running Android 6.0.1) the root of the user storage for this specific app is found at
   /storage/emulated/0/Android/data/com.lightcone.writesdcard/files ,
which was located by passing the null argument to getExternalFilesDir(string).

The following screenshot shows part of the file structure on my phone in the directory /storage/emulated/0/Android/data.



We see that /storage/emulated/0/Android/data contains a whole set of directories associated with different package names. For example, there are six directories associated with the com.lightcone package name that I use for my apps, each distinguished by a different app name (including com.lightcone.writesdcard, which corresponds to the present app). Generally, for Android 4.4 and later external storage read/write permission is not required within the same package space but paths belonging to other packages require read/write external media permissions (see the documentation associated with getExternalFilesDir).

 

Setting Up the Path to the File

Now suppose that we wish create and write to a file called myData.txt in a subdirectory download of the root directory on the SD card for our app. The statements in MainActivity.java that set up the path up for that output are


File dir = new File (root.getAbsolutePath() + "/download"); dir.mkdirs(); File file = new File(dir, "myData.txt");

Thus file now specifies the absolute path

 /storage/emulated/0/Android/data/com.lightcone.writesdcard/files/Documents/download/myData.txt

to the file into which we wish to write.


In the discussion above two different forms of the constructor for File were used that have the same name but different argument lists:
  1. File(String path)

  2. File(File directory, String filename)
This is an example of what is called method overloading in object oriented programming, where two or more definitions of a method exist having the same name but different numbers and/or data types for method arguments.

The name and list of argument types for a method is called its signature. Thus the first method above has the signature File(String) while the second has the signature File(File, String). Even though the overloaded methods share the same name, they have different signatures and the compiler can distinguish among them by examining their argument lists.

NOTE: Java does not consider the return type in distinguishing methods, so you cannot define an overloaded method having the same signature but different return types.

 

Writing Using Output Streams and Writers

To write to the file standard Java i/o stream capability is used, implemented in terms of the classes

  1. FileOutputStream, which subclasses the abstract class OutputStream (the superclass of all classes representing an output stream of bytes) and generates a byte output stream.

  2. PrintWriter, which is used to wrap the FileOutputStream and provide friendly user i/o (capability to deal with lines of ascii text rather than a byte stream).

The relevant code excerpted from the method writeToSDFile() is


try { FileOutputStream f = new FileOutputStream(file); PrintWriter pw = new PrintWriter(f); pw.println("Howdy do to you."); pw.println("Here is a second line."); pw.flush(); pw.close(); f.close(); } catch (FileNotFoundException e) { e.printStackTrace(); Log.i(TAG, "File not found"); } catch (IOException e) { e.printStackTrace(); Log.i(TAG, "I/O exception"); }

which creates a FileOutputStream, wraps it in a PrintWriter, and then appends lines to a file through this stream.

 

Handling Exceptions in the I/O

Note that all of the functional code above for the FileOuputStream is enclosed in a try-catch block. This is standard exception handling in Java.

  1. The constructor FileOutputStream(File file) for FileOutputStream throws FileNotFoundException if the File in its argument cannot be found.

  2. The method close() of FileOutputStream throws IOException if an error occurs in trying to close the stream.

Java requires that these exceptions be handled . The standard way to do that is in a try-catch block, which has the general form


try { // Code in which an exception might be thrown . . } catch (ExceptionType name1) { // Code to process exception type name1 . . } catch (ExceptionType name2) { // Code to process exception type name2 . . }

where one or more appended catch blocks process the named exceptions thrown in the try block. For a more extensive discussion of try-catch blocks, start with the Java Tutorials. In the present code the two catch blocks deal respectively with any FileNotFoundException or IOException that might be thrown in the try block.

For the catch block corresponding to FileNotFoundException we first invoke the printStackTrace() method that FileNotFoundException inherits from Throwable, which sends a human-readable form of the Throwable's stack trace to the System.err stream.


Throwable is the superclass of all Java errors and exceptions that can be thrown by the virtual machine. It has two direct subclasses:
  1. Exception, which corresponds to recoverable errors, and

  2. Error, which corresponds to unrecoverable errors.
FileNotFoundException is a subclass of IOException, which is in turn a subclass of Exception.

The alert Androider will object that there are two other exceptions thrown by code contained in the try block that we have ignored. Indeed, if you check the documentation you will find that
  1. The FileOutputStream constructor throws SecurityException in addition to FileNotFoundException.

  2. The PrintWriter constructor throws NullPointerException.
So why have we ignored these? Well, there are two categories of exceptions in Java:
  1. SecurityException and NullPointerException are subclasses of RuntimeException (the superclass of exceptions occurring as a result of executing an application in the virtual machine). The compiler does not require that a code handle runtime exceptions.

  2. The checked exceptions (those that are not subclasses of RuntimeException or Error---the superclass of all classes representing non-recoverable errors), like FileNotFoundException and IOException, are checked by the compiler and must be handled in your code.
Thus handling of the checked exceptions FileNotFoundException and IOException in our code is mandatory, but handling of the runtime exceptions NullPointerException and SecurityException is optional. For a discussion of the reasoning behind this choice in the Java language, and of situations where you might choose to handle runtime exceptions in your code, see The Java Tutorials.

Within the try-catch block we then instantiate a FileOutputStream f using the constructor with the File file as argument, and create a PrintWriter pw using the just-created FileOutputStream f as argument. Now we can write to the file using the print methods of PrintWriter. In this case we use println(String s) to output lines of text to the file. When we are through writing, the flush() method of PrintWriter is called to ensure that all pending data have been sent to the file, and then the PrintWriter pw and FileOutputStream f are closed.


For output of large datasets, performance may be enhanced by using a BufferedOutputStream to wrap an existing OutputStream and buffer the output. A typical construction is

BufferedOutputStream b = new BufferedOutputStream(new FileOutputStream("file"));
Since most requests can be satisfied by accessing the buffer alone, this minimizes costly interaction with the underlying stream at the (usually smaller) expense of the extra space holding the buffer and time consumed in copying when the buffer is flushed. Likewise, input streams can be wrapped in a BufferedInputStream. We won't use BufferedOutputStream in the present example, but below in readRaw() we will give an example of buffering an input stream.

 

Outputting a File

If this application is executed on a real device or emulator, the screen output shown above should result and you should find that the file

 /storage/emulated/0/Android/data/com.lightcone.writesdcard/files/Documents/download/myData.txt

has been written to the SD card (or equivalent external storage). You can check the content of the file by copying it to your computer using ADB or Android Studio, as described in Transferring Files, or by using a file-management app like WiFi File Explorer. For example, on my Linux Fedora 23 system connected to an Android phone I used ADB to give

   [guidry@m33 ~]$ adb -d pull /storage/emulated/0/Android/data/com.lightcone.writesdcard/files/Documents/download/myData.txt
   0 KB/s (40 bytes in 0.081s)
   [guidry@m33 ~]$ cat myData.txt
   Howdy do to you,
   and the horse you rode in on.
   [guidry@m33 ~]$

indicating that our application has indeed written the requested output to the file on the phone's external storage, and that this file is publicly accessible. (The adb -d pull command copies the file from the device to the computer and the Unix cat command displays the content of the file on the computer.)


The Android file hierarchy (which is a variant of the Linux file hierarchy) became somewhat confusing with the advent of Jelly Bean (Android 4.2). The reason is that Jelly Bean introduced the option of multiple users on a device, and to separate those multiple users the file system was restructured so that each user effectively has a different portion of the "sdcard" (external storage). For example, the address /storage/emulated/0/download/ represents a directory on external storage allocated for the first user (user 0).

This restructuring employs various symbolic links. It is these symbolic links that cause different addresses to appear for different ways to access a file on the post-4.1 file system. For a more extensive (but I think not very clear) discussion, see The bottom line seems to be that (1) These changes are transparent to the actual users (at least of unrooted devices), but matter for programmers if they access the file system directly, or for users with rooted devices for issues like backup. (2) The Android SDK finds the appropriate file locations, whether on earlier devices or newer devices, as long as methods like getExternalStorageDirectory and not hard-wired addresses are used. (3) File-management apps like Astro File Manager or WiFi File Explorer allow correct navigation to files on old and new devices. (4) The ADB often follows the symbolic links properly to an address returned by getExternalStorageDirectory.

 

Appending Rather Than Overwriting

The default behavior of the output stream that we created above is to overwrite the file if it already exists. We can instead append to the file if it already exists by substituting for the constructor FileOutputStream (File file) the constructor FileOutputStream(File file, boolean append), where the file will be overwritten if append is false and appended to if append is true.

 

Reading from a Static Resource File

The method readRaw() illustrates one way to set up an input stream to read in an external file. In this example we assume the external file to be ascii text in the res/raw directory of the application, and read it in line by line until no lines remain.

 

Input Streams and Readers

We begin by defining a buffered reader using the following code sequence.


InputStream is = this.getResources().openRawResource(R.raw.textfile); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr, 8192); // 2nd arg is buffer size

A file that is stored in the res/raw directory of the project will be exported with the application when it is packaged in the APK file and thus can be accessed from the application by this approach. (The res/raw directory is not compressed when it is stored in the application.) The method openRawResource(int resourceID) can be used to open only drawable, sound, and raw resources; it will fail if you try to open string or color resources with it. A file stored in the res/raw directory is readable by the application at runtime, but not writable.


Our BufferedReader is an example of the buffered streams discussed above. Note that in production code we would commonly replace

InputStream is = this.getResources().openRawResource(R.raw.textfile); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr, 8192);
with the compound, chained expression

BufferedReader br = new BufferedReader(new InputStreamReader( this.getResources().openRawResource(R.raw.textfile)), 8192);
since this is more compact and requires defining fewer variables. But it is also less readable, and for our pedagogical purposes in the projects presented here we often write expressions out as individual statements rather than compounding them into more efficient expressions so that what is happening is clearer for the reader.

 

Testing Read-In from a File

If you execute the application, you should find the lines that you put in the file res/raw/textfile.txt displayed on the screen because of the tv.append() statement in the while-loop. As seen in the figure above, for my example I obtained

    Now is the time
    for all good men
    to come to the aid
    of their country.

which corresponds exactly to the four lines that I inserted in the file res/raw/textfile.txt when it was created.


The complete project for the application described above is archived on GitHub at the link WriteSDCard. Instructions for installing it in Android Studio may be found in Packages for All Projects.

Last modified: July 8, 2016


Exercises

1. Use the methods of the File class to add a method to MainActivity that will list the complete path to all files and directories on the SD card of the device, and the size of the files in bytes. [Solution]


Previous  | Next  | Home