Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
ffa4f02
Use requested FTP defaults
erikraft Oct 16, 2026
95d42bf
Align FTP service defaults with settings
erikraft Oct 16, 2026
b2a3b36
Use requested SFTP credentials by default
erikraft Oct 16, 2026
fee040e
Align SFTP service credential defaults
erikraft Oct 16, 2026
98b4c5a
Show FTP connection QR code when starting server
erikraft Oct 16, 2026
848ad8f
Show SFTP connection QR code when starting server
erikraft Oct 16, 2026
b7cd0f2
Size connection QR codes for phone screens
erikraft Oct 16, 2026
eb50ed3
Size connection QR codes for phone screens
erikraft Oct 16, 2026
19d0c77
Enforce FTP read-only mode and configure FTPS explicit or implicit TLS
erikraft Oct 16, 2026
fac4e74
Enforce read-only mode at the SFTP filesystem accessor
erikraft Oct 16, 2026
8e231e9
Add FTPS mode selection, read-only setting and explicit network status
erikraft Oct 16, 2026
75c0cd0
Expose SFTP read-only option and Wi-Fi/IP status
erikraft Oct 16, 2026
48c5c16
Add strings for FTPS modes, read-only and network status
erikraft Oct 16, 2026
ce54e2c
Validate selected storage permissions and folder compatibility
erikraft Oct 16, 2026
0b8e906
Validate selected storage permissions and folder compatibility
erikraft Oct 16, 2026
6f1ebaf
Fix escaped newlines in FTP/SFTP settings Java
erikraft Oct 16, 2026
d0d6664
Fix escaped newlines in FTP/SFTP settings Java
erikraft Oct 16, 2026
6e44e68
Translate FTP/FTPS/SFTP configuration labels across locales
erikraft Oct 16, 2026
13a2e62
Render Wi-Fi status on separate lines in SFTP settings
erikraft Oct 16, 2026
8614774
fix(ftp): use supported authorities for read-only mode
erikraft Oct 16, 2026
4485f65
chore(android): bump version to 10.1.10
erikraft Oct 16, 2026
6bb5da5
test(android): assert version 10.1.10 code 33
erikraft Oct 16, 2026
f4bb29f
Add theme-adaptive ErikrafT Wi-Fi icon
erikraft Oct 16, 2026
35ef875
Add ErikrafT Wi-Fi About section strings
erikraft Oct 16, 2026
ac6c768
Add beta badge and ErikrafT Wi-Fi section to About
erikraft Oct 16, 2026
a36bb5c
Add localized ErikrafT Wi-Fi About strings (af-rZA)
erikraft Oct 16, 2026
0d3d729
Add localized ErikrafT Wi-Fi About strings (ar-rSA)
erikraft Oct 16, 2026
0ee1a95
Add localized ErikrafT Wi-Fi About strings (bg-rBG)
erikraft Oct 16, 2026
c9b076b
Add localized ErikrafT Wi-Fi About strings (ca-rES)
erikraft Oct 16, 2026
9b2bee8
Add localized ErikrafT Wi-Fi About strings (cs-rCZ)
erikraft Oct 16, 2026
2ef1bab
Add localized ErikrafT Wi-Fi About strings (da-rDK)
erikraft Oct 16, 2026
1620509
Add localized ErikrafT Wi-Fi About strings (de-rDE)
erikraft Oct 16, 2026
412ddc5
Add localized ErikrafT Wi-Fi About strings (el-rGR)
erikraft Oct 16, 2026
7d5c499
Add localized ErikrafT Wi-Fi About strings (es-rES)
erikraft Oct 16, 2026
19a50a1
Add localized ErikrafT Wi-Fi About strings (fi-rFI)
erikraft Oct 16, 2026
76c8aee
Add localized ErikrafT Wi-Fi About strings (fr-rFR)
erikraft Oct 16, 2026
239b5ab
Add localized ErikrafT Wi-Fi About strings (hi-rIN)
erikraft Oct 16, 2026
46bc3d8
Add localized ErikrafT Wi-Fi About strings (hr-rHR)
erikraft Oct 16, 2026
e75c66f
Add localized ErikrafT Wi-Fi About strings (hu-rHU)
erikraft Oct 16, 2026
c7ddb48
Add localized ErikrafT Wi-Fi About strings (in-rID)
erikraft Oct 16, 2026
45d39cc
Add localized ErikrafT Wi-Fi About strings (it-rIT)
erikraft Oct 16, 2026
c6c7846
Add localized ErikrafT Wi-Fi About strings (iw-rIL)
erikraft Oct 16, 2026
b8ef98f
Add localized ErikrafT Wi-Fi About strings (ja-rJP)
erikraft Oct 16, 2026
33b6d89
Add localized ErikrafT Wi-Fi About strings (ko-rKR)
erikraft Oct 16, 2026
b7c5350
Add localized ErikrafT Wi-Fi About strings (mk-rMK)
erikraft Oct 16, 2026
56ea899
Add localized ErikrafT Wi-Fi About strings (ne-rNP)
erikraft Oct 16, 2026
33a8778
Add localized ErikrafT Wi-Fi About strings (nl-rNL)
erikraft Oct 16, 2026
53d8863
Add localized ErikrafT Wi-Fi About strings (no-rNO)
erikraft Oct 16, 2026
2e7d494
Add localized ErikrafT Wi-Fi About strings (pl-rPL)
erikraft Oct 16, 2026
5f8d176
Add localized ErikrafT Wi-Fi About strings (pt-rBR)
erikraft Oct 16, 2026
734c42e
Add localized ErikrafT Wi-Fi About strings (pt-rPT)
erikraft Oct 16, 2026
7a3a41e
Add localized ErikrafT Wi-Fi About strings (ro-rRO)
erikraft Oct 16, 2026
8865612
Add localized ErikrafT Wi-Fi About strings (ru-rRU)
erikraft Oct 16, 2026
5bd18f1
Add localized ErikrafT Wi-Fi About strings (sk-rSK)
erikraft Oct 16, 2026
b08e512
Add localized ErikrafT Wi-Fi About strings (sl-rSI)
erikraft Oct 16, 2026
2788c06
Add localized ErikrafT Wi-Fi About strings (sr-rRS)
erikraft Oct 16, 2026
900cb28
Add localized ErikrafT Wi-Fi About strings (sr-rSP)
erikraft Oct 16, 2026
9009075
Add localized ErikrafT Wi-Fi About strings (sr)
erikraft Oct 16, 2026
25f3d86
Add localized ErikrafT Wi-Fi About strings (sv-rSE)
erikraft Oct 16, 2026
2f771d0
Add localized ErikrafT Wi-Fi About strings (tr-rTR)
erikraft Oct 16, 2026
c28454e
Add localized ErikrafT Wi-Fi About strings (uk-rUA)
erikraft Oct 16, 2026
3b8beff
Add localized ErikrafT Wi-Fi About strings (vi-rVN)
erikraft Oct 16, 2026
c3164b0
Add localized ErikrafT Wi-Fi About strings (zh-rCN)
erikraft Oct 16, 2026
12049f8
Add localized ErikrafT Wi-Fi About strings (zh-rTW)
erikraft Oct 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ android {
minSdkVersion 24
targetSdkVersion 36

versionCode 32
versionName "10.1.9"
versionCode 33
versionName "10.1.10"
applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "Drop-Android.apk"
Expand Down
60 changes: 57 additions & 3 deletions app/src/main/java/com/erikraft/drop/AboutActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
import android.net.Uri;
import android.os.Bundle;
import android.text.Html;
import android.view.View;
import android.view.Gravity;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;

import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.chip.Chip;

/** Stable, self-contained About screen with transparent dependency and attribution information. */
public class AboutActivity extends DropPipActivity {
Expand All @@ -35,8 +37,20 @@ protected void onCreate(Bundle savedInstanceState) {
int pad = dp(20);
content.setPadding(pad, dp(18), pad, dp(32));

LinearLayout appTitleRow = new LinearLayout(this);
appTitleRow.setOrientation(LinearLayout.HORIZONTAL);
appTitleRow.setGravity(Gravity.CENTER_VERTICAL);
TextView appName = text(getString(R.string.app_name_long), 28);
content.addView(appName);
appTitleRow.addView(appName, new LinearLayout.LayoutParams(0, -2, 1f));

Chip betaBadge = new Chip(this);
betaBadge.setText(R.string.beta_badge_label);
betaBadge.setCheckable(false);
betaBadge.setClickable(false);
betaBadge.setFocusable(false);
betaBadge.setEnsureMinTouchTargetSize(false);
appTitleRow.addView(betaBadge, new LinearLayout.LayoutParams(-2, -2));
content.addView(appTitleRow);

TextView version = text(
getString(R.string.about_version_format, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE),
Expand All @@ -60,6 +74,8 @@ protected void onCreate(Bundle savedInstanceState) {
missionParams.setMargins(0, dp(18), 0, dp(8));
content.addView(missionCard, missionParams);

addWifiProjectSection(content);

addHeading(content, getString(R.string.about_dependencies_title));
addHtml(content, getString(R.string.about_dependencies_summary), 15);
addHtml(content, buildAndroidDependenciesHtml(), 14);
Expand All @@ -71,7 +87,6 @@ protected void onCreate(Bundle savedInstanceState) {
addHtml(content, getString(R.string.about_licenses_summary), 15);
addButton(content, getString(R.string.about_licenses_title), "https://github.com/erikraft/Drop-Android/blob/master/LICENSE");


addHeading(content, getString(R.string.support_us));
addButton(content, "GitHub — ErikrafT Drop™", "https://github.com/erikraft/Drop");
addButton(content, "GitHub — Android", "https://github.com/erikraft/Drop-Android");
Expand All @@ -83,6 +98,45 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(root);
}

private void addWifiProjectSection(LinearLayout parent) {
MaterialCardView card = new MaterialCardView(this);
card.setRadius(dp(14));
card.setCardElevation(dp(1));
card.setStrokeWidth(dp(1));
card.setUseCompatPadding(true);

LinearLayout section = new LinearLayout(this);
section.setOrientation(LinearLayout.VERTICAL);
section.setPadding(dp(16), dp(14), dp(16), dp(16));

LinearLayout headingRow = new LinearLayout(this);
headingRow.setOrientation(LinearLayout.HORIZONTAL);
headingRow.setGravity(Gravity.CENTER_VERTICAL);

ImageView icon = new ImageView(this);
icon.setImageResource(R.drawable.ic_erikraft_wifi);
icon.setContentDescription(getString(R.string.about_wifi_title));
icon.setScaleType(ImageView.ScaleType.FIT_CENTER);
headingRow.addView(icon, new LinearLayout.LayoutParams(dp(54), dp(58)));

TextView title = text(getString(R.string.about_wifi_title), 20);
LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(0, -2, 1f);
titleParams.setMargins(dp(12), 0, 0, 0);
headingRow.addView(title, titleParams);
section.addView(headingRow);

TextView description = text(getString(R.string.about_wifi_description), 15);
description.setPadding(0, dp(10), 0, dp(8));
section.addView(description);
addButton(section, getString(R.string.about_wifi_open_repository),
"https://github.com/erikraft/WiFi");

card.addView(section);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, -2);
params.setMargins(0, dp(12), 0, dp(8));
parent.addView(card, params);
}

private String buildAndroidDependenciesHtml() {
return "<b>" + getString(R.string.about_dependencies_ui) + "</b><br>"
+ "AndroidX AppCompat 1.7.1<br>"
Expand Down
19 changes: 12 additions & 7 deletions app/src/main/java/com/erikraft/drop/FtpServerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,12 @@ private void startServer(long generation) {
try {
android.content.SharedPreferences prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this);
int port = parsePort(prefs.getString(getString(R.string.pref_ftp_port), "" + DEFAULT_PORT));
String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "admin");
String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "erikraft");
boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), false);
boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), true);
String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "android");
String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "android");
boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), true);
boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), false);
boolean implicitFtps = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
boolean readOnly = prefs.getBoolean(getString(R.string.pref_ftp_read_only), false);
Comment on lines +102 to +107

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Insecure FTP/FTPS/SFTP default configuration: guessable "android"/"android" credentials and TLS disabled by default expose device storage on the local network

