Skip to content

Commit

Permalink
SftpPlugin: use MANAGE_EXTERNAL_STORAGE instead of SAF in Android 11+
Browse files Browse the repository at this point in the history
  • Loading branch information
albertvaka committed Mar 19, 2023
1 parent 76c3cc4 commit 1ba9e59
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 33 deletions.
1 change: 1 addition & 0 deletions AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<uses-permission android:name="android.permission.READ_LOGS" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

<application
android:icon="@mipmap/ic_launcher"
Expand Down
1 change: 1 addition & 0 deletions res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
<string name="sftp_action_mode_menu_delete">Delete</string>
<string name="sftp_no_storage_locations_configured">No storage locations configured</string>
<string name="sftp_saf_permission_explanation">To access files remotely you have to configure storage locations</string>
<string name="sftp_manage_storage_permission_explanation">To allow remote access to files on this device you need to allow KDE Connect to manage the storage.</string>
<string name="no_players_connected">No players found</string>
<string name="send_files">Send files</string>

Expand Down
89 changes: 63 additions & 26 deletions src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.Settings;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import org.json.JSONException;
import org.json.JSONObject;
Expand All @@ -21,9 +28,13 @@
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.AlertDialogFragment;
import org.kde.kdeconnect.UserInterface.DeviceSettingsAlertDialogFragment;
import org.kde.kdeconnect.UserInterface.MainActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
import org.kde.kdeconnect.UserInterface.StartActivityAlertDialogFragment;
import org.kde.kdeconnect_tp.BuildConfig;
import org.kde.kdeconnect_tp.R;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
Expand Down Expand Up @@ -63,19 +74,36 @@ public boolean onCreate() {

@Override
public boolean checkRequiredPermissions() {
return SftpSettingsFragment.getStorageInfoList(context, this).size() != 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
} else {
return SftpSettingsFragment.getStorageInfoList(context, this).size() != 0;
}
}

@Override
public AlertDialogFragment getPermissionExplanationDialog() {
return new DeviceSettingsAlertDialogFragment.Builder()
.setTitle(getDisplayName())
.setMessage(R.string.sftp_saf_permission_explanation)
.setPositiveButton(R.string.ok)
.setNegativeButton(R.string.cancel)
.setDeviceId(device.getDeviceId())
.setPluginKey(getPluginKey())
.create();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return new StartActivityAlertDialogFragment.Builder()
.setTitle(getDisplayName())
.setMessage(R.string.sftp_manage_storage_permission_explanation)
.setPositiveButton(R.string.open_settings)
.setNegativeButton(R.string.cancel)
.setIntentAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
.setIntentUrl("package:" + BuildConfig.APPLICATION_ID)
.setStartForResult(true)
.setRequestCode(MainActivity.RESULT_NEEDS_RELOAD)
.create();
} else {
return new DeviceSettingsAlertDialogFragment.Builder()
.setTitle(getDisplayName())
.setMessage(R.string.sftp_saf_permission_explanation)
.setPositiveButton(R.string.ok)
.setNegativeButton(R.string.cancel)
.setDeviceId(device.getDeviceId())
.setPluginKey(getPluginKey())
.create();
}
}

