├── .gitignore ├── README.md ├── app ├── build.gradle ├── libs │ ├── gson-2.0.jar │ ├── java_websocket.jar │ └── log4j.jar ├── proguard.cfg └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── buscode │ │ └── whatsinput │ │ └── server │ │ ├── BackServiceBinder.aidl │ │ └── BackServiceListener.aidl │ ├── java │ └── com │ │ └── buscode │ │ └── whatsinput │ │ ├── App.java │ │ ├── BackService.java │ │ ├── MainActivity.java │ │ ├── SettingsActivity.java │ │ ├── WifiInputMethod.java │ │ ├── beans │ │ ├── AbstractMsg.java │ │ ├── InputChange.java │ │ ├── InputEdit.java │ │ ├── InputFinish.java │ │ ├── InputKey.java │ │ ├── InputStart.java │ │ ├── InputUpdate.java │ │ ├── Jsonable.java │ │ └── Jsoner.java │ │ ├── common │ │ ├── ActivityPiece.java │ │ ├── ActivityPieceManager.java │ │ ├── Net.java │ │ ├── ServicePiece.java │ │ └── ServicePieceManager.java │ │ └── server │ │ ├── ExHttpConfig.java │ │ ├── ExHttpServer.java │ │ ├── ExWebSocketServer.java │ │ ├── NanoHTTPD.java │ │ └── ServerNotification.java │ └── res │ ├── drawable-hdpi │ ├── btn_default_disabled_focused_holo_dark.9.png │ ├── btn_default_disabled_holo_dark.9.png │ ├── btn_default_focused_holo_dark.9.png │ ├── btn_default_normal_holo_dark.9.png │ ├── btn_default_pressed_holo_dark.9.png │ ├── ic_menu_moreoverflow_normal_holo_dark.png │ ├── ic_menu_refresh.png │ └── panel_background.9.png │ ├── drawable-ldpi │ ├── ic_menu_refresh.png │ └── panel_background.9.png │ ├── drawable-mdpi │ ├── btn_default_disabled_focused_holo_dark.9.png │ ├── btn_default_disabled_holo_dark.9.png │ ├── btn_default_focused_holo_dark.9.png │ ├── btn_default_normal_holo_dark.9.png │ ├── btn_default_pressed_holo_dark.9.png │ ├── ic_menu_moreoverflow_normal_holo_dark.png │ ├── ic_menu_refresh.png │ └── panel_background.9.png │ ├── drawable-xhdpi │ ├── btn_default_disabled_focused_holo_dark.9.png │ ├── btn_default_disabled_holo_dark.9.png │ ├── btn_default_focused_holo_dark.9.png │ ├── btn_default_normal_holo_dark.9.png │ ├── btn_default_pressed_holo_dark.9.png │ ├── ic_menu_moreoverflow_normal_holo_dark.png │ ├── ic_menu_refresh.png │ ├── icon.png │ └── panel_background.9.png │ ├── drawable │ ├── btn_bg_blue.xml │ ├── btn_bg_n.xml │ └── btn_default_holo_dark.xml │ ├── layout │ ├── main.xml │ └── wifi_input_method_view.xml │ ├── menu │ └── popup.xml │ ├── values-v11 │ └── styles.xml │ ├── values-xhdpi │ └── dimens.xml │ ├── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── method.xml │ └── settings.xml ├── build.gradle └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | # key store 11 | Airdroid-keystore-2011-10 12 | 13 | # generated files 14 | build/ 15 | bin/ 16 | gen/ 17 | out/ 18 | annotation/ 19 | .gradle 20 | assets/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | ant.properties 25 | proguard-project.txt 26 | build.xml 27 | 28 | # Eclipse project files 29 | .classpath 30 | .project 31 | 32 | # Proguard folder generated by Eclipse 33 | proguard/ 34 | 35 | # Intellij project files 36 | *.iml 37 | *.ipr 38 | *.iws 39 | .idea/ 40 | 41 | 42 | #Gradle Files 43 | gradle/ 44 | gradlew 45 | gradlew.bat 46 | gradle.properties 47 | 48 | #keystore 49 | *.keystore -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## WhatsInput 2 | 3 | WhatsInput,是一款输入法,通过 WIFI,使你的PC键盘向手机输入内容。 4 | 5 | 1. 漂亮的 ICON 6 | 2. 简单的输入界面 7 | 8 | 希望 WhatsInput 可以在某个情景中,帮助你一些。 9 | 10 | 1. 使用一些移动互联网的 IM 类应用时,假如:陌陌,WhatsApp,KiK 等 11 | 2. 轻量级短信快速的输入方案 12 | 3. 安卓应用测试时,需要内容输入 13 | 4. .... 14 | 15 | ### 使用方法: 16 | 17 | 1. 在设置中启用 WhatsInput 18 | 2. 在需要使用时切换到 WhatsInput 输入法,这时你会看到这样的地址: 19 | 3. 在浏览器中打开 20 | 5. 支持 Enter 和 TAB 键 21 | 22 | 下载地址: 23 | 24 | 25 | ### 手机---> 前端 26 | 27 | 1. 开始编辑 28 | 29 | ``` 30 | {"text":"sssss", "type":"InputStart"} 31 | ``` 32 | 33 | - type 消息类型 InputStart 34 | - text 开始编辑时的内容 35 | 36 | 2. 编辑结束 37 | 38 | ``` 39 | {"type": "InputFinish" } 40 | ``` 41 | 42 | - type 消息类型 InputFinish 43 | 44 | 45 | 3. 编辑框内容变化 46 | 47 | ``` 48 | {"type": "InputChange", text: ""} 49 | ``` 50 | 51 | - type 消息类型 52 | - text 变化后的编辑框内容。 53 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'android' 2 | 3 | android { 4 | compileSdkVersion 19 5 | buildToolsVersion "19.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 7 9 | targetSdkVersion 19 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | signingConfigs { 14 | myConfig { 15 | storeFile file(STORE_FILE) 16 | storePassword STORE_PASSWORD 17 | keyAlias STORE_KEY_ALIAS 18 | keyPassword STORE_KEY_PASSWORD 19 | } 20 | } 21 | buildTypes { 22 | release { 23 | signingConfig signingConfigs.myConfig 24 | runProguard false 25 | proguardFiles 'proguard.cfg' 26 | } 27 | } 28 | } 29 | 30 | dependencies { 31 | compile fileTree(dir: 'libs', include: ['*.jar', '*.aar']) 32 | } 33 | -------------------------------------------------------------------------------- /app/libs/gson-2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/libs/gson-2.0.jar -------------------------------------------------------------------------------- /app/libs/java_websocket.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/libs/java_websocket.jar -------------------------------------------------------------------------------- /app/libs/log4j.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/libs/log4j.jar -------------------------------------------------------------------------------- /app/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -ignorewarnings 9 | 10 | 11 | -keep public class * extends android.app.Activity 12 | -keep public class * extends android.app.Application 13 | -keep public class * extends android.app.Service 14 | -keep public class * extends android.content.BroadcastReceiver 15 | -keep public class * extends android.content.ContentProvider 16 | -keep public class * extends android.app.backup.BackupAgentHelper 17 | -keep public class * extends android.preference.Preference 18 | -keep public class com.android.vending.licensing.ILicensingService 19 | 20 | -keep public class * extends com.sand.json.Jsonable{ 21 | public protected private *; 22 | } 23 | 24 | -assumenosideeffects class android.util.Log { 25 | public static int d(...); 26 | public static int v(...); 27 | public static int e(...); 28 | public static int w(...); 29 | public static int i(...); 30 | } 31 | -assumenosideeffects class com.ad.wd.common.Logger { 32 | public static void write(...); 33 | public static void setLoggerHandler(...); 34 | } 35 | 36 | -keepclasseswithmembernames class * { 37 | native ; 38 | } 39 | 40 | -keepclasseswithmembers class * { 41 | public (android.content.Context, android.util.AttributeSet); 42 | } 43 | 44 | -keepclasseswithmembers class * { 45 | public (android.content.Context, android.util.AttributeSet, int); 46 | } 47 | 48 | -keepclassmembers class * extends android.app.Activity { 49 | public void *(android.view.View); 50 | } 51 | 52 | -keepclassmembers enum * { 53 | public static **[] values(); 54 | public static ** valueOf(java.lang.String); 55 | } 56 | 57 | -keep class * implements android.os.Parcelable { 58 | public static final android.os.Parcelable$Creator *; 59 | } 60 | 61 | -keep class android.support.v4.** { *;} 62 | -keep class com.google.gson.** { *;} 63 | -keepattributes Signature, *Annotation* 64 | -keepclassmembers class * extends com.buscode.whatsinput.beans.Jsonable {*;} 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/aidl/com/buscode/whatsinput/server/BackServiceBinder.aidl: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.server; 2 | 3 | import com.buscode.whatsinput.server.BackServiceListener; 4 | 5 | interface BackServiceBinder { 6 | 7 | void registerListener(BackServiceListener listener); 8 | void startBackService(); 9 | void stopBackService(); 10 | 11 | boolean isBackServiceRunning(); 12 | 13 | void sendMessage(String msg); 14 | } -------------------------------------------------------------------------------- /app/src/main/aidl/com/buscode/whatsinput/server/BackServiceListener.aidl: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.server; 2 | 3 | interface BackServiceListener { 4 | 5 | void onStart(); 6 | void onStop(); 7 | 8 | void onMessage(String msg); 9 | void onOpen(); 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/App.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput; 2 | 3 | import android.app.Application; 4 | 5 | /** 6 | * User: fanxu 7 | * Date: 12-10-28 8 | */ 9 | public class App extends Application { 10 | /*private static final String PATH_LOGGER_FILE = "/sdcard/airdroid/air_input.log"; 11 | 12 | static { 13 | 14 | try { 15 | 16 | final LogConfigurator lc = new LogConfigurator(); 17 | //lc.setFileName(PushConfig.getInstance().getPathLoggerFile()); 18 | lc.setFileName(PATH_LOGGER_FILE); 19 | // %C{1} is Class name exclude the package 20 | // %M is the method name 21 | // %d is date 22 | // %m%n is log message 23 | // lc.setFilePattern("%d - [%p::%C{1}.%M] - %m%n"); 24 | lc.setFilePattern("%d - [%-6p-%c] - %m%n"); 25 | lc.setMaxBackupSize(2); 26 | lc.setMaxFileSize(1024 * 1024); 27 | lc.setRootLevel(Level.DEBUG); 28 | // Set log level of a specific logger 29 | lc.setLevel("org.apache", Level.DEBUG); 30 | lc.configure(); 31 | 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | */ 37 | @Override 38 | public void onCreate() { 39 | super.onCreate(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/BackService.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | import android.os.RemoteException; 7 | import com.buscode.whatsinput.server.*; 8 | import org.apache.log4j.Logger; 9 | import org.java_websocket.WebSocket; 10 | import org.java_websocket.handshake.ClientHandshake; 11 | 12 | /** 13 | * User: fanxu 14 | * Date: 12-10-31 15 | */ 16 | public class BackService extends Service { 17 | 18 | //logger 19 | private Logger logger = Logger.getLogger("BackService" + hashCode()); 20 | 21 | private BackServiceListener mBackServiceListener = null; 22 | 23 | private IBinder mBinder = new BackServiceBinder.Stub() { 24 | @Override 25 | public boolean isBackServiceRunning() throws RemoteException { 26 | return BackService.this.isServersRunning(); 27 | } 28 | 29 | @Override 30 | public void registerListener(BackServiceListener listener) throws RemoteException { 31 | logger.debug("registerListener: listener == null" + (listener == null) ); 32 | mBackServiceListener = listener; 33 | } 34 | 35 | @Override 36 | public void startBackService() throws RemoteException { 37 | logger.debug("HttpServiceBinder.startHttpServer: "); 38 | BackService.this.startBackServersInThread(); 39 | } 40 | 41 | @Override 42 | public void stopBackService() throws RemoteException { 43 | logger.debug("HttpServiceBinder.stopHttpServer: "); 44 | BackService.this.stopBackServersInThread(); 45 | } 46 | 47 | @Override 48 | public void sendMessage(String msg) throws RemoteException { 49 | ExWebSocketServer ws = ExWebSocketServer.getRunningServer(); 50 | if (ws != null) { 51 | ws.sendMessageToAllWSClient(msg); 52 | } 53 | } 54 | }; 55 | 56 | private boolean isServersRunning() { 57 | return ExHttpServer.isRunning(); 58 | } 59 | 60 | public static final String ACTION_BIND_BY_ACTIVITY = "com.sand.airinput.action.bind_by_activity"; 61 | public static final String ACTION_BIND_BY_INPUT_SERVICE = "com.sand.airinput.action.bind_by_input_service"; 62 | 63 | @Override 64 | public IBinder onBind(Intent intent) { 65 | logger.debug("onBind: "); 66 | 67 | // if (intent == null || TextUtils.isEmpty(intent.getAction())) { 68 | // return null; 69 | // } 70 | 71 | // if (ACTION_BIND_BY_ACTIVITY.equals(intent.getAction())) { 72 | // return mBinder; 73 | // } else if (ACTION_BIND_BY_INPUT_SERVICE.equals(intent.getAction())) { 74 | // return mBinder; 75 | // } 76 | 77 | if (!isServersRunning()) { 78 | startBackServersInThread(); 79 | } 80 | 81 | ExWebSocketServer ws = ExWebSocketServer.getRunningServer(); 82 | if (ws != null) { 83 | ws.setListener(mWSClientListener); 84 | } 85 | return mBinder; 86 | } 87 | private ExWebSocketServer.ClientListener mWSClientListener = new ExWebSocketServer.ClientListener() { 88 | @Override 89 | public void onOpen(WebSocket conn, ClientHandshake handshake) { 90 | BackService.this.onOpen(); 91 | } 92 | 93 | @Override 94 | public void onClose(WebSocket conn, int code, String reason, boolean remote) { 95 | 96 | } 97 | 98 | @Override 99 | public void onMessage(WebSocket conn, String message) { 100 | BackService.this.onMessage(message); 101 | } 102 | 103 | @Override 104 | public void onError(WebSocket conn, Exception ex) { 105 | 106 | } 107 | }; 108 | 109 | private void onOpen() { 110 | try { 111 | if (mBackServiceListener != null) { 112 | mBackServiceListener.onOpen(); 113 | } 114 | } catch (Exception e) { 115 | 116 | } 117 | } 118 | private void onMessage(String message) { 119 | 120 | try { 121 | if (mBackServiceListener != null) { 122 | mBackServiceListener.onMessage(message); 123 | } 124 | } catch (Exception e) { 125 | 126 | } 127 | } 128 | 129 | private void startBackServersInThread() { 130 | logger.debug("Start severs..."); 131 | new Thread() { 132 | @Override 133 | public void run() { 134 | startHttpServer(); 135 | startWebSocketServer(); 136 | onBackServerStart(); 137 | } 138 | }.start(); 139 | } 140 | 141 | private void startWebSocketServer() { 142 | try { 143 | mServer = ExWebSocketServer.getRunningServer(); 144 | if (mServer != null) { 145 | mServer.setListener(null); 146 | ExWebSocketServer.stopServer(); 147 | } 148 | mServer = ExWebSocketServer.startNewServer(ExWebSocketServer.PORT, mWSClientListener); 149 | } catch (Exception e) { 150 | e.printStackTrace(); 151 | logger.debug(e.getMessage()); 152 | } 153 | } 154 | 155 | private ExWebSocketServer mServer = null; 156 | 157 | public void startHttpServer() { 158 | try { 159 | //Init configs 160 | ExHttpConfig config = ExHttpConfig.getInstance(); 161 | config.init(this); 162 | 163 | ExHttpServer.startNewServer(this, config.port); 164 | config.setListeningState(); 165 | } catch (Exception e) { 166 | e.printStackTrace(); 167 | //Failed 168 | } 169 | } 170 | 171 | public void stopBackServersInThread() { 172 | new Thread() { 173 | @Override 174 | public void run() { 175 | stopHttpServer(); 176 | stopWebSocketServer(); 177 | onBackServerStop(); 178 | } 179 | }.start(); 180 | } 181 | 182 | private void stopWebSocketServer() { 183 | mServer = ExWebSocketServer.getRunningServer(); 184 | if (mServer != null) { 185 | mServer.setListener(null); 186 | try { 187 | ExWebSocketServer.stopServer(); 188 | } catch (Exception e) { 189 | e.printStackTrace(); 190 | } 191 | } 192 | } 193 | 194 | public void stopHttpServer() { 195 | ExHttpConfig.getInstance().setStoppedState(); 196 | ExHttpServer.stopServer(); 197 | } 198 | private void onBackServerStart() { 199 | 200 | try{ 201 | if (mBackServiceListener != null) { 202 | mBackServiceListener.onStart(); 203 | } 204 | //ServerNotification.showServerNotification(this); 205 | } catch (Exception e) { 206 | 207 | } 208 | } 209 | private void onBackServerStop() { 210 | 211 | try{ 212 | if (mBackServiceListener != null) { 213 | mBackServiceListener.onStop(); 214 | } 215 | //ServerNotification.cancelAll(this); 216 | } catch (Exception e) { 217 | 218 | } 219 | 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput; 2 | 3 | import android.app.Activity; 4 | import android.content.ComponentName; 5 | import android.content.Intent; 6 | import android.content.ServiceConnection; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.os.IBinder; 10 | import android.os.RemoteException; 11 | import android.view.View; 12 | import android.widget.Button; 13 | import android.widget.TextView; 14 | import android.widget.ViewFlipper; 15 | import com.buscode.whatsinput.server.BackServiceBinder; 16 | import com.buscode.whatsinput.server.BackServiceListener; 17 | import com.buscode.whatsinput.server.ExHttpConfig; 18 | import org.apache.log4j.Logger; 19 | 20 | /** 21 | * User: fanxu 22 | * Date: 12-10-31 23 | */ 24 | public class MainActivity extends Activity { 25 | 26 | //logger 27 | private Logger logger = Logger.getLogger("MainActivity"); 28 | 29 | public Logger getLogger() { 30 | return logger; 31 | } 32 | public void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.main); 35 | 36 | findViews(); 37 | initStates(); 38 | } 39 | 40 | private void bindService() { 41 | Intent intent = new Intent(this, BackService.class); 42 | bindService(intent, mServiceConn, BIND_AUTO_CREATE); 43 | } 44 | 45 | private void unbindService() { 46 | unbindService(mServiceConn); 47 | } 48 | private BackServiceBinder mBinder = null; 49 | 50 | private ServiceConnection mServiceConn = new ServiceConnection() { 51 | @Override 52 | public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 53 | logger.debug("onServiceConnected: "); 54 | mBinder = BackServiceBinder.Stub.asInterface(iBinder); 55 | registerHttpServiceListener(); 56 | 57 | tryToStartHttpService(); 58 | updateState(); 59 | } 60 | 61 | @Override 62 | public void onServiceDisconnected(ComponentName componentName) { 63 | logger.debug("onServiceDisconnected: "); 64 | mBinder = null; 65 | } 66 | }; 67 | 68 | private void tryToStartHttpService() { 69 | ExHttpConfig config = ExHttpConfig.getInstance(); 70 | if (config.state == ExHttpConfig.STATE_STOPPED) { 71 | startHttpService(); 72 | } 73 | } 74 | 75 | private void registerHttpServiceListener() { 76 | if (mBinder == null) { 77 | return; 78 | } 79 | try { 80 | mBinder.registerListener(mHttpServiceListener); 81 | logger.debug("registerHttpServiceListener: succeed."); 82 | } catch (RemoteException e) { 83 | e.printStackTrace(); 84 | logger.error("onServiceConnected: Bind failed....", e); 85 | } 86 | } 87 | private void unRegisterHttpServiceListener() { 88 | if (mBinder == null) { 89 | return; 90 | } 91 | try { 92 | mBinder.registerListener(null); 93 | logger.debug("unRegisterHttpServiceListener: "); 94 | } catch (RemoteException e) { 95 | e.printStackTrace(); 96 | } 97 | } 98 | 99 | private BackServiceListener.Stub mHttpServiceListener = new BackServiceListener.Stub() { 100 | @Override 101 | public void onStart() throws RemoteException { 102 | logger.debug("HttpServiceListener.onStart: "); 103 | updateState(); 104 | } 105 | 106 | @Override 107 | public void onStop() throws RemoteException { 108 | logger.debug("HttpServiceListener.onStop: "); 109 | updateState(); 110 | } 111 | 112 | @Override 113 | public void onMessage(String msg) throws RemoteException { 114 | } 115 | 116 | @Override 117 | public void onOpen() throws RemoteException { 118 | 119 | } 120 | }; 121 | 122 | private void findViews() { 123 | vfContent = (ViewFlipper) findViewById(R.id.vfContent); 124 | } 125 | @Override 126 | protected void onDestroy() { 127 | super.onDestroy(); 128 | 129 | } 130 | 131 | @Override 132 | protected void onResume() { 133 | super.onResume(); 134 | bindService(); 135 | updateState(); 136 | } 137 | 138 | @Override 139 | protected void onPause() { 140 | super.onPause(); 141 | //unRegisterHttpServiceListener(); 142 | unbindService(); 143 | } 144 | 145 | private ViewFlipper vfContent; 146 | 147 | public static final int CI_LOADING = 0; 148 | public static final int CI_START = 1; 149 | public static final int CI_RUNNING = 2; 150 | 151 | public void setDisplayContent(int index) { 152 | if (index != vfContent.getDisplayedChild()) { 153 | vfContent.setDisplayedChild(index); 154 | } 155 | } 156 | 157 | private Handler mHandler = new Handler(); 158 | 159 | public Handler getHandler() { 160 | return mHandler; 161 | } 162 | 163 | public boolean startHttpService() { 164 | if (mBinder == null) { 165 | return false; 166 | } 167 | try { 168 | mBinder.startBackService(); 169 | return true; 170 | } catch (RemoteException e) { 171 | e.printStackTrace(); 172 | } 173 | return false; 174 | } 175 | 176 | public boolean stopHttpService() { 177 | if (mBinder == null) { 178 | return false; 179 | } 180 | try { 181 | mBinder.stopBackService(); 182 | return true; 183 | } catch (RemoteException e) { 184 | e.printStackTrace(); 185 | } 186 | return false; 187 | } 188 | 189 | private ConnectionState mStartState, mLoadingState, mRunningState; 190 | 191 | private void initStates() { 192 | mStartState = new StartState(); 193 | mLoadingState = new LoadingState(); 194 | mRunningState = new RunningState(); 195 | 196 | mStartState.init(this); 197 | mLoadingState.init(this); 198 | mRunningState.init(this); 199 | } 200 | public void showLoadingState() { 201 | show(mLoadingState); 202 | } 203 | public void showStartState() { 204 | show(mStartState); 205 | } 206 | public void showRunningState() { 207 | show(mRunningState); 208 | } 209 | private ConnectionState mLastState; 210 | private void show(ConnectionState state) { 211 | if (state == mLastState) { 212 | return; 213 | } 214 | mLastState = state; 215 | mLastState.show(); 216 | logger.debug("show: " + state.toString()); 217 | } 218 | public synchronized void updateState() { 219 | int state = ExHttpConfig.getInstance().state; 220 | if (state == ExHttpConfig.STATE_LISTENING) { 221 | showRunningState(); 222 | } else { 223 | showStartState(); 224 | } 225 | } 226 | } 227 | 228 | interface ConnectionState { 229 | public void init(MainActivity activity); 230 | public void show(); 231 | } 232 | 233 | abstract class AbstractConnectionState implements ConnectionState{ 234 | MainActivity mActivity; 235 | @Override 236 | public void init(MainActivity activity) { 237 | mActivity = activity; 238 | } 239 | 240 | View findViewById( int id ) { 241 | return mActivity.findViewById(id); 242 | } 243 | } 244 | 245 | class StartState extends AbstractConnectionState implements View.OnClickListener { 246 | 247 | private Button btnStart; 248 | 249 | @Override 250 | public void init(MainActivity activity) { 251 | super.init(activity); 252 | 253 | btnStart = (Button) findViewById(R.id.btnStart); 254 | btnStart.setOnClickListener(this); 255 | } 256 | 257 | @Override 258 | public void onClick(View view) { 259 | mActivity.showLoadingState(); 260 | //OnStart clicked... 261 | if (!mActivity.startHttpService()) { 262 | mActivity.getLogger().debug("Failed to start http service..."); 263 | mActivity.showStartState(); 264 | return; 265 | } 266 | } 267 | 268 | @Override 269 | public void show() { 270 | mActivity.setDisplayContent(MainActivity.CI_START); 271 | } 272 | } 273 | 274 | class LoadingState extends AbstractConnectionState { 275 | 276 | private Runnable mCheckLater = new Runnable() { 277 | @Override 278 | public void run() { 279 | mActivity.updateState(); 280 | } 281 | }; 282 | @Override 283 | public void show() { 284 | mActivity.setDisplayContent(MainActivity.CI_LOADING); 285 | mActivity.getHandler().postDelayed(mCheckLater, 1000 * 3); 286 | } 287 | } 288 | 289 | //Have a Stop Button 290 | class RunningState extends AbstractConnectionState implements View.OnClickListener { 291 | private Button btnStop; 292 | private TextView tvAddress; 293 | 294 | @Override 295 | public void init(MainActivity activity) { 296 | super.init(activity); 297 | 298 | btnStop = (Button) findViewById(R.id.btnStop); 299 | btnStop.setOnClickListener(this); 300 | 301 | tvAddress = (TextView) findViewById(R.id.tvAddress); 302 | } 303 | 304 | @Override 305 | public void show() { 306 | mActivity.setDisplayContent(MainActivity.CI_RUNNING); 307 | 308 | tvAddress.setText(ExHttpConfig.getInstance().getLocalAddress()); 309 | } 310 | 311 | @Override 312 | public void onClick(View view) { 313 | mActivity.showLoadingState(); 314 | if (!mActivity.stopHttpService()) { 315 | mActivity.getLogger().debug("Failed to Stop http Service..."); 316 | mActivity.showRunningState(); 317 | return; 318 | } 319 | } 320 | } -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.preference.Preference; 7 | import android.preference.Preference.OnPreferenceClickListener; 8 | import android.preference.PreferenceActivity; 9 | import android.provider.Settings; 10 | import android.text.TextUtils; 11 | import android.view.inputmethod.InputMethodInfo; 12 | import android.view.inputmethod.InputMethodManager; 13 | 14 | import java.util.List; 15 | 16 | public class SettingsActivity extends PreferenceActivity { 17 | 18 | private Preference add_to_list, enable_airinput; 19 | @Override 20 | public void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | 23 | addPreferencesFromResource(R.xml.settings); 24 | } 25 | 26 | @Override 27 | protected void onResume() { 28 | super.onResume(); 29 | initAddTolist(); 30 | initEnableAirInput(); 31 | } 32 | 33 | private void initEnableAirInput() { 34 | enable_airinput = findPreference("enable_airinput"); 35 | enable_airinput.setEnabled(isAirInputInTheList()); 36 | enable_airinput.setOnPreferenceClickListener(new OnPreferenceClickListener() { 37 | 38 | @Override 39 | public boolean onPreferenceClick(Preference preference) { 40 | InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 41 | inputMethodManager.showInputMethodPicker(); 42 | return false; 43 | } 44 | }); 45 | } 46 | 47 | private void initAddTolist() { 48 | add_to_list = findPreference("add_to_list"); 49 | 50 | add_to_list.setOnPreferenceClickListener(new OnPreferenceClickListener() { 51 | 52 | @Override 53 | public boolean onPreferenceClick(Preference preference) { 54 | Intent intent = new Intent(android.provider.Settings.ACTION_INPUT_METHOD_SETTINGS); 55 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 56 | startActivity(intent); 57 | 58 | overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); 59 | return false; 60 | } 61 | }); 62 | } 63 | 64 | boolean isAirInputInTheList() { 65 | InputMethodManager imm = getInputMethodManager(); 66 | List list = imm.getEnabledInputMethodList(); 67 | for (InputMethodInfo info : list) { 68 | if (getPackageName().equals(info.getPackageName())) { 69 | return true; 70 | } 71 | } 72 | return false; 73 | } 74 | 75 | boolean isCurrentAirInput() { 76 | String current = Settings.Secure.getString(getContentResolver(), 77 | Settings.Secure.DEFAULT_INPUT_METHOD); 78 | return !TextUtils.isEmpty(current) 79 | && current.startsWith(getPackageName()); 80 | } 81 | 82 | InputMethodManager getInputMethodManager() { 83 | return (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/WifiInputMethod.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.AlertDialog; 5 | import android.content.*; 6 | import android.content.DialogInterface.OnClickListener; 7 | import android.inputmethodservice.InputMethodService; 8 | import android.os.*; 9 | import android.text.TextUtils; 10 | import android.view.LayoutInflater; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.view.WindowManager; 14 | import android.view.inputmethod.EditorInfo; 15 | import android.view.inputmethod.ExtractedTextRequest; 16 | import android.view.inputmethod.InputConnection; 17 | import android.view.inputmethod.InputMethodManager; 18 | import android.widget.PopupMenu; 19 | import android.widget.PopupMenu.OnMenuItemClickListener; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | import com.buscode.whatsinput.beans.*; 23 | import com.buscode.whatsinput.common.Net; 24 | import com.buscode.whatsinput.server.BackServiceBinder; 25 | import com.buscode.whatsinput.server.BackServiceListener; 26 | import com.buscode.whatsinput.server.ExHttpConfig; 27 | import org.apache.log4j.Logger; 28 | import org.json.JSONObject; 29 | 30 | /** 31 | * User: fanxu 32 | * Date: 12-10-26 33 | */ 34 | public class WifiInputMethod extends InputMethodService { 35 | 36 | //logger 37 | private Logger logger = Logger.getLogger("WifiInputMethod"); 38 | 39 | private boolean mIsEditing = false; 40 | private TextView tvAddress; 41 | 42 | 43 | public boolean isEditing() { 44 | return mIsEditing; 45 | } 46 | private MsgParser mMsgParser = new MsgParser(logger); 47 | 48 | private BackServiceListener.Stub mBackServiceListener = new BackServiceListener.Stub() { 49 | @Override 50 | public void onStart() throws RemoteException { 51 | logger.debug("HttpServiceListener.onStart: "); 52 | } 53 | 54 | @Override 55 | public void onStop() throws RemoteException { 56 | logger.debug("HttpServiceListener.onStop: "); 57 | } 58 | 59 | @Override 60 | public void onMessage(String msg) throws RemoteException { 61 | // logger.debug("onMessage: " + msg); 62 | mMsgParser.onMessage(WifiInputMethod.this, msg); 63 | } 64 | 65 | @Override 66 | public void onOpen() throws RemoteException { 67 | if (isEditing()) { 68 | InputStart msg = new InputStart(); 69 | msg.text = getText(); 70 | 71 | sendMessage(msg.toJson()); 72 | } 73 | } 74 | }; 75 | 76 | public void sendMessage(String msg){ 77 | if (mBinder == null) { 78 | return; 79 | } 80 | try { 81 | mBinder.sendMessage(msg); 82 | } catch (Exception e) { 83 | 84 | } 85 | } 86 | 87 | private BackServiceBinder mBinder = null; 88 | private void registerBackServiceListener() { 89 | if (mBinder == null) { 90 | return; 91 | } 92 | try { 93 | mBinder.registerListener(mBackServiceListener); 94 | logger.debug("registerBackServiceListener: succeed."); 95 | } catch (RemoteException e) { 96 | e.printStackTrace(); 97 | logger.error("onServiceConnected: Bind failed....", e); 98 | } 99 | } 100 | private void unRegisterBackServiceListener() { 101 | if (mBinder == null) { 102 | return; 103 | } 104 | try { 105 | mBinder.registerListener(null); 106 | logger.debug("unRegisterBackServiceListener: "); 107 | } catch (RemoteException e) { 108 | e.printStackTrace(); 109 | } 110 | } 111 | private ServiceConnection mServiceConn = new ServiceConnection() { 112 | @Override 113 | public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 114 | logger.debug("onServiceConnected: "); 115 | mBinder = BackServiceBinder.Stub.asInterface(iBinder); 116 | registerBackServiceListener(); 117 | } 118 | 119 | @Override 120 | public void onServiceDisconnected(ComponentName componentName) { 121 | logger.debug("onServiceDisconnected: "); 122 | mBinder = null; 123 | } 124 | }; 125 | private void bindBackService() { 126 | Intent intent = new Intent(this, BackService.class); 127 | bindService(intent, mServiceConn, BIND_AUTO_CREATE); 128 | } 129 | private void unbindService() { 130 | unRegisterBackServiceListener(); 131 | unbindService(mServiceConn); 132 | } 133 | @Override 134 | public void onCreate() { 135 | super.onCreate(); 136 | bindBackService(); 137 | } 138 | @Override 139 | public void onDestroy() { 140 | super.onDestroy(); 141 | 142 | unbindService(); 143 | } 144 | 145 | private View mContent; 146 | 147 | private View ivRestart; 148 | 149 | @Override 150 | public View onCreateInputView() { 151 | 152 | mContent = LayoutInflater.from(this).inflate(R.layout.wifi_input_method_view, null); 153 | tvAddress = (TextView) mContent.findViewById(R.id.tvIP); 154 | tvAddress.setText(ExHttpConfig.getInstance().getLocalAddress()); 155 | 156 | ivRestart = mContent.findViewById(R.id.ivRestart); 157 | ivRestart.setOnClickListener(mOnRestartClicked); 158 | 159 | return mContent; 160 | } 161 | 162 | private Handler mHandler = new Handler(); 163 | 164 | void postRunnable(Runnable r) { 165 | mHandler.post(r); 166 | } 167 | private View.OnClickListener mOnRestartClicked = new View.OnClickListener() { 168 | @Override 169 | public void onClick(View view) { 170 | if (Build.VERSION.SDK_INT >= 11) { 171 | showPopupMenu(view); 172 | } else { 173 | showAlertDialogList(); 174 | } 175 | } 176 | }; 177 | 178 | private void showAlertDialogList() { 179 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 180 | 181 | builder.setItems(R.array.popup_list, new OnClickListener() { 182 | 183 | @Override 184 | public void onClick(DialogInterface dialog, int which) { 185 | if (which == 0) { //restart server 186 | restartBackService(); 187 | } else if (which == 1) { //change input 188 | changeInputMethod(); 189 | } 190 | } 191 | }); 192 | builder.setPositiveButton(R.string.cancel, null); 193 | AlertDialog ad = builder.create(); 194 | ad.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 195 | ad.setCanceledOnTouchOutside(false); 196 | ad.show(); 197 | } 198 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 199 | private void showPopupMenu(View view) { 200 | PopupMenu pm = new PopupMenu(WifiInputMethod.this, view); 201 | pm.getMenuInflater().inflate(R.menu.popup, pm.getMenu()); 202 | pm.setOnMenuItemClickListener(new OnMenuItemClickListener() { 203 | 204 | @Override 205 | public boolean onMenuItemClick(MenuItem item) { 206 | switch (item.getItemId()) { 207 | case R.id.restart: 208 | { 209 | restartBackService(); 210 | } 211 | break; 212 | case R.id.change: { 213 | changeInputMethod(); 214 | } 215 | default: 216 | break; 217 | } 218 | return false; 219 | } 220 | 221 | 222 | }); 223 | pm.show(); 224 | } 225 | private void changeInputMethod() { 226 | InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 227 | inputMethodManager.showInputMethodPicker(); 228 | } 229 | private void restartBackService() { 230 | new Thread() { 231 | @Override 232 | public void run() { 233 | try { 234 | if (mBinder != null) { 235 | mBinder.stopBackService(); 236 | 237 | Thread.sleep(1000); 238 | 239 | mBinder.startBackService(); 240 | 241 | postRunnable(new Runnable() { 242 | @Override 243 | public void run() { 244 | Toast.makeText(WifiInputMethod.this, R.string.restart_finished, Toast.LENGTH_LONG).show(); 245 | tvAddress.setText(ExHttpConfig.getInstance().getLocalAddress()); 246 | } 247 | }); 248 | } 249 | } catch (Exception e) { 250 | 251 | e.printStackTrace(); 252 | logger.debug(e.getMessage()); 253 | } 254 | } 255 | }.start(); 256 | } 257 | 258 | @Override 259 | public void onStartInputView(EditorInfo info, boolean restarting) { 260 | super.onStartInputView(info, restarting); 261 | mIsEditing = true; 262 | acquireLock(); 263 | 264 | if (Net.WiFi.isWifiConnected(this)) { 265 | String address = ExHttpConfig.getInstance().getLocalAddress(); 266 | if (TextUtils.isEmpty(address)) { 267 | restartBackService(); 268 | tvAddress.setText(R.string.restarting); 269 | } else { 270 | tvAddress.setText(address); 271 | } 272 | } else { 273 | tvAddress.setText(R.string.only_work_in_wifi_); 274 | } 275 | 276 | String text = getText(); 277 | makeToast("onStartInputView: text--" + text); 278 | 279 | InputStart msg = new InputStart(); 280 | msg.text = text; 281 | sendMessage(msg.toJson()); 282 | } 283 | 284 | private PowerManager.WakeLock mWakeLock; 285 | public static final String TAG_LOCK = "InputMethod"; 286 | 287 | synchronized void acquireLock() { 288 | if (mWakeLock != null) { 289 | return; 290 | } 291 | PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 292 | mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_LOCK); 293 | mWakeLock.acquire(); 294 | } 295 | synchronized void releaseWakeLock() { 296 | if (mWakeLock != null) { 297 | mWakeLock.release(); 298 | mWakeLock = null; 299 | } 300 | } 301 | public String getText() { 302 | String text = ""; 303 | try { 304 | InputConnection conn = getCurrentInputConnection(); 305 | ExtractedTextRequest req = new ExtractedTextRequest(); 306 | req.hintMaxChars = 1000000; 307 | req.hintMaxLines = 10000; 308 | req.flags = 0; 309 | req.token = 1; 310 | text = conn.getExtractedText(req, 0).text.toString(); 311 | } catch (Throwable t) { 312 | } 313 | return text; 314 | } 315 | public boolean setText(String text) { 316 | // FIXME: need feedback if the input was lost 317 | InputConnection conn = getCurrentInputConnection(); 318 | if (conn == null) { 319 | // Debug.d("connection closed"); 320 | return false; 321 | } 322 | 323 | conn.beginBatchEdit(); 324 | // FIXME: hack 325 | conn.deleteSurroundingText(100000, 100000); 326 | conn.commitText(text, text.length()); 327 | conn.endBatchEdit(); 328 | return true; 329 | } 330 | ExtractedTextRequest req = new ExtractedTextRequest(); 331 | { 332 | req.hintMaxChars = 100000; 333 | req.hintMaxLines = 10000; 334 | } 335 | void makeToast(String msg) { 336 | // Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); 337 | } 338 | 339 | 340 | @Override 341 | public void onFinishInputView(boolean finishingInput) { 342 | super.onFinishInputView(finishingInput); 343 | mIsEditing = false; 344 | releaseWakeLock(); 345 | 346 | makeToast("onFinishInputView" + finishingInput); 347 | sendMessage(new InputFinish().toJson()); 348 | } 349 | } 350 | class MsgParser { 351 | 352 | public MsgParser(Logger logger) { 353 | this.logger = logger; 354 | } 355 | //logger 356 | private Logger logger; 357 | 358 | public void onMessage(WifiInputMethod service, String msg) { 359 | 360 | if (service == null || TextUtils.isEmpty(msg)) { 361 | return; 362 | } 363 | // logger.debug("onMessage: " + msg);// 364 | try { 365 | String type = getType(msg); 366 | // logger.debug("New Msg: " + type); 367 | Jsoner jsoner = Jsoner.getInstance(); 368 | if (InputEdit.TYPE.equals(type)) { 369 | InputEdit ie = jsoner.fromJson(msg, InputEdit.class); 370 | ie.onMessage(service); 371 | } else if (InputUpdate.TYPE.equals(type)) { 372 | InputUpdate iu = jsoner.fromJson(msg, InputUpdate.class); 373 | iu.onMessage(service); 374 | } else if (InputKey.TYPE.equals(type)) { 375 | InputKey ik = jsoner.fromJson(msg, InputKey.class); 376 | ik.onMessage(service); 377 | } 378 | } catch (Exception e) { 379 | e.printStackTrace(); 380 | logger.debug(e.getMessage()); 381 | } 382 | } 383 | 384 | public String getType(String msg) throws Exception{ 385 | JSONObject jo = new JSONObject(msg); 386 | return jo.optString("type", ""); 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/beans/AbstractMsg.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.beans; 2 | 3 | /** 4 | * User: fanxu 5 | * Date: 12-10-28 6 | */ 7 | public class AbstractMsg extends Jsonable { 8 | 9 | public String type = ""; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/beans/InputChange.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.beans; 2 | 3 | /** 4 | * User: fanxu 5 | * Date: 12-11-1 6 | */ 7 | public class InputChange extends AbstractMsg { 8 | public static final String TYPE = "InputChange"; 9 | 10 | { 11 | type = TYPE; 12 | } 13 | public String text = ""; 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/beans/InputEdit.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.beans; 2 | 3 | import com.buscode.whatsinput.WifiInputMethod; 4 | 5 | /** 6 | * User: fanxu 7 | * Date: 12-10-28 8 | */ 9 | public class InputEdit extends AbstractMsg { 10 | public static final String TYPE = "InputEdit"; 11 | 12 | public String text = ""; 13 | 14 | public void onMessage(WifiInputMethod service) { 15 | service.setText(text); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/beans/InputFinish.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.beans; 2 | 3 | /** 4 | * User: fanxu 5 | * Date: 12-10-28 6 | */ 7 | public class InputFinish extends AbstractMsg { 8 | 9 | public static final String TYPE = "InputFinish"; 10 | 11 | { 12 | type = TYPE; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/beans/InputKey.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.beans; 2 | 3 | import android.view.KeyEvent; 4 | import android.view.inputmethod.InputConnection; 5 | 6 | import com.buscode.whatsinput.WifiInputMethod; 7 | 8 | /** 9 | * User: fanxu 10 | * Date: 12-10-30 11 | */ 12 | public class InputKey extends AbstractMsg { 13 | public static final String TYPE = "InputKey"; 14 | { 15 | type = TYPE; 16 | } 17 | public int code = -1; 18 | 19 | public void onMessage(WifiInputMethod service) { 20 | if (code < 0) { 21 | return; 22 | } 23 | 24 | InputConnection ic = service.getCurrentInputConnection(); 25 | ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, code)); 26 | sleep(100); 27 | ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, code)); 28 | 29 | sleep(500); 30 | 31 | InputChange change = new InputChange(); 32 | change.text = service.getText(); 33 | 34 | service.sendMessage(change.toJson()); 35 | } 36 | 37 | private void sleep(long t) { 38 | try { 39 | Thread.sleep(t); 40 | } catch (InterruptedException e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/beans/InputStart.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.beans; 2 | 3 | /** 4 | * User: fanxu 5 | * Date: 12-10-28 6 | */ 7 | public class InputStart extends AbstractMsg { 8 | public static final String TYPE = "InputStart"; 9 | 10 | { 11 | //Init Type; 12 | type = TYPE; 13 | } 14 | public String text; 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/beans/InputUpdate.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.beans; 2 | 3 | import com.buscode.whatsinput.WifiInputMethod; 4 | 5 | /** 6 | * User: fanxu 7 | * Date: 12-10-28 8 | */ 9 | public class InputUpdate extends AbstractMsg { 10 | 11 | public static final String TYPE = "InputUpdate"; 12 | 13 | public void onMessage(WifiInputMethod service) { 14 | 15 | InputStart msg = new InputStart(); 16 | if (service.isEditing()) { 17 | msg.text = service.getText(); 18 | } 19 | service.sendMessage(msg.toJson()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/beans/Jsonable.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.beans; 2 | 3 | public class Jsonable { 4 | 5 | public String toJson() { 6 | return Jsoner.getInstance().toJson(this); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/beans/Jsoner.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.beans; 2 | 3 | import com.google.gson.Gson; 4 | 5 | public class Jsoner { 6 | 7 | private static Jsoner sInstance; 8 | 9 | public static Jsoner getInstance() { 10 | if (sInstance == null) { 11 | sInstance = new Jsoner(); 12 | } 13 | return sInstance; 14 | } 15 | private Gson mGson; 16 | 17 | private Jsoner() { 18 | mGson = new Gson(); 19 | } 20 | 21 | public String toJson(Object obj) { 22 | return mGson.toJson(obj); 23 | } 24 | 25 | public T fromJson(String s, Class cls) { 26 | return mGson.fromJson(s, cls); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/common/ActivityPiece.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.common; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | 7 | 8 | public interface ActivityPiece { 9 | 10 | /** 11 | * Set this activity 12 | * @param activity 13 | */ 14 | public void setActivity(Activity activity); 15 | 16 | /** 17 | * @param savedInstanceState 18 | * @see {@link android.app.Activity#onCreate(android.os.Bundle savedInstanceState)} 19 | */ 20 | public void onCreate(Bundle savedInstanceState); 21 | 22 | /** 23 | * @see {@link android.app.Activity#onDestroy()} 24 | */ 25 | public void onDestroy(); 26 | /** 27 | * @see {@link android.app.Activity#onResume()} 28 | */ 29 | public void onResume(); 30 | /** 31 | * @see {@link android.app.Activity#onPause()} 32 | */ 33 | public void onPause(); 34 | /** 35 | * @see {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)} 36 | */ 37 | public void onActivityResult(int requestCode, int resultCode, Intent data); 38 | } 39 | 40 | interface ActivityPieceContainer { 41 | /** 42 | * Add piece 43 | * @param piece 44 | */ 45 | public void addPiece(ActivityPiece piece); 46 | /** 47 | * remove piece 48 | * @param piece 49 | */ 50 | public void removePiece(ActivityPiece piece); 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/common/ActivityPieceManager.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.common; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | 7 | import java.util.ArrayList; 8 | 9 | 10 | public class ActivityPieceManager implements ActivityPiece, ActivityPieceContainer { 11 | 12 | private ArrayList mPieces = new ArrayList(); 13 | @Override 14 | public void addPiece(ActivityPiece piece) { 15 | if (mActivity != null) { 16 | piece.setActivity(mActivity); 17 | } 18 | mPieces.add(piece); 19 | } 20 | @Override 21 | public void removePiece(ActivityPiece piece) { 22 | mPieces.remove(piece); 23 | } 24 | 25 | @Override 26 | public void onCreate(Bundle savedInstanceState) { 27 | for (ActivityPiece piece : mPieces) { 28 | piece.onCreate(savedInstanceState); 29 | } 30 | } 31 | 32 | @Override 33 | public void onDestroy() { 34 | for (ActivityPiece piece : mPieces) { 35 | piece.onDestroy(); 36 | } 37 | mPieces.clear(); 38 | } 39 | 40 | @Override 41 | public void onResume() { 42 | for (ActivityPiece piece : mPieces) { 43 | piece.onResume(); 44 | } 45 | } 46 | 47 | @Override 48 | public void onPause() { 49 | for (ActivityPiece piece : mPieces) { 50 | piece.onPause(); 51 | } 52 | } 53 | 54 | @Override 55 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 56 | for (ActivityPiece piece : mPieces) { 57 | piece.onActivityResult(requestCode, resultCode, data); 58 | } 59 | } 60 | 61 | private Activity mActivity; 62 | @Override 63 | public void setActivity(Activity activity) { 64 | mActivity = activity; 65 | for (ActivityPiece piece : mPieces) { 66 | piece.setActivity(activity); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/common/Net.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.common; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | import android.net.wifi.WifiInfo; 7 | import android.net.wifi.WifiManager; 8 | 9 | /** 10 | * User: fanxu 11 | * Date: 12-10-31 12 | */ 13 | public class Net { 14 | public static final String NW_3G = "3g"; 15 | public static final String NW_WIFI = "wifi"; 16 | public static final String NW_NON = "non"; 17 | 18 | public static String getActiveNetwork(Context context) { 19 | 20 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 21 | NetworkInfo info = cm.getActiveNetworkInfo(); 22 | if (info != null) { 23 | if (ConnectivityManager.TYPE_MOBILE == info.getType()) { 24 | return NW_3G; 25 | } else if (ConnectivityManager.TYPE_WIFI == info.getType()){ 26 | return NW_WIFI; 27 | } 28 | } 29 | return NW_NON; 30 | } 31 | 32 | 33 | 34 | public static class WiFi { 35 | /** 36 | * Get Wifi Ip 37 | * @param context 38 | * @return 39 | */ 40 | public static String getWifiIp(Context context) { 41 | String string_ip = ""; 42 | WifiInfo wi = getWifiInfo(context); 43 | int code_ip = wi.getIpAddress(); 44 | if (code_ip != 0) { 45 | string_ip = (code_ip & 0xFF) + "." + 46 | ((code_ip >> 8) & 0xFF) + "." + 47 | ((code_ip >> 16) & 0xFF) + "." + 48 | ((code_ip >> 24) & 0xFF); 49 | } 50 | return string_ip; 51 | } 52 | 53 | /** 54 | * Whether wifi is connected 55 | * @param context 56 | * @return 57 | */ 58 | public static boolean isWifiConnected(Context context) { 59 | ConnectivityManager cm = Net.getConnectivityManager(context); 60 | 61 | return cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState() == NetworkInfo.State.CONNECTED; 62 | } 63 | private static WifiInfo getWifiInfo(Context context) { 64 | WifiManager wm = getWifiManager(context); 65 | return wm.getConnectionInfo(); 66 | } 67 | 68 | public static WifiManager getWifiManager(Context context) { 69 | return (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 70 | } 71 | } 72 | 73 | public static ConnectivityManager getConnectivityManager(Context context) { 74 | return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/common/ServicePiece.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.common; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | 7 | public interface ServicePiece { 8 | /** 9 | * @see {@link android.app.Service#onCreate()} 10 | */ 11 | public void onCreate(); 12 | /** 13 | * @see {@link android.app.Service#onDestroy()} 14 | */ 15 | public void onDestroy(); 16 | /** 17 | * @see {@link android.app.Service#onStartCommand(android.content.Intent, int, int)} 18 | */ 19 | public int onStartCommand(Intent intent, int flags, int startId); 20 | 21 | /** 22 | * !!!Only one target.!!! 23 | * Only when return true, 24 | * {@link #onStartCommand(android.content.Intent, int, int)} are exec. 25 | * @param action Intent's Action 26 | * @return 27 | */ 28 | public boolean isTarget(Intent intent); 29 | /** 30 | * Only one Binder in a Service 31 | * @see {@link android.app.Service#onCreate()} 32 | */ 33 | public IBinder onBind(Intent intent); 34 | 35 | /** 36 | * Set this service. 37 | * @param service 38 | */ 39 | public void setService(Service service); 40 | } 41 | 42 | interface ServicePieceContainer { 43 | /** 44 | * Add new piece 45 | * @param piece 46 | */ 47 | public void addPiece(ServicePiece piece); 48 | /** 49 | * Remove old piece 50 | * @param piece 51 | */ 52 | public void removePiece(ServicePiece piece); 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/common/ServicePieceManager.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.common; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class ServicePieceManager implements ServicePiece, ServicePieceContainer { 10 | 11 | @Override 12 | public void onCreate() { 13 | for (ServicePiece piece : mPieces) { 14 | piece.onCreate(); 15 | } 16 | } 17 | 18 | @Override 19 | public void onDestroy() { 20 | for (ServicePiece piece : mPieces) { 21 | piece.onDestroy(); 22 | } 23 | } 24 | 25 | @Override 26 | public IBinder onBind(Intent intent) { 27 | for (ServicePiece piece : mPieces) { 28 | IBinder binder = piece.onBind(intent); 29 | 30 | if (binder != null) { 31 | return binder; 32 | } 33 | } 34 | return null; 35 | } 36 | 37 | private Service mService; 38 | 39 | @Override 40 | public void setService(Service service) { 41 | mService = service; 42 | for (ServicePiece piece : mPieces) { 43 | piece.setService(service); 44 | } 45 | } 46 | 47 | private ArrayList mPieces = new ArrayList(); 48 | @Override 49 | public void addPiece(ServicePiece piece) { 50 | if (mService != null) { 51 | piece.setService(mService); 52 | } 53 | mPieces.add(piece); 54 | } 55 | @Override 56 | public void removePiece(ServicePiece piece) { 57 | mPieces.remove(piece); 58 | } 59 | 60 | @Override 61 | public int onStartCommand(Intent intent, int flags, int startId) { 62 | 63 | for (ServicePiece piece : mPieces) { 64 | if (piece.isTarget(intent)) { 65 | return piece.onStartCommand(intent, flags, startId); 66 | } 67 | } 68 | return Service.START_NOT_STICKY; 69 | } 70 | 71 | @Override 72 | public boolean isTarget(Intent intent) { 73 | return false; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/server/ExHttpConfig.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.server; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | import com.buscode.whatsinput.common.Net; 6 | 7 | /** 8 | * User: fanxu 9 | * Date: 12-10-31 10 | */ 11 | public class ExHttpConfig { 12 | //Single instance 13 | private ExHttpConfig(){} 14 | private static ExHttpConfig sInstance; 15 | public synchronized static ExHttpConfig getInstance() { 16 | if ( sInstance == null ) { 17 | sInstance = new ExHttpConfig(); 18 | } 19 | return sInstance; 20 | } 21 | 22 | //Ip 23 | public String ip = ""; 24 | 25 | //Port 26 | public int port = ExHttpServer.PORT; 27 | 28 | public String getLocalAddress() { 29 | if (TextUtils.isEmpty(ip)) { 30 | return ""; 31 | } 32 | return String.format("http://%s:%d", ip, port); 33 | } 34 | //Password 35 | public String pwd = ""; 36 | 37 | public static final int STATE_STOPPED = 0; 38 | public static final int STATE_LISTENING = 1; 39 | 40 | public int state = STATE_STOPPED; 41 | 42 | public void setStoppedState() { 43 | state = STATE_STOPPED; 44 | } 45 | public void setListeningState() { 46 | state = STATE_LISTENING; 47 | } 48 | 49 | public synchronized void init(Context context) throws Exception { 50 | if (!Net.WiFi.isWifiConnected(context)) { 51 | throw new IllegalArgumentException("Wifi is not connected..."); 52 | } 53 | 54 | ip = Net.WiFi.getWifiIp(context); 55 | port = ExHttpServer.PORT; 56 | 57 | //Here should read password from Preference 58 | pwd = "1234"; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/server/ExHttpServer.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.server; 2 | 3 | import android.content.Context; 4 | import org.apache.log4j.Logger; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * User: fanxu 10 | * Date: 12-10-28 11 | */ 12 | public class ExHttpServer { 13 | 14 | public static final int PORT = 6688; 15 | 16 | //logger 17 | private Logger logger = Logger.getLogger("ExHttpServer@" + hashCode()); 18 | 19 | public synchronized static ExHttpServer startNewServer(Context context, int port) throws IOException { 20 | 21 | if (sInstance == null) { 22 | sInstance = new ExHttpServer(); 23 | sInstance.start(context, port); 24 | } 25 | return sInstance; 26 | } 27 | 28 | public synchronized static void stopServer() { 29 | if (sInstance != null) { 30 | sInstance.stop(); 31 | sInstance = null; 32 | } 33 | } 34 | 35 | public static ExHttpServer getRunningServer() { 36 | return sInstance; 37 | } 38 | 39 | public static boolean isRunning() { 40 | return sInstance != null; 41 | } 42 | 43 | //Single instance 44 | private ExHttpServer(){} 45 | private static ExHttpServer sInstance; 46 | 47 | 48 | private NanoHTTPD mHttpServer; 49 | 50 | private void start(Context context, int port) throws IOException { 51 | mHttpServer = new NanoHTTPD(port, context.getAssets()); 52 | } 53 | private void stop() { 54 | if (mHttpServer != null) { 55 | mHttpServer.stop(); 56 | mHttpServer = null; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/server/ExWebSocketServer.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.server; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.java_websocket.WebSocket; 5 | import org.java_websocket.server.WebSocketServer; 6 | import org.java_websocket.handshake.ClientHandshake; 7 | 8 | import java.net.InetSocketAddress; 9 | import java.net.UnknownHostException; 10 | import java.util.Set; 11 | 12 | /** 13 | * User: fanxu 14 | * Date: 12-10-28 15 | */ 16 | public class ExWebSocketServer extends WebSocketServer{ 17 | 18 | public static interface ClientListener { 19 | public void onOpen(WebSocket conn, ClientHandshake handshake); 20 | public void onClose(WebSocket conn, int code, String reason, boolean remote); 21 | public void onMessage(WebSocket conn, String message); 22 | public void onError(WebSocket conn, Exception ex); 23 | } 24 | 25 | private static ExWebSocketServer sInstance = null; 26 | 27 | public static final int PORT = 6677; 28 | 29 | /** 30 | * Start a new Server 31 | * @param port 32 | * @return 33 | */ 34 | public synchronized static ExWebSocketServer startNewServer(int port, ClientListener listener) throws Exception{ 35 | 36 | if (sInstance == null) { 37 | 38 | //Don't have a WebSocket Server running... 39 | sInstance = new ExWebSocketServer(new InetSocketAddress(port)); 40 | sInstance.setListener(listener); 41 | sInstance.start(); 42 | } 43 | return sInstance; 44 | } 45 | public void sendMessageToAllWSClient(final String message) { 46 | 47 | final Set conns = connections(); 48 | if (conns == null || conns.size() == 0) { 49 | logger.debug("sendMessageToAllWSClient: Empty Clients"); 50 | return; 51 | } 52 | new Thread() { 53 | @Override 54 | public void run() { 55 | for(final WebSocket ws : conns) { 56 | ws.send(message); 57 | } 58 | } 59 | }.start(); 60 | } 61 | /** 62 | * Stop WebSocket Server 63 | * @throws Exception 64 | */ 65 | public synchronized static void stopServer() throws Exception { 66 | if (sInstance == null) { 67 | return; 68 | } 69 | sInstance.setListener(null); 70 | sInstance.stop(); 71 | sInstance = null; 72 | } 73 | 74 | 75 | /** 76 | * May be null... 77 | * @return 78 | */ 79 | public static ExWebSocketServer getRunningServer() { 80 | return sInstance; 81 | } 82 | //logger 83 | private Logger logger = Logger.getLogger("ExWebSocketServer@" + hashCode()); 84 | 85 | 86 | private ExWebSocketServer(InetSocketAddress address) throws UnknownHostException { 87 | super(address); 88 | } 89 | 90 | private ClientListener mClientLister = null; 91 | 92 | public void setListener(ClientListener listener) { 93 | mClientLister = listener; 94 | } 95 | 96 | @Override 97 | public void onOpen(WebSocket conn, ClientHandshake handshake) { 98 | 99 | logger.debug("onOpen: " /*+ conn.getRemoteSocketAddress().getHostName().toString()*/); 100 | 101 | if (mClientLister != null) { 102 | mClientLister.onOpen(conn, handshake); 103 | } 104 | } 105 | 106 | @Override 107 | public void onClose(WebSocket conn, int code, String reason, boolean remote) { 108 | logger.debug("onClose: "); 109 | if (mClientLister != null) { 110 | mClientLister.onClose(conn, code, reason, remote); 111 | } 112 | } 113 | 114 | @Override 115 | public void onMessage(WebSocket conn, String message) { 116 | logger.debug("onMessage: " + message); 117 | if (mClientLister != null) { 118 | mClientLister.onMessage(conn, message); 119 | } 120 | } 121 | 122 | @Override 123 | public void onError(WebSocket conn, Exception ex) { 124 | logger.debug("onError: "); 125 | if (mClientLister != null) { 126 | mClientLister.onError(conn, ex); 127 | } 128 | } 129 | } 130 | 131 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/server/NanoHTTPD.java: -------------------------------------------------------------------------------- 1 | package com.buscode.whatsinput.server; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.ByteArrayInputStream; 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.io.OutputStream; 11 | import java.io.PrintWriter; 12 | import java.net.ServerSocket; 13 | import java.net.Socket; 14 | import java.net.URLEncoder; 15 | import java.util.Date; 16 | import java.util.Enumeration; 17 | import java.util.Vector; 18 | import java.util.Hashtable; 19 | import java.util.Locale; 20 | import java.util.Properties; 21 | import java.util.StringTokenizer; 22 | import java.util.TimeZone; 23 | 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.FileOutputStream; 26 | 27 | import android.content.res.Resources; 28 | import android.content.res.AssetManager; 29 | import android.content.res.AssetFileDescriptor; 30 | import android.util.Log; 31 | 32 | /** 33 | * A simple, tiny, nicely embeddable HTTP 1.0 (partially 1.1) server in Java 34 | * 35 | *