Severity: HIGH · CWE-798

PR #232 changes the default FTP/FTPS/SFTP credentials from admin/erikraft to trivially guessable android/android, and flips the FTPS (TLS) default from true to false. Because the FTP and SFTP servers bind to all local interfaces, default to writable access (readOnly=false), and serve a directory of the device's storage, any device on the same local network can authenticate with the well-known android/android credential and read or write the exposed directory. For FTP the credential and all transferred data are now transmitted in cleartext. This is a security regression versus the prior defaults.

Suggested change
String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "android");
String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "android");
boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), true);
boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), false);
boolean implicitFtps = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
boolean readOnly = prefs.getBoolean(getString(R.string.pref_ftp_read_only), false);
String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "android");
String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "");
if (password.isEmpty()) {
throw new IllegalStateException("A senha do servidor FTP deve ser definida nas configurações.");
}
boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), false);
boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), true);
boolean implicitFtps = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
boolean readOnly = prefs.getBoolean(getString(R.string.pref_ftp_read_only), false);
Prompt to fix with AI
This is a security vulnerability found during a code review.

Vulnerability: Insecure FTP/FTPS/SFTP default configuration: guessable "android"/"android" credentials and TLS disabled by default expose device storage on the local network
Severity: HIGH
CWE: CWE-798

