├── src └── main │ ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ └── values │ │ ├── styles.xml │ │ └── strings.xml │ ├── aidl │ └── com │ │ └── sonelli │ │ └── juicessh │ │ └── pluginlibrary │ │ ├── parcelables │ │ ├── ParcelableLsEntry.aidl │ │ └── ParcelableSftpATTRS.aidl │ │ ├── listeners │ │ ├── IPingListener.aidl │ │ ├── ISessionFinishedListener.aidl │ │ ├── ISuccessListener.aidl │ │ ├── ISessionStartedListener.aidl │ │ ├── ICommandSuccessListener.aidl │ │ ├── IStringResultListener.aidl │ │ ├── ISessionExecuteListener.aidl │ │ ├── ILsEntryListener.aidl │ │ └── IStatResultListener.aidl │ │ ├── IConnectionPluginService.aidl │ │ ├── IPluginService.aidl │ │ └── ISftpPluginService.aidl │ ├── java │ └── com │ │ └── sonelli │ │ └── juicessh │ │ └── pluginlibrary │ │ ├── listeners │ │ ├── OnSessionFinishedListener.java │ │ ├── OnSuccessListener.java │ │ ├── OnClientStartedListener.java │ │ ├── OnSessionStartedListener.java │ │ ├── OnCommandSuccessListener.java │ │ └── OnSessionExecuteListener.java │ │ ├── exceptions │ │ └── ServiceNotConnectedException.java │ │ ├── parcelables │ │ ├── ParcelableLsEntry.java │ │ └── ParcelableSftpATTRS.java │ │ ├── SftpPluginClient.java │ │ ├── PluginContract.java │ │ ├── ConnectionPluginClient.java │ │ └── PluginClient.java │ └── AndroidManifest.xml ├── .gitignore └── LICENSE /src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sonelli/juicessh-pluginlibrary/HEAD/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sonelli/juicessh-pluginlibrary/HEAD/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sonelli/juicessh-pluginlibrary/HEAD/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/parcelables/ParcelableLsEntry.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.parcelables; 2 | 3 | parcelable ParcelableLsEntry; -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/parcelables/ParcelableSftpATTRS.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.parcelables; 2 | 3 | parcelable ParcelableSftpATTRS; -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/listeners/IPingListener.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | interface IPingListener { 4 | void pong(); 5 | } -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/listeners/ISessionFinishedListener.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | interface ISessionFinishedListener { 4 | void onSessionFinished(); 5 | } -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/listeners/ISuccessListener.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | interface ISuccessListener { 4 | void onSuccess(); 5 | void onFailure(String reason); 6 | } -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/listeners/OnSessionFinishedListener.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | public interface OnSessionFinishedListener { 4 | void onSessionFinished(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/listeners/ISessionStartedListener.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | interface ISessionStartedListener { 4 | void onSessionStarted(int sessionId, String sessionKey); 5 | } -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/listeners/OnSuccessListener.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | public interface OnSuccessListener { 4 | void onSuccess(); 5 | void onFailure(String reason); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/listeners/OnClientStartedListener.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | public interface OnClientStartedListener { 4 | void onClientStarted(); 5 | void onClientStopped(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/listeners/OnSessionStartedListener.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | public interface OnSessionStartedListener { 4 | void onSessionStarted(int sessionId, String sessionKey); 5 | void onSessionCancelled(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/listeners/ICommandSuccessListener.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | interface ICommandSuccessListener { 4 | void onSuccess(); 5 | void onCommandFailure(String reason); 6 | void onSessionFailure(String reason); 7 | } -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/listeners/IStringResultListener.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | interface IStringResultListener { 4 | void onSuccess(String result); 5 | void onCommandFailure(String reason); 6 | void onSessionFailure(String reason); 7 | } -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/listeners/ISessionExecuteListener.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | interface ISessionExecuteListener { 4 | void onCompleted(int returnCode); 5 | void onOutputLine(String line); 6 | void onError(int reason, String message); 7 | } -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/listeners/OnCommandSuccessListener.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | public interface OnCommandSuccessListener { 4 | void onSuccess(); 5 | void onCommandFailure(String reason); 6 | void onSessionFailure(String reason); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/listeners/OnSessionExecuteListener.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | public interface OnSessionExecuteListener { 4 | void onCompleted(int returnCode); 5 | void onOutputLine(String line); 6 | void onError(int reason, String Message); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/listeners/ILsEntryListener.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | import com.sonelli.juicessh.pluginlibrary.parcelables.ParcelableLsEntry; 4 | 5 | interface ILsEntryListener { 6 | void onSuccess(in ParcelableLsEntry[] lsEntry); 7 | void onCommandFailure(String reason); 8 | void onSessionFailure(String reason); 9 | } -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/listeners/IStatResultListener.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.listeners; 2 | 3 | import com.sonelli.juicessh.pluginlibrary.parcelables.ParcelableSftpATTRS; 4 | 5 | interface IStatResultListener { 6 | void onSuccess(in ParcelableSftpATTRS sftpAttrs); 7 | void onCommandFailure(String reason); 8 | void onSessionFailure(String reason); 9 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | /*/build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/exceptions/ServiceNotConnectedException.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.exceptions; 2 | 3 | /** 4 | * Thrown when a plugin attempts to communicate with the plugin service 5 | * but the plugin service isn't bound 6 | */ 7 | public class ServiceNotConnectedException extends Exception { 8 | public ServiceNotConnectedException() { 9 | super("Not connected to the JuiceSSH Plugin Service."); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/IConnectionPluginService.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary; 2 | 3 | // Declare any non-default types here with import statements 4 | import com.sonelli.juicessh.pluginlibrary.listeners.IPingListener; 5 | import com.sonelli.juicessh.pluginlibrary.listeners.ISessionFinishedListener; 6 | 7 | 8 | interface IConnectionPluginService { 9 | 10 | // AIDLs can pass the following basic primitives: 11 | // int, long, boolean, float, double and String. 12 | // Anything else will have to be parcelable. 13 | 14 | void ping(IPingListener listener); 15 | void connect(String id); 16 | void disconnect(int sessionId, String sessionKey); 17 | void addSessionFinishedListener(int sessionId, String sessionKey, ISessionFinishedListener listener); 18 | } -------------------------------------------------------------------------------- /src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | JuiceSSH-PluginLibrary 3 | JuiceSSH Version 4 | This JuiceSSH plugin requires that JuiceSSH %s (or newer) be installed. Please upgrade your copy of JuiceSSH. 5 | Install 6 | Cancel 7 | Security Error 8 | This plugin does not have the necessary permissions required to interact with JuiceSSH. This can happen if JuiceSSH is installed after the plugin. To resolve this you will need to reinstall the plugin.\n\nPress OK to launch the plugin Play Store page. 9 | OK 10 | Play Store not installed 11 | 12 | -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/IPluginService.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary; 2 | 3 | // Declare any non-default types here with import statements 4 | import com.sonelli.juicessh.pluginlibrary.listeners.IPingListener; 5 | import com.sonelli.juicessh.pluginlibrary.listeners.ISessionExecuteListener; 6 | import com.sonelli.juicessh.pluginlibrary.listeners.ISessionFinishedListener; 7 | 8 | 9 | 10 | interface IPluginService { 11 | 12 | // Legacy - See PluginService.java 13 | void ping(IPingListener listener); 14 | void connect(String id); 15 | void disconnect(int sessionId, String sessionKey); 16 | void addSessionFinishedListener(int sessionId, String sessionKey, ISessionFinishedListener listener); 17 | 18 | 19 | // AIDLs can pass the following basic primitives: 20 | // int, long, boolean, float, double and String. 21 | // Anything else will have to be parcelable. 22 | 23 | void attach(int sessionId, String sessionKey); 24 | void executeCommandOnSession(int sessionId, String sessionKey, String command, ISessionExecuteListener listener); 25 | void writeToSession(int sessionId, String sessionKey, String command); 26 | 27 | } -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/parcelables/ParcelableLsEntry.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.parcelables; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | //import com.jcraft.jsch.ChannelSftp; 7 | 8 | 9 | public class ParcelableLsEntry implements Parcelable{ 10 | 11 | private ParcelableSftpATTRS attrs; 12 | private String filename; 13 | private String longname; 14 | private String toString; 15 | 16 | /* public ParcelableLsEntry(ChannelSftp.LsEntry lsEntry) { 17 | attrs = new ParcelableSftpATTRS(lsEntry.getAttrs()); 18 | filename = lsEntry.getFilename(); 19 | longname = lsEntry.getLongname(); 20 | toString = lsEntry.toString(); 21 | } */ 22 | 23 | public ParcelableSftpATTRS getAttrs() { 24 | return attrs; 25 | } 26 | 27 | public String getFilename() { 28 | return filename; 29 | } 30 | 31 | public String getLongname() { 32 | return longname; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return toString; 38 | } 39 | 40 | protected ParcelableLsEntry(Parcel in) { 41 | attrs = in.readParcelable(ParcelableSftpATTRS.class.getClassLoader()); 42 | filename = in.readString(); 43 | longname = in.readString(); 44 | toString = in.readString(); 45 | } 46 | 47 | @Override 48 | public int describeContents() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public void writeToParcel(Parcel dest, int flags) { 54 | dest.writeParcelable(attrs, flags); 55 | dest.writeString(filename); 56 | dest.writeString(longname); 57 | dest.writeString(toString); 58 | } 59 | 60 | @SuppressWarnings("unused") 61 | public static final Creator CREATOR = new Creator() { 62 | @Override 63 | public ParcelableLsEntry createFromParcel(Parcel in) { 64 | return new ParcelableLsEntry(in); 65 | } 66 | 67 | @Override 68 | public ParcelableLsEntry[] newArray(int size) { 69 | return new ParcelableLsEntry[size]; 70 | } 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /src/main/aidl/com/sonelli/juicessh/pluginlibrary/ISftpPluginService.aidl: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary; 2 | 3 | // Declare any non-default types here with import statements 4 | import com.sonelli.juicessh.pluginlibrary.listeners.ILsEntryListener; 5 | import com.sonelli.juicessh.pluginlibrary.listeners.ICommandSuccessListener; 6 | import com.sonelli.juicessh.pluginlibrary.listeners.IStatResultListener; 7 | import com.sonelli.juicessh.pluginlibrary.listeners.IStringResultListener; 8 | 9 | 10 | interface ISftpPluginService { 11 | 12 | // AIDLs can pass the following basic primitives: 13 | // int, long, boolean, float, double and String. 14 | // Anything else will have to be parcelable. 15 | 16 | void cd(int sessionId, String sessionKey, String path, ICommandSuccessListener listener); 17 | void chgrp(int sessionId, String sessionKey, int gid, String path, ICommandSuccessListener listener); 18 | void chmod(int sessionId, String sessionKey, int permissions, String path, ICommandSuccessListener listener); 19 | void chown(int sessionId, String sessionKey, int uid, String path, ICommandSuccessListener listener); 20 | void get(int sessionId, String sessionKey, String src, ICommandSuccessListener listener); 21 | void lcd(int sessionId, String sessionKey, String path, ICommandSuccessListener listener); 22 | void lpwd(int sessionId, String sessionKey, IStringResultListener listener); 23 | void ls(int sessionId, String sessionKey, String path, ILsEntryListener listener); 24 | void lstat(int sessionId, String sessionKey, String path, IStatResultListener listener); 25 | //void put(int sessionId, String sessionKey, Parcel contentUriParcel, String dst); 26 | void pwd(int sessionId, String sessionKey, IStringResultListener listener); 27 | void readlink(int sessionId, String sessionKey, String path, IStringResultListener listener); 28 | void realpath(int sessionId, String sessionKey, String path, IStringResultListener listener); 29 | void rename(int sessionId, String sessionKey, String oldpath, String newpath, ICommandSuccessListener listener); 30 | void rm(int sessionId, String sessionKey, String path, ICommandSuccessListener listener); 31 | void rmdir(int sessionId, String sessionKey, String path, ICommandSuccessListener listener); 32 | void stat(int sessionId, String sessionKey, String path, IStatResultListener listener); 33 | } -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/parcelables/ParcelableSftpATTRS.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary.parcelables; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | //import com.jcraft.jsch.SftpATTRS; 7 | 8 | 9 | public final class ParcelableSftpATTRS implements Parcelable { 10 | 11 | private String getPermissionsString; 12 | private String getAtimeString; 13 | private String getMtimeString; 14 | private boolean isReg; 15 | private boolean isDir; 16 | private boolean isChr; 17 | private boolean isBlk; 18 | private boolean isFifo; 19 | private boolean isLink; 20 | private boolean isSock; 21 | private int getFlags; 22 | private long getSize; 23 | private int getUId; 24 | private int getGId; 25 | private int getPermissions; 26 | private int getATime; 27 | private int getMTime; 28 | private String[] getExtended; 29 | private String toString; 30 | 31 | /* public ParcelableSftpATTRS(SftpATTRS attrs) { 32 | getPermissionsString = attrs.getPermissionsString(); 33 | getAtimeString = attrs.getAtimeString(); 34 | getMtimeString = attrs.getMtimeString(); 35 | isReg = attrs.isReg(); 36 | isDir = attrs.isDir(); 37 | isChr = attrs.isChr(); 38 | isBlk = attrs.isBlk(); 39 | isFifo = attrs.isFifo(); 40 | isLink = attrs.isLink(); 41 | isSock = attrs.isSock(); 42 | getFlags = attrs.getFlags(); 43 | getSize = attrs.getSize(); 44 | getUId = attrs.getUId(); 45 | getGId = attrs.getGId(); 46 | getPermissions = attrs.getPermissions(); 47 | getATime = attrs.getATime(); 48 | getMTime = attrs.getMTime(); 49 | getExtended = attrs.getExtended(); 50 | toString = attrs.toString(); 51 | } */ 52 | 53 | public String getPermissionsString() { 54 | return getPermissionsString; 55 | } 56 | 57 | public String getAtimeString() { 58 | return getAtimeString; 59 | } 60 | 61 | public String getMtimeString() { 62 | return getMtimeString; 63 | } 64 | 65 | public boolean isReg() { 66 | return isReg; 67 | } 68 | 69 | public boolean isDir() { 70 | return isDir; 71 | } 72 | 73 | public boolean isChr() { 74 | return isChr; 75 | } 76 | 77 | public boolean isBlk() { 78 | return isBlk; 79 | } 80 | 81 | public boolean isFifo() { 82 | return isFifo; 83 | } 84 | 85 | public boolean isLink() { 86 | return isLink; 87 | } 88 | 89 | public boolean isSock() { 90 | return isSock; 91 | } 92 | 93 | public int getFlags() { 94 | return getFlags; 95 | } 96 | 97 | public long getSize() { 98 | return getSize; 99 | } 100 | 101 | public int getUId() { 102 | return getUId; 103 | } 104 | 105 | public int getGId() { 106 | return getGId; 107 | } 108 | 109 | public int getPermissions() { 110 | return getPermissions; 111 | } 112 | 113 | public int getATime() { 114 | return getATime; 115 | } 116 | 117 | public int getMTime() { 118 | return getMTime; 119 | } 120 | 121 | public String[] getExtended() { 122 | return getExtended; 123 | } 124 | 125 | @Override 126 | public String toString() { 127 | return toString; 128 | } 129 | 130 | protected ParcelableSftpATTRS(Parcel in) { 131 | getPermissionsString = in.readString(); 132 | getAtimeString = in.readString(); 133 | getMtimeString = in.readString(); 134 | isReg = in.readByte() != 0x00; 135 | isDir = in.readByte() != 0x00; 136 | isChr = in.readByte() != 0x00; 137 | isBlk = in.readByte() != 0x00; 138 | isFifo = in.readByte() != 0x00; 139 | isLink = in.readByte() != 0x00; 140 | isSock = in.readByte() != 0x00; 141 | getFlags = in.readInt(); 142 | getSize = in.readLong(); 143 | getUId = in.readInt(); 144 | getGId = in.readInt(); 145 | getPermissions = in.readInt(); 146 | getATime = in.readInt(); 147 | getMTime = in.readInt(); 148 | getExtended = in.createStringArray(); 149 | toString = in.readString(); 150 | } 151 | 152 | @Override 153 | public int describeContents() { 154 | return 0; 155 | } 156 | 157 | @Override 158 | public void writeToParcel(Parcel dest, int flags) { 159 | dest.writeString(getPermissionsString); 160 | dest.writeString(getAtimeString); 161 | dest.writeString(getMtimeString); 162 | dest.writeByte((byte) (isReg ? 0x01 : 0x00)); 163 | dest.writeByte((byte) (isDir ? 0x01 : 0x00)); 164 | dest.writeByte((byte) (isChr ? 0x01 : 0x00)); 165 | dest.writeByte((byte) (isBlk ? 0x01 : 0x00)); 166 | dest.writeByte((byte) (isFifo ? 0x01 : 0x00)); 167 | dest.writeByte((byte) (isLink ? 0x01 : 0x00)); 168 | dest.writeByte((byte) (isSock ? 0x01 : 0x00)); 169 | dest.writeInt(getFlags); 170 | dest.writeLong(getSize); 171 | dest.writeInt(getUId); 172 | dest.writeInt(getGId); 173 | dest.writeInt(getPermissions); 174 | dest.writeInt(getATime); 175 | dest.writeInt(getMTime); 176 | dest.writeStringArray(getExtended); 177 | dest.writeString(toString); 178 | } 179 | 180 | @SuppressWarnings("unused") 181 | public static final Creator CREATOR = new Creator() { 182 | @Override 183 | public ParcelableSftpATTRS createFromParcel(Parcel in) { 184 | return new ParcelableSftpATTRS(in); 185 | } 186 | 187 | @Override 188 | public ParcelableSftpATTRS[] newArray(int size) { 189 | return new ParcelableSftpATTRS[size]; 190 | } 191 | }; 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/SftpPluginClient.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary; 2 | 3 | 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.ServiceConnection; 8 | import android.content.pm.ResolveInfo; 9 | import android.os.Handler; 10 | import android.os.IBinder; 11 | import android.os.RemoteException; 12 | import android.util.Log; 13 | 14 | import com.sonelli.juicessh.pluginlibrary.exceptions.ServiceNotConnectedException; 15 | import com.sonelli.juicessh.pluginlibrary.listeners.ICommandSuccessListener; 16 | import com.sonelli.juicessh.pluginlibrary.listeners.ILsEntryListener; 17 | import com.sonelli.juicessh.pluginlibrary.listeners.IStatResultListener; 18 | import com.sonelli.juicessh.pluginlibrary.listeners.IStringResultListener; 19 | import com.sonelli.juicessh.pluginlibrary.listeners.OnClientStartedListener; 20 | 21 | 22 | public class SftpPluginClient extends ConnectionPluginClient { 23 | 24 | private static final String TAG = "SftpPluginService"; 25 | 26 | private ServiceConnection connection; 27 | private boolean isConnected = false; 28 | private ISftpPluginService service; 29 | private Handler handler = new Handler(); 30 | 31 | 32 | @Override 33 | protected void add_connect_intent_extras(Intent intent) { 34 | intent.putExtra("sftp", true); 35 | } 36 | 37 | /** 38 | * Start SftpPluginClient and connect to the JuiceSSH Sftp Plugin Service. 39 | * This should be run in Activity.onStart(); 40 | * @param context 41 | * @param listener 42 | */ 43 | @Override 44 | public void start(final Context context, final OnClientStartedListener listener){ 45 | // Dual service listener only triggers when both the connection plugin service and 46 | // this plugin service (dis)connects 47 | final OnClientStartedListener dualServiceListener = new OnClientStartedListener() { 48 | @Override 49 | public void onClientStarted() { 50 | if (isConnected()) 51 | listener.onClientStarted(); 52 | } 53 | 54 | @Override 55 | public void onClientStopped() { 56 | if (!isConnected()) 57 | listener.onClientStopped(); 58 | } 59 | }; 60 | 61 | // Connect the Connection Plugin Service 62 | super.start(context, dualServiceListener); 63 | 64 | this.connection = new ServiceConnection(){ 65 | @Override 66 | public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 67 | Log.d(TAG, "Bound to the JuiceSSH SFTP plugin service"); 68 | isConnected = true; 69 | service = ISftpPluginService.Stub.asInterface(iBinder); 70 | handler.post(new Runnable() { 71 | @Override 72 | public void run() { 73 | dualServiceListener.onClientStarted(); 74 | } 75 | }); 76 | } 77 | 78 | @Override 79 | public void onServiceDisconnected(ComponentName componentName) { 80 | Log.d(TAG, "Unbound from the JuiceSSH SFTP plugin service"); 81 | isConnected = false; 82 | service = null; 83 | handler.post(new Runnable() { 84 | @Override 85 | public void run() { 86 | dualServiceListener.onClientStopped(); 87 | } 88 | }); 89 | } 90 | }; 91 | 92 | // Attempt to lookup the explicit service information 93 | ResolveInfo resolve = context.getPackageManager().resolveService( 94 | new Intent("com.sonelli.juicessh.sftppluginservice"), 95 | 0 96 | ); 97 | 98 | if (resolve == null) { 99 | Log.e(TAG, "Could not look up explicit intent for com.sonelli.juicessh.sftppluginservice service"); 100 | return; 101 | } 102 | 103 | // Create an explicit intent for the service 104 | Intent serviceIntent = new Intent(); 105 | serviceIntent.setComponent(new ComponentName(resolve.serviceInfo.packageName, resolve.serviceInfo.name)); 106 | context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE); 107 | 108 | } 109 | 110 | /** 111 | * Stop SftpPluginClient and unbindfrom the JuiceSSH Sftp Plugin Service. 112 | * This should be run in Activity.onStart(); 113 | * @param context 114 | */ 115 | @Override 116 | public void stop(Context context) { 117 | super.stop(context); 118 | context.unbindService(connection); 119 | } 120 | 121 | @Override 122 | protected boolean isConnected() { 123 | return super.isConnected() && isConnected; 124 | } 125 | 126 | public void cd(int session_id, String session_key, String path, ICommandSuccessListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 127 | if(!isConnected()) 128 | throw new ServiceNotConnectedException(); 129 | service.cd(session_id, session_key, path, listener); 130 | } 131 | 132 | public void chgrp(int sessionId, String sessionKey, int gid, String path, ICommandSuccessListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 133 | if(!isConnected()) 134 | throw new ServiceNotConnectedException(); 135 | service.chgrp(sessionId, sessionKey, gid, path, listener); 136 | } 137 | 138 | public void chmod(int sessionId, String sessionKey, int permissions, String path, ICommandSuccessListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 139 | if(!isConnected()) 140 | throw new ServiceNotConnectedException(); 141 | service.chmod(sessionId, sessionKey, permissions, path, listener); 142 | } 143 | 144 | public void chown(int sessionId, String sessionKey, int uid, String path, ICommandSuccessListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 145 | if(!isConnected()) 146 | throw new ServiceNotConnectedException(); 147 | service.chown(sessionId, sessionKey, uid, path, listener); 148 | } 149 | 150 | public void get(int sessionId, String sessionKey, String src, ICommandSuccessListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 151 | if (!isConnected()) 152 | throw new ServiceNotConnectedException(); 153 | service.get(sessionId, sessionKey, src, listener); 154 | } 155 | 156 | public void lcd(int sessionId, String sessionKey, String path, ICommandSuccessListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 157 | if(!isConnected()) 158 | throw new ServiceNotConnectedException(); 159 | service.lcd(sessionId, sessionKey, path, listener); 160 | } 161 | 162 | public void lpwd(int sessionId, String sessionKey, IStringResultListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 163 | if(!isConnected()) 164 | throw new ServiceNotConnectedException(); 165 | service.lpwd(sessionId, sessionKey, listener); 166 | } 167 | 168 | public void ls(int sessionId, String sessionKey, String path, ILsEntryListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 169 | if(!isConnected()) 170 | throw new ServiceNotConnectedException(); 171 | service.ls(sessionId, sessionKey, path, listener); 172 | } 173 | 174 | public void lstat(int sessionId, String sessionKey, String path, IStatResultListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 175 | if(!isConnected()) 176 | throw new ServiceNotConnectedException(); 177 | service.lstat(sessionId, sessionKey, path, listener); 178 | } 179 | 180 | public void pwd(int sessionId, String sessionKey, IStringResultListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 181 | if(!isConnected()) 182 | throw new ServiceNotConnectedException(); 183 | service.pwd(sessionId, sessionKey, listener); 184 | } 185 | 186 | public void readlink(int sessionId, String sessionKey, String path, IStringResultListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 187 | if(!isConnected()) 188 | throw new ServiceNotConnectedException(); 189 | service.readlink(sessionId, sessionKey, path, listener); 190 | } 191 | 192 | public void realpath(int sessionId, String sessionKey, String path, IStringResultListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 193 | if(!isConnected()) 194 | throw new ServiceNotConnectedException(); 195 | service.realpath(sessionId, sessionKey, path, listener); 196 | } 197 | 198 | public void rename(int sessionId, String sessionKey, String oldpath, String newpath, ICommandSuccessListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 199 | if(!isConnected()) 200 | throw new ServiceNotConnectedException(); 201 | service.rename(sessionId, sessionKey, oldpath, newpath, listener); 202 | } 203 | 204 | public void rm(int sessionId, String sessionKey, String path, ICommandSuccessListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 205 | if(!isConnected()) 206 | throw new ServiceNotConnectedException(); 207 | service.rm(sessionId, sessionKey, path, listener); 208 | } 209 | 210 | public void rmdir(int sessionId, String sessionKey, String path, ICommandSuccessListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 211 | if(!isConnected()) 212 | throw new ServiceNotConnectedException(); 213 | service.rmdir(sessionId, sessionKey, path, listener); 214 | } 215 | 216 | public void stat(int sessionId, String sessionKey, String path, IStatResultListener.Stub listener) throws ServiceNotConnectedException,RemoteException { 217 | if(!isConnected()) 218 | throw new ServiceNotConnectedException(); 219 | service.stat(sessionId, sessionKey, path, listener); 220 | } 221 | 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/PluginContract.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary; 2 | 3 | import android.content.ContentResolver; 4 | import android.net.Uri; 5 | import android.provider.BaseColumns; 6 | 7 | public class PluginContract { 8 | 9 | public static final String AUTHORITY = "com.sonelli.juicessh.api.v1"; 10 | 11 | public static final String PERMISSION_OPEN_SESSIONS = "com.sonelli.juicessh.api.v1.permission.OPEN_SESSIONS"; 12 | 13 | /** 14 | * Published API for Connections 15 | */ 16 | public static final class Connections implements BaseColumns { 17 | 18 | public final static Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/connections"); 19 | public final static String PERMISSION_READ = "com.sonelli.juicessh.api.v1.permission.READ_CONNECTIONS"; 20 | public final static String PERMISSION_WRITE = "com.sonelli.juicessh.api.v1.permission.WRITE_CONNECTIONS"; 21 | 22 | public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/com.sonelli.juicessh.models.connection"; 23 | public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/com.sonelli.juicessh.models.connection"; 24 | public static final String SORT_ORDER_DEFAULT = "name COLLATE NOCASE ASC"; 25 | 26 | public final static int TYPE_SSH = 0; 27 | public final static int TYPE_MOSH = 1; 28 | public final static int TYPE_LOCAL = 2; 29 | public final static int TYPE_TELNET = 3; 30 | 31 | public static final String TABLE_NAME = "connection"; 32 | private static final String _ID = "_id"; 33 | public static final String COLUMN_ID = "id"; 34 | public static final String COLUMN_MODIFIED = "modified"; 35 | public static final String COLUMN_NAME = "name"; 36 | public static final String COLUMN_ADDRESS = "address"; 37 | public static final String COLUMN_PORT = "port"; 38 | public static final String COLUMN_NICKNAME = "nickname"; 39 | public static final String COLUMN_TYPE = "type"; 40 | 41 | public static final String[] PROJECTION = { 42 | COLUMN_ID, 43 | String.format("rowid AS %s", _ID), 44 | COLUMN_MODIFIED, 45 | String.format("COALESCE(NULLIF(nickname,''), address) AS %s", COLUMN_NAME), 46 | COLUMN_ADDRESS, 47 | COLUMN_PORT, 48 | COLUMN_NICKNAME, 49 | COLUMN_TYPE 50 | }; 51 | 52 | } 53 | 54 | /** 55 | * Published API for Snippets 56 | */ 57 | public static final class Snippets implements BaseColumns { 58 | 59 | public final static Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/snippets"); 60 | public final static String PERMISSION_READ = "com.sonelli.juicessh.api.v1.permission.READ_SNIPPETS"; 61 | public final static String PERMISSION_WRITE = "com.sonelli.juicessh.api.v1.permission.WRITE_SNIPPETS"; 62 | 63 | public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/com.sonelli.juicessh.models.snippet"; 64 | public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/com.sonelli.juicessh.models.snippet"; 65 | public static final String SORT_ORDER_DEFAULT = "name COLLATE NOCASE ASC"; 66 | 67 | public static final String TABLE_NAME = "snippet"; 68 | private static final String _ID = "_id"; 69 | public static final String COLUMN_ID = "id"; 70 | public static final String COLUMN_MODIFIED = "modified"; 71 | public static final String COLUMN_NAME = "name"; 72 | public static final String COLUMN_CONTENT = "content"; 73 | 74 | public static final String[] PROJECTION = { 75 | COLUMN_ID, 76 | String.format("rowid AS %s", _ID), 77 | COLUMN_MODIFIED, 78 | COLUMN_NAME, 79 | COLUMN_CONTENT 80 | }; 81 | 82 | } 83 | 84 | /** 85 | * Published API for Port Forwards 86 | */ 87 | public static final class PortForwards implements BaseColumns { 88 | 89 | public final static Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/portforwards"); 90 | public final static String PERMISSION_READ = "com.sonelli.juicessh.api.v1.permission.READ_PORT_FORWARDS"; 91 | public final static String PERMISSION_WRITE = "com.sonelli.juicessh.api.v1.permission.WRITE_PORT_FORWARDS"; 92 | 93 | public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/com.sonelli.juicessh.models.portforward"; 94 | public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/com.sonelli.juicessh.models.portforward"; 95 | public static final String SORT_ORDER_DEFAULT = "name ASC"; 96 | 97 | final public static int MODE_LOCAL = 0; 98 | final public static int MODE_REMOTE = 1; 99 | final public static int MODE_SOCKS = 2; 100 | 101 | public static final String TABLE_NAME = "portforward"; 102 | private static final String _ID = "_id"; 103 | 104 | public static final String COLUMN_ID = "id"; 105 | public static final String COLUMN_MODIFIED = "modified"; 106 | public static final String COLUMN_NAME = "name"; 107 | public static final String COLUMN_MODE = "mode"; 108 | public static final String COLUMN_CONNECTION_ID = "connection_id"; 109 | public static final String COLUMN_HOST = "host"; 110 | public static final String COLUMN_LOCAL_PORT = "localPort"; 111 | public static final String COLUMN_REMOTE_PORT = "remotePort"; 112 | public static final String COLUMN_OPEN_IN_BROWSER = "openInBrowser"; 113 | 114 | public static final String[] PROJECTION = { 115 | COLUMN_ID, 116 | String.format("rowid AS %s", _ID), 117 | COLUMN_MODIFIED, 118 | COLUMN_NAME, 119 | COLUMN_MODE, 120 | COLUMN_CONNECTION_ID, 121 | COLUMN_HOST, 122 | COLUMN_LOCAL_PORT, 123 | COLUMN_REMOTE_PORT, 124 | COLUMN_OPEN_IN_BROWSER 125 | }; 126 | 127 | } 128 | 129 | /** 130 | * Published API for Connection Groups 131 | */ 132 | public static final class ConnectionGroups implements BaseColumns { 133 | 134 | public final static Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/connectiongroups"); 135 | public final static String PERMISSION_READ = "com.sonelli.juicessh.api.v1.permission.READ_CONNECTION_GROUPS"; 136 | public final static String PERMISSION_WRITE = "com.sonelli.juicessh.api.v1.permission.WRITE_CONNECTION_GROUPS"; 137 | 138 | public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/com.sonelli.juicessh.models.connectiongroup"; 139 | public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/com.sonelli.juicessh.models.connectiongroup"; 140 | public static final String SORT_ORDER_DEFAULT = "name COLLATE NOCASE ASC"; 141 | 142 | public static final String TABLE_NAME = "connectiongroup"; 143 | private static final String _ID = "_id"; 144 | public static final String COLUMN_ID = "id"; 145 | public static final String COLUMN_MODIFIED = "modified"; 146 | public static final String COLUMN_NAME = "name"; 147 | 148 | public static final String[] PROJECTION = { 149 | COLUMN_ID, 150 | String.format("rowid AS %s", _ID), 151 | COLUMN_MODIFIED, 152 | COLUMN_NAME, 153 | }; 154 | 155 | } 156 | 157 | /** 158 | * Published API for Connection Group Memberships 159 | */ 160 | public static final class ConnectionGroupMemberships implements BaseColumns { 161 | 162 | public final static Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/connectiongroupmemberships"); 163 | public final static String PERMISSION_READ = "com.sonelli.juicessh.api.v1.permission.READ_CONNECTION_GROUPS"; 164 | public final static String PERMISSION_WRITE = "com.sonelli.juicessh.api.v1.permission.WRITE_CONNECTION_GROUPS"; 165 | 166 | public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/com.sonelli.juicessh.models.connectiongroupmembership"; 167 | public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/com.sonelli.juicessh.models.connectiongroupmembership"; 168 | public static final String SORT_ORDER_DEFAULT = "id ASC"; 169 | 170 | public static final String TABLE_NAME = "connectiongroupmembership"; 171 | private static final String _ID = "_id"; 172 | public static final String COLUMN_ID = "id"; 173 | public static final String COLUMN_MODIFIED = "modified"; 174 | public static final String COLUMN_GROUP_ID = "group_id"; 175 | public static final String COLUMN_CONNECTION_ID = "connection_id"; 176 | 177 | public static final String[] PROJECTION = { 178 | COLUMN_ID, 179 | String.format("rowid AS %s", _ID), 180 | COLUMN_MODIFIED, 181 | COLUMN_GROUP_ID, 182 | COLUMN_CONNECTION_ID 183 | }; 184 | 185 | } 186 | 187 | /** 188 | * Published API for Plugin Audit Log (read-only) 189 | */ 190 | public static final class PluginLog implements BaseColumns { 191 | 192 | public final static Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/pluginlog"); 193 | public final static String PERMISSION_READ = "com.sonelli.juicessh.api.v1.permission.READ_PLUGIN_LOG"; 194 | 195 | public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/com.sonelli.juicessh.models.pluginlog"; 196 | public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/com.sonelli.juicessh.models.pluginlog"; 197 | public static final String SORT_ORDER_DEFAULT = "modified DESC"; 198 | 199 | public static final String TABLE_NAME = "plugin_log"; 200 | private static final String _ID = "_id"; 201 | public static final String COLUMN_ID = "id"; 202 | public static final String COLUMN_MODIFIED = "modified"; 203 | public static final String COLUMN_PACKAGE_NAME = "packageName"; 204 | public static final String COLUMN_MESSAGE = "message"; 205 | 206 | public static final String[] PROJECTION = { 207 | COLUMN_ID, 208 | String.format("rowid AS %s", _ID), 209 | COLUMN_MODIFIED, 210 | COLUMN_PACKAGE_NAME, 211 | COLUMN_MESSAGE 212 | }; 213 | 214 | } 215 | 216 | 217 | } 218 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/ConnectionPluginClient.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary; 2 | 3 | 4 | import android.app.Activity; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.ServiceConnection; 9 | import android.content.pm.ApplicationInfo; 10 | import android.content.pm.PackageManager; 11 | import android.content.pm.ResolveInfo; 12 | import android.net.Uri; 13 | import android.os.Handler; 14 | import android.os.IBinder; 15 | import android.os.RemoteException; 16 | import android.support.v4.app.Fragment; 17 | import android.util.Log; 18 | import android.util.SparseArray; 19 | 20 | import com.sonelli.juicessh.pluginlibrary.exceptions.ServiceNotConnectedException; 21 | import com.sonelli.juicessh.pluginlibrary.listeners.IPingListener; 22 | import com.sonelli.juicessh.pluginlibrary.listeners.ISessionFinishedListener; 23 | import com.sonelli.juicessh.pluginlibrary.listeners.OnClientStartedListener; 24 | import com.sonelli.juicessh.pluginlibrary.listeners.OnSessionFinishedListener; 25 | import com.sonelli.juicessh.pluginlibrary.listeners.OnSessionStartedListener; 26 | 27 | import java.util.UUID; 28 | 29 | 30 | abstract class ConnectionPluginClient { 31 | 32 | private static final String TAG = "ConnectionPluginService"; 33 | 34 | protected ServiceConnection connection; 35 | protected boolean isConnected = false; 36 | protected IConnectionPluginService service; 37 | protected SparseArray sessionStartedListeners = new SparseArray(); 38 | protected final Handler handler = new Handler(); 39 | 40 | protected boolean isConnected() { 41 | return isConnected; 42 | } 43 | 44 | /** 45 | * Sends a PING to the JuiceSSH Plugin Service. 46 | * Internal use only. 47 | * @throws com.sonelli.juicessh.pluginlibrary.exceptions.ServiceNotConnectedException 48 | */ 49 | public void ping() throws ServiceNotConnectedException { 50 | if(!isConnected()) 51 | throw new ServiceNotConnectedException(); 52 | 53 | try { 54 | service.ping(new IPingListener.Stub() { 55 | @Override 56 | public void pong() throws RemoteException { 57 | Log.d(TAG, "JuiceSSH Plugin: Got PONG"); 58 | } 59 | }); 60 | } catch (RemoteException e){ 61 | throw new ServiceNotConnectedException(); 62 | } 63 | } 64 | 65 | /** 66 | * Launches a JuiceSSH connection either in the background or foreground. 67 | * @param activity Activity context required for launching connection (may show authentication dialogs) 68 | * @param id The UUID id of the connection in the JuiceSSH DB 69 | * @param listener Callback for connected/disconnected events 70 | * @throws ServiceNotConnectedException 71 | */ 72 | public void connect(Activity activity, UUID id, OnSessionStartedListener listener, int requestId) throws ServiceNotConnectedException { 73 | 74 | if(!isConnected()) 75 | throw new ServiceNotConnectedException(); 76 | 77 | sessionStartedListeners.put(requestId, listener); 78 | 79 | try { 80 | // This is just for logging/auditing 81 | service.connect(id.toString()); 82 | } catch (RemoteException e){ 83 | throw new ServiceNotConnectedException(); 84 | } 85 | 86 | // Always start sessions in the background initially so that we can get them to return 87 | // instantly with a sessionId & sessionKey. If a foreground session has been requested 88 | // we can always do an resume/attach later. 89 | Intent intent = new Intent(Intent.ACTION_VIEW); 90 | //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); 91 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 92 | intent.setData(Uri.parse("ssh://" + id)); 93 | add_connect_intent_extras(intent); 94 | activity.startActivityForResult(intent, requestId); 95 | 96 | } 97 | 98 | /** 99 | * Launches a JuiceSSH connection either in the background or foreground. 100 | * @param fragment fragment context required for launching connection (may show authentication dialogs) 101 | * @param id The UUID id of the connection in the JuiceSSH DB 102 | * @param listener Callback for connected/disconnected events 103 | * @throws ServiceNotConnectedException 104 | */ 105 | public void connect(Fragment fragment, UUID id, OnSessionStartedListener listener, int requestId) throws ServiceNotConnectedException { 106 | 107 | if(!isConnected()) 108 | throw new ServiceNotConnectedException(); 109 | 110 | sessionStartedListeners.put(requestId, listener); 111 | 112 | try { 113 | // This is just for logging/auditing 114 | service.connect(id.toString()); 115 | } catch (RemoteException e){ 116 | throw new ServiceNotConnectedException(); 117 | } 118 | 119 | // Always start sessions in the background initially so that we can get them to return 120 | // instantly with a sessionId & sessionKey. If a foreground session has been requested 121 | // we can always do an resume/attach later. 122 | Intent intent = new Intent(Intent.ACTION_VIEW); 123 | //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); 124 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 125 | intent.setData(Uri.parse("ssh://" + id)); 126 | add_connect_intent_extras(intent); 127 | fragment.startActivityForResult(intent, requestId); 128 | 129 | } 130 | 131 | protected abstract void add_connect_intent_extras(Intent intent); 132 | 133 | /** 134 | * Disconnects a previously started session. 135 | * @param sessionId The integer session ID returned when the session was started 136 | * @param sessionKey The session key returned when the session was started 137 | * @throws ServiceNotConnectedException 138 | */ 139 | public void disconnect(final int sessionId, String sessionKey) throws ServiceNotConnectedException { 140 | if(!isConnected()) 141 | throw new ServiceNotConnectedException(); 142 | 143 | try { 144 | service.disconnect(sessionId, sessionKey); 145 | } catch (RemoteException e){ 146 | throw new ServiceNotConnectedException(); 147 | } 148 | } 149 | 150 | /** 151 | * Start PluginClient and connect to the JuiceSSH Plugin Service. 152 | * This should be run in your Activity.onStart(); 153 | * @param context 154 | * @param listener 155 | */ 156 | public void start(final Context context, final OnClientStartedListener listener){ 157 | this.connection = new ServiceConnection(){ 158 | 159 | @Override 160 | public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 161 | Log.d(TAG, "Bound to the JuiceSSH connection plugin service"); 162 | isConnected = true; 163 | service = IConnectionPluginService.Stub.asInterface(iBinder); 164 | handler.post(new Runnable() { 165 | @Override 166 | public void run() { 167 | listener.onClientStarted(); 168 | } 169 | }); 170 | } 171 | 172 | @Override 173 | public void onServiceDisconnected(ComponentName componentName) { 174 | Log.d(TAG, "Unbound from the JuiceSSH connection plugin service"); 175 | isConnected = false; 176 | service = null; 177 | handler.post(new Runnable() { 178 | @Override 179 | public void run() { 180 | listener.onClientStopped(); 181 | } 182 | }); 183 | } 184 | }; 185 | 186 | // Attempt to lookup the explicit service information 187 | ResolveInfo resolve = context.getPackageManager().resolveService( 188 | new Intent("com.sonelli.juicessh.connectionpluginservice"), 189 | 0 190 | ); 191 | 192 | if (resolve == null) { 193 | Log.e(TAG, "Could not look up explicit intent for com.sonelli.juicessh.connectionpluginservice service"); 194 | return; 195 | } 196 | 197 | // Create an explicit intent for the service 198 | Intent serviceIntent = new Intent(); 199 | serviceIntent.setComponent(new ComponentName(resolve.serviceInfo.packageName, resolve.serviceInfo.name)); 200 | context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE); 201 | 202 | } 203 | 204 | /** 205 | * Stop the PluginClient and disconnect from the JuiceSSH Plugin Service. 206 | * @param context 207 | */ 208 | public void stop(Context context){ 209 | context.unbindService(connection); 210 | } 211 | 212 | /** 213 | * Registers an OnSessionStartedListener to receive disconnect events for a session 214 | * @param sessionId The integer session ID returned when the session was started 215 | * @param sessionKey The session key returned when the session was started 216 | * @param listener Listener to recieve disconnect events 217 | * @throws ServiceNotConnectedException 218 | */ 219 | public void addSessionFinishedListener(final int sessionId, final String sessionKey, final OnSessionFinishedListener listener) throws ServiceNotConnectedException { 220 | if(!isConnected()) 221 | throw new ServiceNotConnectedException(); 222 | 223 | try { 224 | service.addSessionFinishedListener(sessionId, sessionKey, new ISessionFinishedListener.Stub() { 225 | @Override 226 | public void onSessionFinished() throws RemoteException { 227 | handler.post(new Runnable() { 228 | @Override 229 | public void run() { 230 | listener.onSessionFinished(); 231 | } 232 | }); 233 | } 234 | }); 235 | } catch (RemoteException e){ 236 | throw new ServiceNotConnectedException(); 237 | } 238 | } 239 | 240 | /** 241 | * Gets the name of the plugin for reporting back to the PluginService 242 | * @param context Context required to obtain PackageManager reference 243 | * @return Application label as defined in 244 | */ 245 | private static String getPluginName(Context context){ 246 | 247 | final PackageManager pm = context.getPackageManager(); 248 | 249 | ApplicationInfo applicationInfo; 250 | try { 251 | applicationInfo = pm.getApplicationInfo( context.getPackageName(), 0); 252 | } catch (final PackageManager.NameNotFoundException e) { 253 | applicationInfo = null; 254 | } 255 | 256 | String pluginName = "Unknown"; 257 | if(applicationInfo != null){ 258 | pluginName = (String) pm.getApplicationLabel(applicationInfo); 259 | } 260 | 261 | return pluginName; 262 | 263 | } 264 | 265 | /** 266 | * If you want to interact with JuiceSSH sessions then this should called from your 267 | * Activity.onActivityResult() method if the requestCode is in your connect request ids. 268 | * @param requestCode The requestCode used when connecting 269 | * @param resultCode The resultCode param from onActivityResult 270 | * @param data The data param from onActivityResult 271 | */ 272 | public void gotActivityResult(int requestCode, final int resultCode, final Intent data){ 273 | final OnSessionStartedListener onSessionStartedListener = sessionStartedListeners.get(requestCode); 274 | if(onSessionStartedListener != null){ 275 | switch(resultCode){ 276 | case Activity.RESULT_OK: 277 | 278 | final int sessionId = data.getIntExtra("session_id", 0); 279 | final String sessionKey = data.getStringExtra("session_key"); 280 | handler.post(new Runnable() { 281 | @Override 282 | public void run() { 283 | onSessionStartedListener.onSessionStarted(sessionId, sessionKey); 284 | } 285 | }); 286 | 287 | case Activity.RESULT_CANCELED: 288 | handler.post(new Runnable() { 289 | @Override 290 | public void run() { 291 | onSessionStartedListener.onSessionCancelled(); 292 | } 293 | }); 294 | break; 295 | } 296 | } 297 | } 298 | 299 | } 300 | -------------------------------------------------------------------------------- /src/main/java/com/sonelli/juicessh/pluginlibrary/PluginClient.java: -------------------------------------------------------------------------------- 1 | package com.sonelli.juicessh.pluginlibrary; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.ActivityNotFoundException; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.DialogInterface; 8 | import android.content.Intent; 9 | import android.content.ServiceConnection; 10 | import android.content.pm.PackageInfo; 11 | import android.content.pm.PackageManager; 12 | import android.content.pm.ResolveInfo; 13 | import android.net.Uri; 14 | import android.os.Handler; 15 | import android.os.IBinder; 16 | import android.os.RemoteException; 17 | import android.util.Log; 18 | import android.widget.Toast; 19 | 20 | import com.sonelli.juicessh.pluginlibrary.exceptions.ServiceNotConnectedException; 21 | import com.sonelli.juicessh.pluginlibrary.listeners.ISessionExecuteListener; 22 | import com.sonelli.juicessh.pluginlibrary.listeners.OnClientStartedListener; 23 | import com.sonelli.juicessh.pluginlibrary.listeners.OnSessionExecuteListener; 24 | 25 | 26 | public class PluginClient extends ConnectionPluginClient { 27 | 28 | private static final String TAG = "PluginService"; 29 | 30 | private ServiceConnection connection; 31 | private boolean isConnected = false; 32 | private IPluginService service; 33 | private Handler handler = new Handler(); 34 | 35 | // The minimum JuiceSSH version with plugins support 36 | public static final int MINIMUM_JUICESSH_VERSION_CODE = 89; 37 | public static final String MINIMUM_JUICESSH_VERSION_NAME = "1.5.4"; 38 | 39 | public static final class Errors { 40 | public static final int WRONG_CONNECTION_TYPE = 1; 41 | } 42 | 43 | @Override 44 | protected void add_connect_intent_extras(Intent intent) { 45 | intent.putExtra("background", true); 46 | } 47 | 48 | @Override 49 | public void start(Context context, final OnClientStartedListener listener) { 50 | 51 | // Check the JuiceSSH version 52 | if (!checkVersion(context)) return; 53 | 54 | // Dual service listener only triggers when both the connection plugin service and 55 | // this plugin service (dis)connects 56 | final OnClientStartedListener dualServiceListener = new OnClientStartedListener() { 57 | 58 | int runningCount = 0; 59 | 60 | @Override 61 | public void onClientStarted() { 62 | 63 | runningCount++; 64 | 65 | if (runningCount == 2) 66 | if (isConnected()) 67 | listener.onClientStarted(); 68 | 69 | } 70 | 71 | @Override 72 | public void onClientStopped() { 73 | 74 | runningCount--; 75 | 76 | if (runningCount == 0) 77 | if (!isConnected()) 78 | listener.onClientStopped(); 79 | 80 | } 81 | }; 82 | 83 | // Connect the Connection Plugin Service 84 | super.start(context, dualServiceListener); 85 | 86 | this.connection = new ServiceConnection(){ 87 | @Override 88 | public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 89 | Log.d(TAG, "Bound to the JuiceSSH plugin service"); 90 | isConnected = true; 91 | service = IPluginService.Stub.asInterface(iBinder); 92 | handler.post(new Runnable() { 93 | @Override 94 | public void run() { 95 | dualServiceListener.onClientStarted(); 96 | } 97 | }); 98 | } 99 | 100 | @Override 101 | public void onServiceDisconnected(ComponentName componentName) { 102 | Log.d(TAG, "Unbound from the JuiceSSH plugin service"); 103 | isConnected = false; 104 | service = null; 105 | handler.post(new Runnable() { 106 | @Override 107 | public void run() { 108 | dualServiceListener.onClientStopped(); 109 | } 110 | }); 111 | } 112 | }; 113 | 114 | // Attempt to lookup the explicit service information 115 | ResolveInfo resolve = context.getPackageManager().resolveService( 116 | new Intent("com.sonelli.juicessh.pluginservice"), 117 | 0 118 | ); 119 | 120 | if (resolve == null) { 121 | Log.e(TAG, "Could not look up explicit intent for com.sonelli.juicessh.pluginservice service"); 122 | return; 123 | } 124 | 125 | // Create an explicit intent for the service 126 | Intent serviceIntent = new Intent(); 127 | serviceIntent.setComponent(new ComponentName(resolve.serviceInfo.packageName, resolve.serviceInfo.name)); 128 | context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE); 129 | 130 | } 131 | 132 | /** 133 | * Checks to see if the currently installed JuiceSSH version is 134 | * sufficient to run plugins - if not show a warning dialog. 135 | * 136 | * @param context Context used to show an alert dialog 137 | * @return True if version is OK, false if not 138 | */ 139 | private boolean checkVersion(Context context) { 140 | 141 | // Get the installed version of JuiceSSH 142 | PackageManager pm = context.getPackageManager(); 143 | 144 | try { 145 | PackageInfo info = pm.getPackageInfo("com.sonelli.juicessh", 0); 146 | if (info.versionCode < MINIMUM_JUICESSH_VERSION_CODE) { 147 | showVersionWarning(context); 148 | return false; 149 | } 150 | 151 | // Check that we have the necessary permission granted to launch sessions. 152 | // If JuiceSSH was installed *after* the plugin, then it won't have the correct 153 | // permissions granted to it. Check for this and give the user the bad news. 154 | 155 | String permission = "com.sonelli.juicessh.api.v1.permission.OPEN_SESSIONS"; 156 | int result = context.checkCallingOrSelfPermission(permission); 157 | if(result != PackageManager.PERMISSION_GRANTED){ 158 | showPermissionWarning(context); 159 | return false; 160 | } 161 | 162 | } catch (PackageManager.NameNotFoundException e) { 163 | // JuiceSSH is not installed 164 | showVersionWarning(context); 165 | return false; 166 | } 167 | 168 | return true; 169 | 170 | } 171 | 172 | /** 173 | * Shows a warning stating that JuiceSSH plugins require a newer version of JuiceSSH be installed 174 | * 175 | * @param context Context used to show an alert dialog 176 | */ 177 | private void showVersionWarning(final Context context) { 178 | new AlertDialog.Builder(context) 179 | .setTitle(R.string.juicessh_version) 180 | .setMessage(String.format(context.getString(R.string.juicessh_wrong_version), MINIMUM_JUICESSH_VERSION_NAME)) 181 | .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() { 182 | @Override 183 | public void onClick(DialogInterface dialog, int which) { 184 | 185 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.sonelli.juicessh")); 186 | intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 187 | 188 | try { 189 | context.startActivity(intent); 190 | } catch (ActivityNotFoundException e) { 191 | // Play Store not installed - send user to website 192 | Intent website = new Intent(Intent.ACTION_VIEW, Uri.parse("http://juicessh.com/changelog")); 193 | website.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 194 | context.startActivity(website); 195 | } 196 | 197 | } 198 | }) 199 | .setNegativeButton(R.string.cancel, null) 200 | .show(); 201 | } 202 | 203 | /** 204 | * Shows a warning stating that the required JuiceSSH permissions have not been granted. 205 | * This can happen if JuiceSSH is installed after the plugin. Reinstalling the plugin is 206 | * necessary. 207 | * 208 | * @param context Context used to show an alert dialog 209 | */ 210 | private void showPermissionWarning(final Context context){ 211 | new AlertDialog.Builder(context) 212 | .setTitle(R.string.security_error) 213 | .setMessage(context.getString(R.string.juicessh_permissions_not_granted)) 214 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 215 | @Override 216 | public void onClick(DialogInterface dialog, int which) { 217 | 218 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + context.getPackageName())); 219 | intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 220 | 221 | try { 222 | context.startActivity(intent); 223 | } catch (ActivityNotFoundException e) { 224 | Toast.makeText(context, context.getString(R.string.play_store_not_installed), Toast.LENGTH_SHORT).show(); 225 | } 226 | 227 | } 228 | }) 229 | .setNegativeButton(R.string.cancel, null) 230 | .show(); 231 | 232 | } 233 | 234 | @Override 235 | public void stop(Context context) { 236 | // Disconnect the Connection Plugin Service 237 | super.stop(context); 238 | context.unbindService(connection); 239 | } 240 | 241 | @Override 242 | protected boolean isConnected() { 243 | return super.isConnected() && isConnected; 244 | } 245 | 246 | /** 247 | * Writes out a string to the sessions terminal window 248 | * 249 | * @param sessionId The integer session ID returned when the session was started 250 | * @param sessionKey The session key returned when the session was started 251 | * @param command The command(s) to write 252 | * @throws ServiceNotConnectedException 253 | */ 254 | public void writeToSession(int sessionId, String sessionKey, String command) throws ServiceNotConnectedException { 255 | 256 | if(!isConnected()) 257 | throw new ServiceNotConnectedException(); 258 | 259 | try { 260 | service.writeToSession(sessionId, sessionKey, command); 261 | } catch (RemoteException e){ 262 | throw new ServiceNotConnectedException(); 263 | } 264 | } 265 | 266 | /** 267 | * Opens a new exec channel on an existing SSH connection and executes a command. 268 | * Returns the output to the plugin via an {@link com.sonelli.juicessh.pluginlibrary.listeners.OnSessionExecuteListener }. 269 | * @param sessionId The integer session ID returned when the session was started 270 | * @param sessionKey The session key returned when the session was started 271 | * @param command The command to run 272 | * @param listener The callback listener 273 | * @throws ServiceNotConnectedException 274 | */ 275 | public void executeCommandOnSession(int sessionId, String sessionKey, String command, final OnSessionExecuteListener listener) throws ServiceNotConnectedException { 276 | 277 | if(!isConnected()) 278 | throw new ServiceNotConnectedException(); 279 | 280 | try { 281 | service.executeCommandOnSession(sessionId, sessionKey, command, new ISessionExecuteListener.Stub() { 282 | 283 | @Override 284 | public void onError(final int reason, final String error) throws RemoteException { 285 | if(reason == Errors.WRONG_CONNECTION_TYPE){ 286 | handler.post(new Runnable() { 287 | @Override 288 | public void run() { 289 | if(listener != null) 290 | listener.onError(Errors.WRONG_CONNECTION_TYPE, error); 291 | } 292 | }); 293 | } 294 | } 295 | 296 | @Override 297 | public void onCompleted(final int returnCode) throws RemoteException { 298 | handler.post(new Runnable() { 299 | @Override 300 | public void run() { 301 | if(listener != null) 302 | listener.onCompleted(returnCode); 303 | } 304 | }); 305 | } 306 | 307 | @Override 308 | public void onOutputLine(final String line) throws RemoteException { 309 | handler.post(new Runnable() { 310 | @Override 311 | public void run() { 312 | if(listener != null) 313 | listener.onOutputLine(line); 314 | } 315 | }); 316 | } 317 | }); 318 | } catch (RemoteException e){ 319 | throw new ServiceNotConnectedException(); 320 | } 321 | } 322 | 323 | /** 324 | * Brings a JuiceSSH session to the foreground. 325 | * @param sessionId The integer session ID returned when the session was started 326 | * @param sessionKey The session key returned when the session was started 327 | * @throws ServiceNotConnectedException 328 | */ 329 | public void attach(int sessionId, String sessionKey) throws ServiceNotConnectedException { 330 | 331 | if(!isConnected()) 332 | throw new ServiceNotConnectedException(); 333 | 334 | try { 335 | // This is just for logging/auditing 336 | service.attach(sessionId, sessionKey); 337 | } catch (RemoteException e){ 338 | throw new ServiceNotConnectedException(); 339 | } 340 | 341 | 342 | } 343 | } 344 | --------------------------------------------------------------------------------