3
\$\begingroup\$

This is a follow-up question for Android APP connect to FTP server in Java and Android APP FTP uploading file implementation in Java. 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, I am attempting to use java.util.concurrent for FTP downloading file implementation.

The experimental implementation

  • Project name: FTPDownloadFile

  • FTPconnection class implementation

    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

    #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 for new thread

    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 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

    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.6.1'
        implementation 'com.google.android.material:material:1.11.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
        testImplementation 'junit:junit:4.13.2'
        androidTestImplementation 'androidx.test.ext:junit:1.1.5'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    }
    

Full Testing Code

MainActivity.java implementation:

package com.example.ftpdownloadfile;

import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
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);

        downloadProcess("test.txt");

        mySleep(1000);

        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();
        }
    }

    private void downloadProcess(final String filename) {
        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 {
                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 " + Paths.get(getApplicationInfo().dataDir + "/" + filename) + " successfully!");
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        });
    }

    private void mySleep(final long millis)
    {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private void showToast(@StringRes int res, int duration) {
        showToast(getResources().getString(res), duration);
        return;
    }

    private void showToast(final 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(@NonNull String permission, final int requestCode)
    {
        if (ContextCompat.checkSelfPermission(MainActivity.this, permission) == PackageManager.PERMISSION_DENIED) {

            // Requesting the permission
            ActivityCompat.requestPermissions(MainActivity.this, new String[] { permission }, requestCode);
        }
        else {
            if (BuildConfig.DEBUG) {
                showToast("Permission already granted", Toast.LENGTH_SHORT);
            }
        }
    }
}

All suggestions are welcome.

The summary information:

\$\endgroup\$
2
  • \$\begingroup\$ What's the rationale behind those native methods? \$\endgroup\$
    – D. Jurcau
    Commented Apr 29, 2022 at 7:02
  • \$\begingroup\$ @D.Jurcau Thank you for the comment, For downloading multimedia contents in customized application. \$\endgroup\$
    – JimmyHu
    Commented Apr 30, 2022 at 14:08

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.