@Override
Expand All @@ -92,35 +120,45 @@ public boolean onPacketReceived(NetworkPacket np) {
ArrayList<String> paths = new ArrayList<>();
ArrayList<String> pathNames = new ArrayList<>();

List<StorageInfo> storageInfoList = SftpSettingsFragment.getStorageInfoList(context, this);
Collections.sort(storageInfoList, Comparator.comparing(StorageInfo::getUri));

if (storageInfoList.size() > 0) {
getPathsAndNamesForStorageInfoList(paths, pathNames, storageInfoList);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
List<StorageVolume> volumes = context.getSystemService(StorageManager.class).getStorageVolumes();
for (StorageVolume sv : volumes) {
pathNames.add(sv.getDescription(context));
paths.add(sv.getDirectory().getPath());
}
} else {
NetworkPacket np2 = new NetworkPacket(PACKET_TYPE_SFTP);
np2.set("errorMessage", context.getString(R.string.sftp_no_storage_locations_configured));
device.sendPacket(np2);
return true;
List<StorageInfo> storageInfoList = SftpSettingsFragment.getStorageInfoList(context, this);
Collections.sort(storageInfoList, Comparator.comparing(StorageInfo::getUri));
if (storageInfoList.size() > 0) {
getPathsAndNamesForStorageInfoList(paths, pathNames, storageInfoList);
} else {
NetworkPacket np2 = new NetworkPacket(PACKET_TYPE_SFTP);
np2.set("errorMessage", context.getString(R.string.sftp_no_storage_locations_configured));
device.sendPacket(np2);
return true;
}
removeChildren(storageInfoList);
server.setSafRoots(storageInfoList);
}

removeChildren(storageInfoList);

if (server.start(storageInfoList)) {
if (server.start()) {
if (preferences != null) {
preferences.registerOnSharedPreferenceChangeListener(this);
}

NetworkPacket np2 = new NetworkPacket(PACKET_TYPE_SFTP);

//TODO: ip is not used on desktop any more remove both here and from desktop code when nobody ships 1.2.0
np2.set("ip", server.getLocalIpAddress());
np2.set("ip", server.getLocalIpAddress()); // for backwards compatibility
np2.set("port", server.getPort());
np2.set("user", SimpleSftpServer.USER);
np2.set("password", server.getPassword());

//Kept for compatibility, in case "multiPaths" is not possible or the other end does not support it
np2.set("path", "/");
if (paths.size() == 1) {
np2.set("path", paths.get(0));
} else {
np2.set("path", "/");
}

if (paths.size() > 0) {
np2.set("multiPaths", paths);
Expand Down Expand Up @@ -193,7 +231,7 @@ public String[] getOutgoingPacketTypes() {

@Override
public boolean hasSettings() {
return true;
return Build.VERSION.SDK_INT < Build.VERSION_CODES.R;
}

@Override
Expand Down Expand Up @@ -227,7 +265,6 @@ public PluginSettingsFragment getSettingsFragment(Activity activity) {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(context.getString(PREFERENCE_KEY_STORAGE_INFO_LIST))) {
//TODO: There used to be a way to request an un-mount (see desktop SftpPlugin's Mounter::onPackageReceived) but that is not handled anymore by the SftpPlugin on KDE.
if (server.isStarted()) {
server.stop();

Expand Down
19 changes: 14 additions & 5 deletions src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
package org.kde.kdeconnect.Plugins.SftpPlugin;

import android.content.Context;
import android.os.Build;
import android.util.Log;

import org.apache.sshd.SshServer;
import org.apache.sshd.common.file.nativefs.NativeFileSystemFactory;
import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.PasswordAuthenticator;
Expand Down Expand Up @@ -57,7 +59,11 @@ class SimpleSftpServer {
}

private final SshServer sshd = SshServer.setUpDefaultServer();
private AndroidFileSystemFactory fileSystemFactory;
private AndroidFileSystemFactory safFileSystemFactory;

public void setSafRoots(List<SftpPlugin.StorageInfo> storageInfoList) {
safFileSystemFactory.initRoots(storageInfoList);
}

void init(Context context, Device device) throws GeneralSecurityException {

Expand All @@ -78,8 +84,12 @@ public Iterable<KeyPair> loadKeys() {
}
});

fileSystemFactory = new AndroidFileSystemFactory(context);
sshd.setFileSystemFactory(fileSystemFactory);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
sshd.setFileSystemFactory(new NativeFileSystemFactory());
} else {
safFileSystemFactory = new AndroidFileSystemFactory(context);
sshd.setFileSystemFactory(safFileSystemFactory);
}
sshd.setCommandFactory(new ScpCommandFactory());
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystem.Factory()));

Expand All @@ -89,9 +99,8 @@ public Iterable<KeyPair> loadKeys() {
sshd.setPasswordAuthenticator(passwordAuth);
}

public boolean start(List<SftpPlugin.StorageInfo> storageInfoList) {
public boolean start() {
if (!started) {
fileSystemFactory.initRoots(storageInfoList);
passwordAuth.password = RandomHelper.randomString(28);

port = STARTPORT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@
package org.kde.kdeconnect.UserInterface;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.apache.commons.lang3.StringUtils;
import org.kde.kdeconnect_tp.BuildConfig;

public class StartActivityAlertDialogFragment extends AlertDialogFragment {
private static final String KEY_INTENT_ACTION = "IntentAction";
private static final String KEY_INTENT_URL = "IntentUrl";
private static final String KEY_REQUEST_CODE = "RequestCode";
private static final String KEY_START_FOR_RESULT = "StartForResult";

private String intentAction;
private String intentUrl;
private int requestCode;
private boolean startForResult;

Expand All @@ -34,6 +40,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
}

intentAction = args.getString(KEY_INTENT_ACTION);
intentUrl = args.getString(KEY_INTENT_URL);
requestCode = args.getInt(KEY_REQUEST_CODE, 0);
startForResult = args.getBoolean(KEY_START_FOR_RESULT);

Expand All @@ -44,8 +51,13 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
setCallback(new Callback() {
@Override
public void onPositiveButtonClicked() {
Intent intent = new Intent(intentAction);

Intent intent;
if (StringUtils.isNotEmpty(intentUrl)) {
Uri uri = Uri.parse(intentUrl);
intent = new Intent(intentAction, uri);
} else {
intent = new Intent(intentAction);
}
if (startForResult) {
requireActivity().startActivityForResult(intent, requestCode);
} else {
Expand All @@ -67,6 +79,12 @@ public StartActivityAlertDialogFragment.Builder setIntentAction(@NonNull String
return getThis();
}

public StartActivityAlertDialogFragment.Builder setIntentUrl(@NonNull String intentUrl) {
args.putString(KEY_INTENT_URL, intentUrl);

return getThis();
}

public StartActivityAlertDialogFragment.Builder setRequestCode(int requestCode) {
args.putInt(KEY_REQUEST_CODE, requestCode);

Expand Down

0 comments on commit 1ba9e59

Please sign in to comment.