├── AndroidManifest.xml
├── README.md
├── RemoteIme.apk
├── project.properties
├── res
├── drawable-hdpi
│ ├── ic_action_search.png
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_action_search.png
├── drawable-xhdpi
│ ├── ic_action_search.png
│ └── ic_launcher.png
├── layout
│ ├── input_main.xml
│ └── input_method.xml
├── values
│ └── strings.xml
└── xml
│ └── method.xml
└── src
└── com
└── itleaks
└── remoteime
├── model
├── IRemoteImeEventListener.java
├── ImeSession.java
├── ImeSessionManager.java
├── RemoteImePolicy.java
├── RemoteInputConsumer.java
├── RemoteInputProducer.java
├── SessionManager.java
└── SessionModels.java
└── ui
├── MApplication.java
├── NetStateService.java
├── RemoteInputActivity.java
└── RemoteInputMethod.java
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
9 |
10 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | RemoteIme
2 | =========
3 |
4 | A network InputMethod in Android platform, user can input for a remote TV with a phone
5 |
--------------------------------------------------------------------------------
/RemoteIme.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itleaks/RemoteIme/a736d80c44d3617601ad5593f476a64e883785c3/RemoteIme.apk
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-16
15 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_action_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itleaks/RemoteIme/a736d80c44d3617601ad5593f476a64e883785c3/res/drawable-hdpi/ic_action_search.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itleaks/RemoteIme/a736d80c44d3617601ad5593f476a64e883785c3/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_action_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itleaks/RemoteIme/a736d80c44d3617601ad5593f476a64e883785c3/res/drawable-mdpi/ic_action_search.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_action_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itleaks/RemoteIme/a736d80c44d3617601ad5593f476a64e883785c3/res/drawable-xhdpi/ic_action_search.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itleaks/RemoteIme/a736d80c44d3617601ad5593f476a64e883785c3/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/layout/input_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
12 |
13 |
23 |
24 |
25 |
26 |
27 |
35 |
36 |
44 |
45 |
52 |
53 |
--------------------------------------------------------------------------------
/res/layout/input_method.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
19 |
20 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
47 |
48 |
54 |
55 |
61 |
62 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | helloworld
4 | RemoteIME
5 | RemoteIME
6 | status:idle
7 | search
8 | send
9 | auto send
10 |
--------------------------------------------------------------------------------
/res/xml/method.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
20 |
--------------------------------------------------------------------------------
/src/com/itleaks/remoteime/model/IRemoteImeEventListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This software is intended to be a network InputMethod of android platform
3 | *
4 | * Copyright (c) 2014 Itleaks shen itleaks@126.com
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package com.itleaks.remoteime.model;
26 |
27 | public interface IRemoteImeEventListener {
28 | void onTextRecieved(String text);
29 | void onCommandRecieved(String command, String content);
30 | void onError(int code);
31 | void onStatusChanged(int mStatus, int status2);
32 | }
33 |
--------------------------------------------------------------------------------
/src/com/itleaks/remoteime/model/ImeSession.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This software is intended to be a network InputMethod of android platform
3 | *
4 | * Copyright (c) 2014 Itleaks shen itleaks@126.com
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package com.itleaks.remoteime.model;
26 |
27 | import android.util.Log;
28 |
29 | import com.itleaks.remoteime.model.SessionManager.Session;
30 |
31 | public class ImeSession {
32 | private static final String TAG = "ImeSession";
33 | private static final boolean DEBUG = true;
34 |
35 | public final static int STATUS_INVALID = 0;
36 | public final static int STATUS_IDLE = 1;
37 | public final static int STATUS_SEARCH = 2;
38 | public final static int STATUS_CONNECTING = 3;
39 | public final static int STATUS_CONNECTED = 4;
40 |
41 | Session mSession;
42 | int mStatus;
43 |
44 | public ImeSession(Session session) {
45 | mStatus = STATUS_IDLE;
46 | mSession = session;
47 | }
48 |
49 | public int getStatus() {
50 | return mStatus;
51 | }
52 |
53 | public void setStatus(int status) {
54 | if (DEBUG) Log.d(TAG, "new status:" + getStatusStr(status));
55 | mStatus = status;
56 | }
57 |
58 | public static String getStatusStr(int status) {
59 | switch(status) {
60 | case STATUS_IDLE:
61 | return "idler";
62 | case STATUS_SEARCH:
63 | return "search";
64 | case STATUS_CONNECTING:
65 | return "connecting";
66 | case STATUS_CONNECTED:
67 | return "connected";
68 | default:
69 | return "invalid status";
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/com/itleaks/remoteime/model/ImeSessionManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This software is intended to be a network InputMethod of android platform
3 | *
4 | * Copyright (c) 2014 Itleaks shen itleaks@126.com
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package com.itleaks.remoteime.model;
26 |
27 | import java.util.ArrayList;
28 | import java.util.HashMap;
29 | import java.util.Iterator;
30 | import java.util.List;
31 | import java.util.Map;
32 |
33 | import android.util.Log;
34 |
35 | import com.itleaks.remoteime.model.RemoteImePolicy.CommandData;
36 | import com.itleaks.remoteime.model.SessionManager.Session;
37 |
38 | public class ImeSessionManager {
39 | private static final String TAG = "ImeSessionManager";
40 | private static final boolean DEBUG = true;
41 |
42 | RemoteImePolicy mPolicy;
43 | HashMap mSessions;
44 |
45 | public ImeSessionManager(RemoteImePolicy policy) {
46 | mPolicy = policy;
47 | mSessions = new HashMap();
48 | }
49 |
50 | public void handleCommand(Session session, CommandData command) {
51 | if (DEBUG) Log.d(TAG, "handleCommand session " + session + " command:" + command);
52 | handleCommandInner(getSession(session), command);
53 | }
54 |
55 | protected void handleCommandInner(ImeSession session, CommandData command) {
56 | if(RemoteImePolicy.COMMAND_OFFLINE.equals(command)) {
57 | removeSession(command.ip);
58 | return;
59 | }
60 | return;
61 | }
62 |
63 | public void writeData(byte[] value){
64 | }
65 |
66 | public void writeCommand(byte[] value){
67 | }
68 |
69 | public void writeData(ImeSession session, byte[] value){
70 | mPolicy.writeData(session.mSession, value);
71 | }
72 |
73 | public void writeCommand(ImeSession session, byte[] buf) {
74 | mPolicy.writeCommand(session.mSession, buf);
75 | }
76 |
77 | public void writeCommand(ImeSession session, String command) {
78 | mPolicy.writeCommand(session.mSession, command);
79 | }
80 |
81 | public ImeSession createClientSession(String ip) {
82 | Session session = mPolicy.getClientSession(ip);
83 | return createSession(session);
84 | }
85 |
86 | public ImeSession createSession(Session session) {
87 | ImeSession imeSession = new ImeSession(session);
88 | mSessions.put(session, imeSession);
89 | return imeSession;
90 | }
91 |
92 | public void removeSession(ImeSession session) {
93 | if (session != null) {
94 | mSessions.remove(session.mSession);
95 | mPolicy.removeSession(session.mSession);
96 | } else {
97 | Log.e(TAG, "remove session null");
98 | }
99 | }
100 |
101 | public void removeSession(String ip) {
102 | Iterator iter = mSessions.entrySet().iterator();
103 | List removes = new ArrayList();
104 | while (iter.hasNext()) {
105 | Map.Entry entry = (Map.Entry) iter.next();
106 | ImeSession session = (ImeSession)entry.getKey();
107 | if (session.mSession.getServeIp().equals(ip)) {
108 | removes.add(session);
109 | }
110 | }
111 | for (int i=0; i mClientSessions;
59 | public HashMap mServerSessions;
60 |
61 | public class CommandData {
62 | String ip;
63 | String command;
64 | String content;
65 |
66 | public CommandData(String value) {
67 | parse(value);
68 | }
69 |
70 | private void parse(String value) {
71 | String[] datas = value.split(SPLIT_CHAR);
72 | if (datas != null && datas.length >= 3 && datas[0].equals(MAGIC_HEADER)) {
73 | ip = datas[1];
74 | command = datas[2];
75 | if (datas.length > 3) {
76 | content = datas[3];
77 | }
78 | }
79 | }
80 |
81 | @Override
82 | public String toString() {
83 | return "Command:" + command + " ip:" + ip + " content:" + content;
84 | }
85 | }
86 |
87 | public RemoteImePolicy() {
88 | init(new SessionManager(this));
89 | }
90 |
91 | public void init(SessionManager session) {
92 | // TODO Auto-generated method stub
93 | mSessionManager = session;
94 | mClientSessions = new HashMap();
95 | mServerSessions = new HashMap();
96 | mInputConsumer = new RemoteInputConsumer(this);
97 | mInputProducer = new RemoteInputProducer(this);
98 | }
99 |
100 | public void online() {
101 | if (DEBUG) Log.d(TAG, "online");
102 | broadcast(COMMAND_ONLINE);
103 | }
104 |
105 | public void offline() {
106 | if (DEBUG) Log.d(TAG, "offline");
107 | broadcast(COMMAND_OFFLINE);
108 | }
109 |
110 | public void searchRemoteIme() {
111 | if (DEBUG) Log.d(TAG, "search remote ime");
112 | broadcast(COMMAND_SEARCH_REMOTE_IME);
113 | }
114 |
115 | public void setListener(IRemoteImeEventListener listener) {
116 | mListener = listener;
117 | }
118 |
119 | public void writeData(byte[] value){
120 | mInputProducer.writeData(value);
121 | }
122 |
123 | public void writeData(Session session, byte[] value) {
124 | mSessionManager.writeData(session, value);
125 | }
126 |
127 | public void writeCommand(String command) {
128 | String data = MAGIC_HEADER + SPLIT_CHAR + SessionModels.Utils.getLocalIpAddress();
129 | data += SPLIT_CHAR + command;
130 | mInputProducer.writeCommand(data.getBytes());
131 | }
132 |
133 | public void writeCommand(String command, String Content) {
134 | String data = MAGIC_HEADER + SPLIT_CHAR + SessionModels.Utils.getLocalIpAddress();
135 | data += SPLIT_CHAR + command;
136 | data += SPLIT_CHAR + Content;
137 | mInputProducer.writeCommand(data.getBytes());
138 | }
139 |
140 | public void broadcast(String command) {
141 | String data = MAGIC_HEADER + SPLIT_CHAR + SessionModels.Utils.getLocalIpAddress();
142 | data += SPLIT_CHAR + command;
143 | mSessionManager.broadcast(data.getBytes());
144 | }
145 |
146 | public void writeCommand(Session session, String command) {
147 | String data = MAGIC_HEADER + SPLIT_CHAR + SessionModels.Utils.getLocalIpAddress();
148 | data += SPLIT_CHAR + command;
149 | mSessionManager.writeCommand(session, data.getBytes());
150 | }
151 |
152 | public void writeCommand(Session session, byte[] buf) {
153 | mSessionManager.writeCommand(session, buf);
154 | }
155 |
156 | @Override
157 | public void onBroadcastRecieved(Session session, byte[] data, int length) {
158 | // TODO Auto-generated method stub
159 | if (length <= 0) {
160 | Log.e(TAG, "o lenght");
161 | return;
162 | }
163 | String content = new String(data, 0, length);
164 | if(DEBUG) Log.e(TAG, "onBroadcastRecieved " + content);
165 | handleCommand(session, content);
166 | }
167 |
168 | @Override
169 | public void onCommandRecieved(Session session, byte[] data, int length) {
170 | // TODO Auto-generated method stub
171 | if (length <= 0) {
172 | Log.e(TAG, "o lenght");
173 | return;
174 | }
175 | String content = new String(data, 0, length);
176 | if(DEBUG) Log.e(TAG, "onCommandRecieved " + content);
177 | handleCommand(session, content);
178 | }
179 |
180 | @Override
181 | public void onDataRecieved(Session session, byte[] data, int length) {
182 | // TODO Auto-generated method stub
183 | if (length <= 0) {
184 | Log.e(TAG, "o lenght");
185 | return;
186 | }
187 | String content = new String(data, 0, length);
188 | if(DEBUG) Log.d(TAG, "onDataRecieved " + content);
189 | if (mListener != null) {
190 | mListener.onTextRecieved(content);
191 | }
192 | }
193 |
194 | @Override
195 | public void onError(Session session, int code) {
196 | // TODO Auto-generated method stub
197 | if(DEBUG) Log.e(TAG, "onError " + session);
198 | if (session != null) {
199 | mInputProducer.onError(session, code);
200 | mInputConsumer.onError(session, code);
201 | removeSession(session);
202 | }
203 | //TODO
204 | if (mListener != null) {
205 | mListener.onError(code);
206 | mListener.onStatusChanged(getProducerStatus(), getConsumerStatus());
207 | }
208 | }
209 |
210 | private void handleCommand(Session session, String data) {
211 | CommandData command = new CommandData(data);
212 | if(DEBUG) Log.e(TAG, "handleCommand " + command);
213 | if (COMMAND_SEARCH_REMOTE_IME.equals(command)) {
214 | mInputConsumer.handleCommand(session, command);
215 | } else {
216 | mInputProducer.handleCommand(session, command);
217 | mInputConsumer.handleCommand(session, command);
218 | }
219 | if(COMMAND_OFFLINE.equals(command)) {
220 | removeSession(command.ip);
221 | }
222 | if (mListener != null) {
223 | mListener.onCommandRecieved(command.command, command.content);
224 | mListener.onStatusChanged(getProducerStatus(), getConsumerStatus());
225 | }
226 | }
227 |
228 | public void destroy() {
229 | if (mSessionManager != null) {
230 | mSessionManager.destroy();
231 | mSessionManager = null;
232 | }
233 | }
234 |
235 | public Session getClientSession(String serveIp) {
236 | //One target ip, one client session
237 | if (mClientSessions.get(serveIp) != null) {
238 | //TODO:Current, serveIp is same as session's tag
239 | String tag = serveIp;
240 | return mClientSessions.get(tag);
241 | } else {
242 | Session session = mSessionManager.createClientSession(serveIp);
243 | return session;
244 | }
245 | }
246 |
247 | @Override
248 | public void onSessionCreated(Session session) {
249 | if (session.isClient()) {
250 | mClientSessions.put(session.getTag(), session);
251 | } else {
252 | mServerSessions.put(session.getTag(), session);
253 | }
254 | }
255 |
256 | public void removeSession(Session session) {
257 | // TODO Auto-generated method stub
258 | mClientSessions.remove(session.getTag());
259 | mServerSessions.remove(session.getTag());
260 | mSessionManager.removeSession(session);
261 | }
262 |
263 | public void removeSession(String ip) {
264 | // TODO Auto-generated method stub
265 | List removes = new ArrayList();
266 | Iterator iter = mClientSessions.entrySet().iterator();
267 | while (iter.hasNext()) {
268 | Map.Entry entry = (Map.Entry) iter.next();
269 | Session session = (Session)entry.getKey();
270 | if (session.getServeIp().equals(ip)) {
271 | removes.add(session);
272 | }
273 | }
274 | iter = mServerSessions.entrySet().iterator();
275 | while (iter.hasNext()) {
276 | Map.Entry entry = (Map.Entry) iter.next();
277 | Session session = (Session)entry.getKey();
278 | if (session.getServeIp().equals(ip)) {
279 | removes.add(session);
280 | }
281 | }
282 | for (int i=0; i mSessions;
272 | tcpSessionServer mDataServer = null;
273 | tcpSessionServer mCommandServer = null;
274 | udpSessionServer mBroadcastServer = null;
275 | private ISessionEventListener mListener;
276 |
277 | public SessionManager(ISessionEventListener listener) {
278 | mRunning = true;
279 | mListener = listener;
280 | mSessions = new HashMap();
281 | init();
282 | }
283 |
284 | void init() {
285 | class Worker extends Thread {
286 | final static String TAG = "Netsession Worker";
287 |
288 | public void run() {
289 | Looper.prepare();
290 | mHandler = new Handler() {
291 | public void handleMessage(android.os.Message msg) {
292 | if(DEBUG) Log.e(TAG, "Message:" + msg.what + " Data length:" + msg.arg1);
293 | if (msg.arg1 < 0) {
294 | Log.e(TAG, "Length <0 exception");
295 | }
296 | if (mListener == null) {
297 | Log.e(TAG, "command channel no listener");
298 | return;
299 | }
300 | int what = msg.what;
301 | switch(what) {
302 | case MSG_DATA_WRITE:
303 | ((WriteData)msg.obj).write();
304 | break;
305 | case MSG_COMMAND_WRITE:
306 | ((WriteData)msg.obj).write();
307 | break;
308 | case MSG_RAW_RECIEVE:
309 | if (msg.obj == null) {
310 | break;
311 | }
312 | ReadData data = (ReadData)msg.obj;
313 | int command = data.msg;
314 | if (command == MSG_DATA_RECIEVE) {
315 | mListener.onDataRecieved(data.session, data.data, data.length);
316 | } else if (command == MSG_COMMAND_RECIEVE){
317 | mListener.onCommandRecieved(data.session, data.data, data.length);
318 | } else if (command == MSG_BROADCAST_RECIEVE){
319 | mListener.onBroadcastRecieved(data.session, data.data, data.length);
320 | }
321 | break;
322 | case MSG_DATA_RECIEVE:
323 | if (msg.obj == null) {
324 | break;
325 | }
326 | mListener.onDataRecieved(null, (byte[])msg.obj, msg.arg1);
327 | break;
328 | case MSG_COMMAND_RECIEVE:
329 | if (msg.obj == null) {
330 | break;
331 | }
332 | mListener.onCommandRecieved(null, (byte[])msg.obj, msg.arg1);
333 | break;
334 | case MSG_BROADCAST_RECIEVE:
335 | if (msg.obj == null) {
336 | break;
337 | }
338 | mListener.onBroadcastRecieved(null, (byte[])msg.obj, msg.arg1);
339 | break;
340 | case MSG_BROADCAST_WRITE:
341 | if (msg.obj == null) {
342 | break;
343 | }
344 | SessionModels.udpSessionClient.send("255.255.255.255", BROADCAST_PORT, (byte[])msg.obj);
345 | break;
346 | case MSG_ERROR:
347 | if (msg.obj != null) {
348 | Session session = (Session) msg.obj;
349 | mListener.onError(session, msg.arg1);
350 | } else {
351 | mListener.onError(null, msg.arg1);
352 | }
353 | break;
354 | default:
355 | break;
356 | }
357 | }
358 | };
359 | Looper.loop();
360 | }
361 | }
362 |
363 | new Worker().start();
364 |
365 | new Thread(new Runnable() {
366 | private String TAG = "dataServer";
367 |
368 | @Override
369 | public void run() {
370 | // TODO Auto-generated method stub
371 | while(mRunning) {
372 | mDataServer = new tcpSessionServer(DATA_PORT, mDataClientSessionListener);
373 | while (mDataServer.isActive()){
374 | mDataServer.listen();
375 | }
376 | Log.d(TAG , "Server is not active");
377 | mDataServer.destroy();
378 | postError(null, ERROR_TCP_LOCAL);
379 | try {
380 | Thread.sleep(2000);
381 | } catch (InterruptedException e) {
382 | // TODO Auto-generated catch block
383 | e.printStackTrace();
384 | }
385 | }
386 | if (mDataServer != null) {
387 | mDataServer.destroy();
388 | mDataServer = null;
389 | }
390 | }
391 | }).start();
392 |
393 | new Thread(new Runnable() {
394 | private String TAG = "commandServer";
395 |
396 | @Override
397 | public void run() {
398 | // TODO Auto-generated method stub
399 | while(mRunning) {
400 | mCommandServer = new tcpSessionServer(COMMAND_PORT, mCommandClientSessionListener);
401 | while (mCommandServer.isActive()){
402 | mCommandServer.listen();
403 | }
404 | Log.d(TAG , "Server is not active");
405 | mCommandServer.destroy();
406 | postError(null, ERROR_TCP_LOCAL);
407 | try {
408 | Thread.sleep(2000);
409 | } catch (InterruptedException e) {
410 | // TODO Auto-generated catch block
411 | e.printStackTrace();
412 | }
413 | }
414 | if (mCommandServer != null) {
415 | mCommandServer.destroy();
416 | mCommandServer = null;
417 | }
418 | }
419 | }).start();
420 |
421 | new Thread(new Runnable() {
422 | private String TAG = "broadcastServer";
423 |
424 | @Override
425 | public void run() {
426 | // TODO Auto-generated method stub
427 | while(mRunning) {
428 | mBroadcastServer = new udpSessionServer(BROADCAST_PORT);
429 | while(mBroadcastServer.isActive()){
430 | byte[] data = new byte[512];
431 | int length = mBroadcastServer.read(data);
432 | mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_RECIEVE, length, 0, data));
433 | }
434 | Log.d(TAG , "server is not active");
435 | mBroadcastServer.close();
436 | try {
437 | Thread.sleep(2000);
438 | } catch (InterruptedException e) {
439 | // TODO Auto-generated catch block
440 | e.printStackTrace();
441 | }
442 | }
443 | if (mBroadcastServer != null) {
444 | mBroadcastServer.close();
445 | mBroadcastServer = null;
446 | }
447 | }
448 | }).start();
449 | }
450 |
451 | public Session createClientSession(String ip) {
452 | ClientSession session = new ClientSession(ip, mDataEventListener);
453 | addSession(session);
454 | return session;
455 | }
456 |
457 | public Session createServerSession(NetSession netSession, boolean isDataSession) {
458 | ServerSession session = new ServerSession(netSession,
459 | isDataSession, mDataEventListener);
460 | addSession(session);
461 | return session;
462 | }
463 |
464 | private void addSession(Session session) {
465 | mSessions.put(session.getTag(), session);
466 | mListener.onSessionCreated(session);
467 | }
468 |
469 | public void removeSession(String tag) {
470 | if (!mSessions.containsKey(tag)) {
471 | return;
472 | }
473 | mSessions.get(tag).destroy();
474 | mSessions.remove(tag);
475 | }
476 |
477 | public void removeSession(Session session) {
478 | if (session == null) {
479 | return;
480 | }
481 | removeSession(session.getTag());
482 | }
483 |
484 | public void writeData(Session session, byte[] data) {
485 | WriteData obj = new WriteData(session, data, MSG_DATA_WRITE);
486 | mHandler.sendMessage(mHandler.obtainMessage(MSG_DATA_WRITE, data.length, 0, obj));
487 | }
488 |
489 | public void writeCommand(Session session, byte[] data) {
490 | WriteData obj = new WriteData(session, data, MSG_COMMAND_WRITE);
491 | mHandler.sendMessage(mHandler.obtainMessage(MSG_COMMAND_WRITE, data.length, 0, obj));
492 | }
493 |
494 | public void broadcast(byte[] data) {
495 | mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_WRITE, data.length, 0, data));
496 | }
497 |
498 | private void postError(Session session, int code) {
499 | mHandler.sendMessage(mHandler.obtainMessage(MSG_ERROR, code, 0, session));
500 | }
501 |
502 | protected void finalize() throws Throwable {
503 | destroy();
504 | super.finalize();
505 | }
506 |
507 | public void destroy() {
508 | mRunning = false;
509 | Iterator iter = mSessions.keySet().iterator();
510 | while (iter.hasNext()) {
511 | String tag = iter.next();
512 | removeSession(tag);
513 | }
514 | }
515 |
516 | IClientEventListener mDataClientSessionListener = new IClientEventListener() {
517 |
518 | @Override
519 | public void onSessionCreated(NetSession session) {
520 | // TODO Auto-generated method stub
521 | createServerSession(session, true);
522 | }
523 | };
524 |
525 | IClientEventListener mCommandClientSessionListener = new IClientEventListener() {
526 |
527 | @Override
528 | public void onSessionCreated(NetSession session) {
529 | // TODO Auto-generated method stub
530 | createServerSession(session, false);
531 | }
532 | };
533 |
534 | IDataEventListener mDataEventListener = new IDataEventListener() {
535 |
536 | @Override
537 | public void onDataRecieved(Session session, byte[] data, int length) {
538 | // TODO Auto-generated method stub
539 | if (length < 0) {
540 | onError(session, 0);
541 | return;
542 | }
543 | //Current, Only server session can receive data
544 | ReadData msgData = new ReadData(MSG_DATA_RECIEVE, session, data, length);
545 | mHandler.sendMessage(mHandler.obtainMessage(MSG_RAW_RECIEVE, length, 0, msgData));
546 | }
547 |
548 | @Override
549 | public void onCommandRecieved(Session session, byte[] data, int length) {
550 | // TODO Auto-generated method stub
551 | if (length < 0) {
552 | onError(session, 0);
553 | return;
554 | }
555 | //Current, Only server session can receive data
556 | ReadData msgData = new ReadData(MSG_COMMAND_RECIEVE, session, data, length);
557 | mHandler.sendMessage(mHandler.obtainMessage(MSG_RAW_RECIEVE, length, 0, msgData));
558 | }
559 |
560 | @Override
561 | public void onError(Session session, int code) {
562 | // TODO Auto-generated method stub
563 | postError(session, code);
564 | }
565 |
566 | };
567 | }
568 |
--------------------------------------------------------------------------------
/src/com/itleaks/remoteime/model/SessionModels.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This software is intended to be a network InputMethod of android platform
3 | *
4 | * Copyright (c) 2014 Itleaks shen itleaks@126.com
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package com.itleaks.remoteime.model;
26 |
27 | import java.io.IOException;
28 | import java.io.InputStream;
29 | import java.io.OutputStream;
30 | import java.net.DatagramPacket;
31 | import java.net.DatagramSocket;
32 | import java.net.InetAddress;
33 | import java.net.NetworkInterface;
34 | import java.net.ServerSocket;
35 | import java.net.Socket;
36 | import java.net.SocketException;
37 | import java.util.Enumeration;
38 | import java.util.HashMap;
39 |
40 | import android.util.Log;
41 |
42 | public interface SessionModels {
43 | public static final String TAG = "NetUtils";
44 |
45 | public class tcpSessionClient extends NetSession {
46 | OutputStream ops;
47 | InputStream ips;
48 | Socket mSocket = null;
49 |
50 | public tcpSessionClient(String ip, int port, INetSessionListener listener) {
51 | super(null, listener);
52 | try {
53 | mSocket = new Socket(ip, port);//创建Socket实例,并绑定连接远端IP地址和端口
54 | init(mSocket);
55 | } catch (IOException e) {
56 | // TODO Auto-generated catch block
57 | close();
58 | e.printStackTrace();
59 | }
60 | }
61 | }
62 |
63 | class NetSession extends Thread {
64 | Socket mSocket = null;
65 | private boolean mRunning = true;
66 | OutputStream ops;
67 | InputStream ips;
68 | INetSessionListener mListener;
69 | String mIp, mTag;
70 | public NetSession(Socket socket, INetSessionListener listener) {
71 | setListener(listener);
72 | init(socket);
73 | }
74 |
75 | public void init(Socket socket) {
76 | if (socket == null) {
77 | return;
78 | }
79 | mRunning = true;
80 | mSocket = socket;
81 | try {
82 | ops = mSocket.getOutputStream();
83 | ips = mSocket.getInputStream();
84 | } catch (IOException e) {
85 | // TODO Auto-generated catch block
86 | mRunning = false;
87 | if (mListener != null) {
88 | mListener.onError(this, 0);
89 | }
90 | e.printStackTrace();
91 | }
92 | mIp = mSocket.getInetAddress().toString().substring(1);
93 | mTag = mIp + ":" + mSocket.getPort();
94 | }
95 |
96 | public void setListener(INetSessionListener listener) {
97 | mListener = listener;
98 | }
99 |
100 | @Override
101 | public void run(){
102 | while(mRunning) {
103 | byte[] data = new byte[512];
104 | int length = read(data);
105 | mListener.onDataRecieved(this, data, length);
106 | }
107 | try {
108 | mSocket.close();
109 | mSocket = null;
110 | } catch (IOException e) {
111 | // TODO Auto-generated catch block
112 | e.printStackTrace();
113 | }
114 | }
115 |
116 | public int read(byte[] data) {
117 | int length = -1;
118 | try {
119 | length = ips.read(data);
120 | } catch (IOException e) {
121 | // TODO Auto-generated catch block
122 | e.printStackTrace();
123 | }
124 | return length;
125 | }
126 |
127 | public int write(byte[] data) {
128 | int length = -1;
129 | try {
130 | ops.write(data);
131 | ops.flush();
132 | } catch (IOException e) {
133 | // TODO Auto-generated catch block
134 | if (mListener != null) {
135 | mListener.onError(this, 0);
136 | }
137 | e.printStackTrace();
138 | }
139 | return length;
140 | }
141 |
142 | public void close(){
143 | mRunning = false;
144 | }
145 |
146 | public String getTag() {
147 | return mTag;
148 | }
149 |
150 | public String getIp() {
151 | // TODO Auto-generated method stub
152 | return mIp;
153 | }
154 |
155 | public boolean isActive() {
156 | return mRunning;
157 | }
158 | }
159 |
160 | public interface INetSessionListener {
161 | void onDataRecieved(NetSession session, byte[] data, int length);
162 | void onError(NetSession session, int code);
163 | }
164 |
165 | public class tcpSessionServer {
166 | OutputStream ops;
167 | InputStream ips;
168 | ServerSocket mSocket;
169 | HashMap mSessions;
170 | boolean isActive;
171 | int mCurIndex = 0;
172 | IClientEventListener mListener;
173 |
174 | interface IClientEventListener {
175 | void onSessionCreated(NetSession session);
176 | }
177 |
178 | public tcpSessionServer(int port, IClientEventListener listener) {
179 | mListener = listener;
180 | try {
181 | mSocket = new ServerSocket(port);
182 | isActive = true;
183 | } catch (IOException e) {
184 | e.printStackTrace();
185 | }
186 | }
187 |
188 | public boolean listen() {
189 | try {
190 | Socket socket = mSocket.accept();
191 | createSession(socket);
192 | return true;
193 | } catch (IOException e) {
194 | // TODO Auto-generated catch block
195 | destroy();
196 | isActive = false;
197 | e.printStackTrace();
198 | }
199 | return false;
200 | }
201 |
202 | public void destroy() {
203 | if (mSocket == null) {
204 | return;
205 | }
206 | try {
207 | mSocket.close();
208 | } catch (IOException e1) {
209 | // TODO Auto-generated catch block
210 | e1.printStackTrace();
211 | }
212 | }
213 |
214 | private void createSession(Socket socket) {
215 | NetSession session = new NetSession(socket, null);
216 | session.start();
217 | if (mListener != null) {
218 | mListener.onSessionCreated(session);
219 | }
220 | }
221 |
222 | public boolean isActive() {
223 | return isActive;
224 | }
225 | }
226 |
227 | public class udpSessionClient {
228 | static DatagramSocket socket = null;
229 | public udpSessionClient() {
230 | }
231 |
232 | public static void send(String ip, int port, byte[] data) {
233 | try {
234 | if (socket == null) {
235 | socket = new DatagramSocket();
236 | }
237 | InetAddress serverAddress = InetAddress.getByName(ip);
238 | DatagramPacket packet = new DatagramPacket(data, data.length, serverAddress, port);
239 | socket.send(packet);
240 | //socket.close();
241 | } catch (IOException e) {
242 | // TODO Auto-generated catch block
243 | if (socket != null) {
244 | socket.close();
245 | socket = null;
246 | }
247 | e.printStackTrace();
248 | }
249 | }
250 | }
251 |
252 | public class udpSessionServer {
253 | boolean isActive;
254 | DatagramSocket ds = null;
255 |
256 | public udpSessionServer(int port) {
257 | try {
258 | ds = new DatagramSocket(port);
259 | isActive = true;
260 | } catch (IOException e) {
261 | // TODO Auto-generated catch block
262 | close();
263 | e.printStackTrace();
264 | }
265 | }
266 |
267 | public int read(byte[] data) {
268 | try {
269 | DatagramPacket dp = new DatagramPacket(data, data.length-1);
270 | ds.receive(dp);
271 | if (SessionModels.Utils.getLocalIpAddress().equals(dp.getAddress().toString().substring(1))) {
272 | return 0;
273 | } else {
274 | return dp.getLength();
275 | }
276 | } catch (IOException e) {
277 | // TODO Auto-generated catch block
278 | close();
279 | Log.d(TAG, "error", e);
280 | }
281 | return 0;
282 | }
283 |
284 | public boolean isActive() {
285 | return isActive;
286 | }
287 |
288 | public void close() {
289 | isActive = false;
290 | if (ds != null) {
291 | ds.close();
292 | ds = null;
293 | }
294 | }
295 | }
296 |
297 | public class Utils {
298 | private static String mIp = "";
299 | public static String getLocalIpAddress() {
300 | return mIp;
301 | }
302 |
303 | public static void updateLocalIpAddress() {
304 | try {
305 | for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
306 | NetworkInterface intf = en.nextElement();
307 | for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
308 | InetAddress inetAddress = enumIpAddr.nextElement();
309 | if (!inetAddress.isLoopbackAddress()) {
310 | mIp = inetAddress.getHostAddress().toString();
311 | }
312 | }
313 | }
314 | } catch (SocketException ex) {
315 | Log.e(TAG, ex.toString());
316 | }
317 | }
318 | }
319 | }
320 |
--------------------------------------------------------------------------------
/src/com/itleaks/remoteime/ui/MApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This software is intended to be a network InputMethod of android platform
3 | *
4 | * Copyright (c) 2014 Itleaks shen itleaks@126.com
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package com.itleaks.remoteime.ui;
26 |
27 | import java.util.ArrayList;
28 |
29 | import android.app.Application;
30 | import android.util.Log;
31 |
32 | import com.itleaks.remoteime.model.IRemoteImeEventListener;
33 | import com.itleaks.remoteime.model.RemoteImePolicy;
34 |
35 | public class MApplication extends Application implements IRemoteImeEventListener {
36 | private ArrayList mImeEventListeners =
37 | new ArrayList();
38 |
39 | @Override
40 | public void onCreate() {
41 | super.onCreate();
42 | }
43 |
44 | public void addImeEventListener(IRemoteImeEventListener listener) {
45 | if (!mImeEventListeners.contains(listener)) {
46 | mImeEventListeners.add(listener);
47 | }
48 | }
49 |
50 | public void removeImeEventListener(IRemoteImeEventListener listener) {
51 | if (mImeEventListeners.contains(listener)) {
52 | mImeEventListeners.remove(listener);
53 | }
54 | }
55 |
56 | @Override
57 | public void onCommandRecieved(String command, String content) {
58 | for (int i=0; i