PR #232 changes the default FTP/FTPS/SFTP credentials from `admin`/`erikraft` to trivially guessable `android`/`android`, and flips the FTPS (TLS) default from `true` to `false`. Because the FTP and SFTP servers bind to all local interfaces, default to writable access (`readOnly=false`), and serve a directory of the device's storage, any device on the same local network can authenticate with the well-known `android`/`android` credential and read or write the exposed directory. For FTP the credential and all transferred data are now transmitted in cleartext. This is a security regression versus the prior defaults.

Location: app/src/main/java/com/erikraft/drop/FtpServerService.java:102-107
Context: FTP service insecure defaults (credential, TLS off, anonymous on)
```
// Before:
            String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "android");
            String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "android");
            boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), true);
            boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), false);
            boolean implicitFtps = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
            boolean readOnly = prefs.getBoolean(getString(R.string.pref_ftp_read_only), false);
// After:
            String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "android");
            String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "");
            if (password.isEmpty()) {
                throw new IllegalStateException("A senha do servidor FTP deve ser definida nas configurações.");
            }
            boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), false);
            boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), true);
            boolean implicitFtps = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
            boolean readOnly = prefs.getBoolean(getString(R.string.pref_ftp_read_only), false);
```

Location: app/src/main/java/com/erikraft/drop/SftpServerService.java:105-110
Context: SFTP service insecure default credential
```
// Before:
            String username = valueOrDefault(
                    prefs.getString(getString(R.string.pref_sftp_username), ""), "android");
            String password = valueOrDefault(
                    prefs.getString(getString(R.string.pref_sftp_password), ""), "android");
            String configuredHome = prefs.getString(getString(R.string.pref_sftp_save_location), "");
            boolean readOnly = prefs.getBoolean(getString(R.string.pref_sftp_read_only), false);
// After:
            String username = valueOrDefault(
                    prefs.getString(getString(R.string.pref_sftp_username), ""), "android");
            String password = valueOrDefault(
                    prefs.getString(getString(R.string.pref_sftp_password), ""), "");
            if (password.isEmpty()) {
                throw new IllegalStateException("A senha do servidor SFTP deve ser definida nas configurações.");
            }
            String configuredHome = prefs.getString(getString(R.string.pref_sftp_save_location), "");
            boolean readOnly = prefs.getBoolean(getString(R.string.pref_sftp_read_only), false);
```

