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 due to [_AsyncTask was deprecated in API level 30_](https://developer.android.com/reference/android/os/AsyncTask), I am attempting to use `java.util.concurrent` for FTP downloading file implementation. **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 31 defaultConfig { applicationId "com.example.ftpdownloadfile" minSdk 26 targetSdk 31 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.1' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.0' 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.