NanoHTTPD version 1.24, 36 | * Copyright © 2001,2005-2011 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/) 37 | * and Copyright © 2010 Konstantinos Togias (info@ktogias.gr, http://ktogias.gr) 38 | * 39 | *

Features + limitations:

    40 | * 41 | *
  • Only one Java file
  • 42 | *
  • Java 1.1 compatible
  • 43 | *
  • Released as open source, Modified BSD licence
  • 44 | *
  • No fixed config files, logging, authorization etc. (Implement yourself if you need them.)
  • 45 | *
  • Supports parameter parsing of GET and POST methods
  • 46 | *
  • Supports both dynamic content and file serving
  • 47 | *
  • Supports file upload (since version 1.2, 2010)
  • 48 | *
  • Supports partial content (streaming)
  • 49 | *
  • Supports ETags
  • 50 | *
  • Never caches anything
  • 51 | *
  • Doesn't limit bandwidth, request time or simultaneous connections
  • 52 | *
  • Default code serves files and shows all HTTP parameters and headers
  • 53 | *
  • File server supports directory listing, index.html and index.htm
  • 54 | *
  • File server supports partial content (streaming)
  • 55 | *
  • File server supports ETags
  • 56 | *
  • File server does the 301 redirection trick for directories without '/'
  • 57 | *
  • File server supports simple skipping for files (continue download)
  • 58 | *
  • File server serves also very long files without memory overhead
  • 59 | *
  • Contains a built-in list of most common mime types
  • 60 | *
  • All header names are converted lowercase so they don't vary between browsers/clients
  • 61 | * 62 | *
63 | * 64 | *

Ways to use:

    65 | * 66 | *
  • Run as a standalone app, serves files and shows requests
  • 67 | *
  • Subclass serve() and embed to your own program
  • 68 | *
  • Call serveFile() from serve() with your own base directory
  • 69 | * 70 | *
71 | * 72 | * See the end of the source file for distribution license 73 | * (Modified BSD licence) 74 | */ 75 | public class NanoHTTPD 76 | { 77 | // ================================================== 78 | // API parts 79 | // ================================================== 80 | 81 | /** 82 | * Override this to customize the server.

83 | * 84 | * (By default, this delegates to serveFile() and allows directory listing.) 85 | * 86 | * @param uri Percent-decoded URI without parameters, for example "/index.cgi" 87 | * @param method "GET", "POST" etc. 88 | * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data. 89 | * @param header Header entries, percent decoded 90 | * @return HTTP response, see class Response for details 91 | */ 92 | public Response serve( String uri, String method, Properties header, Properties parms, Properties files ) 93 | { 94 | System.out.println( method + " '" + uri + "' " ); 95 | 96 | Enumeration e = header.propertyNames(); 97 | while ( e.hasMoreElements()) 98 | { 99 | String value = (String)e.nextElement(); 100 | System.out.println( " HDR: '" + value + "' = '" + 101 | header.getProperty( value ) + "'" ); 102 | } 103 | e = parms.propertyNames(); 104 | while ( e.hasMoreElements()) 105 | { 106 | String value = (String)e.nextElement(); 107 | System.out.println( " PRM: '" + value + "' = '" + 108 | parms.getProperty( value ) + "'" ); 109 | } 110 | e = files.propertyNames(); 111 | while ( e.hasMoreElements()) 112 | { 113 | String value = (String)e.nextElement(); 114 | System.out.println( " UPLOADED: '" + value + "' = '" + 115 | files.getProperty( value ) + "'" ); 116 | } 117 | 118 | return serveFile( uri, header, myRootDir, true ); 119 | } 120 | 121 | /** 122 | * Override this to handle a request is done. 123 | * 124 | * (By default, do nothing) 125 | * 126 | */ 127 | public void serveDone(Response r) { 128 | } 129 | 130 | /** 131 | * HTTP response. 132 | * Return one of these from serve(). 133 | */ 134 | public class Response 135 | { 136 | /** 137 | * Default constructor: response = HTTP_OK, data = mime = 'null' 138 | */ 139 | public Response() 140 | { 141 | this.status = HTTP_OK; 142 | } 143 | 144 | /** 145 | * Basic constructor. 146 | */ 147 | public Response( String status, String mimeType, InputStream data ) 148 | { 149 | this.status = status; 150 | this.mimeType = mimeType; 151 | this.data = data; 152 | } 153 | 154 | /** 155 | * Convenience method that makes an InputStream out of 156 | * given text. 157 | */ 158 | public Response( String status, String mimeType, String txt ) 159 | { 160 | this.status = status; 161 | this.mimeType = mimeType; 162 | try 163 | { 164 | this.data = new ByteArrayInputStream( txt.getBytes("UTF-8")); 165 | } 166 | catch ( java.io.UnsupportedEncodingException uee ) 167 | { 168 | uee.printStackTrace(); 169 | } 170 | } 171 | 172 | /** 173 | * Adds given line to the header. 174 | */ 175 | public void addHeader( String name, String value ) 176 | { 177 | header.put( name, value ); 178 | } 179 | 180 | /** 181 | * HTTP status code after processing, e.g. "200 OK", HTTP_OK 182 | */ 183 | public String status; 184 | 185 | /** 186 | * MIME type of content, e.g. "text/html" 187 | */ 188 | public String mimeType; 189 | 190 | /** 191 | * Data of the response, may be null. 192 | */ 193 | public InputStream data; 194 | 195 | /** 196 | * Headers for the HTTP response. Use addHeader() 197 | * to add lines. 198 | */ 199 | public Properties header = new Properties(); 200 | 201 | /** 202 | * Is streaming mode 203 | */ 204 | public boolean isStreaming = false; 205 | 206 | /** 207 | * The requested uri 208 | */ 209 | public String uri; 210 | } 211 | 212 | /** 213 | * Some HTTP response status codes 214 | */ 215 | public static final String 216 | HTTP_OK = "200 OK", 217 | HTTP_PARTIALCONTENT = "206 Partial Content", 218 | HTTP_RANGE_NOT_SATISFIABLE = "416 Requested Range Not Satisfiable", 219 | HTTP_REDIRECT = "301 Moved Permanently", 220 | HTTP_FORBIDDEN = "403 Forbidden", 221 | HTTP_NOTFOUND = "404 Not Found", 222 | HTTP_BADREQUEST = "400 Bad Request", 223 | HTTP_INTERNALERROR = "500 Internal Server Error", 224 | HTTP_NOTIMPLEMENTED = "501 Not Implemented"; 225 | 226 | /** 227 | * Common mime types for dynamic content 228 | */ 229 | public static final String 230 | MIME_PLAINTEXT = "text/plain", 231 | MIME_HTML = "text/html", 232 | MIME_DEFAULT_BINARY = "application/octet-stream", 233 | MIME_XML = "text/xml"; 234 | 235 | // ================================================== 236 | // Socket & server code 237 | // ================================================== 238 | 239 | /** 240 | * Starts a HTTP server to given port.

241 | * Throws an IOException if the socket is already in use 242 | */ 243 | public NanoHTTPD( int port, File wwwroot ) throws IOException 244 | { 245 | myTcpPort = port; 246 | myRootDir = wwwroot; 247 | myAssets = null; 248 | 249 | myServerSocket = new ServerSocket( myTcpPort ); 250 | beginDaemon(); 251 | } 252 | 253 | public NanoHTTPD( int port, AssetManager wwwroot ) throws IOException 254 | { 255 | myTcpPort = port; 256 | myAssets = wwwroot; 257 | myRootDir = null; 258 | 259 | myServerSocket = new ServerSocket( myTcpPort ); 260 | beginDaemon(); 261 | } 262 | 263 | private void beginDaemon() throws IOException { 264 | myThread = new Thread( new Runnable() 265 | { 266 | public void run() 267 | { 268 | try 269 | { 270 | while( true ) 271 | new HTTPSession( myServerSocket.accept()); 272 | } 273 | catch ( IOException ioe ) 274 | {} 275 | } 276 | }); 277 | myThread.setDaemon( true ); 278 | myThread.start(); 279 | } 280 | /** 281 | * Stops the server. 282 | */ 283 | public void stop() 284 | { 285 | try 286 | { 287 | myServerSocket.close(); 288 | myThread.join(); 289 | } 290 | catch ( IOException ioe ) {} 291 | catch ( InterruptedException e ) {} 292 | } 293 | 294 | 295 | /** 296 | * Starts as a standalone file server and waits for Enter. 297 | */ 298 | public static void main( String[] args ) 299 | { 300 | System.out.println( "NanoHTTPD 1.24 (C) 2001,2005-2011 Jarno Elonen and (C) 2010 Konstantinos Togias\n" + 301 | "(Command line options: [-p port] [-d root-dir] [--licence])\n" ); 302 | 303 | // Defaults 304 | int port = 80; 305 | File wwwroot = new File(".").getAbsoluteFile(); 306 | 307 | // Show licence if requested 308 | for ( int i=0; i= 0 && size > 0 ) 417 | { 418 | rlen = is.read(buf, 0, 512); 419 | size -= rlen; 420 | if (rlen > 0) 421 | f.write(buf, 0, rlen); 422 | } 423 | 424 | // Get the raw body as a byte [] 425 | byte [] fbuf = f.toByteArray(); 426 | 427 | // Create a BufferedReader for easily reading it as string. 428 | ByteArrayInputStream bin = new ByteArrayInputStream(fbuf); 429 | BufferedReader in = new BufferedReader( new InputStreamReader(bin)); 430 | 431 | // If the method is POST, there may be parameters 432 | // in data section, too, read it: 433 | if ( method.equalsIgnoreCase( "POST" )) 434 | { 435 | String contentType = ""; 436 | String contentTypeHeader = header.getProperty("content-type"); 437 | StringTokenizer st = new StringTokenizer( contentTypeHeader , "; " ); 438 | if ( st.hasMoreTokens()) { 439 | contentType = st.nextToken(); 440 | } 441 | 442 | if (contentType.equalsIgnoreCase("multipart/form-data")) 443 | { 444 | // Handle multipart/form-data 445 | if ( !st.hasMoreTokens()) 446 | sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html" ); 447 | String boundaryExp = st.nextToken(); 448 | st = new StringTokenizer( boundaryExp , "=" ); 449 | if (st.countTokens() != 2) 450 | sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary syntax error. Usage: GET /example/file.html" ); 451 | st.nextToken(); 452 | String boundary = st.nextToken(); 453 | 454 | decodeMultipartData(boundary, fbuf, in, parms, files); 455 | } 456 | else 457 | { 458 | // Handle application/x-www-form-urlencoded 459 | String postLine = ""; 460 | char pbuf[] = new char[512]; 461 | int read = in.read(pbuf); 462 | while ( read >= 0 && !postLine.endsWith("\r\n") ) 463 | { 464 | postLine += String.valueOf(pbuf, 0, read); 465 | read = in.read(pbuf); 466 | } 467 | postLine = postLine.trim(); 468 | decodeParms( postLine, parms ); 469 | } 470 | } 471 | 472 | // Ok, now do the serve() 473 | Response r = serve( uri, method, header, parms, files ); 474 | if ( r == null ) 475 | sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: Serve() returned a null response." ); 476 | else 477 | sendResponse( r.status, r.mimeType, r.header, r.data, r.isStreaming ); 478 | 479 | in.close(); 480 | is.close(); 481 | 482 | // Ok, finish this http request 483 | serveDone(r); 484 | } 485 | catch ( IOException ioe ) 486 | { 487 | try 488 | { 489 | sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); 490 | } 491 | catch ( Throwable t ) {} 492 | } 493 | catch ( InterruptedException ie ) 494 | { 495 | // Thrown by sendError, ignore and exit the thread. 496 | } 497 | 498 | } 499 | 500 | /** 501 | * Decodes the sent headers and loads the data into 502 | * java Properties' key - value pairs 503 | **/ 504 | private void decodeHeader(BufferedReader in, Properties pre, Properties parms, Properties header) 505 | throws InterruptedException 506 | { 507 | try { 508 | // Read the request line 509 | String inLine = in.readLine(); 510 | if (inLine == null) return; 511 | StringTokenizer st = new StringTokenizer( inLine ); 512 | if ( !st.hasMoreTokens()) 513 | sendError( HTTP_BADREQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html" ); 514 | 515 | String method = st.nextToken(); 516 | pre.put("method", method); 517 | 518 | if ( !st.hasMoreTokens()) 519 | sendError( HTTP_BADREQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html" ); 520 | 521 | String uri = st.nextToken(); 522 | 523 | // Decode parameters from the URI 524 | int qmi = uri.indexOf( '?' ); 525 | if ( qmi >= 0 ) 526 | { 527 | decodeParms( uri.substring( qmi+1 ), parms ); 528 | uri = decodePercent( uri.substring( 0, qmi )); 529 | } 530 | else uri = decodePercent(uri); 531 | 532 | // If there's another token, it's protocol version, 533 | // followed by HTTP headers. Ignore version but parse headers. 534 | // NOTE: this now forces header names lowercase since they are 535 | // case insensitive and vary by client. 536 | if ( st.hasMoreTokens()) 537 | { 538 | String line = in.readLine(); 539 | while ( line != null && line.trim().length() > 0 ) 540 | { 541 | int p = line.indexOf( ':' ); 542 | if ( p >= 0 ) 543 | header.put( line.substring(0,p).trim().toLowerCase(), line.substring(p+1).trim()); 544 | line = in.readLine(); 545 | } 546 | } 547 | 548 | pre.put("uri", uri); 549 | } 550 | catch ( IOException ioe ) 551 | { 552 | sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); 553 | } 554 | } 555 | 556 | /** 557 | * Decodes the Multipart Body data and put it 558 | * into java Properties' key - value pairs. 559 | **/ 560 | private void decodeMultipartData(String boundary, byte[] fbuf, BufferedReader in, Properties parms, Properties files) 561 | throws InterruptedException 562 | { 563 | try 564 | { 565 | int[] bpositions = getBoundaryPositions(fbuf,boundary.getBytes()); 566 | int boundarycount = 1; 567 | String mpline = in.readLine(); 568 | while ( mpline != null ) 569 | { 570 | if (mpline.indexOf(boundary) == -1) 571 | sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html" ); 572 | boundarycount++; 573 | Properties item = new Properties(); 574 | mpline = in.readLine(); 575 | while (mpline != null && mpline.trim().length() > 0) 576 | { 577 | int p = mpline.indexOf( ':' ); 578 | if (p != -1) 579 | item.put( mpline.substring(0,p).trim().toLowerCase(), mpline.substring(p+1).trim()); 580 | mpline = in.readLine(); 581 | } 582 | if (mpline != null) 583 | { 584 | String contentDisposition = item.getProperty("content-disposition"); 585 | if (contentDisposition == null) 586 | { 587 | sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html" ); 588 | } 589 | StringTokenizer st = new StringTokenizer( contentDisposition , "; " ); 590 | Properties disposition = new Properties(); 591 | while ( st.hasMoreTokens()) 592 | { 593 | String token = st.nextToken(); 594 | int p = token.indexOf( '=' ); 595 | if (p!=-1) 596 | disposition.put( token.substring(0,p).trim().toLowerCase(), token.substring(p+1).trim()); 597 | } 598 | String pname = disposition.getProperty("name"); 599 | pname = pname.substring(1,pname.length()-1); 600 | 601 | String value = ""; 602 | if (item.getProperty("content-type") == null) { 603 | while (mpline != null && mpline.indexOf(boundary) == -1) 604 | { 605 | mpline = in.readLine(); 606 | if ( mpline != null) 607 | { 608 | int d = mpline.indexOf(boundary); 609 | if (d == -1) 610 | value+=mpline; 611 | else 612 | value+=mpline.substring(0,d-2); 613 | } 614 | } 615 | } 616 | else 617 | { 618 | if (boundarycount> bpositions.length) 619 | sendError( HTTP_INTERNALERROR, "Error processing request" ); 620 | int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount-2]); 621 | String path = saveTmpFile(fbuf, offset, bpositions[boundarycount-1]-offset-4); 622 | files.put(pname, path); 623 | value = disposition.getProperty("filename"); 624 | value = value.substring(1,value.length()-1); 625 | do { 626 | mpline = in.readLine(); 627 | } while (mpline != null && mpline.indexOf(boundary) == -1); 628 | } 629 | parms.put(pname, value); 630 | } 631 | } 632 | } 633 | catch ( IOException ioe ) 634 | { 635 | sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); 636 | } 637 | } 638 | 639 | /** 640 | * Find the byte positions where multipart boundaries start. 641 | **/ 642 | public int[] getBoundaryPositions(byte[] b, byte[] boundary) 643 | { 644 | int matchcount = 0; 645 | int matchbyte = -1; 646 | Vector matchbytes = new Vector(); 647 | for (int i=0; i 0) 685 | { 686 | String tmpdir = System.getProperty("java.io.tmpdir"); 687 | try { 688 | File temp = File.createTempFile("NanoHTTPD", "", new File(tmpdir)); 689 | OutputStream fstream = new FileOutputStream(temp); 690 | fstream.write(b, offset, len); 691 | fstream.close(); 692 | path = temp.getAbsolutePath(); 693 | } catch (Exception e) { // Catch exception if any 694 | System.err.println("Error: " + e.getMessage()); 695 | } 696 | } 697 | return path; 698 | } 699 | 700 | 701 | /** 702 | * It returns the offset separating multipart file headers 703 | * from the file's data. 704 | **/ 705 | private int stripMultipartHeaders(byte[] b, int offset) 706 | { 707 | int i = 0; 708 | for (i=offset; i 718 | * For example: "an+example%20string" -> "an example string" 719 | */ 720 | private String decodePercent( String str ) throws InterruptedException 721 | { 722 | try 723 | { 724 | StringBuffer sb = new StringBuffer(); 725 | for( int i=0; i0) 825 | { 826 | int read = data.read( buff, 0, ( (pending>2048) ? 2048 : pending )); 827 | if (read <= 0) break; 828 | out.write( buff, 0, read ); 829 | pending -= read; 830 | } 831 | } else { 832 | byte[] buff = new byte[2048]; 833 | while (true) 834 | { 835 | int read = data.read( buff, 0, 2048); 836 | if (read <= 0) 837 | break; 838 | if (read > 0) 839 | out.write( buff, 0, read ); 840 | } 841 | } 842 | } 843 | out.flush(); 844 | out.close(); 845 | } 846 | catch( IOException ioe ) 847 | { 848 | // Couldn't write? No can do. 849 | try { mySocket.close(); } catch( Throwable t ) {} 850 | } 851 | } 852 | 853 | private Socket mySocket; 854 | } 855 | 856 | /** 857 | * URL-encodes everything between "/"-characters. 858 | * Encodes spaces as '%20' instead of '+'. 859 | */ 860 | private String encodeUri( String uri ) 861 | { 862 | String newUri = ""; 863 | StringTokenizer st = new StringTokenizer( uri, "/ ", true ); 864 | while ( st.hasMoreTokens()) 865 | { 866 | String tok = st.nextToken(); 867 | if ( tok.equals( "/" )) 868 | newUri += "/"; 869 | else if ( tok.equals( " " )) 870 | newUri += "%20"; 871 | else 872 | { 873 | newUri += URLEncoder.encode( tok ); 874 | // For Java 1.4 you'll want to use this instead: 875 | // try { newUri += URLEncoder.encode( tok, "UTF-8" ); } catch ( java.io.UnsupportedEncodingException uee ) {} 876 | } 877 | } 878 | return newUri; 879 | } 880 | 881 | private int myTcpPort; 882 | private final ServerSocket myServerSocket; 883 | private Thread myThread; 884 | private File myRootDir; 885 | private AssetManager myAssets; 886 | 887 | // ================================================== 888 | // File server code 889 | // ================================================== 890 | 891 | /** 892 | * Serves file from homeDir and its' subdirectories (only). 893 | * Uses only URI, ignores all headers and HTTP parameters. 894 | */ 895 | public Response serveFile( String uri, Properties header, File homeDir, 896 | boolean allowDirectoryListing ) 897 | { 898 | Response res = null; 899 | 900 | if ( homeDir == null) { 901 | return serveAssets(uri, header); 902 | } 903 | // Make sure we won't die of an exception later 904 | if ( !homeDir.isDirectory()) 905 | res = new Response( HTTP_INTERNALERROR, MIME_PLAINTEXT, 906 | "INTERNAL ERRROR: serveFile(): given homeDir is not a directory." ); 907 | 908 | if ( res == null ) 909 | { 910 | // Remove URL arguments 911 | uri = uri.trim().replace( File.separatorChar, '/' ); 912 | if ( uri.indexOf( '?' ) >= 0 ) 913 | uri = uri.substring(0, uri.indexOf( '?' )); 914 | 915 | // Prohibit getting out of current directory 916 | if ( uri.startsWith( ".." ) || uri.endsWith( ".." ) || uri.indexOf( "../" ) >= 0 ) 917 | res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, 918 | "FORBIDDEN: Won't serve ../ for security reasons." ); 919 | } 920 | 921 | File f = new File( homeDir, uri ); 922 | if ( res == null && !f.exists()) 923 | res = new Response( HTTP_NOTFOUND, MIME_PLAINTEXT, 924 | "Error 404, file not found." ); 925 | 926 | // List the directory, if necessary 927 | if ( res == null && f.isDirectory()) 928 | { 929 | // Browsers get confused without '/' after the 930 | // directory, send a redirect. 931 | if ( !uri.endsWith( "/" )) 932 | { 933 | uri += "/"; 934 | res = new Response( HTTP_REDIRECT, MIME_HTML, 935 | "Redirected: " + 936 | uri + ""); 937 | res.addHeader( "Location", uri ); 938 | } 939 | 940 | if ( res == null ) 941 | { 942 | // First try index.html and index.htm 943 | if ( new File( f, "index.html" ).exists()) 944 | f = new File( homeDir, uri + "/index.html" ); 945 | else if ( new File( f, "index.htm" ).exists()) 946 | f = new File( homeDir, uri + "/index.htm" ); 947 | // No index file, list the directory if it is readable 948 | else if ( allowDirectoryListing && f.canRead() ) 949 | { 950 | String[] files = f.list(); 951 | String msg = "

Directory " + uri + "


"; 952 | 953 | if ( uri.length() > 1 ) 954 | { 955 | String u = uri.substring( 0, uri.length()-1 ); 956 | int slash = u.lastIndexOf( '/' ); 957 | if ( slash >= 0 && slash < u.length()) 958 | msg += "..
"; 959 | } 960 | 961 | if (files!=null) 962 | { 963 | for ( int i=0; i" + 974 | files[i] + ""; 975 | 976 | // Show file size 977 | if ( curFile.isFile()) 978 | { 979 | long len = curFile.length(); 980 | msg += "  ("; 981 | if ( len < 1024 ) 982 | msg += len + " bytes"; 983 | else if ( len < 1024 * 1024 ) 984 | msg += len/1024 + "." + (len%1024/10%100) + " KB"; 985 | else 986 | msg += len/(1024*1024) + "." + len%(1024*1024)/10%100 + " MB"; 987 | 988 | msg += ")"; 989 | } 990 | msg += "
"; 991 | if ( dir ) msg += ""; 992 | } 993 | } 994 | msg += ""; 995 | res = new Response( HTTP_OK, MIME_HTML, msg ); 996 | } 997 | else 998 | { 999 | res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, 1000 | "FORBIDDEN: No directory listing." ); 1001 | } 1002 | } 1003 | } 1004 | 1005 | try 1006 | { 1007 | if ( res == null ) 1008 | { 1009 | // Get MIME type from file name extension, if possible 1010 | String mime = null; 1011 | int dot = f.getCanonicalPath().lastIndexOf( '.' ); 1012 | if ( dot >= 0 ) 1013 | mime = (String)theMimeTypes.get( f.getCanonicalPath().substring( dot + 1 ).toLowerCase()); 1014 | if ( mime == null ) 1015 | mime = MIME_DEFAULT_BINARY; 1016 | 1017 | // Calculate etag 1018 | String etag = Integer.toHexString((f.getAbsolutePath() + f.lastModified() + "" + f.length()).hashCode()); 1019 | 1020 | // Support (simple) skipping: 1021 | long startFrom = 0; 1022 | long endAt = -1; 1023 | String range = header.getProperty( "range" ); 1024 | if ( range != null ) 1025 | { 1026 | if ( range.startsWith( "bytes=" )) 1027 | { 1028 | range = range.substring( "bytes=".length()); 1029 | int minus = range.indexOf( '-' ); 1030 | try { 1031 | if ( minus > 0 ) 1032 | { 1033 | startFrom = Long.parseLong( range.substring( 0, minus )); 1034 | endAt = Long.parseLong( range.substring( minus+1 )); 1035 | } 1036 | } 1037 | catch ( NumberFormatException nfe ) {} 1038 | } 1039 | } 1040 | 1041 | // Change return code and add Content-Range header when skipping is requested 1042 | long fileLen = f.length(); 1043 | if (range != null && startFrom >= 0) 1044 | { 1045 | if ( startFrom >= fileLen) 1046 | { 1047 | res = new Response( HTTP_RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "" ); 1048 | res.addHeader( "Content-Range", "bytes 0-0/" + fileLen); 1049 | res.addHeader( "ETag", etag); 1050 | } 1051 | else 1052 | { 1053 | if ( endAt < 0 ) 1054 | endAt = fileLen-1; 1055 | long newLen = endAt - startFrom + 1; 1056 | if ( newLen < 0 ) newLen = 0; 1057 | 1058 | final long dataLen = newLen; 1059 | FileInputStream fis = new FileInputStream( f ) { 1060 | public int available() throws IOException { return (int)dataLen; } 1061 | }; 1062 | fis.skip( startFrom ); 1063 | 1064 | res = new Response( HTTP_PARTIALCONTENT, mime, fis ); 1065 | res.addHeader( "Content-Length", "" + dataLen); 1066 | res.addHeader( "Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen); 1067 | res.addHeader( "ETag", etag); 1068 | } 1069 | } 1070 | else 1071 | { 1072 | res = new Response( HTTP_OK, mime, new FileInputStream( f )); 1073 | res.addHeader( "Content-Length", "" + fileLen); 1074 | res.addHeader( "ETag", etag); 1075 | } 1076 | } 1077 | } 1078 | catch( IOException ioe ) 1079 | { 1080 | res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Reading file failed." ); 1081 | } 1082 | 1083 | res.addHeader( "Accept-Ranges", "bytes"); // Announce that the file server accepts partial content requestes 1084 | return res; 1085 | } 1086 | 1087 | private Response serveAssets( String uri, Properties header) { 1088 | Response res = null; 1089 | 1090 | // Remove URL arguments 1091 | uri = uri.trim().replace( File.separatorChar, '/' ); 1092 | if ( uri.startsWith("/") ) { 1093 | uri = uri.substring(1, uri.length()); 1094 | } 1095 | if ( uri.indexOf( '?' ) >= 0 ) 1096 | uri = uri.substring(0, uri.indexOf( '?' )); 1097 | 1098 | // Prohibit getting out of current directory 1099 | if ( uri.startsWith( ".." ) || uri.endsWith( ".." ) || uri.indexOf( "../" ) >= 0 ) { 1100 | res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, 1101 | "FORBIDDEN: Won't serve ../ for security reasons." ); 1102 | } 1103 | 1104 | if ( uri.endsWith("/") || uri.equalsIgnoreCase("") ) { 1105 | uri = uri + "index.html"; 1106 | } 1107 | 1108 | 1109 | InputStream assetFile = null; 1110 | try { 1111 | assetFile = myAssets.open(uri); 1112 | } catch ( IOException ex) { 1113 | assetFile = null; 1114 | } 1115 | if ( res == null && assetFile == null) { 1116 | res = new Response( HTTP_NOTFOUND, MIME_PLAINTEXT, 1117 | "Error 404, file not found." ); 1118 | } 1119 | 1120 | try { 1121 | if ( res == null) { 1122 | // Get MIME type from file name extension, if possible 1123 | String mime = null; 1124 | int dot = uri.lastIndexOf( '.' ); 1125 | if ( dot >= 0 ) 1126 | mime = (String)theMimeTypes.get( uri.substring( dot + 1 ).toLowerCase()); 1127 | if ( mime == null ) 1128 | mime = MIME_DEFAULT_BINARY; 1129 | 1130 | // Calculate etag 1131 | String etag = Integer.toHexString((uri + "" + assetFile.available()).hashCode()); 1132 | 1133 | // Support (simple) skipping: 1134 | long startFrom = 0; 1135 | long endAt = -1; 1136 | String range = header.getProperty( "range" ); 1137 | if ( range != null ) 1138 | { 1139 | if ( range.startsWith( "bytes=" )) 1140 | { 1141 | range = range.substring( "bytes=".length()); 1142 | int minus = range.indexOf( '-' ); 1143 | try { 1144 | if ( minus > 0 ) 1145 | { 1146 | startFrom = Long.parseLong( range.substring( 0, minus )); 1147 | endAt = Long.parseLong( range.substring( minus+1 )); 1148 | } 1149 | } 1150 | catch ( NumberFormatException nfe ) {} 1151 | } 1152 | } 1153 | 1154 | // Change return code and add Content-Range header when skipping is requested 1155 | long fileLen = assetFile.available(); 1156 | if (range != null && startFrom >= 0) 1157 | { 1158 | if ( startFrom >= fileLen) 1159 | { 1160 | res = new Response( HTTP_RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "" ); 1161 | res.addHeader( "Content-Range", "bytes 0-0/" + fileLen); 1162 | res.addHeader( "ETag", etag); 1163 | } 1164 | else 1165 | { 1166 | if ( endAt < 0 ) 1167 | endAt = fileLen-1; 1168 | long newLen = endAt - startFrom + 1; 1169 | if ( newLen < 0 ) newLen = 0; 1170 | 1171 | final long dataLen = newLen; 1172 | assetFile.skip( startFrom ); 1173 | 1174 | res = new Response( HTTP_PARTIALCONTENT, mime, assetFile ); 1175 | res.addHeader( "Content-Length", "" + dataLen); 1176 | res.addHeader( "Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen); 1177 | res.addHeader( "ETag", etag); 1178 | } 1179 | } 1180 | else 1181 | { 1182 | res = new Response( HTTP_OK, mime, assetFile ); 1183 | res.addHeader( "Content-Length", "" + fileLen); 1184 | res.addHeader( "ETag", etag); 1185 | } 1186 | 1187 | } 1188 | } 1189 | catch( IOException ioe ) 1190 | { 1191 | res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Reading file failed." ); 1192 | } 1193 | 1194 | res.addHeader( "Accept-Ranges", "bytes"); // Announce that the file server accepts partial content requestes 1195 | return res; 1196 | } 1197 | 1198 | /** 1199 | * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE 1200 | */ 1201 | private static Hashtable theMimeTypes = new Hashtable(); 1202 | static 1203 | { 1204 | StringTokenizer st = new StringTokenizer( 1205 | "css text/css "+ 1206 | "htm text/html "+ 1207 | "html text/html "+ 1208 | "xml text/xml "+ 1209 | "txt text/plain "+ 1210 | "asc text/plain "+ 1211 | "gif image/gif "+ 1212 | "jpg image/jpeg "+ 1213 | "jpeg image/jpeg "+ 1214 | "png image/png "+ 1215 | "mp3 audio/mpeg "+ 1216 | "m3u audio/mpeg-url " + 1217 | "mp4 video/mp4 " + 1218 | "ogv video/ogg " + 1219 | "flv video/x-flv " + 1220 | "mov video/quicktime " + 1221 | "swf application/x-shockwave-flash " + 1222 | "js application/javascript "+ 1223 | "pdf application/pdf "+ 1224 | "doc application/msword "+ 1225 | "ogg application/x-ogg "+ 1226 | "zip application/octet-stream "+ 1227 | "exe application/octet-stream "+ 1228 | "class application/octet-stream " ); 1229 | while ( st.hasMoreTokens()) 1230 | theMimeTypes.put( st.nextToken(), st.nextToken()); 1231 | } 1232 | 1233 | /** 1234 | * GMT date formatter 1235 | */ 1236 | private static java.text.SimpleDateFormat gmtFrmt; 1237 | static 1238 | { 1239 | gmtFrmt = new java.text.SimpleDateFormat( "E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); 1240 | gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT")); 1241 | } 1242 | 1243 | /** 1244 | * The distribution licence 1245 | */ 1246 | private static final String LICENCE = 1247 | "Copyright (C) 2001,2005-2011 by Jarno Elonen \n"+ 1248 | "and Copyright (C) 2010 by Konstantinos Togias \n"+ 1249 | "\n"+ 1250 | "Redistribution and use in source and binary forms, with or without\n"+ 1251 | "modification, are permitted provided that the following conditions\n"+ 1252 | "are met:\n"+ 1253 | "\n"+ 1254 | "Redistributions of source code must retain the above copyright notice,\n"+ 1255 | "this list of conditions and the following disclaimer. Redistributions in\n"+ 1256 | "binary form must reproduce the above copyright notice, this list of\n"+ 1257 | "conditions and the following disclaimer in the documentation and/or other\n"+ 1258 | "materials provided with the distribution. The name of the author may not\n"+ 1259 | "be used to endorse or promote products derived from this software without\n"+ 1260 | "specific prior written permission. \n"+ 1261 | " \n"+ 1262 | "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"+ 1263 | "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"+ 1264 | "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"+ 1265 | "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"+ 1266 | "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"+ 1267 | "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"+ 1268 | "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"+ 1269 | "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"+ 1270 | "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"+ 1271 | "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."; 1272 | } 1273 | 1274 | -------------------------------------------------------------------------------- /app/src/main/java/com/buscode/whatsinput/server/ServerNotification.java: -------------------------------------------------------------------------------- 1 | package com.sand.airinput.server; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationManager; 5 | import android.app.PendingIntent; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import com.buscode.whatsinput.MainActivity; 9 | import com.buscode.whatsinput.server.ExHttpConfig; 10 | import com.buscode.whatsinput.R; 11 | 12 | public class ServerNotification { 13 | 14 | 15 | public static final int ID_SPS_HTTP = 100; 16 | 17 | public static synchronized void showServerNotification(Context context) { 18 | 19 | ExHttpConfig config = ExHttpConfig.getInstance(); 20 | String contentText = config.getLocalAddress(); 21 | 22 | Notification notification = makeSPSNotification(context, contentText, true); 23 | notify(context, notification); 24 | } 25 | 26 | public static synchronized void cancelAll(Context context) { 27 | NotificationManager nm = getNotificationManager(context); 28 | nm.cancelAll(); 29 | } 30 | 31 | private static void notify(Context context, Notification notification) { 32 | NotificationManager nm = getNotificationManager(context); 33 | nm.notify(ID_SPS_HTTP, notification); 34 | } 35 | 36 | private static NotificationManager getNotificationManager(Context context) { 37 | return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 38 | } 39 | 40 | private static Notification makeSPSNotification(Context context, String contentText, boolean notice) { 41 | Builder builder = new Builder(context); 42 | 43 | builder.setIcon(R.drawable.icon); 44 | builder.setTickerText("Sps ScreenShot"); 45 | builder.setWhen(System.currentTimeMillis()); 46 | builder.addFlags(Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT); 47 | 48 | //Latest Event Info 49 | String contentTitle = "Sps ScreenShot"; 50 | PendingIntent contentIntent = getNotificationPendingIntent(context); 51 | 52 | builder.setLatestEventInfo(contentTitle, contentText, contentIntent); 53 | builder.setDefaults(notice ? Notification.DEFAULT_SOUND : 0); 54 | 55 | return builder.build(); 56 | } 57 | private static PendingIntent getNotificationPendingIntent(Context context) { 58 | return PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0); 59 | } 60 | public static class Builder { 61 | 62 | private Context mContext; 63 | 64 | private Notification mNotification; 65 | 66 | public Builder(Context context) { 67 | 68 | mContext = context; 69 | mNotification = new Notification(); 70 | } 71 | 72 | public Builder setIcon(int icon) { 73 | 74 | mNotification.icon = icon; 75 | return this; 76 | } 77 | public Builder setTickerText(String text) { 78 | mNotification.tickerText = text; 79 | return this; 80 | } 81 | 82 | public Builder setWhen(long when) { 83 | mNotification.when = when; 84 | return this; 85 | } 86 | 87 | public Builder setContentIntent(PendingIntent contentIntent) { 88 | mNotification.contentIntent = contentIntent; 89 | return this; 90 | } 91 | 92 | public Builder addFlags(int flags) { 93 | mNotification.flags |= flags; 94 | return this; 95 | } 96 | 97 | /** 98 | * 99 | * Sound, VIBRATE 100 | * Notification.DEFAULT_SOUND, 101 | * Notification.DEFAULT_VIBRATE 102 | * 103 | * @param defaults 104 | * @return 105 | */ 106 | public Builder setDefaults(int defaults) { 107 | 108 | mNotification.defaults = defaults; 109 | return this; 110 | } 111 | 112 | public Builder setLatestEventInfo(String contentTitle, String contentText, PendingIntent contentIntent) { 113 | 114 | mNotification.setLatestEventInfo(mContext, contentTitle, contentText, contentIntent); 115 | return this; 116 | } 117 | 118 | public Notification build() { 119 | return mNotification; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-hdpi/btn_default_disabled_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-hdpi/btn_default_disabled_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_default_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-hdpi/btn_default_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_default_normal_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-hdpi/btn_default_normal_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_menu_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-hdpi/ic_menu_refresh.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/panel_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-hdpi/panel_background.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_menu_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-ldpi/ic_menu_refresh.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/panel_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-ldpi/panel_background.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-mdpi/btn_default_disabled_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-mdpi/btn_default_disabled_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/btn_default_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-mdpi/btn_default_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/btn_default_normal_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-mdpi/btn_default_normal_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_menu_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-mdpi/ic_menu_refresh.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/panel_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-mdpi/panel_background.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_default_disabled_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-xhdpi/btn_default_disabled_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_default_disabled_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-xhdpi/btn_default_disabled_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_default_focused_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-xhdpi/btn_default_focused_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_default_normal_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-xhdpi/btn_default_normal_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/btn_default_pressed_holo_dark.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-xhdpi/btn_default_pressed_holo_dark.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_menu_moreoverflow_normal_holo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-xhdpi/ic_menu_moreoverflow_normal_holo_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_menu_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-xhdpi/ic_menu_refresh.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/panel_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willerce/WhatsInput/73a898b0116559c8bf114cd6c903c5e385588eb7/app/src/main/res/drawable-xhdpi/panel_background.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_bg_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_bg_n.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_default_holo_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 32 | 33 | 35 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 15 | 16 | 17 | 23 | 24 | 28 | 29 | 37 | 38 | 39 | 40 | 45 | 46 | 54 | 55 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 81 | 82 | 91 | 92 |