mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 09:58:08 +00:00
SftpPlugin: use MANAGE_EXTERNAL_STORAGE instead of SAF in Android 11+
https://developer.android.com/training/data-storage/manage-all-files BUG: 447636 BUG: 464431
This commit is contained in:
parent
76c3cc4c57
commit
1ba9e59872
@ -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"
|
||||
|
@ -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>
|
||||
|
||||
|
@ -8,11 +8,18 @@ package org.kde.kdeconnect.Plugins.SftpPlugin;
|
||||
|
||||
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;
|
||||
@ -21,9 +28,13 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
||||
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;
|
||||
@ -63,11 +74,27 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
|
||||
|
||||
@Override
|
||||
public boolean checkRequiredPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
return Environment.isExternalStorageManager();
|
||||
} else {
|
||||
return SftpSettingsFragment.getStorageInfoList(context, this).size() != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialogFragment getPermissionExplanationDialog() {
|
||||
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)
|
||||
@ -77,6 +104,7 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
|
||||
.setPluginKey(getPluginKey())
|
||||
.create();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
@ -92,9 +120,15 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
|
||||
ArrayList<String> paths = new ArrayList<>();
|
||||
ArrayList<String> pathNames = new ArrayList<>();
|
||||
|
||||
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 {
|
||||
List<StorageInfo> storageInfoList = SftpSettingsFragment.getStorageInfoList(context, this);
|
||||
Collections.sort(storageInfoList, Comparator.comparing(StorageInfo::getUri));
|
||||
|
||||
if (storageInfoList.size() > 0) {
|
||||
getPathsAndNamesForStorageInfoList(paths, pathNames, storageInfoList);
|
||||
} else {
|
||||
@ -103,24 +137,28 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
|
||||
device.sendPacket(np2);
|
||||
return true;
|
||||
}
|
||||
|
||||
removeChildren(storageInfoList);
|
||||
server.setSafRoots(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
|
||||
if (paths.size() == 1) {
|
||||
np2.set("path", paths.get(0));
|
||||
} else {
|
||||
np2.set("path", "/");
|
||||
}
|
||||
|
||||
if (paths.size() > 0) {
|
||||
np2.set("multiPaths", paths);
|
||||
@ -193,7 +231,7 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
|
||||
|
||||
@Override
|
||||
public boolean hasSettings() {
|
||||
return true;
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.R;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -227,7 +265,6 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
|
||||
@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();
|
||||
|
||||
|
@ -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;
|
||||
@ -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 {
|
||||
|
||||
@ -78,8 +84,12 @@ class SimpleSftpServer {
|
||||
}
|
||||
});
|
||||
|
||||
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()));
|
||||
|
||||
@ -89,9 +99,8 @@ class SimpleSftpServer {
|
||||
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;
|
||||
|
@ -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;
|
||||
|
||||
@ -34,6 +40,7 @@ public class StartActivityAlertDialogFragment extends AlertDialogFragment {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -44,8 +51,13 @@ public class StartActivityAlertDialogFragment extends AlertDialogFragment {
|
||||
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 {
|
||||
@ -67,6 +79,12 @@ public class StartActivityAlertDialogFragment extends AlertDialogFragment {
|
||||
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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user