mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 01:51:47 +00:00
first draft SFTP implementation
This commit is contained in:
parent
5ccf215a54
commit
13a7c1e5fe
@ -26,4 +26,5 @@ dependencies {
|
||||
compile "com.android.support:support-v4:19.0.+"
|
||||
compile "com.android.support:appcompat-v7:19.0.+"
|
||||
compile "org.apache.mina:mina-core:2.0.+"
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
}
|
||||
|
@ -25,37 +25,37 @@
|
||||
<output url="file://$MODULE_DIR$/build/classes/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/r/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/r/debug" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/debug" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/debug" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/debug" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/debug" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/r/test" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/test" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/test" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/test" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/test" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/test" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/assets" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/res" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/resources" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.git" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||
@ -67,7 +67,6 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
@ -77,6 +76,9 @@
|
||||
<orderEntry type="library" exported="" name="mina-core-2.0.7" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-19.0.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="ComAndroidSupportAppcompatV71901.aar" level="project" />
|
||||
<orderEntry type="library" name="bcprov-jdk16-145" level="project" />
|
||||
<orderEntry type="library" name="tomcat-apr-5.5.23" level="project" />
|
||||
<orderEntry type="library" name="sshd-core-0.7.0" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
|
BIN
libs/bcprov-jdk16-145.jar
Normal file
BIN
libs/bcprov-jdk16-145.jar
Normal file
Binary file not shown.
BIN
libs/sshd-core-0.7.0.jar
Normal file
BIN
libs/sshd-core-0.7.0.jar
Normal file
Binary file not shown.
BIN
libs/tomcat-apr-5.5.23.jar
Normal file
BIN
libs/tomcat-apr-5.5.23.jar
Normal file
Binary file not shown.
@ -26,6 +26,7 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" android:required="false" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
@ -33,6 +33,7 @@ public class NetworkPackage {
|
||||
public final static String PACKAGE_TYPE_PING = "kdeconnect.ping";
|
||||
public final static String PACKAGE_TYPE_TELEPHONY = "kdeconnect.telephony";
|
||||
public final static String PACKAGE_TYPE_BATTERY = "kdeconnect.battery";
|
||||
public final static String PACKAGE_TYPE_SFTP = "kdeconnect.sftp";
|
||||
public final static String PACKAGE_TYPE_NOTIFICATION = "kdeconnect.notification";
|
||||
public final static String PACKAGE_TYPE_CLIPBOARD = "kdeconnect.clipboard";
|
||||
public final static String PACKAGE_TYPE_MPRIS = "kdeconnect.mpris";
|
||||
|
@ -7,6 +7,7 @@ import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin;
|
||||
import org.kde.kdeconnect.Plugins.SftpPlugin.SftpPlugin;
|
||||
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin;
|
||||
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin;
|
||||
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationsPlugin;
|
||||
@ -67,6 +68,7 @@ public class PluginFactory {
|
||||
PluginFactory.registerPlugin(MprisPlugin.class);
|
||||
PluginFactory.registerPlugin(ClipboardPlugin.class);
|
||||
PluginFactory.registerPlugin(BatteryPlugin.class);
|
||||
PluginFactory.registerPlugin(SftpPlugin.class);
|
||||
PluginFactory.registerPlugin(NotificationsPlugin.class);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,271 @@
|
||||
package org.kde.kdeconnect.Plugins.SftpPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Environment;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.apache.sshd.SshServer;
|
||||
import org.apache.sshd.common.NamedFactory;
|
||||
import org.apache.sshd.server.Command;
|
||||
import org.apache.sshd.server.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.command.ScpCommandFactory;
|
||||
import org.apache.sshd.server.filesystem.NativeFileSystemFactory;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.apache.sshd.server.sftp.SftpSubsystem;
|
||||
import org.kde.kdeconnect.NetworkPackage;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class SftpPlugin extends Plugin {
|
||||
|
||||
private static final int PORT = 8022;
|
||||
private static final String USER = "kdeconnect";
|
||||
private static final String PASSWORD = "kdeconnectpassword";
|
||||
|
||||
private final SshServer sshd = SshServer.setUpDefaultServer();
|
||||
private boolean started = false;
|
||||
|
||||
public class SimplePasswordAuthenticator implements PasswordAuthenticator {
|
||||
|
||||
public void setUser(String user) {this.user = user;}
|
||||
|
||||
public void setPassword(String password) {this.password = password;}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String user, String password, ServerSession session) {
|
||||
return user.equals(this.user) && password.equals(this.password);
|
||||
}
|
||||
|
||||
private String user;
|
||||
private String password;
|
||||
}
|
||||
|
||||
|
||||
private final SimplePasswordAuthenticator passwordAuth = new SimplePasswordAuthenticator();
|
||||
//private final SimplePublicKeyAuthenticator publicKeyAuth = new SimplePublicKeyAuthenticator();
|
||||
|
||||
/*static {
|
||||
PluginFactory.registerPlugin(BatteryPlugin.class);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public String getPluginName() {
|
||||
return "plugin_sftp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return context.getResources().getString(R.string.pref_plugin_sftp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return context.getResources().getString(R.string.pref_plugin_sftp_desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getIcon() {
|
||||
return context.getResources().getDrawable(R.drawable.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent batteryIntent) {
|
||||
//
|
||||
// Intent batteryChargeIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||||
// int level = batteryChargeIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
||||
// int scale = batteryChargeIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
|
||||
// int currentCharge = level*100 / scale;
|
||||
// boolean isCharging = (0 != batteryChargeIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0));
|
||||
// boolean lowBattery = Intent.ACTION_BATTERY_LOW.equals(batteryIntent.getAction());
|
||||
// int thresholdEvent = lowBattery? THRESHOLD_EVENT_BATTERY_LOW : THRESHOLD_EVENT_NONE;
|
||||
//
|
||||
// if (lastInfo != null
|
||||
// && isCharging != lastInfo.getBoolean("isCharging")
|
||||
// && currentCharge != lastInfo.getInt("currentCharge")
|
||||
// && thresholdEvent != lastInfo.getInt("thresholdEvent")
|
||||
// ) {
|
||||
//
|
||||
// //Do not send again if nothing has changed
|
||||
// return;
|
||||
//
|
||||
// } else {
|
||||
//
|
||||
// NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_BATTERY);
|
||||
// np.set("currentCharge", currentCharge);
|
||||
// np.set("isCharging", isCharging);
|
||||
// np.set("thresholdEvent", thresholdEvent);
|
||||
// device.sendPackage(np);
|
||||
// lastInfo = np;
|
||||
//
|
||||
// }
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
passwordAuth.setUser(USER);
|
||||
passwordAuth.setPassword(PASSWORD);
|
||||
sshd.setPort(PORT);
|
||||
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("key.ser"));
|
||||
|
||||
sshd.setFileSystemFactory(new NativeFileSystemFactory());
|
||||
//sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i", "-l" }));
|
||||
sshd.setCommandFactory(new ScpCommandFactory());
|
||||
sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
|
||||
|
||||
sshd.setPasswordAuthenticator(passwordAuth);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
try {
|
||||
started = false;
|
||||
sshd.stop();
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public String getLocalIpAddress() {
|
||||
try {
|
||||
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
|
||||
NetworkInterface intf = en.nextElement();
|
||||
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
|
||||
InetAddress inetAddress = enumIpAddr.nextElement();
|
||||
if (!inetAddress.isLoopbackAddress()) {
|
||||
return inetAddress.getHostAddress().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPackageReceived(NetworkPackage np) {
|
||||
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_SFTP)) return false;
|
||||
//
|
||||
if (np.getBoolean("startBrowsing")) {
|
||||
try {
|
||||
if (!started) {
|
||||
sshd.start();
|
||||
started = true;
|
||||
}
|
||||
|
||||
NetworkPackage np2 = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SFTP);
|
||||
np2.set("ip", getLocalIpAddress());
|
||||
np2.set("port", PORT);
|
||||
np2.set("user", USER);
|
||||
np2.set("password", PASSWORD);
|
||||
np2.set("home", Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||
device.sendPackage(np2);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog getErrorDialog(Context baseContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button getInterfaceButton(Activity activity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// =======================================
|
||||
// Comented to be example of customization od SSHD
|
||||
// =======================================
|
||||
|
||||
// static class SecureFileSystemFactory implements FileSystemFactory {
|
||||
// //
|
||||
// public SecureFileSystemFactory() {
|
||||
// }
|
||||
// //
|
||||
// @Override
|
||||
// public FileSystemView createFileSystemView(final Session username) {
|
||||
// final String userName = username.getUsername();
|
||||
// final String home = "/mnt/sdcard/";
|
||||
// return new SecureFileSystemView(home, userName, false);
|
||||
// }
|
||||
// }
|
||||
|
||||
// static class SecureFileSystemView extends NativeFileSystemView {
|
||||
// // the first and the last character will always be '/'
|
||||
// // It is always with respect to the root directory.
|
||||
// private String currDir = "/";
|
||||
// private String rootDir = "/";
|
||||
// private String userName;
|
||||
// private boolean isReadOnly = true;
|
||||
// private boolean caseInsensitive = false;
|
||||
// //
|
||||
// public SecureFileSystemView(final String rootDir, final String userName, final boolean isReadOnly) {
|
||||
// super(userName);
|
||||
// this.rootDir = NativeSshFile.normalizeSeparateChar(rootDir);
|
||||
// this.userName = userName;
|
||||
// this.isReadOnly = isReadOnly;
|
||||
// }
|
||||
// //
|
||||
// @Override
|
||||
// public SshFile getFile(final String file) {
|
||||
// return getFile(currDir, file);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public SshFile getFile(final SshFile baseDir, final String file) {
|
||||
// return getFile(baseDir.getAbsolutePath(), file);
|
||||
// }
|
||||
//
|
||||
// //
|
||||
// protected SshFile getFile(final String dir, final String file) {
|
||||
// // get actual file object
|
||||
// String physicalName = NativeSshFile.getPhysicalName("/", dir, file, caseInsensitive);
|
||||
// File fileObj = new File(rootDir, physicalName); // chroot
|
||||
//
|
||||
// // strip the root directory and return
|
||||
// String userFileName = physicalName.substring("/".length() - 1);
|
||||
// return new SecureSshFile(userFileName, fileObj, userName, isReadOnly);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// static class SecureSshFile extends NativeSshFile {
|
||||
// final boolean isReadOnly;
|
||||
// //
|
||||
// public SecureSshFile(final String fileName, final File file, final String userName, final boolean isReadOnly) {
|
||||
// super(fileName, file, userName);
|
||||
// this.isReadOnly = isReadOnly;
|
||||
// }
|
||||
// //
|
||||
// public boolean isWritable() {
|
||||
// if (isReadOnly)
|
||||
// return false;
|
||||
// return super.isWritable();
|
||||
// }
|
||||
// }
|
||||
}
|
@ -4,6 +4,8 @@
|
||||
<string name="pref_plugin_telephony_desc">Отправлять уведомления о SMS и звонках</string>
|
||||
<string name="pref_plugin_battery">Состояние батареи</string>
|
||||
<string name="pref_plugin_battery_desc">Периодически сообщать о состоянии батареи</string>
|
||||
<string name="pref_plugin_sftp">Доступ по SFTP</string>
|
||||
<string name="pref_plugin_sftp_desc">Возможность доступа к файлам по SFTP</string>
|
||||
<string name="pref_plugin_clipboard">Синхронизация буфера обмена</string>
|
||||
<string name="pref_plugin_clipboard_desc">Сделать содержимое буфера обмена общим</string>
|
||||
<string name="pref_plugin_mpris">ДУ для мультимедия</string>
|
||||
|
@ -4,6 +4,8 @@
|
||||
<string name="pref_plugin_telephony_desc">Надсилання сповіщень щодо SMS та дзвінків</string>
|
||||
<string name="pref_plugin_battery">Звіт щодо заряду</string>
|
||||
<string name="pref_plugin_battery_desc">Періодична інформація щодо стану акумулятора</string>
|
||||
<string name="pref_plugin_sftp">Доступ по SFTP</string>
|
||||
<string name="pref_plugin_sftp_desc">Возможность доступа к файлам по SFTP</string>
|
||||
<string name="pref_plugin_clipboard">Синхронізація буфера</string>
|
||||
<string name="pref_plugin_clipboard_desc">Спільне використання буфера обміну даними</string>
|
||||
<string name="pref_plugin_mpris">Дистанційне керування відтворенням</string>
|
||||
|
@ -5,6 +5,8 @@
|
||||
<string name="pref_plugin_telephony_desc">Send notifications for SMS and calls</string>
|
||||
<string name="pref_plugin_battery">Battery report</string>
|
||||
<string name="pref_plugin_battery_desc">Periodically report battery status</string>
|
||||
<string name="pref_plugin_sftp">SFTP access</string>
|
||||
<string name="pref_plugin_sftp_desc">Possibility to browse filesystem through SFTP</string>
|
||||
<string name="pref_plugin_clipboard">Clipboard sync</string>
|
||||
<string name="pref_plugin_clipboard_desc">Share the clipboard content</string>
|
||||
<string name="pref_plugin_mpris">Multimedia remote controls</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user