4
\$\begingroup\$

I am attempting to build an Android APP with the ability to connect specified FTP server in Java. The connection operation has been performed in FTPconnection class and the network call of FTP has been separated from main thread via java.util.concurrent.Executors.

The experimental implementation

  • Project name: FTPConnectionTest

  • FTPconnection class implementation

    package com.example.ftpconnectiontest;
    
    import android.util.Log;
    
    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.SocketException;
    import java.net.UnknownHostException;
    
    import org.apache.commons.net.SocketClient;
    import org.apache.commons.net.ftp.FTP;
    import org.apache.commons.net.ftp.FTPClient;
    import org.apache.commons.net.ftp.FTPReply;
    
    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();
        }
    
        public FTPClient connectftp()
        {
            //  Reference: https://stackoverflow.com/a/8761268/6667035
            FTPClient ftp = new FTPClient();
            try {
                //  Reference: https://stackoverflow.com/a/55950845/6667035
                //  The argument of `FTPClient.connect` method is hostname, not URL.
                ftp.connect(FTPHostName, FTPPort);
                boolean status = ftp.login(FTPUsername, FTPPassword);
                if (status)
                {
                    ftp.enterLocalPassiveMode();
                    ftp.setFileType(FTP.BINARY_FILE_TYPE);
                    ftp.sendCommand("OPTS UTF8 ON");
                }
                System.out.println("status : " + ftp.getStatus());
            } catch (UnknownHostException ex) {
                ex.printStackTrace();
            } catch (SocketException en) {
                en.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return ftp;
        }
    }
    
  • ftpconnectiontest.cpp: The place where store the authorization information. I am wondering is the design safe enough in general FTP connection application.

    #include <jni.h>
    #include <string>
    
    extern "C"
    JNIEXPORT jstring JNICALL
    Java_com_example_ftpconnectiontest_FTPconnection_getHostname(JNIEnv *env, jobject thiz) {
        std::string output = "Hostname";
        return env->NewStringUTF(output.c_str());
    }
    extern "C"
    JNIEXPORT jstring JNICALL
    Java_com_example_ftpconnectiontest_FTPconnection_getUsername(JNIEnv *env, jobject thiz) {
        std::string output = "Username";
        return env->NewStringUTF(output.c_str());
    }
    extern "C"
    JNIEXPORT jstring JNICALL
    Java_com_example_ftpconnectiontest_FTPconnection_getPassword(JNIEnv *env, jobject thiz) {
        std::string output = "Password";
        return env->NewStringUTF(output.c_str());
    }
    
  • java.util.concurrent.Executors class implementation

    ExecutorService executor = Executors.newSingleThreadExecutor();
          executor.submit(() -> {
              String threadName = Thread.currentThread().getName();
              showToast(threadName, Toast.LENGTH_SHORT);
              FTPconnection ftpConnect = new FTPconnection();
              FTPClient ftpClient = ftpConnect.connectftp();
              final String LOG_TAG = "executor";
              try {
                  Log.v(LOG_TAG, Boolean.toString(ftpClient.isConnected()));
                  showToast(Boolean.toString(ftpClient.isConnected()), Toast.LENGTH_SHORT);
              } 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.ftpconnectiontest">
    
        <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.FTPConnectionTest">
            <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.ftpconnectiontest"
            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.4.2'
        implementation 'com.google.android.material:material:1.6.1'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.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:

package com.example.ftpconnectiontest;

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.Bundle;
import android.util.Log;
import android.widget.Toast;

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

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {
    private static final int ACCESS_NETWORK_STATE_CODE = 1;

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        checkPermission(Manifest.permission.ACCESS_NETWORK_STATE, ACCESS_NETWORK_STATE_CODE);

        FTPConnectionTest();
    }

    private void FTPConnectionTest() {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(() -> {
            if (BuildConfig.DEBUG) {
                String threadName = Thread.currentThread().getName();
                showToast(threadName, Toast.LENGTH_SHORT);
            }
            FTPconnection ftpConnect = new FTPconnection();
            FTPClient ftpClient = ftpConnect.connectftp();
            final String LOG_TAG = "executor";
            try {
                Log.v(LOG_TAG, Boolean.toString(ftpClient.isConnected()));
                showToast(Boolean.toString(ftpClient.isConnected()), Toast.LENGTH_SHORT);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        });
    }

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

    private 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(@NonNull 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 {
            if (BuildConfig.DEBUG) {
                showToast("Permission already granted", Toast.LENGTH_SHORT);
            }
        }
    }
}

If there is any possible improvement, please let me know.

\$\endgroup\$
1
  • \$\begingroup\$ I think the only part that it's causing trouble trying to run it's the Executors class implementation, but thus far your project it's great! \$\endgroup\$ Commented Jun 16, 2022 at 21:54

0

Your Answer

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