├── 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 |
--------------------------------------------------------------------------------