This is a follow-up question for [Android APP connect to FTP server in Java](https://codereview.stackexchange.com/q/260576/231235) and [Android APP FTP uploading file implementation in Java](https://codereview.stackexchange.com/q/261514/231235). I am attempting to perform the download operation to specified FTP server in Android APP. The public class `FTPconnection` has been implemented for performing the download details and the class `FtpDownloadTask` is to make the download operation separate from main thread.

**The experimental implementation**

- Project name: FTPDownloadFile

- `FTPconnection` class implementation
  
  ```Java
  package com.example.ftpdownloadfile;
  
  import android.util.Log;
  
  import org.apache.commons.net.ftp.FTP;
  import org.apache.commons.net.ftp.FTPClient;
  import org.apache.commons.net.ftp.FTPReply;
  
  import java.io.BufferedOutputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.net.SocketException;
  import java.net.UnknownHostException;
  
  public class FTPconnection {
  
      private native String getHostname();
      private native String getUsername();
      private native String getPassword();
  
      private static String FTPHostName = "";
      private static final int FTPPort = 21;
      private static String FTPUsername = "";
      private static String FTPPassword = "";
  
      public FTPconnection()
      {
          FTPHostName = getHostname();
          FTPUsername = getUsername();
          FTPPassword = getPassword();
      }
  
      //  Reference: https://stackoverflow.com/a/24605713/6667035
      public Boolean downloadAndSaveFile(String filename, File localFile)
              throws IOException {
          String LOG_TAG = "FTPconnection_download";
          FTPClient ftp = null;
          try {
              ftp = new FTPClient();
              ftp.connect(FTPHostName, FTPPort);
              Log.d(LOG_TAG, "Connected. Reply: " + ftp.getReplyString());
  
              ftp.login(FTPUsername, FTPPassword);
              Log.d(LOG_TAG, "Logged in");
              ftp.setFileType(FTP.BINARY_FILE_TYPE);
              Log.d(LOG_TAG, "Downloading");
              ftp.enterLocalPassiveMode();
  
              OutputStream outputStream = null;
              boolean success = false;
              try {
                  outputStream = new BufferedOutputStream(new FileOutputStream(
                          localFile));
                  success = ftp.retrieveFile(filename, outputStream);
              } finally {
                  if (outputStream != null) {
                      outputStream.close();
                  }
              }
  
              return success;
          } finally {
              if (ftp != null) {
                  ftp.logout();
                  ftp.disconnect();
              }
          }
      }
  }
  ```
  
- `ftpdownloadfile.cpp`
  
  ```C++
  #include <jni.h>
  #include <string>
  
  extern "C"
  JNIEXPORT jstring JNICALL
  Java_com_example_ftpdownloadfile_FTPconnection_getHostname(JNIEnv *env, jobject thiz) {
      std::string output = "Hostname";
      return env->NewStringUTF(output.c_str());
  }
  
  extern "C"
  JNIEXPORT jstring JNICALL
  Java_com_example_ftpdownloadfile_FTPconnection_getUsername(JNIEnv *env, jobject thiz) {
      std::string output = "Username";
      return env->NewStringUTF(output.c_str());
  }
  
  extern "C"
  JNIEXPORT jstring JNICALL
  Java_com_example_ftpdownloadfile_FTPconnection_getPassword(JNIEnv *env, jobject thiz) {
      std::string output = "Password";
      return env->NewStringUTF(output.c_str());
  }
  ```
  
- [`ExecutorService`](https://developer.android.com/reference/java/util/concurrent/Executor) for new thread
  
  ```Java
  ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(() -> {
            String threadName = Thread.currentThread().getName();
            showToast(threadName, Toast.LENGTH_SHORT);
            FTPconnection ftpConnect = new FTPconnection();
            final String LOG_TAG = "executor";
            try {
                String filename = "test.txt";
                Log.v(LOG_TAG,"Preparing to download " + filename);
                var result  = ftpConnect.downloadAndSaveFile(filename, Paths.get(getApplicationInfo().dataDir + "/" + filename).toFile());
                if (result == false) {
                    Log.v(LOG_TAG,"Fail to download " + filename + "!");
                } else {
                    Log.v(LOG_TAG,"Download " + filename + " successfully!");
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        });
  ```
  
- User permission setting
  
  ```
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  ```

- `AndroidManifest.xml`
  
  ```XML
  <?xml version="1.0" encoding="utf-8"?>
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.example.ftpdownloadfile">
  
      <application
          android:allowBackup="true"
          android:icon="@mipmap/ic_launcher"
          android:label="@string/app_name"
          android:roundIcon="@mipmap/ic_launcher_round"
          android:supportsRtl="true"
          android:theme="@style/Theme.FTPDownloadFile">
          <activity
              android:name=".MainActivity"
              android:exported="true">
              <intent-filter>
                  <action android:name="android.intent.action.MAIN" />
  
                  <category android:name="android.intent.category.LAUNCHER" />
              </intent-filter>
          </activity>
      </application>
  
      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
      <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  </manifest>
  ```

- `build.gradle`
  
  ```Gradle
  plugins {
      id 'com.android.application'
  }
  
  android {
      compileSdk 30
  
      defaultConfig {
          applicationId "com.example.ftpdownloadfile"
          minSdk 26
          targetSdk 30
          versionCode 1
          versionName "1.0"
  
          testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
          externalNativeBuild {
              cmake {
                  cppFlags ''
              }
          }
      }
  
      buildTypes {
          release {
              minifyEnabled false
              proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
          }
      }
      compileOptions {
          sourceCompatibility JavaVersion.VERSION_11
          targetCompatibility JavaVersion.VERSION_11
      }
      externalNativeBuild {
          cmake {
              path file('src/main/cpp/CMakeLists.txt')
              version '3.18.1'
          }
      }
  }
  
  dependencies {
  
      implementation fileTree(include: ['*.jar'], dir: 'libs')
      // https://mvnrepository.com/artifact/commons-net/commons-net
      implementation group: 'commons-net', name: 'commons-net', version: '20030805.205232'
      implementation 'androidx.appcompat:appcompat:1.3.0'
      implementation 'com.google.android.material:material:1.4.0'
      implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
      testImplementation 'junit:junit:4.13.2'
      androidTestImplementation 'androidx.test.ext:junit:1.1.3'
      androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
  }
  ```

**Full Testing Code**

`MainActivity.java` implementation:

```Java
package com.example.ftpdownloadfile;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import org.apache.commons.net.ftp.FTPClient;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("ftpdownloadfile");
    }

    private static final int READ_EXTERNAL_STORAGE_CODE = 1;
    private static final int WRITE_EXTERNAL_STORAGE_CODE = 2;
    private static final int ACCESS_NETWORK_STATE_CODE = 3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE_CODE);
        checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE_CODE);
        checkPermission(Manifest.permission.ACCESS_NETWORK_STATE, ACCESS_NETWORK_STATE_CODE);

        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(() -> {
            String threadName = Thread.currentThread().getName();
            showToast(threadName, Toast.LENGTH_SHORT);
            FTPconnection ftpConnect = new FTPconnection();
            final String LOG_TAG = "executor";
            try {
                String filename = "test.txt";
                Log.v(LOG_TAG,"Preparing to download " + filename);
                var result  = ftpConnect.downloadAndSaveFile(filename, Paths.get(getApplicationInfo().dataDir + "/" + filename).toFile());
                if (result == false) {
                    Log.v(LOG_TAG,"Fail to download " + filename + "!");
                } else {
                    Log.v(LOG_TAG,"Download " + filename + " successfully!");
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        });

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        try {
            BufferedReader fileReader = new BufferedReader(new FileReader(this.getApplicationInfo().dataDir + "/test.txt"));
            String row;
            while ((row = fileReader.readLine()) != null) {
                showToast(row, Toast.LENGTH_SHORT);
            }
            fileReader.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    void showToast(String textInput, int duration)
    {
        Context context = getApplicationContext();
        CharSequence text = textInput;
        //  Reference: https://stackoverflow.com/a/12897386/6667035
        runOnUiThread(() -> Toast.makeText(context, text, duration).show());
    }

    // Function to check and request permission.
    public void checkPermission(String permission, int requestCode) {
        if (ContextCompat.checkSelfPermission(MainActivity.this, permission) == PackageManager.PERMISSION_DENIED) {

            // Requesting the permission
            ActivityCompat.requestPermissions(MainActivity.this, new String[] { permission }, requestCode);
        }
        else {
            Toast.makeText(MainActivity.this, "Permission already granted", Toast.LENGTH_SHORT).show();
        }
    }
}
```

All suggestions are welcome.

The summary information:

- Which question it is a follow-up to?
  
  [Android APP connect to FTP server in Java](https://codereview.stackexchange.com/q/260576/231235) and
  
  [Android APP FTP uploading file implementation in Java](https://codereview.stackexchange.com/q/261514/231235)
  
- What changes has been made in the code since last question?
  
  I am attempting to perform the download operation to specified FTP server in Android APP in this post.
  
- Why a new review is being asked for?
  
  If there is any possible improvement, please let me know.