How to fix:
Restore secure defaults by requiring the user to set a non-default password (or generate a strong random credential) before the server can start, keeping FTPS/TLS enabled by default (`true`), and keeping anonymous login disabled by default (`false`). Prefer not to ship any hard-coded fallback credential; if a fallback must exist, generate a per-installation random token rather than a shared static value. Display the current credential to the user through the existing QR code / settings UI so it remains discoverable without weakening security.

Please fix this vulnerability. If you propose a fix, make it concise and minimal.

React 👍 / 👎 to tune Strix for this repo. A repo collaborator (or the PR author) can resolve this thread to dismiss the finding.

File home = resolveHome(prefs.getString(getString(R.string.pref_save_location), ""));
if (!home.exists() && !home.mkdirs()) throw new IllegalStateException("Não foi possível criar a pasta base: " + home);

Expand All @@ -120,7 +122,10 @@ private void startServer(long generation) {
user.setPassword(password);
user.setHomeDirectory(home.getAbsolutePath());
List<org.apache.ftpserver.ftplet.Authority> authorities = new ArrayList<>();
authorities.add(new WritePermission());
// Apache FtpServer 1.2.1 has no ReadPermission authority. An empty
// authority list grants no write operations; add WritePermission only
// when the user explicitly enables write access.
if (!readOnly) authorities.add(new WritePermission());
user.setAuthorities(authorities);
userManager.save(user);

Expand Down Expand Up @@ -151,9 +156,9 @@ private void startServer(long generation) {
sslFactory.setSslProtocol("TLS");
SslConfiguration ssl = sslFactory.createSslConfiguration();
listener.setSslConfiguration(ssl);
listener.setImplicitSsl(false);
listener.setImplicitSsl(implicitFtps);
data.setSslConfiguration(ssl);
data.setImplicitSsl(false);
data.setImplicitSsl(implicitFtps);
}

listener.setDataConnectionConfiguration(data.createDataConnectionConfiguration());
Expand Down
80 changes: 74 additions & 6 deletions app/src/main/java/com/erikraft/drop/FtpSettingsActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.widget.ImageView;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.qrcode.QRCodeWriter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
Expand Down Expand Up @@ -43,6 +50,9 @@ public class FtpSettingsActivity extends DropPipActivity {
private TextInputEditText passwordInput;
private SwitchCompat anonymousSwitch;
private SwitchCompat ftpsSwitch;
private SwitchCompat readOnlySwitch;
private MaterialButton ftpsModeButton;
private TextView networkState;
private SwitchCompat stopNotificationSwitch;
private TextView directorySummary;
private TextView status;
Expand Down Expand Up @@ -90,7 +100,10 @@ protected void onCreate(Bundle savedInstanceState) {
}
} catch (SecurityException ignored) { }
DocumentFile folder = DocumentFile.fromTreeUri(this, uri);
if (folder == null) return;
if (folder == null || !folder.isDirectory() || !folder.canRead() || !folder.canWrite()) {
Toast.makeText(this, getString(R.string.transfer_error_folder_unsupported), Toast.LENGTH_LONG).show();
return;
}
String path = DocumentFileUtils.getAbsolutePath(folder, this);
if (path == null || path.trim().isEmpty()) {
Toast.makeText(this, getString(R.string.transfer_error_directory_path), Toast.LENGTH_LONG).show();
Expand Down Expand Up @@ -138,18 +151,35 @@ private void buildUi() {
});
portInput = field(getString(R.string.ftp_port_hint), prefs.getString(getString(R.string.pref_ftp_port), "2221"), InputType.TYPE_CLASS_NUMBER);
content.addView((TextInputLayout) portInput.getTag(), lpTop(dp(12)));
usernameInput = field(getString(R.string.ftp_username_hint), prefs.getString(getString(R.string.pref_ftp_username), "admin"), InputType.TYPE_CLASS_TEXT);
usernameInput = field(getString(R.string.ftp_username_hint), prefs.getString(getString(R.string.pref_ftp_username), "android"), InputType.TYPE_CLASS_TEXT);
content.addView((TextInputLayout) usernameInput.getTag(), lpTop(dp(8)));
passwordInput = field(getString(R.string.ftp_password_hint), prefs.getString(getString(R.string.pref_ftp_password), "admin"), InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
passwordInput = field(getString(R.string.ftp_password_hint), prefs.getString(getString(R.string.pref_ftp_password), "android"), InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
content.addView((TextInputLayout) passwordInput.getTag(), lpTop(dp(8)));
anonymousSwitch = new SwitchCompat(this);
anonymousSwitch.setText(R.string.ftp_anonymous_title);
anonymousSwitch.setChecked(prefs.getBoolean(getString(R.string.pref_ftp_anonymous), false));
anonymousSwitch.setChecked(prefs.getBoolean(getString(R.string.pref_ftp_anonymous), true));
content.addView(anonymousSwitch, lpTop(dp(8)));
ftpsSwitch = new SwitchCompat(this);
ftpsSwitch.setText(R.string.ftp_ftps_title);
ftpsSwitch.setChecked(prefs.getBoolean(getString(R.string.pref_ftp_ftps), true));
ftpsSwitch.setChecked(prefs.getBoolean(getString(R.string.pref_ftp_ftps), false));
content.addView(ftpsSwitch, lpTop(dp(4)));
ftpsModeButton = new MaterialButton(this);
content.addView(ftpsModeButton, lpTop(dp(4)));
updateFtpsModeLabel();
ftpsModeButton.setOnClickListener(v -> {
final String[] modes = {getString(R.string.ftp_ftps_explicit), getString(R.string.ftp_ftps_implicit)};
new AlertDialog.Builder(this).setTitle(R.string.ftp_ftps_mode_title)
.setSingleChoiceItems(modes, prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false) ? 1 : 0,
(dialog, which) -> {
prefs.edit().putBoolean(getString(R.string.pref_ftp_ftps_implicit), which == 1).apply();
updateFtpsModeLabel();
dialog.dismiss();
}).setNegativeButton(android.R.string.cancel, null).show();
});
readOnlySwitch = new SwitchCompat(this);
readOnlySwitch.setText(R.string.ftp_read_only_title);
readOnlySwitch.setChecked(prefs.getBoolean(getString(R.string.pref_ftp_read_only), false));
content.addView(readOnlySwitch, lpTop(dp(8)));
stopNotificationSwitch = new SwitchCompat(this);
stopNotificationSwitch.setText(R.string.ftp_stop_notification_title);
stopNotificationSwitch.setChecked(prefs.getBoolean(getString(R.string.pref_ftp_stop_notification), false));
Expand Down Expand Up @@ -241,6 +271,7 @@ private void saveAndStart() {
.apply();
ContextCompat.startForegroundService(this, FtpServerService.startIntent(this));
status.setText(R.string.ftp_starting);
showConnectionQr(buildAddress());
}

private void stopServer() {
Expand All @@ -258,10 +289,25 @@ private void updateAddress() {

private String buildAddress() {
String port = prefs.getString(getString(R.string.pref_ftp_port), "2221");
String scheme = prefs.getBoolean(getString(R.string.pref_ftp_ftps), true) ? "ftps" : "ftp";
String scheme = prefs.getBoolean(getString(R.string.pref_ftp_ftps), false) ? "ftps" : "ftp";
return scheme + "://" + getWifiAddress() + ":" + port;
}

private void updateFtpsModeLabel() {
if (ftpsModeButton == null) return;
boolean implicit = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
ftpsModeButton.setText(getString(R.string.ftp_ftps_mode_label, getString(implicit ? R.string.ftp_ftps_implicit : R.string.ftp_ftps_explicit)));
ftpsModeButton.setEnabled(ftpsSwitch != null && ftpsSwitch.isChecked());
if (ftpsSwitch != null) ftpsSwitch.setOnCheckedChangeListener((button, checked) -> { if (ftpsModeButton != null) ftpsModeButton.setEnabled(checked); });
}

private String buildNetworkStatus() {
android.net.ConnectivityManager cm = (android.net.ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
android.net.NetworkInfo info = cm == null ? null : cm.getActiveNetworkInfo();
boolean wifiConnected = info != null && info.isConnected() && info.getType() == android.net.ConnectivityManager.TYPE_WIFI;
return getString(wifiConnected ? R.string.ftp_wifi_connected : R.string.ftp_wifi_disconnected) + "\n" + getString(R.string.ftp_ip_address, com.erikraft.drop.utils.NetworkUtils.getIpAddress(this));
}

private String getWifiAddress() {
try {
for (NetworkInterface network : Collections.list(NetworkInterface.getNetworkInterfaces())) {
Expand All @@ -285,6 +331,28 @@ private void copyAddress() {
Toast.makeText(this, R.string.ftp_address_copied, Toast.LENGTH_SHORT).show();
}

private void showConnectionQr(String connectionAddress) {
try {
com.google.zxing.common.BitMatrix matrix = new QRCodeWriter().encode(connectionAddress, BarcodeFormat.QR_CODE, 480, 480);
Bitmap bitmap = Bitmap.createBitmap(480, 480, Bitmap.Config.RGB_565);
for (int y = 0; y < 480; y++) {
for (int x = 0; x < 480; x++) bitmap.setPixel(x, y, matrix.get(x, y) ? Color.BLACK : Color.WHITE);
}
ImageView image = new ImageView(this);
image.setImageBitmap(bitmap);
image.setAdjustViewBounds(true);
image.setScaleType(ImageView.ScaleType.FIT_CENTER);
int padding = dp(12);
image.setPadding(padding, padding, padding, padding);
image.setLayoutParams(new android.view.ViewGroup.LayoutParams(dp(240), dp(240)));
new AlertDialog.Builder(this).setTitle(R.string.ftp_settings_title)
.setMessage(connectionAddress).setView(image)
.setPositiveButton(android.R.string.ok, null).show();
} catch (WriterException e) {
Toast.makeText(this, connectionAddress, Toast.LENGTH_LONG).show();
}
}

private TextView text(String value, float size) {
TextView view = new TextView(this);
view.setText(value);
Expand Down
Loading
Loading