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 implementationpackage 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 implementationExecutorService 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.